572 lines
21 KiB
JavaScript
572 lines
21 KiB
JavaScript
import { getOssSignatureApi, createMintOrderApi, uploadMaterialApi, bindAssetMaterialsApi } from '@/utils/api.js'
|
||
import { resolveH5OssPostUrl } from '@/utils/h5OssPostUrl.js'
|
||
import { buildCastloveFormSnapshot } from '@/utils/castloveMintForm.js'
|
||
|
||
function uploadFileToOss(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: (res) => {
|
||
if (res.statusCode === 200 || res.statusCode === 204) {
|
||
resolve(`${ossData.host}/${ossData.dir}${fileName}`)
|
||
} else {
|
||
reject(new Error(`上传失败 ${res.statusCode}`))
|
||
}
|
||
},
|
||
fail: reject,
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 上传文件到 OSS 并返回文件大小和 hash(用于注册素材)
|
||
*/
|
||
function uploadFileToOssWithInfo(tempFilePath, ossData) {
|
||
return new Promise((resolve, reject) => {
|
||
console.log('[craftMintSubmit] uploadFileToOssWithInfo tempFilePath:', tempFilePath)
|
||
const fileName = `${Date.now()}.jpg`
|
||
uni.uploadFile({
|
||
url: resolveH5OssPostUrl(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) => {
|
||
console.log('[craftMintSubmit] uploadFileToOssWithInfo statusCode:', res.statusCode)
|
||
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)
|
||
}
|
||
// 计算文件 hash
|
||
const hash = await computeHashFromPath(tempFilePath)
|
||
console.log('[craftMintSubmit] uploadFileToOssWithInfo hash:', hash ? hash.substring(0, 10) + '...' : 'empty')
|
||
resolve({ url, size, hash })
|
||
} else {
|
||
reject(new Error(`上传失败 ${res.statusCode}`))
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('[craftMintSubmit] uploadFileToOssWithInfo fail:', err)
|
||
reject(err)
|
||
},
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 纯 JavaScript SHA256 实现(用于 App 等不支持 crypto.subtle 的环境)
|
||
*/
|
||
function sha256Sync(str) {
|
||
// SHA256 实现
|
||
const rotateRight = (n, s) => (n >>> s) | ((n << (32 - s)) >>> 0)
|
||
const choice = (x, y, z) => (x & y) ^ (~x & z)
|
||
const majority = (x, y, z) => (x & y) ^ (x & z) ^ (y & z)
|
||
const maj = (x, y, z) => (x & y) ^ (x & z) ^ (y & z)
|
||
const ch = (x, y, z) => (x & y) ^ (~x & z)
|
||
|
||
const K = [
|
||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||
0x49b40821, 0xfef9e05c, 0x4fd9c4a7, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814,
|
||
0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||
]
|
||
|
||
// 转换为 bytes
|
||
const msgBytes = []
|
||
for (let i = 0; i < str.length; i++) {
|
||
const code = str.charCodeAt(i)
|
||
if (code < 0x80) {
|
||
msgBytes.push(code)
|
||
} else if (code < 0x800) {
|
||
msgBytes.push(0xc0 | (code >> 6))
|
||
msgBytes.push(0x80 | (code & 0x3f))
|
||
} else if (code < 0xd800 || code >= 0xe000) {
|
||
msgBytes.push(0xe0 | (code >> 12))
|
||
msgBytes.push(0x80 | ((code >> 6) & 0x3f))
|
||
msgBytes.push(0x80 | (code & 0x3f))
|
||
} else {
|
||
const cp = 0x10000 + ((code - 0xd800) << 10) | (str.charCodeAt(++i) - 0xdc00)
|
||
msgBytes.push(0xf0 | (cp >> 18))
|
||
msgBytes.push(0x80 | ((cp >> 12) & 0x3f))
|
||
msgBytes.push(0x80 | ((cp >> 6) & 0x3f))
|
||
msgBytes.push(0x80 | (cp & 0x3f))
|
||
}
|
||
}
|
||
|
||
// Padding
|
||
const msgLen = msgBytes.length
|
||
const bitLen = msgLen * 8
|
||
msgBytes.push(0x80)
|
||
while ((msgBytes.length % 64) !== 56) msgBytes.push(0)
|
||
for (let i = 7; i >= 0; i--) msgBytes.push((bitLen >>> (i * 8)) & 0xff)
|
||
|
||
// Initialize hash values
|
||
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a
|
||
let h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19
|
||
|
||
// Process blocks
|
||
for (let chunk = 0; chunk < msgBytes.length / 64; chunk++) {
|
||
const w = new Array(64)
|
||
for (let j = 0; j < 16; j++) {
|
||
w[j] = (msgBytes[chunk * 64 + j * 4] << 24) | (msgBytes[chunk * 64 + j * 4 + 1] << 16) |
|
||
(msgBytes[chunk * 64 + j * 4 + 2] << 8) | msgBytes[chunk * 64 + j * 4 + 3]
|
||
}
|
||
for (let j = 16; j < 64; j++) {
|
||
const s0 = rotateRight(w[j - 15], 7) ^ rotateRight(w[j - 15], 18) ^ (w[j - 15] >>> 3)
|
||
const s1 = rotateRight(w[j - 2], 17) ^ rotateRight(w[j - 2], 19) ^ (w[j - 2] >>> 10)
|
||
w[j] = (w[j - 16] + s0 + w[j - 7] + s1) >>> 0
|
||
}
|
||
let [ah0, ah1, ah2, ah3, ah4, ah5, ah6, ah7] = [h0, h1, h2, h3, h4, h5, h6, h7]
|
||
for (let j = 0; j < 64; j++) {
|
||
const S1 = rotateRight(ah6, 6) ^ rotateRight(ah6, 11) ^ rotateRight(ah6, 25)
|
||
const ch2 = ch(ah6, ah5, ah4)
|
||
const temp1 = (ah7 + S1 + ch2 + K[j] + w[j]) >>> 0
|
||
const S0 = rotateRight(ah0, 2) ^ rotateRight(ah0, 13) ^ rotateRight(ah0, 22)
|
||
const maj2 = maj(ah0, ah1, ah2)
|
||
const temp2 = (S0 + maj2) >>> 0
|
||
ah7 = (ah6 + temp1) >>> 0
|
||
ah6 = (ah5 + temp1) >>> 0
|
||
ah5 = (ah4 + temp1) >>> 0
|
||
ah4 = (ah3 + temp1) >>> 0
|
||
ah3 = (ah2 + temp1) >>> 0
|
||
ah2 = (ah1 + temp1) >>> 0
|
||
ah1 = (ah0 + temp1) >>> 0
|
||
ah0 = (temp1 + temp2) >>> 0
|
||
}
|
||
h0 = (h0 + ah0) >>> 0; h1 = (h1 + ah1) >>> 0; h2 = (h2 + ah2) >>> 0; h3 = (h3 + ah3) >>> 0
|
||
h4 = (h4 + ah4) >>> 0; h5 = (h5 + ah5) >>> 0; h6 = (h6 + ah6) >>> 0; h7 = (h7 + ah7) >>> 0
|
||
}
|
||
|
||
const toHex = (n) => n.toString(16).padStart(8, '0')
|
||
return toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4) + toHex(h5) + toHex(h6) + toHex(h7)
|
||
}
|
||
|
||
/**
|
||
* 计算文件 SHA256 哈希(使用 Web Crypto API)
|
||
*/
|
||
async function computeFileHash(file) {
|
||
console.log('[craftMintSubmit] computeFileHash start, file type:', typeof file, file instanceof Blob ? `Blob(size=${file.size})` : '')
|
||
try {
|
||
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
||
const buffer = await file.arrayBuffer()
|
||
console.log('[craftMintSubmit] computeFileHash arrayBuffer length:', buffer.byteLength)
|
||
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
|
||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||
const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||
console.log('[craftMintSubmit] computeFileHash result:', hash.substring(0, 20) + '...')
|
||
return hash
|
||
} else {
|
||
console.warn('[craftMintSubmit] computeFileHash: crypto.subtle not available')
|
||
}
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] computeFileHash failed:', e)
|
||
}
|
||
// Fallback: 返回空字符串,后端会重新计算
|
||
return ''
|
||
}
|
||
|
||
/**
|
||
* 从文件路径读取内容并计算 hash(支持各端)
|
||
*/
|
||
async function computeHashFromPath(filePath) {
|
||
console.log('[craftMintSubmit] computeHashFromPath start, path:', filePath ? filePath.substring(0, 80) : 'empty')
|
||
try {
|
||
// 处理 data:URL 格式
|
||
if (filePath && filePath.startsWith('data:')) {
|
||
console.log('[craftMintSubmit] computeHashFromPath data:URL format')
|
||
try {
|
||
const base64Data = filePath.split(',')[1] || ''
|
||
const binaryStr = atob(base64Data)
|
||
// 优先用 crypto.subtle,失败则用纯JS
|
||
if (typeof crypto !== 'undefined' && crypto.subtle) {
|
||
const bytes = new Uint8Array(binaryStr.length)
|
||
for (let i = 0; i < binaryStr.length; i++) {
|
||
bytes[i] = binaryStr.charCodeAt(i)
|
||
}
|
||
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes)
|
||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||
const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||
console.log('[craftMintSubmit] computeHashFromPath data:URL hash:', hash.substring(0, 20) + '...')
|
||
return hash
|
||
} else {
|
||
// Fallback 纯JS SHA256
|
||
const hash = sha256Sync(binaryStr)
|
||
console.log('[craftMintSubmit] computeHashFromPath data:URL sha256Sync hash:', hash.substring(0, 20) + '...')
|
||
return hash
|
||
}
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] data:URL hash failed:', e)
|
||
}
|
||
return ''
|
||
}
|
||
|
||
let content = ''
|
||
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||
console.log('[craftMintSubmit] computeHashFromPath in miniprogram')
|
||
try {
|
||
const fs = uni.getFileSystemManager()
|
||
content = await new Promise((resolve, reject) => {
|
||
fs.readFile({
|
||
filePath: filePath,
|
||
encoding: 'base64',
|
||
success: (res) => {
|
||
console.log('[craftMintSubmit] fs.readFile success, data length:', res.data?.length)
|
||
resolve(res.data)
|
||
},
|
||
fail: (err) => {
|
||
console.error('[craftMintSubmit] fs.readFile fail:', err)
|
||
reject(err)
|
||
}
|
||
})
|
||
})
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] 小程序读取文件失败:', e)
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
if (!content) {
|
||
console.log('[craftMintSubmit] computeHashFromPath in app-plus')
|
||
try {
|
||
content = await new Promise((resolve, reject) => {
|
||
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
|
||
entry.file((file) => {
|
||
const reader = new plus.io.FileReader()
|
||
reader.onloadend = (e) => {
|
||
console.log('[craftMintSubmit] FileReader onloadend, result length:', e.target.result?.length)
|
||
resolve(e.target.result.split(',')[1] || '')
|
||
}
|
||
reader.onerror = (e) => {
|
||
console.error('[craftMintSubmit] FileReader error:', e)
|
||
reject(e)
|
||
}
|
||
reader.readAsDataURL(file)
|
||
}, (err) => {
|
||
console.error('[craftMintSubmit] getFile error:', err)
|
||
reject(err)
|
||
})
|
||
}, (err) => {
|
||
console.error('[craftMintSubmit] resolveLocalFileSystemURL error:', err)
|
||
reject(err)
|
||
})
|
||
})
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] App读取文件失败:', e)
|
||
}
|
||
}
|
||
// #endif
|
||
|
||
console.log('[craftMintSubmit] computeHashFromPath content length:', content?.length || 0)
|
||
console.log('[craftMintSubmit] computeHashFromPath crypto:', typeof crypto, crypto ? 'exists' : 'null', ', subtle:', crypto?.subtle ? 'exists' : 'null')
|
||
if (content && typeof crypto !== 'undefined' && crypto && crypto.subtle) {
|
||
try {
|
||
// 将 base64 转换为 ArrayBuffer 并计算 hash
|
||
const binary = atob(content)
|
||
const bytes = new Uint8Array(binary.length)
|
||
for (let i = 0; i < binary.length; i++) {
|
||
bytes[i] = binary.charCodeAt(i)
|
||
}
|
||
const hashBuffer = await crypto.subtle.digest('SHA-256', bytes)
|
||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||
const hash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
|
||
console.log('[craftMintSubmit] computeHashFromPath hash:', hash.substring(0, 20) + '...')
|
||
return hash
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] hash计算失败:', e)
|
||
}
|
||
} else {
|
||
// Fallback: 使用纯JS SHA256 实现 (App 环境 crypto.subtle 可能不可用)
|
||
try {
|
||
console.log('[craftMintSubmit] computeHashFromPath 使用纯JS SHA256 fallback, content length:', content.length)
|
||
// content 是 base64 字符串,需要先解码为 binary string
|
||
const binaryStr = atob(content)
|
||
const hash = sha256Sync(binaryStr)
|
||
console.log('[craftMintSubmit] computeHashFromPath sha256Sync hash:', hash.substring(0, 20) + '...')
|
||
return hash
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] SHA256 fallback failed:', e)
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('[craftMintSubmit] computeHashFromPath failed:', e)
|
||
}
|
||
console.log('[craftMintSubmit] computeHashFromPath return empty')
|
||
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 (imagePath.startsWith('data:')) {
|
||
console.log('[craftMintSubmit] H5 base64 upload')
|
||
// H5: data:URL 需要手动转换 为 Blob
|
||
const base64Data = imagePath.split(',')[1] || ''
|
||
const binaryStr = atob(base64Data)
|
||
const bytes = new Uint8Array(binaryStr.length)
|
||
for (let i = 0; i < binaryStr.length; i++) {
|
||
bytes[i] = binaryStr.charCodeAt(i)
|
||
}
|
||
const blob = new Blob([bytes], { type: 'image/jpeg' })
|
||
uploadFileSize = blob.size
|
||
uploadHash = await computeFileHash(blob)
|
||
console.log('[craftMintSubmit] H5 base64 hash computed:', uploadHash.substring(0, 20) + '...')
|
||
const fd = new FormData()
|
||
const fileName = `${Date.now()}.jpg`
|
||
fd.append('key', ossData.dir + fileName)
|
||
fd.append('policy', ossData.policy)
|
||
fd.append('success_action_status', '200')
|
||
fd.append('x-oss-credential', ossData.x_oss_credential)
|
||
fd.append('x-oss-date', ossData.x_oss_date)
|
||
fd.append('x-oss-security-token', ossData.security_token)
|
||
fd.append('x-oss-signature', ossData.signature)
|
||
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}`
|
||
console.log('[craftMintSubmit] H5 base64 upload complete, hash:', uploadHash.substring(0, 20) + '...')
|
||
} else {
|
||
console.log('[craftMintSubmit] 小程序/非H5上传')
|
||
const uploadResult = await uploadFileToOssWithInfo(imagePath, ossData)
|
||
imageUrl = uploadResult.url
|
||
uploadFileSize = uploadResult.size || 0
|
||
uploadHash = uploadResult.hash || ''
|
||
}
|
||
// #endif
|
||
// #ifndef H5
|
||
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. 注册素材到后端
|
||
console.log('[craftMintSubmit] uploadMaterialApi ossKey:', ossKey, 'mimeType:', mimeType, 'fileSize:', uploadFileSize)
|
||
const materialRes = await uploadMaterialApi({
|
||
oss_key: ossKey,
|
||
original_name: originalName || `${materialType}.jpg`,
|
||
file_size: uploadFileSize,
|
||
mime_type: mimeType,
|
||
hash: uploadHash,
|
||
material_type: materialType,
|
||
})
|
||
console.log('[craftMintSubmit] uploadMaterialApi response:', materialRes)
|
||
|
||
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 || '',
|
||
materialTypes: formData.materialTypes || ['粉丝自制'],
|
||
materialTypeIndex: formData.materialTypeIndex ?? 0,
|
||
pageName: formData.craft_name || formData.typeName || '',
|
||
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)
|
||
|
||
// 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) {
|
||
console.log('[craftMintSubmit] 3. 上传背景图并注册')
|
||
bgMaterialId = (await uploadImageAndRegisterMaterial(bgPath, ossData, originalFileName(bgPath), 'bg')).materialId
|
||
}
|
||
}
|
||
|
||
// 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,
|
||
material_url: materialUrl,
|
||
description: snap.description || '',
|
||
grade: snap.grade ?? 0,
|
||
tags: Array.isArray(snap.tags) ? snap.tags : [],
|
||
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 || '创建订单失败')
|
||
}
|
||
|
||
const assetId =
|
||
response.data?.asset?.asset_id ||
|
||
response.data?.asset_id ||
|
||
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: `${ossData.host}/${mainResult.ossKey}`, // 完整OSS URL
|
||
name: snap.name,
|
||
description: snap.description || '',
|
||
material_type: snap.material_type,
|
||
tags: snap.tags,
|
||
order_id: orderId,
|
||
asset_id: assetId,
|
||
info: snap.info,
|
||
event: snap.info,
|
||
}
|
||
// 如果是光栅卡,存储背景图路径供 success 页面展示
|
||
if (isLenticular && bgImagePath) {
|
||
nftData.bg_image = bgImagePath
|
||
nftData.is_lenticular = true
|
||
}
|
||
uni.setStorageSync('temp_nft_data', JSON.stringify(nftData))
|
||
uni.removeStorageSync('castlove_form_data')
|
||
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'
|
||
}
|
||
} |