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>
126 lines
3.4 KiB
JavaScript
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,
|
|
}
|
|
}
|
|
|