修改经验问题和光栅卡铸造bug修改

This commit is contained in:
zerosaturation 2026-05-16 04:32:43 +08:00
parent d30eda151e
commit cf8671b5bb
9 changed files with 328 additions and 69 deletions

View File

@ -1532,10 +1532,15 @@ func (ctrl *AssetController) BindAssetMaterials(c *gin.Context) {
return
}
if _, exists := c.Get("user_id"); !exists {
userIDVal, exists := c.Get("user_id")
if !exists {
response.Error(c, http.StatusUnauthorized, "未授权")
return
}
uid, _ := userIDVal.(int64)
starIDVal, _ := c.Get("star_id")
sid, _ := starIDVal.(int64)
_ = uid; _ = sid // suppress unused
var req dto.BindAssetMaterialsRequestDTO
if err := c.ShouldBindJSON(&req); err != nil {
@ -1546,6 +1551,11 @@ func (ctrl *AssetController) BindAssetMaterials(c *gin.Context) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{
"user_id": strconv.FormatInt(uid, 10),
"star_id": strconv.FormatInt(sid, 10),
})
materials := make([]*pbAsset.AssetMaterialRelation, 0, len(req.Materials))
for _, item := range req.Materials {
m := &pbAsset.AssetMaterialRelation{

View File

@ -213,7 +213,7 @@ type UploadMaterialRequestDTO struct {
MimeType string `json:"mime_type" binding:"required"`
Width *int `json:"width"`
Height *int `json:"height"`
Hash string `json:"hash" binding:"required"`
Hash string `json:"hash"` // 可选,用于去重
MaterialType string `json:"material_type"`
}

View File

@ -60,3 +60,15 @@ type AssetMaterialRelation struct {
}
func (AssetMaterialRelation) TableName() string { return "asset_material_relations" }
func (r *AssetMaterialRelation) BeforeCreate(tx *gorm.DB) error {
now := time.Now().UnixMilli()
r.CreatedAt = now
r.UpdatedAt = now
return nil
}
func (r *AssetMaterialRelation) BeforeUpdate(tx *gorm.DB) error {
r.UpdatedAt = time.Now().UnixMilli()
return nil
}

View File

@ -232,14 +232,18 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
var asset *models.Asset
err = s.db.Transaction(func(tx *gorm.DB) error {
// 3.0 取出阶段一订单,并校验状态/所有者
logger.Logger.Info("[MintOrder] Step 3.0: 获取订单", zap.String("order_id", req.OrderId), zap.Int64("user_id", userID), zap.Int64("star_id", starID))
existing, err := s.mintOrderRepo.GetByOrderIDAndUser(req.OrderId, userID, starID)
if err != nil {
logger.Logger.Error("[MintOrder] Step 3.0 失败: 获取订单错误", zap.String("order_id", req.OrderId), zap.Error(err))
return fmt.Errorf("order not found: %w", err)
}
logger.Logger.Info("[MintOrder] Step 3.0 查询成功", zap.String("order_id", existing.OrderID), zap.String("status", existing.Status))
if existing.Status != models.MintOrderStatusPending {
return fmt.Errorf("订单状态为%s不能继续铸造", existing.Status)
}
mintOrder = existing
logger.Logger.Info("[MintOrder] Step 3.0 完成: 订单状态", zap.String("status", existing.Status))
// 若阶段二传了字段,则覆盖阶段一的存储值(允许再次编辑元数据)
if req.MaterialUrl != "" {
@ -276,34 +280,27 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
// 3.1 获取铸造消耗配置(阶梯计价)
// 本次铸造是第 currentMintCount+1 次
logger.Logger.Info("[MintOrder] Step 3.1: 获取铸造配置", zap.Int32("current_mint_count", currentMintCount))
mintCost, err := s.GetMintCost(currentMintCount + 1)
if err != nil {
logger.Logger.Error("[MintOrder] Step 3.1 失败", zap.Error(err))
return fmt.Errorf("获取铸造消耗配置失败: %w", err)
}
logger.Logger.Info("[MintOrder] Step 3.1 完成", zap.Int64("cost_crystal", mintCost.CostCrystal))
// 3.2 扣除水晶余额(调用 User Service RPC
logger.Logger.Info("[MintOrder] Step 3.2: 扣除水晶", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Int64("delta", -mintCost.CostCrystal))
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
newBalance, err := s.userClient.UpdateCrystalBalance(ctx, userID, starID, -mintCost.CostCrystal,
"mint_cost", req.OrderId, fmt.Sprintf("铸造藏品 #%s", req.OrderId))
if err != nil {
logger.Logger.Error("Failed to deduct crystal balance",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("cost", mintCost.CostCrystal),
zap.Error(err),
)
logger.Logger.Error("[MintOrder] Step 3.2 失败: 扣除水晶错误", zap.Error(err))
return fmt.Errorf("水晶余额不足或扣除失败: %w", err)
}
logger.Logger.Info("Crystal balance deducted with tiered cost",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Int64("cost", mintCost.CostCrystal),
zap.Int32("mint_count", currentMintCount+1),
zap.Int64("new_balance", newBalance),
)
logger.Logger.Info("[MintOrder] Step 3.2 完成", zap.Int64("new_balance", newBalance))
// 3.3 检查是否触发保底(概率触发)
var boostBps int32 = 0
@ -322,19 +319,19 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
}
// 3.4 更新用户铸爱次数和收益提升
logger.Logger.Info("[MintOrder] Step 3.4: 更新铸爱次数", zap.Int64("user_id", userID), zap.Int64("star_id", starID))
if err := s.UpdateMintCountAndBoost(ctx, tx, userID, starID, boostBps); err != nil {
logger.Logger.Error("Failed to update mint count and boost",
zap.Int64("user_id", userID),
zap.Int64("star_id", starID),
zap.Error(err))
// 不阻断主流程,只记录错误
logger.Logger.Error("[MintOrder] Step 3.4 失败: 更新铸爱次数错误,将回滚事务", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.Error(err))
return fmt.Errorf("failed to update mint count: %w", err) // 回滚事务,不继续
}
// 3.5 创建资产记录状态ActiveCoverURL 直接使用 MaterialURL
logger.Logger.Info("[MintOrder] Step 3.5: 创建资产")
materialURLValue := getStringValue(mintOrder.MaterialURL)
mintedAt := time.Now().UnixMilli()
mockTxHash := fmt.Sprintf("0x%x", sha256.Sum256([]byte(fmt.Sprintf("%s-%d-%d-%d", mintOrder.OrderID, userID, starID, time.Now().UnixNano()))))
mockBlockNumber := int64(time.Now().Unix()) // 使用当前时间戳作为模拟区块号
logger.Logger.Info("[MintOrder] 创建资产参数", zap.Int64("user_id", userID), zap.Int64("star_id", starID), zap.String("name", getStringValue(mintOrder.Name)), zap.String("material_url", materialURLValue))
asset = &models.Asset{
OwnerUID: userID,
StarID: starID,
@ -359,20 +356,15 @@ func (s *mintService) CreateMintOrder(req *pb.CreateMintOrderRequest, userID, st
asset.Tags = models.StringArray(req.Tags)
}
logger.Logger.Info("[MintOrder] 执行 tx.Create(asset)")
if err := tx.Create(asset).Error; err != nil {
logger.Logger.Error("Failed to create asset",
zap.Int64("user_id", userID),
zap.Error(err),
)
logger.Logger.Error("[MintOrder] Step 3.5 失败: 创建资产错误", zap.String("material_url", materialURLValue), zap.Error(err))
return fmt.Errorf("failed to create asset: %w", err)
}
logger.Logger.Info("Asset created",
zap.Int64("asset_id", asset.ID),
zap.Int64("user_id", userID),
)
// 3.3 同步写入 asset_registry普通藏品纳入星册体系
grade := int32(1) // 普通藏品初始等级为1
registry := &models.AssetRegistry{
AssetID: asset.ID,
@ -817,7 +809,7 @@ func (s *mintService) GetUserMintCount(userID, starID int64) (int32, error) {
// 在事务内调用tx 为nil时会创建新事务
func (s *mintService) UpdateMintCountAndBoost(ctx context.Context, tx *gorm.DB, userID, starID int64, boostBps int32) error {
// 获取或创建用户铸爱累计记录
record, isNew, err := s.userMintCountRepo.GetOrCreate(tx, userID, starID)
record, _, err := s.userMintCountRepo.GetOrCreate(tx, userID, starID)
if err != nil {
logger.Logger.Error("Failed to get or create user mint count",
zap.Int64("user_id", userID),
@ -840,14 +832,10 @@ func (s *mintService) UpdateMintCountAndBoost(ctx context.Context, tx *gorm.DB,
}
record.UpdatedAt = time.Now().UnixMilli()
if isNew {
if err := tx.Create(record).Error; err != nil {
return fmt.Errorf("创建用户铸爱累计记录失败: %w", err)
}
} else {
if err := tx.Save(record).Error; err != nil {
return fmt.Errorf("更新用户铸爱累计记录失败: %w", err)
}
// 注意GetOrCreate 已经插入了一条记录(isNew=true),所以这里直接 Save 更新即可
// 不需要再 Create否则会违反 user_id+star_id 唯一索引
if err := tx.Save(record).Error; err != nil {
return fmt.Errorf("更新用户铸爱累计记录失败: %w", err)
}
logger.Logger.Info("Updated mint count and boost",

View File

@ -336,6 +336,42 @@ func (s *revenueService) OnExhibitionCompleted(ctx context.Context, req *pb.OnEx
return &pb.OnExhibitionCompletedResponse{Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL_ERROR}}, err
}
// 增加用户累计上架时长(展位主人获得上架时长累计)
// 计算实际上架时长(毫秒转小时)
startTime := req.StartTime
expireAt := req.ExpireAt
actualHours := (expireAt - startTime) / 3600000
if actualHours < 1 {
actualHours = 1
}
// sourceID 用于去重,避免重复累计
sourceID := fmt.Sprintf("exhibition_%d", req.ExhibitionId)
newLevel, levelDelta, crystalReward, err := s.userRPCClient.AddExhibitionHours(
ctx,
req.SlotOwnerUid,
req.OccupierStarId,
actualHours,
sourceID,
)
if err != nil {
logger.Logger.Error("OnExhibitionCompleted: AddExhibitionHours failed",
zap.Int64("slot_owner_uid", req.SlotOwnerUid),
zap.Int64("hours", actualHours),
zap.Error(err))
// 不返回错误,因为收益记录已创建
} else if levelDelta > 0 {
logger.Logger.Info("OnExhibitionCompleted: 展位主人累计上架时长触发升级",
zap.Int64("slot_owner_uid", req.SlotOwnerUid),
zap.Int64("exhibition_id", req.ExhibitionId),
zap.Int64("hours", actualHours),
zap.Int32("old_level", newLevel-levelDelta),
zap.Int32("new_level", newLevel),
zap.Int32("level_delta", levelDelta),
zap.Int64("crystal_reward", crystalReward))
}
logger.Logger.Info("OnExhibitionCompleted: success",
zap.Int64("exhibition_id", req.ExhibitionId),
zap.Int64("revenue_record_id", createdRecord.ID))

View File

@ -753,6 +753,12 @@ const handleCraftMint = async () => {
const bgImagePath = isCraftLenticular.value
? lenticularLayers.value.find((l) => l.id === 'base')?.src || ''
: undefined;
console.log('[asset-detail] handleCraftMint', {
isCraftLenticular: isCraftLenticular.value,
bgImagePath: bgImagePath,
lenticularLayers: lenticularLayers.value.map(l => ({ id: l.id, src: l.src ? l.src.substring(0, 30) : '' })),
craftFormDataKeys: Object.keys(craftFormData.value || {})
})
craftMinting.value = true;
uni.showLoading({ title: '铸造中…', mask: true });
try {
@ -781,7 +787,15 @@ onLoad((options) => {
assetIdParam.value = options?.asset_id || '';
orderIdParam.value = options?.order_id || '';
fromParam.value = options?.from || '';
studioKindParam.value = options?.studio_kind || '';
loadCurrentUser();
// craft_confirm loadData
if (fromParam.value === 'craft_confirm') {
console.log('[asset-detail] craft_confirm 模式,加载本地数据');
craftConfirmMode.value = true;
loadCraftConfirm();
}
});
onShow(() => {

View File

@ -83,7 +83,7 @@
>
<text class="button-text">重新生成</text>
</view>
<view class="action-button" @click="selectAsset" @tap="selectAsset">
<view class="action-button" @tap="selectAsset">
<text class="button-text">{{ primaryActionLabel }}</text>
</view>
</view>
@ -717,8 +717,10 @@ const selectAsset = async () => {
const formDataStr = uni.getStorageSync(CASTLOVE_FORM_KEY);
const orderValue = formDataStr ? JSON.parse(formDataStr) : craftFormData.value || {};
const selectedImage = getSelectedImageUrl();
console.log('1')
if (isDetailAfterSelect(orderValue)) {
console.log('1')
isUploading.value = true;
uni.showLoading({ title: '准备中…', mask: true });
try {

View File

@ -558,6 +558,24 @@ export function getMintOrderDetailApi(orderId) {
})
}
// 上传素材(返回 material_id
export function uploadMaterialApi(data) {
return request({
url: '/api/v1/assets/materials/upload',
method: 'POST',
data
})
}
// 绑定资产素材关联
export function bindAssetMaterialsApi(assetId, materials) {
return request({
url: `/api/v1/assets/${assetId}/materials`,
method: 'POST',
data: { materials }
})
}
// 图生图
export function imageGenerationApi(params) {
return request({

View File

@ -1,4 +1,4 @@
import { getOssSignatureApi, createMintOrderApi } from '@/utils/api.js'
import { getOssSignatureApi, createMintOrderApi, uploadMaterialApi, bindAssetMaterialsApi } from '@/utils/api.js'
import { resolveH5OssPostUrl } from '@/utils/h5OssPostUrl.js'
import { buildCastloveFormSnapshot } from '@/utils/castloveMintForm.js'
@ -32,26 +32,96 @@ function uploadFileToOss(tempFilePath, ossData) {
}
/**
* 铸爱确认页上传选中图并创建铸造订单
* @param {{ imagePath: string, bgImagePath?: string, formData: object }} opts
* 上传文件到 OSS 并返回文件大小用于注册素材
*/
export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData }) {
const path = String(imagePath || '').trim()
if (!path) {
throw new Error('缺少作品图片')
}
const signRes = await getOssSignatureApi('asset')
if (!signRes || signRes.code !== 200 || !signRes.data) {
throw new Error(signRes?.message || '获取签名失败')
}
const ossData = signRes.data
const orderId = ossData.order_id || ''
function uploadFileToOssWithInfo(tempFilePath, ossData) {
return new Promise((resolve, reject) => {
const fileName = `${Date.now()}.jpg`
uni.uploadFile({
url: ossData.host,
filePath: tempFilePath,
name: 'file',
formData: {
key: ossData.dir + fileName,
policy: ossData.policy,
success_action_status: '200',
'x-oss-credential': ossData.x_oss_credential,
'x-oss-date': ossData.x_oss_date,
'x-oss-security-token': ossData.security_token,
'x-oss-signature': ossData.signature,
'x-oss-signature-version': ossData.x_oss_signature_version,
},
success: async (res) => {
if (res.statusCode === 200 || res.statusCode === 204) {
const url = `${ossData.host}/${ossData.dir}${fileName}`
// 获取文件大小
let size = 0
try {
const fileInfo = await new Promise((res2, rej) => {
uni.getFileInfo({
filePath: tempFilePath,
success: (info) => res2(info.size),
fail: rej
})
})
size = fileInfo || 0
} catch (e) {
console.warn('[craftMintSubmit] getFileInfo failed', e)
}
resolve({ url, size, hash: '' })
} else {
reject(new Error(`上传失败 ${res.statusCode}`))
}
},
fail: reject,
})
})
}
let imageUrl = path
if (!path.startsWith('http')) {
/**
* 计算文件 SHA256 哈希使用 Web Crypto API
*/
async function computeFileHash(file) {
if (typeof crypto !== 'undefined' && crypto.subtle) {
const buffer = await file.arrayBuffer()
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
const hashArray = Array.from(new Uint8Array(hashBuffer))
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
}
// Fallback: 返回空字符串,后端会重新计算
return ''
}
/**
* 获取文件 MIME 类型
*/
function getFileMimeType(filePath) {
const ext = filePath?.toLowerCase().split('.').pop()
if (ext === 'png') return 'image/png'
return 'image/jpeg'
}
/**
* 上传图片到 OSS 并注册为素材
* @param {string} imagePath - 图片路径或 base64
* @param {object} ossData - OSS 签名数据
* @param {string} originalName - 原始文件名
* @param {string} materialType - 素材类型 (main/bg/sticker/patch )
* @returns {Promise<{ ossKey: string, materialId: number }>}
*/
async function uploadImageAndRegisterMaterial(imagePath, ossData, originalName, materialType) {
console.log('[craftMintSubmit] uploadImageAndRegisterMaterial', { materialType, imagePath: String(imagePath || '').substring(0, 50) })
// 1. 上传图片到 OSS
let imageUrl = imagePath
let uploadFileSize = 0
let uploadHash = ''
if (!imagePath.startsWith('http')) {
// #ifdef H5
if (path.startsWith('data:')) {
const blob = await fetch(path).then((r) => r.blob())
if (imagePath.startsWith('data:')) {
console.log('[craftMintSubmit] H5 base64 upload')
const blob = await fetch(imagePath).then((r) => r.blob())
uploadFileSize = blob.size
uploadHash = await computeFileHash(blob)
const fd = new FormData()
const fileName = `${Date.now()}.jpg`
fd.append('key', ossData.dir + fileName)
@ -64,18 +134,73 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData
fd.append('x-oss-signature-version', ossData.x_oss_signature_version)
fd.append('file', blob, fileName)
const res = await fetch(resolveH5OssPostUrl(ossData.host), { method: 'POST', body: fd })
console.log('[craftMintSubmit] OSS upload res:', res.status, res.ok)
if (!res.ok && res.status !== 204) {
throw new Error('上传失败')
}
imageUrl = `${ossData.host}/${ossData.dir}${fileName}`
} else {
imageUrl = await uploadFileToOss(path, ossData)
console.log('[craftMintSubmit] 小程序/非H5上传')
const uploadResult = await uploadFileToOssWithInfo(imagePath, ossData)
imageUrl = uploadResult.url
uploadFileSize = uploadResult.size || 0
uploadHash = uploadResult.hash || ''
}
// #endif
// #ifndef H5
imageUrl = await uploadFileToOss(path, ossData)
const uploadResult = await uploadFileToOssWithInfo(imagePath, ossData)
imageUrl = uploadResult.url
uploadFileSize = uploadResult.size || 0
uploadHash = uploadResult.hash || ''
// #endif
}
console.log('[craftMintSubmit] imageUrl:', imageUrl, 'fileSize:', uploadFileSize, 'hash:', uploadHash.substring(0, 20))
// 2. 构建 oss_key去掉 host 前缀)
const ossKey = imageUrl.replace(/^https?:\/\/[^/]+\/(.+)$/, '$1')
// 3. 获取 MIME 类型
const mimeType = getFileMimeType(imagePath)
// 4. 注册素材到后端
const materialRes = await uploadMaterialApi({
oss_key: ossKey,
original_name: originalName || `${materialType}.jpg`,
file_size: uploadFileSize,
mime_type: mimeType,
hash: uploadHash,
material_type: materialType,
})
if (!materialRes || materialRes.code !== 200 || !materialRes.data) {
throw new Error(materialRes?.message || '素材注册失败')
}
const materialId = materialRes.data.material_id
return { ossKey, materialId }
}
/**
* 铸爱确认页上传选中图并创建铸造订单支持多素材
* @param {{ imagePath: string, bgImagePath?: string, formData: object }} opts
*/
export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData }) {
console.log('[craftMintSubmit] start', { imagePath: String(imagePath || '').trim().substring(0, 50), bgImagePath: !!bgImagePath, formDataKeys: Object.keys(formData || {}) })
const path = String(imagePath || '').trim()
if (!path) {
throw new Error('缺少作品图片')
}
// 1. 获取 OSS 签名
console.log('[craftMintSubmit] 1. 获取 OSS 签名')
const signRes = await getOssSignatureApi('asset')
console.log('[craftMintSubmit] OSS 签名响应:', signRes)
if (!signRes || signRes.code !== 200 || !signRes.data) {
throw new Error(signRes?.message || '获取签名失败')
}
const ossData = signRes.data
const orderId = ossData.order_id || ''
const snap = buildCastloveFormSnapshot({
nftInfo: formData.info || formData.nftInfo || '',
@ -85,23 +210,32 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData
uploadedImage: path,
uploadedImageBase64: formData.imageBase64 || '',
})
console.log('[craftMintSubmit] snap:', { name: snap.name, tags: snap.tags, isLenticular: snap.tags?.includes('craft:lenticular') })
const isLenticular = Array.isArray(snap.tags) && snap.tags.includes('craft:lenticular')
console.log('[craftMintSubmit] isLenticular:', isLenticular)
let bgUrl = ''
// 2. 上传主图并注册为主素材
console.log('[craftMintSubmit] 2. 上传主图并注册')
const mainResult = await uploadImageAndRegisterMaterial(path, ossData, originalFileName(path), 'main')
console.log('[craftMintSubmit] mainResult:', mainResult)
// 3. 如果是光栅卡,上传背景图并注册为 bg 素材
let bgMaterialId = null
if (isLenticular && bgImagePath) {
const bgPath = String(bgImagePath || '').trim()
if (bgPath && !bgPath.startsWith('http')) {
bgUrl = await uploadFileToOss(bgPath, ossData)
} else if (bgPath) {
bgUrl = bgPath
if (bgPath) {
console.log('[craftMintSubmit] 3. 上传背景图并注册')
bgMaterialId = (await uploadImageAndRegisterMaterial(bgPath, ossData, originalFileName(bgPath), 'bg')).materialId
}
}
const materialUrl = isLenticular && bgUrl
? JSON.stringify({ main: imageUrl, bg: bgUrl })
: imageUrl
// 4. 构建 material_url使用主素材 oss_key 作为封面)
// 后端铸造时会自动使用此 URL 生成封面图
const materialUrl = mainResult.ossKey
// 5. 创建铸造订单(后端自动扣除水晶消耗)
console.log('[craftMintSubmit] 5. 创建铸造订单', { orderId, materialUrl })
const orderData = {
order_id: orderId,
name: snap.name,
@ -112,8 +246,10 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData
material_type: snap.material_type,
info: snap.info || '',
}
console.log('[craftMintSubmit] orderData:', orderData)
const response = await createMintOrderApi(orderData)
console.log('[craftMintSubmit] createMintOrderApi 响应:', response)
if (!response || response.code !== 200) {
throw new Error(response?.message || '创建订单失败')
}
@ -124,8 +260,34 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData
response.data?.assetId ||
''
if (!assetId) {
throw new Error('创建订单成功但未返回资产 ID')
}
console.log('[craftMintSubmit] assetId:', assetId)
// 6. 绑定多素材到资产
const materialsToBind = [
{ material_id: mainResult.materialId, material_type: 'main', layer_order: 0 }
]
if (bgMaterialId) {
materialsToBind.push({ material_id: bgMaterialId, material_type: 'bg', layer_order: 1 })
}
console.log('[craftMintSubmit] 6. 绑定多素材:', materialsToBind)
try {
const bindRes = await bindAssetMaterialsApi(assetId, materialsToBind)
console.log('[craftMintSubmit] bindRes:', bindRes)
if (!bindRes || bindRes.code !== 200) {
console.warn('[craftMintSubmit] bind materials failed, but mint order created:', bindRes?.message)
}
} catch (e) {
console.warn('[craftMintSubmit] bind materials error:', e)
// 不阻断铸造流程,资产已创建成功
}
// 7. 构建 NFT 数据并跳转
const nftData = {
image: imageUrl,
image: mainResult.ossKey, // oss_key
name: snap.name,
description: snap.description || '',
material_type: snap.material_type,
@ -134,9 +296,26 @@ export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData
asset_id: assetId,
info: snap.info,
event: snap.info,
...(isLenticular && bgUrl ? { bg_image: bgUrl } : {}),
...(bgMaterialId ? { bg_material_id: bgMaterialId } : {}),
}
uni.setStorageSync('temp_nft_data', JSON.stringify(nftData))
uni.removeStorageSync('castlove_form_data')
return { imageUrl, orderId }
console.log('[craftMintSubmit] 完成')
return { imageUrl: mainResult.ossKey, orderId }
}
/**
* 从文件路径提取原始文件名
*/
function originalFileName(filePath) {
if (!filePath) return ''
try {
if (filePath.startsWith('data:')) {
return 'image.jpg'
}
return filePath.split('/').pop() || 'image.jpg'
} catch {
return 'image.jpg'
}
}