From e7b8c5bf43e2e1b16e1571535ece3eb127bd160c Mon Sep 17 00:00:00 2001 From: duanshuwen Date: Thu, 20 Aug 2026 21:58:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E6=8E=88=E6=9D=83=E8=8E=B7=E5=8F=96=E4=B8=8E?= =?UTF-8?q?=E4=B8=AA=E4=BA=BA=E4=B8=AD=E5=BF=83=E5=B1=95=E7=A4=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 `wechat-profile` 工具库,实现微信用户信息的本地存储、格式校验与授权获取逻辑 - 编写对应单元测试覆盖核心功能流程 - 重构个人中心成员组件,新增头像、昵称展示与授权触发按钮 - 更新个人中心页面,集成微信头像授权流程与状态管理 --- WonderQ-MiniAPP/src/lib/wechat-profile.ts | 49 ++++++++++++ .../src/pages/mine/components/MineMember.vue | 79 +++++++++++++++++-- WonderQ-MiniAPP/src/pages/mine/index.vue | 42 +++++++++- WonderQ-MiniAPP/tests/wechat-profile.test.ts | 51 ++++++++++++ 4 files changed, 213 insertions(+), 8 deletions(-) create mode 100644 WonderQ-MiniAPP/src/lib/wechat-profile.ts create mode 100644 WonderQ-MiniAPP/tests/wechat-profile.test.ts diff --git a/WonderQ-MiniAPP/src/lib/wechat-profile.ts b/WonderQ-MiniAPP/src/lib/wechat-profile.ts new file mode 100644 index 0000000..8b61c74 --- /dev/null +++ b/WonderQ-MiniAPP/src/lib/wechat-profile.ts @@ -0,0 +1,49 @@ +export const WECHAT_PROFILE_KEY = "miniapp:wechat-profile"; + +export type WechatProfile = { + avatarUrl: string; + nickName?: string; +}; + +function isWechatProfile(value: unknown): value is WechatProfile { + if (!value || typeof value !== "object") return false; + const profile = value as Partial; + return typeof profile.avatarUrl === "string" && profile.avatarUrl.trim().length > 0; +} + +export function getStoredWechatProfile(): WechatProfile | null { + try { + const value = uni.getStorageSync(WECHAT_PROFILE_KEY); + return isWechatProfile(value) ? value : null; + } catch { + return null; + } +} + +export function saveWechatProfile(profile: WechatProfile) { + if (!isWechatProfile(profile)) return; + uni.setStorageSync(WECHAT_PROFILE_KEY, profile); +} + +export function requestWechatProfile(): Promise { + return new Promise((resolve) => { + if (typeof uni.getUserProfile !== "function") { + resolve(null); + return; + } + + uni.getUserProfile({ + provider: "weixin", + desc: "用于展示您的微信头像", + lang: "zh_CN", + success: (result) => { + const profile = { + avatarUrl: result.userInfo?.avatarUrl?.trim() ?? "", + nickName: result.userInfo?.nickName?.trim() || undefined, + }; + resolve(isWechatProfile(profile) ? profile : null); + }, + fail: () => resolve(null), + }); + }); +} diff --git a/WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue b/WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue index 4bfafc4..368baad 100644 --- a/WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue +++ b/WonderQ-MiniAPP/src/pages/mine/components/MineMember.vue @@ -1,15 +1,80 @@ diff --git a/WonderQ-MiniAPP/src/pages/mine/index.vue b/WonderQ-MiniAPP/src/pages/mine/index.vue index 85e4889..6daeb75 100644 --- a/WonderQ-MiniAPP/src/pages/mine/index.vue +++ b/WonderQ-MiniAPP/src/pages/mine/index.vue @@ -3,7 +3,12 @@ - + @@ -14,15 +19,29 @@ import { ref } from "vue"; import { onShow } from "@dcloudio/uni-app"; import { getAuthSession, loginWithPhoneCode } from "@/lib/auth"; import { loadAppData } from "@/lib/store"; +import { + getStoredWechatProfile, + requestWechatProfile, + saveWechatProfile, +} from "@/lib/wechat-profile"; import type { AuthSession } from "@/lib/types"; import MineGuest from "./components/MineGuest.vue"; import MineMember from "./components/MineMember.vue"; const session = ref(null); const customerLine = ref("可查看当前设备的需求、收藏和浏览记录。"); +const wechatProfile = ref(getStoredWechatProfile()); +const isWechatMiniProgram = ref(false); const isLoggingIn = ref(false); +const isLoadingWechatProfile = ref(false); onShow(() => { session.value = getAuthSession(); + wechatProfile.value = getStoredWechatProfile(); + try { + isWechatMiniProgram.value = uni.getSystemInfoSync().uniPlatform === "mp-weixin"; + } catch { + isWechatMiniProgram.value = false; + } customerLine.value = session.value ? `已绑定手机号 ${session.value.customer.phoneMasked}` : "可查看当前设备的需求、收藏和浏览记录。"; @@ -61,4 +80,25 @@ async function goLogin(event: WechatPhoneNumberEvent) { uni.hideLoading(); } } + +async function loadWechatProfile() { + if (isLoadingWechatProfile.value) return; + + isLoadingWechatProfile.value = true; + uni.showLoading({ title: "获取头像中" }); + try { + const profile = await requestWechatProfile(); + if (!profile) { + uni.showToast({ title: "未获取到头像,请允许授权后重试", icon: "none" }); + return; + } + + saveWechatProfile(profile); + wechatProfile.value = profile; + uni.showToast({ title: "头像已更新", icon: "none" }); + } finally { + isLoadingWechatProfile.value = false; + uni.hideLoading(); + } +} diff --git a/WonderQ-MiniAPP/tests/wechat-profile.test.ts b/WonderQ-MiniAPP/tests/wechat-profile.test.ts new file mode 100644 index 0000000..748ed40 --- /dev/null +++ b/WonderQ-MiniAPP/tests/wechat-profile.test.ts @@ -0,0 +1,51 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { + getStoredWechatProfile, + requestWechatProfile, + saveWechatProfile, +} from "@/lib/wechat-profile"; + +describe("wechat profile", () => { + const storage = new Map(); + const getUserProfile = vi.fn(); + + beforeEach(() => { + storage.clear(); + getUserProfile.mockReset(); + vi.stubGlobal("uni", { + getStorageSync: (key: string) => storage.get(key), + setStorageSync: (key: string, value: unknown) => storage.set(key, value), + getUserProfile, + }); + }); + + it("stores and restores a valid avatar profile", () => { + saveWechatProfile({ avatarUrl: "https://example.com/avatar.jpg", nickName: "Damon" }); + + expect(getStoredWechatProfile()).toEqual({ + avatarUrl: "https://example.com/avatar.jpg", + nickName: "Damon", + }); + }); + + it("requests the profile only through the explicit profile API", async () => { + getUserProfile.mockImplementation(({ success }: { success: (result: unknown) => void }) => { + success({ userInfo: { avatarUrl: "https://example.com/wechat.jpg", nickName: "旅行者" } }); + }); + + await expect(requestWechatProfile()).resolves.toEqual({ + avatarUrl: "https://example.com/wechat.jpg", + nickName: "旅行者", + }); + expect(getUserProfile).toHaveBeenCalledWith( + expect.objectContaining({ provider: "weixin", lang: "zh_CN" }), + ); + }); + + it("returns null when the user declines profile permission", async () => { + getUserProfile.mockImplementation(({ fail }: { fail: () => void }) => fail()); + + await expect(requestWechatProfile()).resolves.toBeNull(); + }); +});