我的页面中的头像
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { View, Text, Image } from '@tarojs/components';
|
||||
import { View, Text, Image, Button, Input } from '@tarojs/components';
|
||||
import Taro, { useDidShow } from '@tarojs/taro';
|
||||
import { isWxAuthorizeEnabled, type ClientRuntimeConfig } from '@dukang/shared-types';
|
||||
import PageShell from '../../components/PageShell';
|
||||
@@ -10,8 +10,10 @@ import { goLogin } from '../../lib/auth-nav';
|
||||
import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import { fetchUserProfile } from '../../lib/pay-wechat';
|
||||
import {
|
||||
fetchMiniWechatUserInfo,
|
||||
mergeWxDisplayProfile,
|
||||
needsWxProfileFill,
|
||||
uploadAvatarTempFile,
|
||||
uploadMiniWechatProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api';
|
||||
import { isWechatEnv } from '../../lib/weixin';
|
||||
@@ -29,6 +31,8 @@ const SERVICES = [
|
||||
{ icon: '关', label: '关于我们', action: 'about' as const },
|
||||
] as const;
|
||||
|
||||
const isWeapp = process.env.TARO_ENV === 'weapp';
|
||||
|
||||
function formatMoney(amount: number) {
|
||||
return amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
@@ -40,11 +44,30 @@ export default function MinePage() {
|
||||
const [orderCounts, setOrderCounts] = useState<Record<string, number>>({});
|
||||
const [wxAuthorize, setWxAuthorize] = useState(true);
|
||||
const [bindingWx, setBindingWx] = useState(false);
|
||||
const [savingProfile, setSavingProfile] = useState(false);
|
||||
const [editingProfile, setEditingProfile] = useState(false);
|
||||
const [draftNickname, setDraftNickname] = useState('');
|
||||
|
||||
function resetGuestState() {
|
||||
setProfile(null);
|
||||
setBenefitBalance(0);
|
||||
setOrderCounts({});
|
||||
setDraftNickname('');
|
||||
setEditingProfile(false);
|
||||
}
|
||||
|
||||
function applyProfile(me: UserProfile) {
|
||||
const merged = mergeWxDisplayProfile(me);
|
||||
setProfile(merged);
|
||||
if (!needsWxProfileFill(merged)) {
|
||||
setDraftNickname(merged.nickname || '');
|
||||
setEditingProfile(false);
|
||||
return;
|
||||
}
|
||||
// 缺头像但有真实昵称时预填
|
||||
if (merged.nickname && !/^用户\d{4}$/.test(merged.nickname) && merged.nickname !== '微信用户') {
|
||||
setDraftNickname(merged.nickname);
|
||||
}
|
||||
}
|
||||
|
||||
function loadProfile() {
|
||||
@@ -57,7 +80,7 @@ export default function MinePage() {
|
||||
),
|
||||
])
|
||||
.then(([me, coupons, ...totals]) => {
|
||||
setProfile(mergeWxDisplayProfile(me));
|
||||
applyProfile(me);
|
||||
const balance = (coupons as Array<Record<string, unknown>>).reduce((sum, c) => {
|
||||
if (String(c.status) === 'ACTIVE') return sum + Number(c.balance || 0);
|
||||
return sum;
|
||||
@@ -89,93 +112,134 @@ export default function MinePage() {
|
||||
.catch(() => setWxAuthorize(true));
|
||||
}, []);
|
||||
|
||||
async function handleAvatarTap() {
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
if (bindingWx) return;
|
||||
|
||||
let current = profile;
|
||||
if (!current) {
|
||||
try {
|
||||
current = await fetchUserProfile();
|
||||
setProfile(current);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (current?.hasWechat) return;
|
||||
|
||||
async function ensureWechatBound(): Promise<boolean> {
|
||||
if (profile?.hasWechat) return true;
|
||||
if (!wxAuthorize) {
|
||||
toast('当前环境未开启微信授权');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
setBindingWx(true);
|
||||
try {
|
||||
if (process.env.TARO_ENV === 'h5') {
|
||||
if (!isWeapp) {
|
||||
if (!isWechatEnv()) {
|
||||
toast('请在微信内打开后授权');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const result = await bindWechatForUser();
|
||||
if (!result.ok && 'redirecting' in result && result.redirecting) {
|
||||
return;
|
||||
}
|
||||
if (!result.ok && 'redirecting' in result && result.redirecting) return false;
|
||||
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
|
||||
goLogin('/pages/mine/index', {
|
||||
bindMode: '1',
|
||||
wxSessionKey: result.wxSessionKey,
|
||||
});
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (result.ok) {
|
||||
setProfile(
|
||||
mergeWxDisplayProfile({
|
||||
...(result.profile ?? {}),
|
||||
id: result.profile?.id ?? profile?.id ?? '',
|
||||
hasWechat: true,
|
||||
}),
|
||||
);
|
||||
loadProfile();
|
||||
toast('微信授权成功', 'success');
|
||||
if (result.ok && result.profile) {
|
||||
applyProfile({ ...result.profile, hasWechat: true });
|
||||
return true;
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let wxInfo = null;
|
||||
try {
|
||||
wxInfo = await fetchMiniWechatUserInfo();
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '需要授权微信头像和昵称');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await bindWechatForUser(wxInfo);
|
||||
const result = await bindWechatForUser(null);
|
||||
if (!result.ok && 'needBindPhone' in result && result.needBindPhone) {
|
||||
goLogin('/pages/mine/index', { bindMode: '1', wxSessionKey: result.wxSessionKey });
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (result.ok) {
|
||||
const merged = mergeWxDisplayProfile({
|
||||
...(result.profile ?? {}),
|
||||
id: result.profile?.id ?? profile?.id ?? '',
|
||||
hasWechat: true,
|
||||
nickname: result.profile?.nickname || wxInfo.nickname || profile?.nickname,
|
||||
avatarUrl: result.profile?.avatarUrl || wxInfo.avatarUrl || profile?.avatarUrl,
|
||||
});
|
||||
setProfile(merged);
|
||||
loadProfile();
|
||||
toast('微信授权成功', 'success');
|
||||
if (result.ok && result.profile) {
|
||||
applyProfile({ ...result.profile, hasWechat: true });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '微信授权失败');
|
||||
return false;
|
||||
} finally {
|
||||
setBindingWx(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function persistProfile(patch: { nickname?: string; avatarUrl?: string }, opts?: { silent?: boolean }) {
|
||||
setSavingProfile(true);
|
||||
try {
|
||||
const bound = await ensureWechatBound();
|
||||
if (!bound) return false;
|
||||
const updated = await uploadMiniWechatProfile(patch);
|
||||
if (updated) {
|
||||
applyProfile(updated);
|
||||
} else {
|
||||
const me = await fetchUserProfile();
|
||||
applyProfile(me);
|
||||
}
|
||||
if (!opts?.silent) toast('资料已更新', 'success');
|
||||
return true;
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '保存失败');
|
||||
return false;
|
||||
} finally {
|
||||
setSavingProfile(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleChooseAvatar(e: { detail?: { avatarUrl?: string } }) {
|
||||
const tempPath = e.detail?.avatarUrl?.trim();
|
||||
if (!tempPath) {
|
||||
toast('未获取到头像');
|
||||
return;
|
||||
}
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSavingProfile(true);
|
||||
const bound = await ensureWechatBound();
|
||||
if (!bound) return;
|
||||
const url = await uploadAvatarTempFile(tempPath);
|
||||
setProfile((prev) =>
|
||||
prev
|
||||
? mergeWxDisplayProfile({ ...prev, hasWechat: true, avatarUrl: url })
|
||||
: { id: '', hasWechat: true, avatarUrl: url, nickname: draftNickname || null },
|
||||
);
|
||||
setSavingProfile(false);
|
||||
const ok = await persistProfile(
|
||||
{
|
||||
avatarUrl: url,
|
||||
nickname: draftNickname.trim() || undefined,
|
||||
},
|
||||
{ silent: true },
|
||||
);
|
||||
if (ok) toast('头像已更新', 'success');
|
||||
} catch (err) {
|
||||
toast(err instanceof Error ? err.message : '头像更新失败');
|
||||
setSavingProfile(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNicknameConfirm() {
|
||||
const nickname = draftNickname.trim();
|
||||
if (!nickname) {
|
||||
toast('请填写微信昵称');
|
||||
return;
|
||||
}
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
await persistProfile({ nickname });
|
||||
}
|
||||
|
||||
/** H5:点击头像走公众号 OAuth */
|
||||
async function handleH5AvatarTap() {
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
if (profile?.hasWechat && !needsWxProfileFill(profile)) return;
|
||||
await ensureWechatBound();
|
||||
loadProfile();
|
||||
}
|
||||
|
||||
function handleService(item: (typeof SERVICES)[number]) {
|
||||
if ('url' in item && item.url) {
|
||||
Taro.navigateTo({ url: item.url });
|
||||
@@ -190,17 +254,6 @@ export default function MinePage() {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDisplayProfile(profile: UserProfile | null, hasWechat: boolean) {
|
||||
if (!profile) {
|
||||
return { nickname: '用户', avatarUrl: null as string | null };
|
||||
}
|
||||
const merged = hasWechat ? mergeWxDisplayProfile(profile) : profile;
|
||||
return {
|
||||
nickname: merged.nickname || '用户',
|
||||
avatarUrl: merged.avatarUrl || null,
|
||||
};
|
||||
}
|
||||
|
||||
function renderAvatarContent(displayAvatarUrl: string | null) {
|
||||
if (displayAvatarUrl) {
|
||||
return <Image className="mine-avatar-img" src={displayAvatarUrl} mode="aspectFill" />;
|
||||
@@ -228,10 +281,7 @@ export default function MinePage() {
|
||||
</View>
|
||||
<View className="mine-login-gate">
|
||||
<View className="mine-login-gate-hint">登录后管理订单与个人信息</View>
|
||||
<View
|
||||
className="mine-login-btn"
|
||||
onClick={() => goLogin('/pages/mine/index')}
|
||||
>
|
||||
<View className="mine-login-btn" onClick={() => goLogin('/pages/mine/index')}>
|
||||
<Text>去登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -241,10 +291,19 @@ export default function MinePage() {
|
||||
}
|
||||
|
||||
const hasWechat = !!profile?.hasWechat;
|
||||
const canWxBind = wxAuthorize && process.env.TARO_ENV === 'weapp';
|
||||
const display = resolveDisplayProfile(profile, hasWechat);
|
||||
const nickname = display.nickname;
|
||||
const memberLabel = hasWechat ? '好客会员' : canWxBind ? '微信未授权' : '未授权微信';
|
||||
const needFill = needsWxProfileFill(profile) || editingProfile;
|
||||
const canWxProfile = wxAuthorize && isWeapp;
|
||||
const display = mergeWxDisplayProfile(
|
||||
profile || { id: '', nickname: null, avatarUrl: null, hasWechat: false },
|
||||
);
|
||||
const nickname = display.nickname || '用户';
|
||||
const memberLabel = hasWechat
|
||||
? needFill
|
||||
? '完善头像昵称'
|
||||
: '好客会员'
|
||||
: canWxProfile
|
||||
? '微信未授权'
|
||||
: '未授权微信';
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page no-tab-header">
|
||||
@@ -252,29 +311,77 @@ export default function MinePage() {
|
||||
<View className="mine-header">
|
||||
<View className="mine-header-texture" />
|
||||
<View className="mine-profile">
|
||||
<View
|
||||
className={`mine-avatar-wrap${!hasWechat && canWxBind ? ' mine-avatar-wrap--action' : ''}`}
|
||||
onClick={() => void handleAvatarTap()}
|
||||
>
|
||||
<View
|
||||
className={`mine-avatar${hasWechat ? ' mine-avatar--wx-ok' : canWxBind ? ' mine-avatar--wx-pending' : ''}`}
|
||||
{canWxProfile ? (
|
||||
<Button
|
||||
className="mine-avatar-btn"
|
||||
openType="chooseAvatar"
|
||||
onChooseAvatar={(e) => void handleChooseAvatar(e)}
|
||||
disabled={bindingWx || savingProfile}
|
||||
>
|
||||
{renderAvatarContent(display.avatarUrl)}
|
||||
</View>
|
||||
{!hasWechat && canWxBind ? (
|
||||
<View className="mine-avatar-status mine-avatar-status--pending">
|
||||
<Text>{bindingWx ? '授权中' : '去授权'}</Text>
|
||||
<View
|
||||
className={`mine-avatar${hasWechat && !needFill ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'}`}
|
||||
>
|
||||
{renderAvatarContent(display.avatarUrl)}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View>
|
||||
<Text className="mine-profile-name">{nickname}</Text>
|
||||
<Text className={`mine-member-tag${hasWechat ? ' mine-member-tag--wechat' : ''}`}>
|
||||
{memberLabel}
|
||||
</Text>
|
||||
{!hasWechat && canWxBind ? (
|
||||
<Text className="mine-wechat-hint">点击头像完成微信授权</Text>
|
||||
) : null}
|
||||
{needFill || !hasWechat ? (
|
||||
<View className="mine-avatar-status mine-avatar-status--pending">
|
||||
<Text>{bindingWx || savingProfile ? '处理中' : hasWechat ? '选头像' : '去授权'}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</Button>
|
||||
) : (
|
||||
<View
|
||||
className={`mine-avatar-wrap${!hasWechat ? ' mine-avatar-wrap--action' : ''}`}
|
||||
onClick={() => void handleH5AvatarTap()}
|
||||
>
|
||||
<View
|
||||
className={`mine-avatar${hasWechat ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'}`}
|
||||
>
|
||||
{renderAvatarContent(display.avatarUrl)}
|
||||
</View>
|
||||
{!hasWechat ? (
|
||||
<View className="mine-avatar-status mine-avatar-status--pending">
|
||||
<Text>{bindingWx ? '授权中' : '去授权'}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View className="mine-profile-meta">
|
||||
{canWxProfile && needFill ? (
|
||||
<>
|
||||
<Input
|
||||
className="mine-nickname-input"
|
||||
type="nickname"
|
||||
value={draftNickname}
|
||||
placeholder="点击填写微信昵称"
|
||||
maxlength={32}
|
||||
onInput={(e) => setDraftNickname(e.detail.value)}
|
||||
onBlur={() => void handleNicknameConfirm()}
|
||||
onConfirm={() => void handleNicknameConfirm()}
|
||||
/>
|
||||
<Text className="mine-member-tag">{memberLabel}</Text>
|
||||
<Text className="mine-wechat-hint">点击头像选微信头像,并填写昵称</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text className="mine-profile-name">{nickname}</Text>
|
||||
<Text className={`mine-member-tag${hasWechat && !needFill ? ' mine-member-tag--wechat' : ''}`}>
|
||||
{memberLabel}
|
||||
</Text>
|
||||
{canWxProfile ? (
|
||||
<Text
|
||||
className="mine-wechat-hint mine-wechat-hint--link"
|
||||
onClick={() => {
|
||||
setDraftNickname(needsWxProfileFill(profile) ? '' : nickname);
|
||||
setEditingProfile(true);
|
||||
}}
|
||||
>
|
||||
更新头像昵称
|
||||
</Text>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user