# 光栅卡铸造后写入 asset_registry 方案 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 确保光栅卡铸造完成后:1) 两张图片(main 主图 + bg 背景图)都注册到 `materials` 表;2) `asset_registry` 有记录;3) `asset_material_relations` 正确绑定两组图片 **Architecture:** - 前端在 `lenticular-result.vue` 的 `selectAsset` 中调用 `submitCraftMintFromPath` 完成上传和铸造 - `submitCraftMintFromPath` 已经支持:上传主图(main) + 背景图(bg)、注册素材、创建订单、绑定多素材 - 问题:`lenticular-result.vue` 引用了未定义的 `isCraftLenticular` 和 `craftCoverUrl`,导致铸造流程断裂 **Tech Stack:** Vue3 + uni-app 前端,Go 后端,Supabase/PostgreSQL --- ## 数据库设计解析 ### 核心表关系 ``` asset_registry ← 资产索引表(用户有哪些藏品) │ asset_id ▼ assets ← 资产表(名称、封面、描述) │ id ▼ asset_material_relations ← 资产-素材关联表(同一资产有哪些图片) │ asset_id, material_id ▼ materials ← 素材表(OSS key、MIME、尺寸) ``` ### `asset_registry` 表结构(不存图片) ```sql CREATE TABLE public.asset_registry ( id bigint, asset_id bigint, -- 关联 assets.ID asset_type varchar(20), -- 'regular'|'collection'|'activity' owner_uid bigint, star_id bigint, grade integer, status integer, -- 0=待处理, 1=已激活 like_count integer, display_status integer, created_at bigint, updated_at bigint ); ``` **注意:** `asset_registry` 只存储资产索引和元数据,**不存储任何图片 URL**。 ### 图片通过 `asset_material_relations` 关联 光栅卡有**两张图片**(main 主图 + bg 背景图),通过 `asset_material_relations` 表实现多对一关联: ```sql -- 同一个 asset_id = 123 的光栅卡,有两条素材记录 INSERT INTO asset_material_relations (asset_id, material_id, material_type, layer_order) VALUES (123, 456, 'main', 0), -- 主图(人物层) (123, 789, 'bg', 1); -- 背景图 -- materials 表存储实际图片信息 INSERT INTO materials (id, oss_key, original_name, file_size, mime_type, hash, ...) VALUES (456, 'asset/7/88/main/xxx.jpg', 'subject.jpg', 102400, 'image/jpeg', 'abc123', ...), (789, 'asset/7/88/bg/xxx.jpg', 'bg.png', 204800, 'image/png', 'def456', ...); ``` ### 封面图来源 铸造时,前端传入的 `material_url`(main 素材的 oss_key)会被设为 `assets.CoverURL`,用于列表页展示: ``` craftMintSubmit.js → createMintOrderApi({ material_url: main_oss_key }) ↓ mint_service.go:339 assets.CoverURL = materialURLValue ↓ 前端列表页通过 assets.CoverURL 获取封面图 ``` ### 完整数据流 ``` submitCraftMintFromPath({ imagePath, bgImagePath, formData }) │ ├─ uploadImageAndRegisterMaterial(imagePath, 'main') → materials.id=456 ├─ uploadImageAndRegisterMaterial(bgImagePath, 'bg') → materials.id=789 │ ├─ createMintOrderApi({ material_url: main_oss_key }) │ ↓ │ mint_service.go │ ├── tx.Create(asset) → CoverURL = main_oss_key │ ├── tx.Create(registry) → asset_registry (status=1) │ └── 更新订单状态为 SUCCESS │ └─ bindAssetMaterialsApi(asset_id, [ { material_id: 456, material_type: 'main', layer_order: 0 }, { material_id: 789, material_type: 'bg', layer_order: 1 } ]) ↓ asset_material_relations 写入两条记录 ``` --- ## 问题分析 ### 当前代码问题(`lenticular-result.vue` 第 596-623 行) ```javascript const selectAsset = async () => { // line 596-625 是完整代码块 const imagePath = isCraftLenticular.value // ❌ isCraftLenticular 未定义 ? lenticularLayers.value.find((l) => l.id === 'mid')?.src || craftCoverUrl.value : craftCoverUrl.value; // ❌ craftCoverUrl 未定义 if (!imagePath) { ... } const bgImagePath = isCraftLenticular.value // ❌ isCraftLenticular 未定义 ? lenticularLayers.value.find((l) => l.id === 'base')?.src || '' : undefined; try { await submitCraftMintFromPath({ imagePath, bgImagePath, formData: craftFormData.value }); uni.navigateTo({ url: '/pages/castlove/success' }); return; // line 623 return 后不会执行 } catch (e) { ... } // 以下代码 dead code(return 后的逻辑) if (!isLenticularDisplay.value && selectedIndex.value === -1) { ... } if (isUploading.value) { ... } // ... 上传到 OSS 并创建订单的旧代码 ... }; ``` ### 现有正确流程(`craftMintSubmit.js` 的 `submitCraftMintFromPath`) ```javascript // 第 187-306 行 export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData }) { // 1. 获取 OSS 签名 // 2. 上传主图并注册为 'main' 素材 // 3. 如果是光栅卡,上传背景图并注册为 'bg' 素材 // 4. 创建铸造订单(后端自动写入 asset_registry) // 5. 绑定多素材到资产(main + bg) // 6. 返回 } ``` 铸造流程本身是正确的,问题在前端调用方。 --- ## 文件结构 | 文件 | 修改类型 | 职责 | |------|----------|------| | `frontend/pages/castlove/lenticular/lenticular-result.vue` | 修改 | 修复 `selectAsset` 函数,添加缺失的 computed | | `frontend/utils/craftMintSubmit.js` | 已有 | 铸造流程(已正确,无需修改) | --- ## Task 1: 修复 `lenticular-result.vue` 的 `selectAsset` 函数 **Files:** - Modify: `frontend/pages/castlove/lenticular/lenticular-result.vue:595-625` - [ ] **Step 1: 确认缺失的 import 并添加** 在 `