diff --git a/apps/FrontendDesigner/package.json b/apps/FrontendDesigner/package.json index 87e2b7a..f4a475b 100644 --- a/apps/FrontendDesigner/package.json +++ b/apps/FrontendDesigner/package.json @@ -26,6 +26,7 @@ "eslint": "^9.15.0", "eslint-plugin-vue": "^9.32.0", "prettier": "^3.3.3", + "terser": "^5.44.1", "unplugin-auto-import": "^20.2.0", "unplugin-vue-components": "^30.0.0", "vite": "^7.2.2" diff --git a/apps/FrontendDesigner/src/router/index.js b/apps/FrontendDesigner/src/router/index.js index 53873a2..5f0873b 100644 --- a/apps/FrontendDesigner/src/router/index.js +++ b/apps/FrontendDesigner/src/router/index.js @@ -1,4 +1,4 @@ -import { createRouter, createWebHistory } from 'vue-router' +import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router' import About from '@/views/About.vue' import NotFound from '@/views/NotFound.vue' import AdminLogin from '@/views/AdminLogin/AdminLogin.vue' @@ -120,7 +120,8 @@ const routes = [ ] const router = createRouter({ - history: createWebHistory(), + // history: createWebHistory(), + history: createWebHashHistory(), routes, scrollBehavior(to, from, savedPosition) { if (savedPosition) { diff --git a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js index b151b2c..f374616 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js +++ b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.js @@ -4,9 +4,13 @@ export class AdminDisassemblyDetail { constructor() { } //拆件 - async disassemble(imgurl,callback) { + async disassemble(imgurl,callback,errorCallback) { + try{ const result = await gimiServer.handleGenerateImage(imgurl, prompt.Hairseparation) console.log('resultresult',result); callback(result) + }catch(error){ + errorCallback(error); } + } } \ No newline at end of file diff --git a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue index ec877e6..dc68526 100644 --- a/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue +++ b/apps/FrontendDesigner/src/views/admin/AdminDisassemblyDetail/AdminDisassemblyDetail.vue @@ -470,6 +470,9 @@ const handleDisassembly = () => { disassembledImages.value.push(result) currentStep.value = 2 disassemblyLoading.value = false + },(error) => { + disassemblyLoading.value = false + ElMessage.error('拆件失败,请稍后重试') }) } diff --git a/apps/FrontendDesigner/vite.config.js b/apps/FrontendDesigner/vite.config.js index 1eed346..8314b9d 100644 --- a/apps/FrontendDesigner/vite.config.js +++ b/apps/FrontendDesigner/vite.config.js @@ -16,6 +16,18 @@ export default defineConfig({ resolvers: [ElementPlusResolver()], }), ], + build: { + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true + }, + format: { + comments: false + } + } + }, server: { port: 3000, host: true, diff --git a/apps/frontend/.env.development b/apps/frontend/.env.development index 5cd5fd5..2a780af 100644 --- a/apps/frontend/.env.development +++ b/apps/frontend/.env.development @@ -1,4 +1,20 @@ +# 开发环境变量配置 +# Google AI API Key(用于 AI 功能) +VITE_GOOGLE_API_KEY=your_google_api_key_here + +# 基础API地址(生产环境) +VITE_BASE_URL=https://api.deotaland.ai + +# 基础API地址(备用) +VITE_APP_BASE_API=https://api.deotaland.ai + +# Stripe 支付配置 +VITE_STRIPE_PUBLISHABLE_KEY=pk_test_51SUf06BzlmfuPpixQn3nBDvLcO2qTyeqseM1wcwPcTfGo2Rivggc0axNbFyPrVCfoKIfWuuzIeBzUQl3Fn4Hz0Ea008vLhvv5g + +# 应用配置 +VITE_APP_TITLE=DeotalandAI +VITE_APP_DESCRIPTION=AI-Powered Creation Platform + # 开发环境配置 -VITE_BASE_URL=/api VITE_DEV_MODE=true -VITE_LOG_LEVEL=info \ No newline at end of file +VITE_LOG_LEVEL=debug \ No newline at end of file diff --git a/apps/frontend/.env.example b/apps/frontend/.env.production similarity index 76% rename from apps/frontend/.env.example rename to apps/frontend/.env.production index 7d859f1..e266716 100644 --- a/apps/frontend/.env.example +++ b/apps/frontend/.env.production @@ -5,19 +5,14 @@ VITE_BASE_URL=https://api.deotaland.ai # 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_STRIPE_PUBLISHABLE_KEY=pk_test_51SUf06BzlmfuPpixQn3nBDvLcO2qTyeqseM1wcwPcTfGo2Rivggc0axNbFyPrVCfoKIfWuuzIeBzUQl3Fn4Hz0Ea008vLhvv5g # 应用配置 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/frontend/index.html b/apps/frontend/index.html index c253e8c..cfdb16f 100644 --- a/apps/frontend/index.html +++ b/apps/frontend/index.html @@ -12,6 +12,7 @@
+ diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 7f8b01f..6344829 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@iconify-json/feather": "^1.2.1", "@vitejs/plugin-vue": "^6.0.1", + "terser": "^5.44.1", "unplugin-auto-import": "^20.2.0", "unplugin-icons": "^22.5.0", "unplugin-vue-components": "^30.0.0", diff --git a/apps/frontend/src/assets/sketches/2c315fb7-0d2e-42c7-beaa-cd3da8117cbf (1).png b/apps/frontend/src/assets/sketches/2c315fb7-0d2e-42c7-beaa-cd3da8117cbf (1).png new file mode 100644 index 0000000..13d4bce Binary files /dev/null and b/apps/frontend/src/assets/sketches/2c315fb7-0d2e-42c7-beaa-cd3da8117cbf (1).png differ diff --git a/apps/frontend/src/components/GuideModal/index.vue b/apps/frontend/src/components/GuideModal/index.vue index 2619516..5c176a1 100644 --- a/apps/frontend/src/components/GuideModal/index.vue +++ b/apps/frontend/src/components/GuideModal/index.vue @@ -2,7 +2,7 @@
- @@ -54,7 +54,7 @@ class="action-button secondary" @click="prevStep" > - 上一步 + {{ t('header.previous') }} @@ -62,7 +62,7 @@ class="action-button skip" @click="skipGuide" > - 跳过引导 + {{ t('header.skipGuide') }} @@ -70,7 +70,7 @@ class="action-button primary" @click="nextStep" > - {{ isLastStep ? '开始创作' : '下一步' }} + {{ isLastStep ? t('header.startCreating') : t('header.next') }}
@@ -80,7 +80,7 @@
- {{ currentStep + 1 }} / {{ guideSteps.length }} + {{ t('header.step') }} {{ currentStep + 1 }} / {{ guideSteps.length }}
@@ -88,6 +88,7 @@ \ No newline at end of file diff --git a/apps/frontend/src/components/IPCard/index.vue b/apps/frontend/src/components/IPCard/index.vue index 20040bc..56a7eec 100644 --- a/apps/frontend/src/components/IPCard/index.vue +++ b/apps/frontend/src/components/IPCard/index.vue @@ -1,20 +1,5 @@ + + \ No newline at end of file diff --git a/apps/frontend/vite.config.js b/apps/frontend/vite.config.js index 7d19ba8..3c802a6 100644 --- a/apps/frontend/vite.config.js +++ b/apps/frontend/vite.config.js @@ -40,6 +40,16 @@ export default defineConfig({ outDir: 'dist', assetsDir: 'assets', sourcemap: false, + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true + }, + format: { + comments: false + } + }, rollupOptions: { output: { manualChunks: { diff --git a/docs/stripe-payment-data-flow.md b/docs/stripe-payment-data-flow.md new file mode 100644 index 0000000..ea871da --- /dev/null +++ b/docs/stripe-payment-data-flow.md @@ -0,0 +1,260 @@ +# Stripe支付数据流程详解 + +## 1. 价格传递机制 + +### 1.1 基本价格传递 + +StripePaymentForm组件通过props接收基本价格信息: + +```javascript +const props = defineProps({ + amount: { + type: Number, + required: true // 金额(分) + }, + currency: { + type: String, + default: 'usd' // 货币类型 + }, + // 其他props... +}) +``` + +**关键说明**: +- `amount` 以**分**为单位(例如:100表示1美元或100日元) +- `currency` 默认为'usd',支持Stripe支持的所有货币 + +### 1.2 费用计算逻辑 + +组件内部会根据基础金额计算税费和运费: + +```javascript +// 计算税费和运费 +const calculateFees = () => { + // 模拟税费计算(8%) + taxAmount.value = Math.round(props.amount * 0.08) + + // 模拟运费计算(满99免费) + shippingAmount.value = props.amount >= 9900 ? 0 : 1000 +} +``` + +### 1.3 优惠券处理 + +组件支持优惠券折扣,会从最终金额中扣除: + +```javascript +// 优惠券应用逻辑 +if (discount < 1) { + // 百分比折扣 + discountAmount.value = Math.round(props.amount * discount) +} else { + // 固定金额折扣 + discountAmount.value = Math.min(discount, props.amount) +} +``` + +### 1.4 最终金额计算 + +通过计算属性`finalAmount`得出最终支付金额: + +```javascript +const finalAmount = computed(() => { + return props.amount + taxAmount.value + shippingAmount.value - discountAmount.value +}) +``` + +## 2. Stripe订单数据格式 + +### 2.1 支付方式创建数据 + +在`processPayment`函数中,创建支付方式时使用的数据格式: + +```javascript +const { error, paymentMethod: pm } = await stripe.value.createPaymentMethod({ + type: 'card', + card: cardElement.value, + billing_details: { email: props.customerEmail } +}) +``` + +**参数说明**: +- `type`: 支付方式类型,这里固定为'card' +- `card`: Stripe卡片元素实例 +- `billing_details`: 账单详情,包含客户邮箱 + +### 2.2 支付方式返回数据结构 + +Stripe返回的支付方式数据结构示例: + +```javascript +{ + id: 'pm_1234567890', + object: 'payment_method', + billing_details: { + address: { + city: null, + country: null, + line1: null, + line2: null, + postal_code: null, + state: null + }, + email: 'customer@example.com', + name: null, + phone: null + }, + card: { + brand: 'visa', + checks: { + address_line1_check: null, + address_postal_code_check: null, + cvc_check: 'pass' + }, + country: 'US', + exp_month: 12, + exp_year: 2025, + fingerprint: 'abcdef1234567890', + funding: 'credit', + last4: '4242', + networks: { + available: ['visa'], + preferred: null + }, + three_d_secure_usage: { + supported: true + }, + wallet: null + }, + created: 1678901234, + customer: null, + livemode: false, + type: 'card' +} +``` + +### 2.3 支付成功事件数据 + +支付成功后,组件通过`payment-success`事件返回的数据格式: + +```javascript +emit('payment-success', { + paymentMethodId: paymentMethod?.id, + orderId: props.orderId, + amount: finalAmount.value, + currency: props.currency +}) +``` + +**返回数据说明**: +- `paymentMethodId`: Stripe支付方式ID +- `orderId`: 订单ID(从props接收) +- `amount`: 最终支付金额(分) +- `currency`: 货币类型 + +### 2.4 与后端交互的数据格式 + +在实际项目中,前端需要将支付信息发送到后端,后端再与Stripe API交互。典型的数据格式: + +```javascript +// 前端发送给后端的数据 +const paymentData = { + orderId: props.orderId, + paymentMethodId: paymentMethod.id, + amount: finalAmount.value, + currency: props.currency, + customerEmail: props.customerEmail, + // 其他订单相关信息 +} + +// 后端返回的数据 +const response = { + success: true, + paymentIntentId: 'pi_1234567890', + chargeId: 'ch_1234567890', + orderStatus: 'paid' +} +``` + +## 3. 数据流向图 + +``` +父组件 + │ + ├─── 传递基本价格信息 ───► StripePaymentForm组件 + │ │ + │ ├─── 计算税费和运费 + │ │ + │ ├─── 应用优惠券折扣 + │ │ + │ ├─── 创建支付方式 ───► Stripe API + │ │ │ + │ │ └─── 返回支付方式数据 + │ │ + │ ├─── 发送支付数据 ───► 后端API + │ │ │ + │ │ └─── 返回支付结果 + │ │ + │ └─── 触发支付事件 ───► 父组件 + │ + └─── 处理支付结果 +``` + +## 4. 实际使用示例 + +### 4.1 父组件传递价格 + +```vue + +``` + +### 4.2 支付成功处理 + +```javascript +const handlePaymentSuccess = (paymentResult) => { + console.log('Payment successful:', paymentResult) + // 输出示例: + // { + // paymentMethodId: 'pm_1234567890', + // orderId: 'ORDER-20250101-001', + // amount: 10800, // 108.00 元(含8%税费) + // currency: 'cny' + // } + + // 跳转到支付成功页面或更新订单状态 +} +``` + +## 5. 注意事项 + +1. **金额单位**:始终使用分作为金额单位,避免浮点数精度问题 +2. **货币一致性**:确保前端和后端使用相同的货币类型 +3. **税费计算**:实际项目中应根据地区和法规调整税费计算逻辑 +4. **运费规则**:根据实际业务需求调整运费计算规则 +5. **优惠券验证**:生产环境中应通过后端API验证优惠券有效性 +6. **支付结果处理**:必须处理支付成功和失败的情况 +7. **幂等性设计**:确保重复支付请求不会导致多次扣款 + +## 6. 扩展建议 + +1. **支持多种货币**:根据用户地区自动切换货币 +2. **动态税费计算**:根据不同地区和商品类型计算税费 +3. **灵活运费规则**:支持多种运费模板和免运费条件 +4. **优惠券系统集成**:与后端优惠券系统深度集成 +5. **支付方式扩展**:支持Apple Pay、Google Pay等多种支付方式 +6. **支付意图创建**:在后端创建Payment Intent,提高支付安全性 + +## 7. 总结 + +StripePaymentForm组件通过props接收基本价格信息,内部计算税费、运费和优惠券折扣,最终生成支付金额。支付过程中,组件与Stripe API交互创建支付方式,然后将支付信息发送到后端处理。支付结果通过事件通知父组件,完成整个支付流程。 + +了解数据流向和格式对于集成和扩展Stripe支付功能至关重要,可以帮助开发人员更好地理解和调试支付流程,确保支付系统的安全性和可靠性。 \ No newline at end of file diff --git a/docs/stripe-payment-integration.md b/docs/stripe-payment-integration.md new file mode 100644 index 0000000..6401dc1 --- /dev/null +++ b/docs/stripe-payment-integration.md @@ -0,0 +1,233 @@ +# Stripe支付集成文档 + +## 1. 概述 + +本文档介绍了如何在DeotalandAi项目中集成Stripe支付功能,包括组件结构、支付流程、配置方法和测试步骤。 + +## 2. 项目结构 + +Stripe支付功能主要通过`StripePaymentForm.vue`组件实现,该组件位于`apps/frontend/src/components/`目录下。 + +## 3. 技术栈 + +- Vue 3 (Composition API) +- Stripe.js v3 +- Element Plus (UI组件库) +- Vue I18n (国际化支持) + +## 4. 组件功能详解 + +### 4.1 核心功能 + +- 信用卡支付处理 +- 订单金额计算 +- 优惠券应用 +- 支付状态管理 +- 响应式设计 +- 暗色主题支持 +- 中英文切换 + +### 4.2 组件结构 + +``` +StripePaymentForm.vue +├── template +│ ├── 卡片输入区域 +│ ├── 订单摘要 +│ ├── 优惠券输入 +│ ├── 支付按钮 +│ └── 安全提示 +├── script +│ ├── 初始化Stripe +│ ├── 计算税费和运费 +│ ├── 应用优惠券 +│ ├── 处理支付 +│ └── 生命周期管理 +└── style + ├── 基础样式 + ├── 暗色主题适配 + └── 响应式设计 +``` + +## 5. 支付流程详解 + +### 5.1 初始化流程 + +1. 组件挂载时调用`initializeStripe()`函数 +2. 加载Stripe.js库 +3. 创建Stripe元素实例 +4. 配置卡片输入样式 +5. 挂载卡片输入元素到DOM +6. 计算初始税费和运费 + +### 5.2 支付处理流程 + +1. 用户输入信用卡信息 +2. 系统验证卡片信息 +3. 用户点击"立即支付"按钮 +4. 调用`processPayment()`函数 +5. 创建支付方式(Payment Method) +6. 发送支付请求到后端 +7. 处理支付结果 +8. 触发相应的事件回调 + +### 5.3 关键代码解析 + +```javascript +// 初始化Stripe +const initializeStripe = async () => { + try { + stripe.value = await loadStripe(STRIPE_PUBLISHABLE_KEY) + // ... 配置Stripe元素 + } catch (error) { + console.error('Error initializing Stripe:', error) + } +} + +// 处理支付 +const processPayment = async () => { + // ... 验证Stripe实例 + + // 创建支付方式 + const { error, paymentMethod: pm } = await stripe.value.createPaymentMethod({ + type: 'card', + card: cardElement.value, + billing_details: { email: props.customerEmail } + }) + + // ... 发送支付请求到后端 + // ... 处理支付结果 +} +``` + +## 6. 配置和使用 + +### 6.1 Stripe API密钥配置 + +在组件中,Stripe公钥直接定义在代码中: + +```javascript +const STRIPE_PUBLISHABLE_KEY = 'pk_test_51SRnUTG9Oq8PDokQxhKzpPYaf5rTFR5OZ8QqTkGVtL9YUwTZFgU4ipN42Lub6NEYjXRvcIx8hvAvJGkKskDQ0pf9003uZhrC9Y' +``` + +**生产环境建议**:将API密钥存储在环境变量中,通过`.env`文件加载。 + +### 6.2 组件使用 + +在父组件中引入并使用StripePaymentForm组件: + +```vue + + + +``` + +### 6.3 Props参数 + +| 参数名 | 类型 | 必填 | 默认值 | 描述 | +|--------|------|------|--------|------| +| amount | Number | 是 | - | 订单金额(分) | +| currency | String | 否 | 'usd' | 货币类型 | +| orderId | String | 是 | - | 订单ID | +| customerEmail | String | 否 | '' | 客户邮箱 | + +### 6.4 事件 + +| 事件名 | 描述 | 回调参数 | +|--------|------|----------| +| payment-success | 支付成功时触发 | paymentResult对象 | +| payment-error | 支付失败时触发 | 错误对象 | +| cancel | 用户取消支付时触发 | - | + +## 7. 测试和调试 + +### 7.1 测试卡号 + +Stripe提供了测试卡号,用于开发和测试: + +- 成功支付:4242 4242 4242 4242 +- 过期卡片:4000 0000 0000 0069 +- 资金不足:4000 0000 0000 9995 + +### 7.2 测试流程 + +1. 确保前端项目正常运行 +2. 进入支付页面 +3. 输入测试卡号 +4. 输入任意过期日期(未来日期) +5. 输入任意CVC码 +6. 点击"立即支付"按钮 +7. 观察支付结果 + +### 7.3 调试技巧 + +- 打开浏览器控制台查看日志 +- 使用Stripe Dashboard查看支付记录 +- 检查网络请求是否正常 +- 验证后端API是否正确处理支付请求 + +## 8. 常见问题和解决方案 + +### 8.1 问题:Stripe初始化失败 + +**解决方案**: +- 检查Stripe公钥是否正确 +- 确保网络连接正常 +- 检查浏览器控制台是否有错误信息 + +### 8.2 问题:支付被拒绝 + +**解决方案**: +- 检查测试卡号是否正确 +- 确保订单金额大于0 +- 检查Stripe账户是否有足够的余额 + +### 8.3 问题:优惠券不生效 + +**解决方案**: +- 检查优惠券代码是否正确 +- 验证优惠券是否在有效期内 +- 检查优惠券使用条件是否满足 + +## 9. 支付流程优化建议 + +1. **添加支付方式选择**:支持更多支付方式,如Apple Pay、Google Pay等 +2. **增强错误处理**:提供更详细的错误信息和解决方案 +3. **添加支付进度提示**:提升用户体验 +4. **实现支付结果页面**:统一处理支付成功和失败的情况 +5. **添加支付记录查询**:方便用户查看历史支付记录 + +## 10. 安全注意事项 + +1. 不要在前端代码中暴露Stripe密钥 +2. 所有支付请求必须经过后端验证 +3. 定期更新Stripe库版本 +4. 遵循PCI DSS安全标准 +5. 加密敏感支付信息 + +## 11. 部署建议 + +1. 生产环境使用真实的Stripe API密钥 +2. 配置Webhook接收Stripe事件通知 +3. 实现幂等性处理,防止重复支付 +4. 定期备份支付数据 +5. 监控支付系统性能和错误率 + +## 12. 总结 + +Stripe支付集成是DeotalandAi项目中的重要功能,通过`StripePaymentForm.vue`组件实现了完整的支付流程。本文档详细介绍了组件结构、支付流程、配置方法和测试步骤,希望能帮助开发人员更好地理解和使用Stripe支付功能。 + +如需进一步了解Stripe支付API,请参考[Stripe官方文档](https://stripe.com/docs/api)。 \ No newline at end of file diff --git a/openspec/changes/monorepo-architecture-design/design.md b/openspec/changes/monorepo-architecture-design/design.md index a3c8289..d9a8aa9 100644 --- a/openspec/changes/monorepo-architecture-design/design.md +++ b/openspec/changes/monorepo-architecture-design/design.md @@ -432,7 +432,7 @@ export interface Agent { avatar: string category: string tags: string[] - createdAt: string + created_at: string updatedAt: string } @@ -443,7 +443,7 @@ export interface Order { status: 'pending' | 'processing' | 'completed' | 'failed' amount: number currency: string - createdAt: string + created_at: string updatedAt: string } ``` diff --git a/package-lock.json b/package-lock.json index 0274912..2c4d911 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "devDependencies": { "@iconify-json/feather": "^1.2.1", "@vitejs/plugin-vue": "^6.0.1", + "terser": "^5.44.1", "unplugin-auto-import": "^20.2.0", "unplugin-icons": "^22.5.0", "unplugin-vue-components": "^30.0.0", @@ -63,6 +64,7 @@ "version": "0.0.0", "dependencies": { "@element-plus/icons-vue": "^2.3.2", + "@google/genai": "^1.27.0", "@types/three": "^0.180.0", "element-plus": "^2.11.7", "pinia": "^2.2.6", @@ -76,6 +78,7 @@ "eslint": "^9.15.0", "eslint-plugin-vue": "^9.32.0", "prettier": "^3.3.3", + "terser": "^5.44.1", "unplugin-auto-import": "^20.2.0", "unplugin-vue-components": "^30.0.0", "vite": "^7.2.2" @@ -1279,6 +1282,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -2208,6 +2222,13 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2362,6 +2383,13 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz", @@ -4537,6 +4565,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4546,6 +4584,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmmirror.com/spawn-command/-/spawn-command-0.0.2.tgz", @@ -4705,6 +4754,25 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmmirror.com/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz", @@ -5676,7 +5744,8 @@ "version": "1.0.0", "license": "MIT", "devDependencies": { - "rimraf": "^5.0.0" + "rimraf": "^5.0.0", + "terser": "^5.44.1" }, "peerDependencies": { "element-plus": "^2.0.0", diff --git a/packages/ui/package.json b/packages/ui/package.json index e538a73..8a4f84f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,11 +27,12 @@ "clean:plugins": "rimraf plugins/*/dist" }, "peerDependencies": { - "vue": "^3.0.0", - "element-plus": "^2.0.0" + "element-plus": "^2.0.0", + "vue": "^3.0.0" }, "devDependencies": { - "rimraf": "^5.0.0" + "rimraf": "^5.0.0", + "terser": "^5.44.1" }, "keywords": [ "vue3", @@ -40,4 +41,4 @@ ], "author": "Deotaland AI Team", "license": "MIT" -} \ No newline at end of file +} diff --git a/packages/ui/vite.config.js b/packages/ui/vite.config.js index 6e80f94..1ba16e9 100644 --- a/packages/ui/vite.config.js +++ b/packages/ui/vite.config.js @@ -5,6 +5,16 @@ import { resolve } from 'path' export default defineConfig({ plugins: [vue()], build: { + minify: 'terser', + terserOptions: { + compress: { + drop_console: true, + drop_debugger: true + }, + format: { + comments: false + } + }, lib: { entry: resolve(__dirname, 'src/index.js'), name: 'DeotalandUI', diff --git a/packages/utils/src/api/frontend/index.js b/packages/utils/src/api/frontend/index.js index 1b4f79c..6956431 100644 --- a/packages/utils/src/api/frontend/index.js +++ b/packages/utils/src/api/frontend/index.js @@ -1,8 +1,14 @@ import login from './login.js'; import meshy from './meshy.js'; import gemini from './gemini.js'; +import project from './project.js'; +import pay from './pay.js'; +import order from './order.js'; export default { ...meshy, ...login, ...gemini, + ...project, + ...pay, + ...order, }; \ No newline at end of file diff --git a/packages/utils/src/api/frontend/order.js b/packages/utils/src/api/frontend/order.js new file mode 100644 index 0000000..04c39ae --- /dev/null +++ b/packages/utils/src/api/frontend/order.js @@ -0,0 +1,9 @@ +const order = { + getOrderList:{url:'/api-core/front/order/list',method:'GET'},// 获取订单列表 + getOrderDetail:{url:'/api-core/front/order/get',method:'GET'},// 获取订单详情 + orderCancel:{url:'/api-core/front/order/cancel',method:'POST'},// 取消订单支付 + receiveAddress:{url:'/api-core/front/order/receive',method:'POST'},// 确认收货 + refundOrder:{url:'/api-core/front/order/refund',method:'POST'},// 退款订单 + orderStatistics:{url:'/api-core/front/order/statistics',method:'GET'},// 订单状态统计 +} +export default order; diff --git a/packages/utils/src/api/frontend/pay.js b/packages/utils/src/api/frontend/pay.js new file mode 100644 index 0000000..8040f3b --- /dev/null +++ b/packages/utils/src/api/frontend/pay.js @@ -0,0 +1,6 @@ +const pay = { + createPaymentintention:{url:'/createPaymentintention',method:'POST'},// 创建支付意图 + createCheckoutSession:{url:'/createCheckoutSession',method:'POST'},// 创建会话支付(购物车) + createPayorOrder:{url:'/api-core/front/stripe/create-and-checkout',method:'POST'}//创建订单并且返回支付链接 +} +export default pay; diff --git a/packages/utils/src/api/frontend/project.js b/packages/utils/src/api/frontend/project.js new file mode 100644 index 0000000..79ce8af --- /dev/null +++ b/packages/utils/src/api/frontend/project.js @@ -0,0 +1,29 @@ +const login = { + /** + * 创建项目接口 + * 引用示例: PROJECT_CREATE + */ + PROJECT_CREATE:{url:'/api-core/front/project/create',method:'POST'}, + /** + * 更新项目接口 + * 引用示例: login.UPDATE + */ + PROJECT_UPDATE:{url:'/api-core/front/project/update',method:'POST'}, + /** + * 删除项目接口 + * 引用示例: PROJECT_DELETE + */ + PROJECT_DELETE:{url:'/api-core/front/project/delete',method:'POST'}, + + /** + * 项目详情接口 + * 引用示例: PROJECT_GET + */ + PROJECT_GET:{url:'/api-core/front/project/get',method:'GET'}, + /** + * 项目列表接口 + * 引用示例: PROJECT_LIST + */ + PROJECT_LIST:{url:'/api-core/front/project/list',method:'POST'}, +} +export default login; diff --git a/packages/utils/src/index.js b/packages/utils/src/index.js index 8a76589..fcbd3d1 100644 --- a/packages/utils/src/index.js +++ b/packages/utils/src/index.js @@ -16,7 +16,8 @@ import * as adminApi from './api/frontend/index.js'; import * as clientApi from './api/frontend/index.js'; import { MeshyServer } from './servers/meshyserver.js'; import { GiminiServer } from './servers/giminiserver.js'; -import prompt from './servers/prompt.js' +import prompt from './servers/prompt.js'; +import { PayServer } from './servers/payserver.js'; // 合并所有工具函数 const deotalandUtils = { string: stringUtils, @@ -32,6 +33,7 @@ const deotalandUtils = { MeshyServer, GiminiServer, prompt, + PayServer, // 全局常用方法 debounce: stringUtils.debounce || createDebounce(), throttle: stringUtils.throttle || createThrottle(), @@ -59,6 +61,7 @@ export { MeshyServer, prompt, GiminiServer, + PayServer, } /** diff --git a/packages/utils/src/servers/fileserver.js b/packages/utils/src/servers/fileserver.js index f0c2a7b..86b6f43 100644 --- a/packages/utils/src/servers/fileserver.js +++ b/packages/utils/src/servers/fileserver.js @@ -9,6 +9,129 @@ export class FileServer { concatUrl(url) { return urlRule.replace('IMGURL',url) } + //文件压缩 + /** + * 压缩文件 - 支持多种格式 + * @param {File|string} fileInput - 文件对象、本地路径、线上URL或base64字符串 + * @param {number} quality - 压缩质量 (0-1),默认0.5 + * @param {number} maxWidth - 最大宽度,默认原图宽度 + * @param {number} maxHeight - 最大高度,默认原图高度 + * @returns {Promise} 压缩后的base64字符串 + */ + async compressFile(fileInput, quality = 0.5, maxWidth = null, maxHeight = null) { + try { + let base64String; + + // 根据输入类型获取base64字符串 + if (fileInput instanceof File) { + // 直接是File对象 + base64String = await this.fileToBase64(URL.createObjectURL(fileInput)); + } else if (typeof fileInput === 'string') { + if (fileInput.startsWith('data:')) { + // 已经是base64格式 + base64String = fileInput; + } else if (fileInput.startsWith('http://') || fileInput.startsWith('https://')) { + // 线上URL + base64String = await this.fileToBase64(fileInput); + } else { + // 本地路径,尝试转换为base64 + try { + base64String = await this.fileToBase64(fileInput); + } catch (error) { + throw new Error(`无法处理本地路径: ${fileInput}`); + } + } + } else { + throw new Error('不支持的文件输入类型'); + } + + // 压缩图片 + return await this.compressImageFromBase64(base64String, quality, maxWidth, maxHeight); + + } catch (error) { + console.error('文件压缩失败:', error); + throw error; + } + } + + /** + * 从base64字符串压缩图片 + * @param {string} base64String - base64图片字符串 + * @param {number} quality - 压缩质量 + * @param {number} maxWidth - 最大宽度 + * @param {number} maxHeight - 最大高度 + * @returns {Promise} 压缩后的base64字符串 + */ + compressImageFromBase64(base64String, quality = 0.5, maxWidth = null, maxHeight = null) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + try { + // 计算新的尺寸 + let { width, height } = img; + + if (maxWidth && width > maxWidth) { + height = (height * maxWidth) / width; + width = maxWidth; + } + + if (maxHeight && height > maxHeight) { + width = (width * maxHeight) / height; + height = maxHeight; + } + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + canvas.width = width; + canvas.height = height; + + // 使用高质量的图像缩放 + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + + ctx.drawImage(img, 0, 0, width, height); + const compressed = canvas.toDataURL('image/jpeg', quality); + resolve(compressed); + } catch (error) { + reject(error); + } + }; + img.onerror = () => { + reject(new Error('图片加载失败')); + }; + img.src = base64String; + }); + } + + /** + * 批量压缩文件 + * @param {Array} fileInputs - 文件输入数组 + * @param {Object} options - 压缩选项 + * @returns {Promise} 压缩后的base64数组 + */ + async compressFilesBatch(fileInputs, options = {}) { + const { quality = 0.5, maxWidth = null, maxHeight = null } = options; + const results = []; + + for (const fileInput of fileInputs) { + try { + const compressed = await this.compressFile(fileInput, quality, maxWidth, maxHeight); + results.push({ + success: true, + original: fileInput, + compressed: compressed + }); + } catch (error) { + results.push({ + success: false, + original: fileInput, + error: error.message + }); + } + } + + return results; + } /** * 从URL中提取有效的文件名 * @param {*} url 文件URL diff --git a/packages/utils/src/servers/giminiserver.js b/packages/utils/src/servers/giminiserver.js index fe396dd..4ab6219 100644 --- a/packages/utils/src/servers/giminiserver.js +++ b/packages/utils/src/servers/giminiserver.js @@ -84,10 +84,10 @@ export class GiminiServer extends FileServer { const images = Array.isArray(baseImages) ? baseImages : [baseImages]; try { if (images.length > maxImages) { - reject(`参考图片数量不能超过 ${maxImages} 张`, ERROR_TYPES.VALIDATION, 'low'); + reject(`参考图片数量不能超过 ${maxImages} 张`); } if (!prompt || !prompt.trim()) { - reject('请提供图片生成提示词', ERROR_TYPES.VALIDATION, 'low'); + reject('请提供图片生成提示词'); } // 处理多个参考图片 const imageParts = await Promise.all(images.map(async image =>{ @@ -128,9 +128,8 @@ export class GiminiServer extends FileServer { }) }; //线上生成模型 - async generateImageFromMultipleImagesOnline(baseImages, prompt, options = {}){ + async generateImageFromMultipleImagesOnline(baseImages, prompt,config){ return new Promise(async (resolve, reject) => { - const { maxImages = 5 } = options; // 标准化输入:确保 baseImages 是数组 baseImages = Array.isArray(baseImages) ? baseImages : [baseImages]; const images = await Promise.all(baseImages.map(async (image) => { @@ -140,19 +139,34 @@ export class GiminiServer extends FileServer { return await this.uploadFile(image); })); try { - if (images.length > maxImages) { - reject(`参考图片数量不能超过 ${maxImages} 张`, ERROR_TYPES.VALIDATION, 'low'); + if (images.length > 5) { + reject(`参考图片数量不能超过5张`); } if (!prompt || !prompt.trim()) { - reject('请提供图片生成提示词', ERROR_TYPES.VALIDATION, 'low'); + reject('请提供图片生成提示词'); } // 处理多个参考图片 const imageParts = await Promise.all(images.map(async image =>{ return await this.dataUrlToGenerativePart(image,'url'); } )); // 构建请求的 parts 数组 - const params = { - aspect_ratio:'9:16', +// const params = { +// "aspect_ratio": "9:16", +// "model": "gemini-2.5-flash-image", +// "location": "global", +// "vertexai": true, +// ...config, +// inputs: [ +// ...imageParts, +// { text: prompt } +// ] +// } +const params = { + "aspect_ratio": "9:16", + "model": "gemini-2.5-flash-image", + "location": "global", + "vertexai": true, + ...config, inputs: [ ...imageParts, { text: prompt } @@ -171,9 +185,13 @@ export class GiminiServer extends FileServer { // } // ] // } -// } +// } + if(response.data.error){ + reject(response.data.error.message); + return; + } if(response.code!=0){ - reject(response.msg, ERROR_TYPES.VALIDATION, 'low'); + reject(response.msg); return; } let data = response.data; @@ -187,11 +205,15 @@ export class GiminiServer extends FileServer { }) } //模型生图功能 - async handleGenerateImage(referenceImages = [], prompt = '') { - return new Promise(async (resolve) => { + async handleGenerateImage(referenceImages = [], prompt = '',config) { + return new Promise(async (resolve,reject) => { // let result = await this.generateImageFromMultipleImages(referenceImages, prompt); - let result = await this.generateImageFromMultipleImagesOnline(referenceImages, prompt); - resolve(result); + try { + let result = await this.generateImageFromMultipleImagesOnline(referenceImages, prompt,config); + resolve(result); + } catch (error) { + reject(error); + } }) } } diff --git a/packages/utils/src/servers/meshyserver.js b/packages/utils/src/servers/meshyserver.js index 1ed914b..45bdce3 100644 --- a/packages/utils/src/servers/meshyserver.js +++ b/packages/utils/src/servers/meshyserver.js @@ -1,6 +1,7 @@ import { requestUtils,clientApi } from "../index"; import { FileServer } from './fileserver.js'; export class MeshyServer extends FileServer { + static pollingEnabled = true; constructor() { super(); } @@ -8,18 +9,20 @@ export class MeshyServer extends FileServer { async createModelTask(item={},callback,errorCallback,config={}) { try { let params = { - project_id: item.project_id||0, + project_id: item.project_id, "payload": { image_url:'', ai_model: 'latest', enable_pbr: true, - should_remesh: true, + should_remesh: false, should_texture: true, save_pre_remeshed_model: true, ...config } } - let imgurl = await this.uploadFile(item.image_url); + let imgurl = item.image_url.indexOf('https://api.deotaland.ai') !== -1 + ? item.image_url + : await this.uploadFile(item.image_url); // let imgurl = 'https://api.deotaland.ai/upload/aabf8b4a8df447fa8c3e3f7978c523cc.png'; params.payload.image_url = imgurl; const response = await requestUtils.common(clientApi.default.IMAGE_TO_3D, params); @@ -28,7 +31,7 @@ export class MeshyServer extends FileServer { // "message": "", // "success": true, // "data": { -// "result": "019abf5d-450f-74f3-bc9c-a6a7e8995fd1" +// "result": "019ac8d3-959d-7432-ac15-b5bab5c75b74" // } // }; if(response.code==0){ @@ -62,6 +65,9 @@ export class MeshyServer extends FileServer { errorCallback&&errorCallback(); break; default: + if(!MeshyServer.pollingEnabled){//如果禁用轮询,直接返回 + return + } // 等待三秒 await new Promise(resolve => setTimeout(resolve, 3000)); progressCallback&&progressCallback(data.progress); diff --git a/packages/utils/src/servers/payserver.js b/packages/utils/src/servers/payserver.js new file mode 100644 index 0000000..f269e3e --- /dev/null +++ b/packages/utils/src/servers/payserver.js @@ -0,0 +1,146 @@ +import { loadStripe } from '@stripe/stripe-js'; +import { requestUtils, clientApi } from '../index'; +//获取Stripe公钥 +export function getStripePublishableKey() { + if (typeof window !== 'undefined') { + // Vite 环境变量 + if (import.meta?.env?.VITE_STRIPE_PUBLISHABLE_KEY) { + return import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY; + } + // 其他环境变量 + return window.VITE_STRIPE_PUBLISHABLE_KEY || ''; + } + // Node.js 环境 + if (typeof process !== 'undefined') { + return process.env.VITE_STRIPE_PUBLISHABLE_KEY || ''; + } +} + +export class PayServer { + static stripe = null// Stripe实例 + constructor() { + } + //初始化 + async init() { + return new Promise(async (resolve) => { + if (!PayServer.stripe) { + await loadStripe(getStripePublishableKey()).then((stripe) => { + PayServer.stripe = stripe; + resolve(PayServer.stripe); + }); + } else { + resolve(PayServer.stripe); + } + }) + } + /** + * 创建支付意图 + * @param {number} amount - 支付金额,单位为分 + * @param {string} currency - 支付币种,默认值为 'usd' + * @param {object} skuinfo - 商品信息 + * @returns {Promise} - 包含 client_secret 和 element 的 Promise 对象 + */ + async createPaymentIntent(amount, currency = 'usd', skuinfo = {}) { + await this.init(); + return new Promise(async (resolve, reject) => { + let pamras = { + amount: amount, + currency: currency, + payment_method_types: ['card', 'alipay', 'wechat_pay', 'link'], + sku_info: skuinfo, + } + // let res = await requestUtils.common(clientApi.default.createPaymentintention,pamras) + let res = { + code: 0, + data: { + client_secret: "pi_3SZS2UG9Oq8PDokQ1mYY4ntx_secret_pAE1H7ySo6EN1XO1lRsj5SWYn", + } + } + if (res.code === 0) { + const Element = PayServer.stripe.elements({ + clientSecret: res.data.client_secret, + appearance: { theme: 'stripe' }, + }); + resolve({ + client_secret: res.data.client_secret, + element: Element, + cardElement: Element.create('payment'), + }) + } else { + reject(res.msg) + } + }) + } + //确认支付 + async confirmPaymentIntent(Element, email) { + await this.init(); + return new Promise(async (resolve, reject) => { + // 确认支付 + const { error, paymentIntent } = await PayServer.stripe.confirmPayment({ + elements: Element, + confirmParams: { + // return_url: `${window.location.origin}/success`, + receipt_email: email,// 可选,用于接收收据的邮箱 + }, + redirect: 'if_required'// 如果需要重定向,Stripe会自动处理 + }); + if (error) { + reject(error.message) + } else if (paymentIntent && paymentIntent.status === 'succeeded') { + resolve({ + paymentIntent: paymentIntent + }) + } + }); + } + //创建会话支付(购物车) + async createCheckoutSession(arrSkus) { + await this.init(); + return new Promise(async (resolve, reject) => { + let pamras = { + payment_method_types: ['card'], + line_items: arrSkus, + success_url: '', + cancel_url: '', + mode: 'payment', + } + let res = await requestUtils.common(clientApi.default.createCheckoutSession, pamras); + if (res.code === 0) { + let { id } = res.data; + const { error } = await PayServer.stripe.redirectToCheckout({ sessionId: id });// 重定向到Stripe Checkout页面 + if (error) { + reject(error.message) + } else { + resolve(id) + } + } else { + reject(res.msg) + } + }) + } + //创建订单并且支付 + async createPayorOrder(orderInfo) { + await this.init(); + return new Promise(async (resolve, reject) => { + let pamras = { + "methods": [ + "card" + ], + "success_url":"https://www.deotaland.ai/#/order-management", + "cancel_url":"https://www.deotaland.ai/#/order-management", + "quantity":orderInfo.quantity, + "project_id": orderInfo.project_id, + "project_details": orderInfo.project_details, + "order_info": orderInfo.order_info + } + let res = await requestUtils.common(clientApi.default.createPayorOrder, pamras); + if (res.code == 0) { + let data = res.data + window.location.href = data.url + resolve(data); + } else { + reject(res.msg) + } + }) + } +} diff --git a/packages/utils/src/utils/request.js b/packages/utils/src/utils/request.js index b00315f..7f6aa4c 100644 --- a/packages/utils/src/utils/request.js +++ b/packages/utils/src/utils/request.js @@ -36,6 +36,7 @@ service.interceptors.request.use( // config.headers['token'] = `${token}`; config.headers['Authorization'] = `123`; config.headers['token'] = `123`; + // config.headers['accept-language'] = 'en'; } return config; }, diff --git a/requestData.md b/requestData.md new file mode 100644 index 0000000..31915d6 --- /dev/null +++ b/requestData.md @@ -0,0 +1,611 @@ + "data": { + "items": [ + { + "id": 20, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T05:36:19.758116+00:00", + "updated_at": "2025-11-28T06:05:04.982516+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 19, + "user_id": 1, + "thumbnail": "", + "title": "蛇系", + "description": "init", + "details": { + "prompt": "", + "node_card": [ + { + "type": "image", + "ipType": "人物", + "prompt": "蛇系美女", + "status": "success", + "zIndex": 112, + "offsetX": "-191px", + "offsetY": "-230px", + "imageUrl": "https://api.deotaland.ai/upload/932512adb172459db6cb1d02c12c842e.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:34:17.545Z", + "project_id": "19", + "inspirationImage": "" + }, + { + "type": "image", + "ipType": "人物", + "prompt": "蛇系美女", + "status": "success", + "zIndex": 117, + "offsetX": "96px", + "offsetY": "-247px", + "imageUrl": "https://api.deotaland.ai/upload/2e58e1dfc1d8459eb2b094e7c1ce459e.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:34:17.546Z", + "project_id": "19", + "inspirationImage": "" + }, + { + "type": "image", + "ipType": "人物", + "prompt": "蛇系美女", + "status": "success", + "zIndex": 121, + "offsetX": "81px", + "offsetY": "212px", + "imageUrl": "https://api.deotaland.ai/upload/9d9d70a5ecbe4e1ab4298d3ad14a8402.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:34:17.546Z", + "project_id": "19", + "inspirationImage": "" + }, + { + "type": "model", + "status": "success", + "taskId": "019ac8f5-c5b2-775e-a36d-19a4651275dd", + "zIndex": 117, + "offsetX": "-223px", + "offsetY": "210px", + "imageUrl": "https://api.deotaland.ai/upload/2e58e1dfc1d8459eb2b094e7c1ce459e.png", + "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8f5-c5b2-775e-a36d-19a4651275dd/output/model.glb?Expires=4917908316&Signature=Vo3dEReKA3MeFRoxQxs6xf0Ypj4lkf8sJiOk43~2M95Mw7qv5WvjPmmKRktj6XIoAlZ4wvtB-zjmhR8rxK9CMR0uRJC~v06e8jDJBViDpjaB5TC6cyvb5EQK8gEPq-lr4p8wfWoNDtO0sX6Zmby~4Z-vhp2wg9Vc4GuzqJ6Uj89qBJgAx-~i2QaGDdRaJ~qqIdCp82zZnNi8Rl-2JEfiMeMWDAUzDbqTG4Hy4yKLsZ2TBQh2YZEJZcwOYdwbwVokFPuAHn-EyIDcxHtUsLqUp8JDB~yALWmuidw1ETQUUPl914T~FfmkWfb0SqVcdIDDkNl-sLwKaxdW8hyCOCYpXA__&Key-Pair-Id=KL5I0C8H7HX83", + "cardWidth": "250", + "timestamp": "2025-11-28T05:35:41.218Z", + "project_id": "19", + "generateFourView": false + } + ], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T05:33:35.161488+00:00", + "updated_at": "2025-11-28T06:05:01.156032+00:00", + "is_delete": 0, + "gemini_success_count": 4, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 1 + }, + { + "id": 18, + "user_id": 1, + "thumbnail": "", + "title": "蛇系", + "description": "init", + "details": { + "prompt": "", + "node_card": [ + { + "type": "image", + "ipType": "人物", + "prompt": "蛇系女孩", + "status": "success", + "zIndex": 102, + "offsetX": "-446px", + "offsetY": "-210px", + "imageUrl": "https://api.deotaland.ai/upload/2ada0aaffd0945b49f67a0da85afc20c.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:29:06.741Z", + "project_id": 18, + "inspirationImage": "" + }, + { + "type": "model", + "status": "success", + "taskId": "019ac8f0-1ff1-78a9-885c-9258854baedb", + "zIndex": 102, + "offsetX": "8px", + "offsetY": "-236px", + "imageUrl": "https://api.deotaland.ai/upload/2ada0aaffd0945b49f67a0da85afc20c.png", + "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8f0-1ff1-78a9-885c-9258854baedb/output/model.glb?Expires=4917907940&Signature=XcZPFzqb3JSAN1hYpm2SU~NXxn40ytlfJv~WkULZGw5ZBnNijdBxAPLYLde-FZaoX8OPHZTexnP6odftS1MXtt9NAv2Q-goLd9ey~iHrl25ihXCdTENYilhXSCi5jQS54I6x-GerPpbfKi-61hxRLO4flLAwqxTMXqueDNVMZBq7sQwJ5CsA~9wft503w2qQZBajBBTykFxUYAGFkSFYPcb5uO7Xw1JTrj3BQ~r7QCzSOAyZmK2hnDKKi160gt425h28351DOW3jP2eiV5TsLaTkJnHyUT7bVX1~JFEuO53R9d~rGwdrToLYb6NW~bkiII218VOb6ZN51oU~SVkKPA__&Key-Pair-Id=KL5I0C8H7HX83", + "cardWidth": "250", + "timestamp": "2025-11-28T05:29:31.065Z", + "project_id": 18, + "generateFourView": false + } + ], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T05:28:39.047049+00:00", + "updated_at": "2025-11-28T05:32:35.485785+00:00", + "is_delete": 0, + "gemini_success_count": 1, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 1 + }, + { + "id": 17, + "user_id": 1, + "thumbnail": "", + "title": "恐龙妹系列", + "description": "init", + "details": { + "prompt": "", + "node_card": [ + { + "type": "image", + "ipType": "人物", + "prompt": "恐龙妹妹", + "status": "success", + "zIndex": 106, + "offsetX": "-409px", + "offsetY": "-336px", + "imageUrl": "https://api.deotaland.ai/upload/580886031063469781f18d6ef7f0b09c.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:24:06.918Z", + "project_id": 17, + "inspirationImage": "" + } + ], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T05:23:50.666448+00:00", + "updated_at": "2025-11-28T05:24:38.270082+00:00", + "is_delete": 0, + "gemini_success_count": 1, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 1 + }, + { + "id": 16, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T05:23:20.613163+00:00", + "updated_at": "2025-11-28T05:23:23.442095+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 15, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T04:16:02.800209+00:00", + "updated_at": "2025-11-28T04:16:02.800209+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 14, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T04:14:37.402123+00:00", + "updated_at": "2025-11-28T04:14:41.210826+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 13, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T04:14:21.190171+00:00", + "updated_at": "2025-11-28T04:14:23.700132+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 12, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T04:14:16.233977+00:00", + "updated_at": "2025-11-28T04:14:18.747615+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 11, + "user_id": 1, + "thumbnail": "", + "title": "猫系玩偶系列", + "description": "init", + "details": { + "prompt": "", + "node_card": [ + { + "type": "image", + "ipType": "人物", + "prompt": "猫女", + "status": "success", + "zIndex": 101, + "offsetX": "92px", + "offsetY": "-395px", + "imageUrl": "https://api.deotaland.ai/upload/526afeec880e46688d6e2edd1893e778.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T04:11:03.655Z", + "project_id": "11", + "inspirationImage": "" + }, + { + "type": "model", + "status": "success", + "taskId": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroo1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83", + "zIndex": 101, + "offsetX": "-263px", + "offsetY": "-388px", + "imageUrl": "https://api.deotaland.ai/upload/526afeec880e46688d6e2edd1893e778.png", + "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8d3-959d-7432-ac15-b5bab5c75b74/output/model.glb?Expires=4917906081&Signature=W88kkM3xYsvvJkiroo1RV141Jfp03bSQCMATSoBQ2gvct87jfWADJTm80rJTDWuNCBmXcF7dt2kDgB2mGWXjQri1DhAwzY87gIp~bJsvtis6UreApFesKHPhU~BkZGfOW2culwT29NTIY~PkTL9Whg8WZ-Mu9xb6Og6iJHol6E4gIX-jGTrXN67~BWsh17kvgSHN5bmackn9IZ~GGwC2threl0BzeFzgpYEkFuP60fkXeCvFAiZBCd0shZR9tuXa4drdzeg7FJMTWpPhhSBPXEFNeDxOpxiLd8iCrQuyu5s1sxNJuGeDcMfTTDh864J0r4-ap11L0LAnWXyHE69-kw__&Key-Pair-Id=KL5I0C8H7HX83", + "cardWidth": "250", + "timestamp": "2025-11-28T04:51:05.620Z", + "project_id": "11", + "generateFourView": false + }, + { + "type": "image", + "ipType": "人物", + "prompt": "猫男", + "status": "success", + "zIndex": 102, + "offsetX": "-232px", + "offsetY": "66px", + "imageUrl": "https://api.deotaland.ai/upload/eacac612e5d34e23bbf26491a00fef20.png", + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T05:19:26.236Z", + "project_id": "11", + "inspirationImage": "" + }, + { + "type": "model", + "status": "success", + "taskId": "019ac8e7-3cf0-7aa8-b441-f94d8d445b88", + "zIndex": 102, + "offsetX": "466px", + "offsetY": "-380px", + "imageUrl": "https://api.deotaland.ai/upload/eacac612e5d34e23bbf26491a00fef20.png", + "modelUrl": "https://api.deotaland.ai/model/ae501c6d-79fd-4bca-9768-f315ee977749/tasks/019ac8e7-3cf0-7aa8-b441-f94d8d445b88/output/model.glb?Expires=4917907360&Signature=HTG71MVGMBj-inbrX4fjHwf3B7xBE~WX~cB--F6cMR-~03fdqyzYKAi-UDt9rnW3md03mJbTiMa2z9z6B~3NP8Exmzp783nfrAQaWfxarDX8iaH9GXPuEePhveXXYmcg2QEBTifPqkU~fwinI6kY9dfNGRTvrp8RHglr3Q8kuhUZS2Mo79OSCg5X-oZ17uEd-uU7hae8IBAPUSiIsbS1dQRjI32WOhDP6NfSZrf304Fn6HMfpyUxbauuPmb7MJ3dQAWUA1g-iBISThp3degZyV4YhhPkxuaBX5t2GlRuibjqmRlUXB2dEasTmKwq-L5zLYbbaVPXg1HfW9zdnuZJ8A__&Key-Pair-Id=KL5I0C8H7HX83", + "cardWidth": "250", + "timestamp": "2025-11-28T05:19:48.668Z", + "project_id": "11", + "generateFourView": false + } + ], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T03:40:04.226536+00:00", + "updated_at": "2025-11-28T05:22:47.719360+00:00", + "is_delete": 0, + "gemini_success_count": 4, + "gemini_failure_count": 2, + "meshy_success_count": 0, + "meshy_failure_count": 3 + }, + { + "id": 10, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T03:39:51.024726+00:00", + "updated_at": "2025-11-28T04:15:11.880275+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 9, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T03:39:18.867572+00:00", + "updated_at": "2025-11-28T03:39:18.867572+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 8, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": "", + "node_card": [ + { + "type": "image", + "ipType": "人物", + "prompt": "111", + "status": "success", + "zIndex": 106, + "offsetX": "-462px", + "offsetY": "-105px", + "imageUrl": 1, + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T03:17:49.448Z", + "inspirationImage": "" + }, + { + "type": "image", + "ipType": "人物", + "prompt": "111", + "status": "success", + "zIndex": 102, + "offsetX": "55px", + "offsetY": "78px", + "imageUrl": 2, + "ipTypeImg": "/src/assets/sketches/tcww.png", + "timestamp": "2025-11-28T03:17:49.448Z", + "inspirationImage": "" + } + ], + "thumbnail": "" + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T03:05:53.897607+00:00", + "updated_at": "2025-11-28T03:39:15.975164+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 7, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": [], + "ip_card": [], + "thumbnail": "", + "model_card": [] + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-28T01:27:47.283082+00:00", + "updated_at": "2025-11-28T01:27:47.283082+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 6, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": [], + "ip_card": [], + "thumbnail": "", + "model_card": [] + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-27T09:38:13.654692+00:00", + "updated_at": "2025-11-27T09:38:13.654692+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 5, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": [], + "ip_card": [], + "thumbnail": "", + "model_card": [] + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-27T09:29:49.987926+00:00", + "updated_at": "2025-11-27T09:29:49.987926+00:00", + "is_delete": 0, + "gemini_success_count": 27, + "gemini_failure_count": 20, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 4, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": [], + "ip_card": [], + "thumbnail": "", + "model_card": [] + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-27T09:29:23.927850+00:00", + "updated_at": "2025-11-27T09:29:23.927850+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + }, + { + "id": 3, + "user_id": 1, + "thumbnail": "", + "title": "project", + "description": "init", + "details": { + "prompt": [], + "ip_card": [], + "thumbnail": "", + "model_card": [] + }, + "tags": [ + "doll" + ], + "duration_seconds": 0, + "created_at": "2025-11-27T09:21:24.768031+00:00", + "updated_at": "2025-11-27T09:21:24.768031+00:00", + "is_delete": 0, + "gemini_success_count": 0, + "gemini_failure_count": 0, + "meshy_success_count": 0, + "meshy_failure_count": 0 + } + ], + "page": 1, + "page_size": 20, + "total": 18 + } \ No newline at end of file