chore(deploy): stop tracking auto-release.env; feat(mini-user): avatar profile sheet
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Remove secrets file from the index (keep local). Mine page can choose avatar and nickname via WeChat components. Co-authored-by: Cursor <cursoragent@cursor.com>
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,11 @@ import { goLogin } from '../../lib/auth-nav';
|
||||
import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import {
|
||||
fetchMiniWechatUserInfo,
|
||||
isDefaultMiniNickname,
|
||||
mergeWxDisplayProfile,
|
||||
needsWxProfileFill,
|
||||
uploadAvatarTempFile,
|
||||
uploadMiniWechatProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api';
|
||||
import { isWechatEnv } from '../../lib/weixin';
|
||||
@@ -42,6 +45,11 @@ export default function MinePage() {
|
||||
const [orderCounts, setOrderCounts] = useState<Record<string, number>>({});
|
||||
const [wxAuthorize, setWxAuthorize] = useState(true);
|
||||
const [bindingWx, setBindingWx] = useState(false);
|
||||
const [profileSheetOpen, setProfileSheetOpen] = useState(false);
|
||||
const [draftAvatarTemp, setDraftAvatarTemp] = useState('');
|
||||
const [draftAvatarUrl, setDraftAvatarUrl] = useState('');
|
||||
const [draftNickname, setDraftNickname] = useState('');
|
||||
const [savingProfile, setSavingProfile] = useState(false);
|
||||
|
||||
function resetGuestState() {
|
||||
setProfile(null);
|
||||
@@ -135,22 +143,83 @@ export default function MinePage() {
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '微信授权失败');
|
||||
toast(e instanceof Error ? e.message : '授权失败');
|
||||
return false;
|
||||
} finally {
|
||||
setBindingWx(false);
|
||||
}
|
||||
}
|
||||
|
||||
/** 未绑定微信时点击头像走授权(小程序 openId / H5 公众号 OAuth) */
|
||||
function openProfileSheet(me?: UserProfile | null) {
|
||||
const base = mergeWxDisplayProfile(
|
||||
me || profile || { id: '', nickname: null, avatarUrl: null, hasWechat: false },
|
||||
);
|
||||
setDraftAvatarTemp('');
|
||||
setDraftAvatarUrl(base.avatarUrl || '');
|
||||
setDraftNickname(isDefaultMiniNickname(base.nickname) ? '' : base.nickname || '');
|
||||
setProfileSheetOpen(true);
|
||||
}
|
||||
|
||||
/** 点击头像:绑定账号后用 chooseAvatar + nickname 获取头像昵称 */
|
||||
async function handleAvatarTap() {
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
if (profile?.hasWechat) return;
|
||||
await ensureWechatBound();
|
||||
loadProfile();
|
||||
if (!isWeapp) {
|
||||
if (!profile?.hasWechat) {
|
||||
const ok = await ensureWechatBound();
|
||||
if (ok) loadProfile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!profile?.hasWechat) {
|
||||
const ok = await ensureWechatBound();
|
||||
if (!ok) return;
|
||||
}
|
||||
openProfileSheet();
|
||||
}
|
||||
|
||||
async function onChooseAvatar(e: { detail?: { avatarUrl?: string } }) {
|
||||
const tempPath = e.detail?.avatarUrl?.trim();
|
||||
if (!tempPath) {
|
||||
toast('未获取到头像,请重试');
|
||||
return;
|
||||
}
|
||||
setDraftAvatarTemp(tempPath);
|
||||
setDraftAvatarUrl(tempPath);
|
||||
}
|
||||
|
||||
async function saveWxProfile() {
|
||||
const nickname = draftNickname.trim();
|
||||
if (!nickname) {
|
||||
toast('请填写昵称');
|
||||
return;
|
||||
}
|
||||
if (!draftAvatarTemp && !draftAvatarUrl) {
|
||||
toast('请选择头像');
|
||||
return;
|
||||
}
|
||||
setSavingProfile(true);
|
||||
try {
|
||||
if (!profile?.hasWechat) {
|
||||
const ok = await ensureWechatBound();
|
||||
if (!ok) return;
|
||||
}
|
||||
let avatarUrl = draftAvatarUrl;
|
||||
if (draftAvatarTemp) {
|
||||
avatarUrl = await uploadAvatarTempFile(draftAvatarTemp);
|
||||
}
|
||||
const updated = await uploadMiniWechatProfile({ nickname, avatarUrl });
|
||||
if (updated) applyProfile({ ...updated, hasWechat: true });
|
||||
setProfileSheetOpen(false);
|
||||
toast('头像昵称已更新', 'success');
|
||||
loadProfile();
|
||||
} catch (err) {
|
||||
toast(err instanceof Error ? err.message : '保存失败');
|
||||
} finally {
|
||||
setSavingProfile(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleService(item: (typeof SERVICES)[number]) {
|
||||
@@ -213,8 +282,16 @@ export default function MinePage() {
|
||||
throw new Error('wx profile helpers missing');
|
||||
}
|
||||
const nickname = display.nickname || '用户';
|
||||
const memberLabel = hasWechat ? '好客会员' : canWxAuth ? '未完成授权' : '未完成授权';
|
||||
void needsWxProfileFill(display);
|
||||
const needProfileFill = isWeapp && needsWxProfileFill(display);
|
||||
const memberLabel = needProfileFill
|
||||
? '点击头像完善资料'
|
||||
: hasWechat
|
||||
? '好客会员'
|
||||
: canWxAuth
|
||||
? '点击头像授权'
|
||||
: '好客会员';
|
||||
const avatarClickable = isWeapp || (!hasWechat && canWxAuth);
|
||||
const previewAvatar = draftAvatarUrl || display.avatarUrl;
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page no-tab-header">
|
||||
@@ -223,24 +300,37 @@ export default function MinePage() {
|
||||
<View className="mine-header-texture" />
|
||||
<View className="mine-profile">
|
||||
<View
|
||||
className={`mine-avatar-wrap${!hasWechat && canWxAuth ? ' mine-avatar-wrap--action' : ''}`}
|
||||
onClick={!hasWechat && canWxAuth ? () => void handleAvatarTap() : undefined}
|
||||
className={`mine-avatar-wrap${avatarClickable ? ' mine-avatar-wrap--action' : ''}`}
|
||||
onClick={avatarClickable ? () => void handleAvatarTap() : undefined}
|
||||
>
|
||||
<View
|
||||
className={`mine-avatar${hasWechat ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'}`}
|
||||
className={`mine-avatar${
|
||||
!needProfileFill && hasWechat ? ' mine-avatar--wx-ok' : ' mine-avatar--wx-pending'
|
||||
}`}
|
||||
>
|
||||
{renderAvatarContent(display.avatarUrl)}
|
||||
</View>
|
||||
{!hasWechat && canWxAuth ? (
|
||||
<View className="mine-avatar-status mine-avatar-status--pending">
|
||||
<Text>{bindingWx ? '授权中' : '去授权'}</Text>
|
||||
{avatarClickable ? (
|
||||
<View
|
||||
className={`mine-avatar-status${
|
||||
needProfileFill || !hasWechat
|
||||
? ' mine-avatar-status--pending'
|
||||
: ' mine-avatar-status--ok'
|
||||
}`}
|
||||
>
|
||||
<Text>
|
||||
{bindingWx ? '授权中' : needProfileFill || !hasWechat ? '去完善' : '更换'}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View className="mine-profile-meta">
|
||||
<View
|
||||
className="mine-profile-meta"
|
||||
onClick={avatarClickable ? () => void handleAvatarTap() : undefined}
|
||||
>
|
||||
<Text className="mine-profile-name">{nickname}</Text>
|
||||
<Text className={`mine-member-tag${hasWechat ? ' mine-member-tag--wechat' : ''}`}>
|
||||
<Text className={`mine-member-tag${hasWechat && !needProfileFill ? ' mine-member-tag--wechat' : ''}`}>
|
||||
{memberLabel}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -338,6 +428,58 @@ export default function MinePage() {
|
||||
</View>
|
||||
|
||||
{shouldRenderPageTabBar() ? <UserTabBar selected={3} /> : null}
|
||||
|
||||
{profileSheetOpen ? (
|
||||
<View className="mine-profile-sheet-mask" onClick={() => !savingProfile && setProfileSheetOpen(false)}>
|
||||
<View className="mine-profile-sheet" onClick={(e) => e.stopPropagation()}>
|
||||
<Text className="mine-profile-sheet-title">完善头像与昵称</Text>
|
||||
<Text className="mine-profile-sheet-hint">
|
||||
点击头像选择,并填写昵称后保存(用于个人中心展示)
|
||||
</Text>
|
||||
<Button
|
||||
className="mine-profile-avatar-btn"
|
||||
openType="chooseAvatar"
|
||||
hoverClass="none"
|
||||
onChooseAvatar={onChooseAvatar}
|
||||
>
|
||||
<View className="mine-profile-avatar-preview">
|
||||
{previewAvatar ? (
|
||||
<Image className="mine-avatar-img" src={previewAvatar} mode="aspectFill" />
|
||||
) : (
|
||||
<Image className="mine-avatar-img" src={BRAND_LOGO_MARK_URL} mode="aspectFit" />
|
||||
)}
|
||||
</View>
|
||||
<Text className="mine-profile-avatar-tip">点击选择头像</Text>
|
||||
</Button>
|
||||
<View className="mine-profile-nickname-wrap">
|
||||
<Text className="mine-profile-nickname-label">昵称</Text>
|
||||
<Input
|
||||
className="mine-profile-nickname-input"
|
||||
type="nickname"
|
||||
maxlength={32}
|
||||
placeholder="点击填写昵称"
|
||||
value={draftNickname}
|
||||
onInput={(e) => setDraftNickname(e.detail.value)}
|
||||
onBlur={(e) => setDraftNickname(e.detail.value.trim())}
|
||||
/>
|
||||
</View>
|
||||
<View className="mine-profile-sheet-actions">
|
||||
<View
|
||||
className="mine-profile-sheet-cancel"
|
||||
onClick={() => !savingProfile && setProfileSheetOpen(false)}
|
||||
>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View
|
||||
className={`mine-profile-sheet-save${savingProfile ? ' is-disabled' : ''}`}
|
||||
onClick={savingProfile ? undefined : () => void saveWxProfile()}
|
||||
>
|
||||
<Text>{savingProfile ? '保存中...' : '保存'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user