topfans/frontend/composables/useLaserMint.js
zerosaturation 83999995f5 refactor(frontend): consolidate user storage read via getStoredUser helper
uni.getStorageSync('user') 在 key 从未写过的 UniApp 平台下默认返回 ""(空串)
而非 null/undefined,旧式 `JSON.parse(uni.getStorageSync('user')) || {}` 在
parse 阶段直接抛 `SyntaxError: Unexpected end of JSON input`,导致 quickLogin
setup 崩溃,并触发 Vue 二次告警 `Invalid vnode type: undefined`。

新增 utils/getStoredUser.js 单点封装,统一处理:
  - 空值/损坏 JSON 兜底(返回 null,不抛)
  - 与 store/modules/user.js 初始化块同源守卫
  - 失败 warn 级别日志,不影响业务

替换全库 16 处不安全 `JSON.parse(userStr)` 调用:
  composables/useLaserMint.js                  composables/useShare.js
  utils/guideConfig.js                         utils/preloadApi/core.js
  pages/castlove/lenticular/lenticular-result.vue
  pages/support-activity/index.vue             pages/support-activity/components/{TopRanking,ActionBar}.vue
  pages/components/{Header,ShareModal,ShareReportButtons}.vue
  pages/tasks/{GuideModal,daily-tasks}.vue
  pages/asset-detail/asset-detail.vue          pages/exhibition/exhibition.vue
  pages/profile/profile.vue                    pages/login/quickLogin.vue(原崩点)

净 -18 行,17 文件改动 + 1 文件新增。后续如需支持多 key(如 'adminUser'),
把 getStoredUser 扩成泛型 getStoredJSON(key) 即可。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 17:16:49 +08:00

126 lines
3.4 KiB
JavaScript

import { ref } from 'vue'
import { estimateMintCostApi } from '@/utils/api.js'
import { submitCraftMintFromPath } from '@/utils/craftMintSubmit.js'
import { getStoredUser } from '@/utils/getStoredUser.js'
function updateLocalBalance(newBalance) {
try {
// ★ 2026-07-27 refactor:旧写法 `typeof userStr === 'string' ? JSON.parse(userStr) : {...}`
// 在 userStr 是 "" 时 typeof 仍是 'string',仍会撞到 JSON.parse crash。
// 统一用 @/utils/getStoredUser,任何异常路径都已封装,这里只管写回。
const user = getStoredUser()
if (!user) return
user.crystal_balance = Number(newBalance) || 0
uni.setStorageSync('user', JSON.stringify(user))
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
} catch (e) {
console.warn('[useLaserMint] updateLocalBalance failed:', e)
}
}
/**
* laser-result 选卡确认后的铸造(估价 → ConfirmModal → craftMintSubmit 多素材上传)
*/
export function useLaserMint({ getSelectedImagePath, getSelectedOssKey, getSelectedPresetIndex, formDataRef, getInstanceNo }) {
const showConfirmModal = ref(false)
const confirmCostInfo = ref({
costCrystal: 0,
currentBalance: 0,
mintCount: 0,
nextTierCost: 0,
})
const selectAsset = async () => {
const imagePath = getSelectedImagePath()
if (!imagePath) {
uni.showToast({ title: '缺少作品图', icon: 'none' })
return
}
try {
uni.showLoading({ title: '加载中…', mask: true })
const costRes = await estimateMintCostApi()
uni.hideLoading()
if (costRes.code === 0 && costRes.data) {
confirmCostInfo.value = {
costCrystal: costRes.data.cost_crystal || 0,
currentBalance: costRes.data.current_balance || 0,
mintCount: costRes.data.mint_count || 0,
nextTierCost: costRes.data.next_tier_cost || 0,
}
} else {
confirmCostInfo.value = {
costCrystal: 100,
currentBalance: 0,
mintCount: 0,
nextTierCost: 0,
}
}
} catch (e) {
uni.hideLoading()
console.error('[useLaserMint] estimateMintCostApi failed:', e)
confirmCostInfo.value = {
costCrystal: 100,
currentBalance: 0,
mintCount: 0,
nextTierCost: 0,
}
}
showConfirmModal.value = true
}
const handleConfirmMint = async () => {
showConfirmModal.value = false
const imagePath = getSelectedImagePath()
if (!imagePath) {
uni.showToast({ title: '缺少作品图', icon: 'none' })
return
}
if (confirmCostInfo.value.currentBalance < confirmCostInfo.value.costCrystal) {
uni.showToast({ title: '水晶余额不足,无法铸造', icon: 'none' })
return
}
uni.showLoading({ title: '铸造中…', mask: true })
try {
await submitCraftMintFromPath({
imagePath,
ossKey: getSelectedOssKey ? getSelectedOssKey() : '',
instanceNo: getInstanceNo ? getInstanceNo() : '',
formData: formDataRef.value,
laserPresetIndex: getSelectedPresetIndex(),
})
const balanceAfter = Math.max(
0,
Number(confirmCostInfo.value.currentBalance) - Number(confirmCostInfo.value.costCrystal)
)
updateLocalBalance(balanceAfter)
uni.navigateTo({ url: '/pages/castlove/success' })
} catch (e) {
console.error('[useLaserMint] mint failed:', e)
uni.showToast({ title: e?.message || '铸造失败', icon: 'none' })
} finally {
uni.hideLoading()
}
}
const handleCancelMint = () => {
showConfirmModal.value = false
}
return {
showConfirmModal,
confirmCostInfo,
selectAsset,
handleConfirmMint,
handleCancelMint,
}
}