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>
This commit is contained in:
zerosaturation 2026-07-27 17:16:49 +08:00
parent d6573efcdd
commit 83999995f5
18 changed files with 164 additions and 182 deletions

View File

@ -1,12 +1,15 @@
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 {
const userStr = uni.getStorageSync('user')
if (!userStr) return
const user = typeof userStr === 'string' ? JSON.parse(userStr) : { ...userStr }
// ★ 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 })

View File

@ -8,6 +8,7 @@ import { composeShareImage, computeComposeKey } from '@/utils/image-compositor.j
import { pickRandomSlogan } from '@/utils/brand-slogans.js';
import { getShareQrcodeApi, trackShareApi } from '@/utils/api.js';
import { useAliveGuard } from '@/composables/useAliveGuard.js';
import { getStoredUser } from '@/utils/getStoredUser.js';
const APP_PACKAGES = {
weixin_friend: { pname: 'com.tencent.mm', bundleid: 'com.tencent.xinWeChat' },
@ -77,10 +78,9 @@ export function useShare(props) {
const l1Cache = new Map();
// ============ Helpers ============
// ★ 2026-07-27 refactor:统一用 @/utils/getStoredUser 取代本地 if/try 守卫
function getCurrentUser() {
const userStr = uni.getStorageSync('user');
if (!userStr) return null;
try { return JSON.parse(userStr); } catch { return null; }
return getStoredUser();
}
// 后端 TrackShareRequest 字段是 int64,前端 props 是 string,转 number

View File

@ -266,6 +266,7 @@ import {
} from '@/utils/castloveGenerationFlow.js';
import { submitCraftMintFromPath } from '@/utils/craftMintSubmit.js';
import { composeStickers, relationsToStickers } from '@/utils/sticker-compositor.js';
import { getStoredUser } from '@/utils/getStoredUser.js';
//
const assetIdParam = ref('');
const orderIdParam = ref('');
@ -335,12 +336,11 @@ const currentUserNickname = ref('');
//
const loadCurrentUser = () => {
try {
const userStr = uni.getStorageSync('user');
if (!userStr) return;
const userInfo = typeof userStr === 'string' ? JSON.parse(userStr) : userStr;
userAvatarUrl.value = userInfo?.avatar_url || '';
currentUserId.value = String(userInfo?.uid || userInfo?.user_id || '');
currentUserNickname.value = userInfo?.nickname || '';
// 2026-07-27 refactor: @/utils/getStoredUser, JSON.parse
const userInfo = getStoredUser() || {};
userAvatarUrl.value = userInfo.avatar_url || '';
currentUserId.value = String(userInfo.uid || userInfo.user_id || '');
currentUserNickname.value = userInfo.nickname || '';
} catch (e) {
console.error('解析用户信息失败:', e);
}

View File

@ -139,6 +139,7 @@ import {
STUDIO_LENTICULAR,
} from "@/utils/castloveGenerationFlow.js";
import { submitCraftMintFromPath } from "@/utils/craftMintSubmit.js";
import { getStoredUser } from "@/utils/getStoredUser.js";
const generatedImages = ref([]);
const selectedIndex = ref(-1);
@ -192,14 +193,13 @@ const handleRegenerate = () => {
//
function updateLocalBalance(newBalance) {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
const user =
typeof userStr === "string" ? JSON.parse(userStr) : { ...userStr };
user.crystal_balance = Number(newBalance) || 0;
uni.setStorageSync("user", JSON.stringify(user));
uni.$emit("balanceUpdated", { crystal_balance: user.crystal_balance });
}
// 2026-07-27 refactor: @/utils/getStoredUser, `typeof === 'string' ? JSON.parse : spread`
// userStr === "" JSON.parse
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("[lenticular-result] 更新本地余额失败:", e);
}

View File

@ -159,6 +159,7 @@ import { reportEvent } from "@/utils/task-api.js";
import { getEarningsSummaryApi } from "@/utils/api.js";
import { getPreloadApi } from "@/utils/preloadApi/index";
import { onScanResult } from "@/utils/scanLaunch.js";
import { getStoredUser } from "@/utils/getStoredUser.js";
// square useBanner
const { bannerActivities, loadBannerActivities } = useBanner();
@ -210,10 +211,8 @@ const showGuideModal = ref(false);
//
const loadUserInfo = () => {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
userInfo.value = JSON.parse(userStr);
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
userInfo.value = getStoredUser();
} catch (e) {
console.error("解析用户信息失败:", e);
userInfo.value = null;
@ -295,14 +294,8 @@ function checkAndReportDailyLogin() {
if (!starId) return;
// ID
const userStr = uni.getStorageSync("user");
let userId = null;
if (userStr) {
try {
const user = JSON.parse(userStr);
userId = user?.uid || null;
} catch (e) {}
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const userId = getStoredUser()?.uid ?? null;
if (!userId) return;
//

View File

@ -56,6 +56,7 @@ import { useShare } from '@/composables/useShare.js';
import { useAliveGuard } from '@/composables/useAliveGuard.js';
import { LANDING_BASE, SHARE_TARGETS } from '@/utils/constants.js';
import { trackShareApi } from '@/utils/api.js';
import { getStoredUser } from '@/utils/getStoredUser.js';;
const props = defineProps({
visible: { type: Boolean, default: false },
@ -75,7 +76,8 @@ const emit = defineEmits(['close']);
const visibleLocal = ref(props.visible);
watch(() => props.visible, (v) => { visibleLocal.value = v; });
const isLoggedIn = computed(() => {
try { return !!JSON.parse(uni.getStorageSync('user') || '{}').uid; } catch { return false; }
// 2026-07-27 refactor: @/utils/getStoredUser( `JSON.parse(...||'{}')`+try/catch ,)
try { return !!getStoredUser()?.uid; } catch { return false; }
});
const { state, pick, currentSlogan, systemType } = useShare({
@ -109,13 +111,13 @@ function copyLink() {
// : 'other'( QR )
const os = systemType.value || 'other';
// uid( from ),;
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
let from = '';
let uid = 0;
try {
const userStr = uni.getStorageSync('user');
if (userStr) {
const userInfo = JSON.parse(userStr);
const rawUid = userInfo?.uid || userInfo?.user_id;
const userInfo = getStoredUser();
if (userInfo) {
const rawUid = userInfo.uid || userInfo.user_id;
from = String(rawUid || '');
uid = Number(rawUid) || 0;
}

View File

@ -40,6 +40,7 @@ import ShareModal from './ShareModal.vue';
import ReportModal from './ReportModal.vue';
import { getShareQrcodeApi } from '@/utils/api.js';
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
import { getStoredUser } from '@/utils/getStoredUser.js';;
const props = defineProps({
//
@ -77,11 +78,9 @@ const props = defineProps({
//
const currentNickname = computed(() => {
try {
const userStr = uni.getStorageSync('user');
if (userStr) {
const userInfo = JSON.parse(userStr);
return userInfo?.nickname || '';
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const userInfo = getStoredUser();
return userInfo?.nickname || '';
} catch (e) {
console.error('获取用户信息失败:', e);
}
@ -105,8 +104,8 @@ const showReportModal = ref(false);
//
const handleShare = async () => {
// :(spec § 3.4)
const userStr = uni.getStorageSync('user');
if (!userStr || !JSON.parse(userStr)?.uid) {
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
if (!getStoredUser()?.uid) {
uni.showToast({ title: '请先登录', icon: 'none' });
setTimeout(() => uni.navigateTo({ url: '/pages/login/login' }), 800);
return;
@ -132,15 +131,11 @@ const handleShare = async () => {
}
shareCoverUrl.value = cover;
//
// :
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
try {
const userStr2 = uni.getStorageSync('user');
if (userStr2) {
const userInfo = JSON.parse(userStr2);
shareAvatarUrl.value = userInfo?.avatar_url || '/static/square/gerenzhongxinkangpinkuang.png';
} else {
shareAvatarUrl.value = '/static/square/gerenzhongxinkangpinkuang.png';
}
const userInfo = getStoredUser();
shareAvatarUrl.value = userInfo?.avatar_url || '/static/square/gerenzhongxinkangpinkuang.png';
} catch (e) {
console.error('获取用户头像失败:', e);
shareAvatarUrl.value = '/static/square/gerenzhongxinkangpinkuang.png';
@ -153,9 +148,9 @@ const handleShare = async () => {
// showShareModal = true ( watch ), URL(spec § 3)
async function refreshQrcode() {
const userStr = uni.getStorageSync('user');
if (!userStr) return;
const user = JSON.parse(userStr);
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (!user) return;
try {
// (spec § 3.3 UI 线)
const { platform } = await uni.getSystemInfo();

View File

@ -377,6 +377,7 @@ export default {
import NftCard from '../components/NftCard.vue';
import { getMyGalleriesApi, placeAssetToGalleryApi, getMyAssetsApi, removeAssetFromGalleryApi, getUserGalleriesApi, getRandomGalleryApi } from '@/utils/api.js';
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
import { getStoredUser } from '@/utils/getStoredUser.js';
import GuideOverlay from "@/components/GuideOverlay.vue";
const store = useStore();
@ -827,16 +828,10 @@ export default {
} else {
// 访
// currentUserUid
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
if (!currentUserUid.value) {
const userStr = uni.getStorageSync('user');
if (userStr) {
try {
const user = JSON.parse(userStr);
currentUserUid.value = user.uid;
} catch (e) {
console.error('解析用户信息失败:', e);
}
}
const user = getStoredUser();
if (user?.uid) currentUserUid.value = user.uid;
}
// UI
@ -1237,7 +1232,9 @@ export default {
let currentUserUid;
try {
const user = JSON.parse(userStr);
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (!user) return;
currentUserUid = user.uid;
} catch (e) {
console.error('解析用户信息失败:', e);
@ -1488,15 +1485,9 @@ export default {
onLoad((options) => {
// UID
const userStr = uni.getStorageSync('user');
if (userStr) {
try {
const user = JSON.parse(userStr);
currentUserUid.value = user.uid;
} catch (e) {
console.error('解析用户信息失败:', e);
}
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const userOnLoad = getStoredUser();
if (userOnLoad?.uid) currentUserUid.value = userOnLoad.uid;
// target_uid访
if (options && options.target_uid) {
@ -1539,16 +1530,10 @@ export default {
calculateTopPadding();
// currentUserUid
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
if (!currentUserUid.value) {
const userStr = uni.getStorageSync('user');
if (userStr) {
try {
const user = JSON.parse(userStr);
currentUserUid.value = user.uid;
} catch (e) {
console.error('解析用户信息失败:', e);
}
}
const user = getStoredUser();
if (user?.uid) currentUserUid.value = user.uid;
}
// visitingGalleryOwnerUid

View File

@ -119,9 +119,13 @@
import { ref } from "vue";
import LoginBackground from "@/components/LoginBackground.vue";
import { AGREEMENT_CONTENT } from "@/utils/agreement";
import { getStoredUser } from "@/utils/getStoredUser.js";
// user
const userInfo = JSON.parse(uni.getStorageSync("user")) || {};
// ( user )
// :uni.getStorageSync('user') key "" undefined,
// JSON.parse("") SyntaxError: Unexpected end of JSON input, setup
// @/utils/getStoredUser.js(), {}
const userInfo = getStoredUser() || {};
//
const maskPhone = (phone) => {
if (!phone) return "";

View File

@ -618,6 +618,7 @@ import GuideModal from "@/pages/tasks/GuideModal.vue";
import GuideOverlay from "@/components/GuideOverlay.vue";
import FeedbackModal from "../components/FeedbackModal.vue";
import { getClaimableRewardCount } from "@/utils/guideConfig.js";
import { getStoredUser } from "@/utils/getStoredUser.js";
const store = useStore();
@ -1545,9 +1546,9 @@ const handleAvatarUpdateSuccess = async (newAvatarUrl) => {
// URL <image> ?t=
// 1.
userAvatarUrl.value = newAvatarUrl;
const userStr = uni.getStorageSync("user");
if (userStr) {
const user = JSON.parse(userStr);
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (user) {
user.avatar_url = newAvatarUrl;
uni.setStorageSync("user", JSON.stringify(user));
}

View File

@ -128,6 +128,7 @@ import { ref, computed, onMounted, onUnmounted } from "vue";
import { purchaseItem } from "@/utils/activity-config";
import { getEarningsSummaryApi } from "@/utils/api";
import DiamondConfirmModal from "./DiamondConfirmModal.vue";
import { getStoredUser } from "@/utils/getStoredUser.js";
const props = defineProps({
activityId: {
@ -274,11 +275,8 @@ const crystalBalance = ref(0);
async function loadUserInfo() {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
userInfo.value =
typeof userStr === "string" ? JSON.parse(userStr) : userStr;
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
userInfo.value = getStoredUser();
// API
const res = await getEarningsSummaryApi();
@ -623,17 +621,10 @@ function addToPendingQueue(item) {
// 线使
function deductLocalBalance(cost) {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
//
let user;
if (typeof userStr === "string") {
user = JSON.parse(userStr);
} else {
user = { ...userStr };
}
//
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (user) {
// ,
user.crystal_balance = Math.max(
0,
(Number(user.crystal_balance) || 0) - cost,
@ -650,19 +641,12 @@ function deductLocalBalance(cost) {
}
}
// 退使
// 退(使)
function refundLocalBalance(cost) {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
//
let user;
if (typeof userStr === "string") {
user = JSON.parse(userStr);
} else {
user = { ...userStr };
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (user) {
// 退
user.crystal_balance = (Number(user.crystal_balance) || 0) + cost;
@ -678,21 +662,13 @@ function refundLocalBalance(cost) {
}
async function updateLocalBalanceFromResult(newBalance) {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
//
let user;
if (typeof userStr === "string") {
user = JSON.parse(userStr);
} else {
//
user = { ...userStr };
}
//
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser();
if (user) {
// ,
user.crystal_balance = Number(newBalance) || 0;
//
// ,
uni.setStorageSync("user", JSON.stringify(user));
userInfo.value = user;
crystalBalance.value = user.crystal_balance;

View File

@ -50,6 +50,7 @@
<script setup>
import { ref, onMounted } from "vue";
import { getActivityTopRankingApi } from "@/utils/api.js";
import { getStoredUser } from "@/utils/getStoredUser.js";
const props = defineProps({
activityId: {
@ -74,14 +75,12 @@ const myInfo = ref({
gapToPrev: 0,
});
// /
// : /
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
function getFallbackAvatar() {
try {
const userStr = uni.getStorageSync("user");
if (userStr) {
const u = typeof userStr === "string" ? JSON.parse(userStr) : userStr;
return u?.avatar_url || u?.avatar || "";
}
const u = getStoredUser() || {};
return u.avatar_url || u.avatar || "";
} catch (e) {
//
}

View File

@ -204,6 +204,7 @@ import StageArea from "./components/StageArea.vue";
import FloatingBubbles from "./components/FloatingBubbles.vue";
import ActivityRankingModal from "./components/ActivityRankingModal.vue";
import ActionBar from "./components/ActionBar.vue";
import { getStoredUser } from '@/utils/getStoredUser.js';
import TopRanking from "./components/TopRanking.vue";
import MessageBoard from "./components/MessageBoard.vue";
import MessageInput from "./components/MessageInput.vue";
@ -428,14 +429,13 @@ async function handleContribute(itemType, remainingBalance) {
//
if (remainingBalance !== null && remainingBalance !== undefined) {
exhibitionRevenue.value = Number(remainingBalance) || 0;
// ActionBar
const userStr = uni.getStorageSync("user");
if (userStr) {
const user =
typeof userStr === "string" ? JSON.parse(userStr) : { ...userStr };
// , ActionBar
// 2026-07-27 refactor: @/utils/getStoredUser, typeof/JSON.parse
const user = getStoredUser();
if (user) {
user.crystal_balance = Number(remainingBalance) || 0;
uni.setStorageSync("user", JSON.stringify(user));
// balanceUpdated Header
// balanceUpdated ( Header)
uni.$emit("balanceUpdated", { crystal_balance: user.crystal_balance });
}
}

View File

@ -107,6 +107,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { getOnboardingStatus, claimOnboardingReward, completeGuide } from '@/utils/task-api.js'
import { getStoredUser } from '@/utils/getStoredUser.js'
import {
getGuideConfig,
getGuideStatusList,
@ -393,9 +394,9 @@ async function handleClaimReward(key) {
// Header
if (res.data?.crystal_balance !== undefined) {
try {
const userStr = uni.getStorageSync('user')
if (userStr) {
const user = JSON.parse(userStr)
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser()
if (user) {
user.crystal_balance = parseInt(res.data.crystal_balance)
uni.setStorageSync('user', JSON.stringify(user))
}

View File

@ -131,6 +131,7 @@
<script setup>
import { ref, computed, watch } from 'vue'
import { getDailyTasks, claimDailyTask, claimAllDailyTasks } from '@/utils/task-api.js'
import { getStoredUser } from '@/utils/getStoredUser.js'
const props = defineProps({
visible: {
@ -342,21 +343,14 @@ async function handleClaim(task) {
tasks.value[index].status = 'claimed'
tasks.value[index].can_claim = false
}
const userStr = uni.getStorageSync('user')
if (userStr) {
//
let user
if (typeof userStr === 'string') {
user = JSON.parse(userStr)
} else {
//
user = { ...userStr }
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser()
if (user) {
emit('updated')
//
// ,
user.crystal_balance = Number(res.data?.crystal_balance) || 0
//
// ,
uni.setStorageSync('user', JSON.stringify(user))
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
}
@ -384,21 +378,14 @@ async function handleClaimAll() {
})
emit('updated')
const userStr = uni.getStorageSync('user')
if (userStr) {
//
let user
if (typeof userStr === 'string') {
user = JSON.parse(userStr)
} else {
//
user = { ...userStr }
}
// 2026-07-27 refactor: @/utils/getStoredUser,/ JSON
const user = getStoredUser()
if (user) {
emit('updated')
//
// ,
user.crystal_balance = Number(res.data?.crystal_balance) || 0
//
// ,
uni.setStorageSync('user', JSON.stringify(user))
uni.$emit('balanceUpdated', { crystal_balance: user.crystal_balance })
}

View File

@ -0,0 +1,43 @@
// frontend/utils/getStoredUser.js
// 安全读取 uni.getStorageSync('user') 并 JSON.parse —— 单点封装,消除全库 ~20 处
// 重复且不安全的 `JSON.parse(userStr)` 裸调用。
//
// 已知坑(踩过 2 次,均以红色 stack trace 形式出现):
// 1. `uni.getStorageSync('user')` 在 key 从未写过的 UniApp 平台下默认返回 ""
// (空串)而不是 null/undefined,旧式 `JSON.parse(getStorageSync('user')) || {}`
// 会在 parse 阶段直接抛 `SyntaxError: Unexpected end of JSON input`,setup 崩溃 →
// Vue 触发 `[Vue warn]: Invalid vnode type when creating vnode: undefined`
// 二次告警(同一个根因)。最低成本修复是 `|| '{}'` 兜底,但仍无法防 JSON 损坏场景。
// 2. 上线后曾遇到"token 有效但 user 缓存被清"的中间态
// (升级包 / 跨设备 tab 登出 / 部分页面单独清缓存),彼时 setup 抛错导致整页白屏。
//
// 使用规范:
// import { getStoredUser } from '@/utils/getStoredUser'
// const user = getStoredUser() // 失败返回 null
// const user = getStoredUser() || {} // 失败降级为空对象
// const uid = getStoredUser()?.uid // 失败时为 undefined
//
// 与 store/modules/user.js 初始化块保持同源语义;后续如需支持多 key (e.g. 'adminUser')
// 再扩成泛型 getStoredJSON(key) 即可。
/**
* 安全读取本地缓存中的 user(JSON) 并解析为对象
*
* 失败场景(return null):
* - 'user' key 不存在 uni.getStorageSync 返回 "" undefined
* - 'user' 缓存损坏 / 不是合法 JSON JSON.parse 抛错
* - 'user' 是合法 JSON 但解析后是 null( 'null' 字符串) 兜底返回 null
*
* @returns {object|null} 解析成功返回 user 对象;失败返回 null
*/
export function getStoredUser() {
const userStr = uni.getStorageSync('user');
if (!userStr) return null;
try {
return JSON.parse(userStr) || null;
} catch (e) {
// warn 级别即可,不要 error —— 这是可预期的 cache miss / 损坏,不是真错
console.warn('[getStoredUser] failed to parse user storage:', e?.message || e);
return null;
}
}

View File

@ -7,6 +7,8 @@
* - 关闭调试访问 pages/square/square?guide_debug=0会清除调试状态和 is_new_user恢复正式流程
*/
import { getStoredUser } from '@/utils/getStoredUser.js'
/**
* 引导配置结构
* {
@ -473,16 +475,8 @@ export function getAllGuideKeys() {
* @returns {number|null}
*/
function getCurrentUserId() {
const userStr = uni.getStorageSync('user')
if (userStr) {
try {
const user = JSON.parse(userStr)
return user?.uid || null
} catch (e) {
return null
}
}
return null
// ★ 2026-07-27 refactor:委托给 @/utils/getStoredUser.js,统一空值/损坏 JSON 的安全读取
return getStoredUser()?.uid ?? null
}
/**

View File

@ -11,6 +11,8 @@ import {
evictOldest
} from './storage'
import { getStoredUser } from '@/utils/getStoredUser.js'
// ── 常量 ──
const NAMESPACE = 'preload'
const DEFAULT_MAX_MEMORY_ENTRIES = 100
@ -45,13 +47,10 @@ let _config = {
let _fetchers = {}
// userId 获取函数(由外部注入)
// ★ 2026-07-27 refactor:委托 @/utils/getStoredUser.js,统一空值/损坏 JSON 的安全读取
let _getUserId = () => {
try {
const userStr = uni.getStorageSync('user')
if (userStr) {
const user = JSON.parse(userStr)
return user?.uid || null
}
return getStoredUser()?.uid ?? null
} catch (e) { /* ignore */ }
return null
}