用户端小程序修改
This commit is contained in:
@@ -7,7 +7,7 @@ import RegionPicker from '../../components/RegionPicker';
|
||||
import { buildAddressListUrl, readCheckoutContext } from '../../lib/checkout-nav';
|
||||
import { DEFAULT_REGION, formatRegion, type RegionSelection } from '../../lib/region-data';
|
||||
import { normalizePhoneInput, validateMobilePhone } from '../../lib/phone';
|
||||
import { getStoredUserPhone } from '../../lib/user-phone';
|
||||
import { getStoredUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
||||
import { request, toast, type UserProfile } from '../../lib/api';
|
||||
|
||||
type AddressForm = {
|
||||
@@ -28,24 +28,23 @@ export default function AddressEditPage() {
|
||||
const [pickerOpen, setPickerOpen] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [form, setForm] = useState<AddressForm>({
|
||||
const [form, setForm] = useState<AddressForm>(() => ({
|
||||
receiverName: '',
|
||||
phone: '',
|
||||
phone: id ? '' : getStoredUserPhone(),
|
||||
province: DEFAULT_REGION.province,
|
||||
city: DEFAULT_REGION.city,
|
||||
district: DEFAULT_REGION.district,
|
||||
detail: '',
|
||||
isDefault: true,
|
||||
});
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (id) return;
|
||||
request<UserProfile>('/auth/me')
|
||||
.then((me) => {
|
||||
if (!me.phoneVerified) return;
|
||||
const stored = getStoredUserPhone();
|
||||
if (!stored) return;
|
||||
setForm((prev) => (prev.phone ? prev : { ...prev, phone: stored }));
|
||||
const phone = resolveDefaultUserPhone(me);
|
||||
if (!phone) return;
|
||||
setForm((prev) => (prev.phone ? prev : { ...prev, phone }));
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [id]);
|
||||
|
||||
@@ -5,7 +5,7 @@ import PageShell from '../../components/PageShell';
|
||||
import UserTabBar, { shouldRenderPageTabBar, syncTabBarSelected } from '../../components/UserTabBar';
|
||||
import { goLogin } from '../../lib/auth-nav';
|
||||
import { isLoggedIn, request, toast } from '../../lib/api';
|
||||
import { navBarStyle, useNavBarMetrics } from '../../lib/nav-bar';
|
||||
import { navBarStyle, tabNavContentStyle, useNavBarMetrics } from '../../lib/nav-bar';
|
||||
|
||||
type BenefitSummary = {
|
||||
totalBalance: number;
|
||||
@@ -64,18 +64,15 @@ export default function BenefitPage() {
|
||||
return (
|
||||
<PageShell variant="tab" className="benefit-page">
|
||||
<View className="benefit-header" style={navBarStyle(metrics)}>
|
||||
<Text className="benefit-header-title">好客权益</Text>
|
||||
<View
|
||||
className="benefit-header__content"
|
||||
style={{ height: `${metrics.navContentHeight}px` }}
|
||||
style={tabNavContentStyle(metrics)}
|
||||
>
|
||||
<View className="benefit-header-city">
|
||||
<View className="benefit-header-city-pin" />
|
||||
<Text>郑州市</Text>
|
||||
</View>
|
||||
<Text className="benefit-header-title">好客权益</Text>
|
||||
<View className="benefit-header-btn" onClick={() => toast('消息通知即将开放')}>
|
||||
<Text>🔔</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -13,7 +13,13 @@ import { BRAND_LOGO_WIDE_URL } from '@dukang/shared-types';
|
||||
import { finishLoginNavigate } from '../../lib/auth-nav';
|
||||
import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import { fetchUserProfile } from '../../lib/pay-wechat';
|
||||
import { saveUserPhone } from '../../lib/user-phone';
|
||||
import { saveUserPhone, resolveDefaultUserPhone } from '../../lib/user-phone';
|
||||
import {
|
||||
fetchMiniWechatUserInfo,
|
||||
getCachedWxProfile,
|
||||
syncMiniWechatProfile,
|
||||
type MiniWechatProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, request, saveAuth, toast, type SessionPayload } from '../../lib/api';
|
||||
import { loginWithWechat } from '../../lib/wechat-auth';
|
||||
|
||||
@@ -91,18 +97,28 @@ export default function LoginPage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function applySessionAndLeave(data: SessionPayload | WechatLoginResult, phone?: string) {
|
||||
function applySessionAndLeave(
|
||||
data: SessionPayload | WechatLoginResult,
|
||||
phone?: string,
|
||||
wxInfo?: MiniWechatProfile | null,
|
||||
) {
|
||||
if (!data.accessToken) return;
|
||||
if (phone) saveUserPhone(phone);
|
||||
saveAuth({
|
||||
accessToken: data.accessToken,
|
||||
refreshToken: data.refreshToken,
|
||||
});
|
||||
void syncMiniWechatProfile(wxInfo ?? getCachedWxProfile());
|
||||
if (!phone) {
|
||||
void fetchUserProfile()
|
||||
.then((me) => resolveDefaultUserPhone(me))
|
||||
.catch(() => {});
|
||||
}
|
||||
toast('登录成功', 'success');
|
||||
finishLoginNavigate(returnTo);
|
||||
}
|
||||
|
||||
function handleWechatLoginResult(result: WechatLoginResult) {
|
||||
function handleWechatLoginResult(result: WechatLoginResult, wxInfo?: MiniWechatProfile | null) {
|
||||
if (result.needBindPhone && result.wxSessionKey) {
|
||||
setBindMode(true);
|
||||
setWxSessionKey(result.wxSessionKey);
|
||||
@@ -111,7 +127,7 @@ export default function LoginPage() {
|
||||
return;
|
||||
}
|
||||
if (result.accessToken) {
|
||||
applySessionAndLeave(result);
|
||||
applySessionAndLeave(result, undefined, wxInfo);
|
||||
return;
|
||||
}
|
||||
setMsg('微信登录未完成,请重试或使用手机号登录');
|
||||
@@ -196,8 +212,18 @@ export default function LoginPage() {
|
||||
setSentHint('');
|
||||
setWxLoading(true);
|
||||
try {
|
||||
let wxInfo: MiniWechatProfile | null = null;
|
||||
if (process.env.TARO_ENV === 'weapp') {
|
||||
try {
|
||||
wxInfo = await fetchMiniWechatUserInfo();
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '需要授权微信头像和昵称');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (completeMode === 'wechat' && isLoggedIn()) {
|
||||
const result = await bindWechatForUser();
|
||||
const result = await bindWechatForUser(wxInfo);
|
||||
if (!result.ok && result.needBindPhone) {
|
||||
setBindMode(true);
|
||||
setWxSessionKey(result.wxSessionKey);
|
||||
@@ -206,6 +232,7 @@ export default function LoginPage() {
|
||||
return;
|
||||
}
|
||||
if (result.ok) {
|
||||
await syncMiniWechatProfile(wxInfo);
|
||||
toast('微信授权成功', 'success');
|
||||
finishLoginNavigate(returnTo);
|
||||
return;
|
||||
@@ -213,7 +240,7 @@ export default function LoginPage() {
|
||||
return;
|
||||
}
|
||||
const result = await loginWithWechat();
|
||||
handleWechatLoginResult(result);
|
||||
handleWechatLoginResult(result, wxInfo);
|
||||
} catch (e) {
|
||||
const raw = e instanceof Error ? e.message : '微信登录失败';
|
||||
const hint = /invalid code/i.test(raw)
|
||||
|
||||
@@ -8,6 +8,12 @@ import UserTabBar, { shouldRenderPageTabBar, syncTabBarSelected } from '../../co
|
||||
import { BRAND_LOGO_MARK_URL } from '@dukang/shared-types';
|
||||
import { goLogin } from '../../lib/auth-nav';
|
||||
import { bindWechatForUser } from '../../lib/wechat-auth';
|
||||
import { fetchUserProfile } from '../../lib/pay-wechat';
|
||||
import {
|
||||
fetchMiniWechatUserInfo,
|
||||
getCachedWxProfile,
|
||||
mergeWxDisplayProfile,
|
||||
} from '../../lib/mini-wechat-profile';
|
||||
import { isLoggedIn, logout, request, toast, type UserProfile } from '../../lib/api';
|
||||
|
||||
const ORDER_SHORTCUTS = [
|
||||
@@ -28,22 +34,21 @@ function formatMoney(amount: number) {
|
||||
}
|
||||
|
||||
export default function MinePage() {
|
||||
const loggedIn = isLoggedIn();
|
||||
const [authed, setAuthed] = useState(() => isLoggedIn());
|
||||
const [profile, setProfile] = useState<UserProfile | null>(null);
|
||||
const [benefitBalance, setBenefitBalance] = useState(0);
|
||||
const [orderCounts, setOrderCounts] = useState<Record<string, number>>({});
|
||||
const [wxAuthorize, setWxAuthorize] = useState(true);
|
||||
const [bindingWx, setBindingWx] = useState(false);
|
||||
|
||||
useDidShow(() => {
|
||||
syncTabBarSelected(3);
|
||||
if (isLoggedIn()) {
|
||||
loadProfile();
|
||||
}
|
||||
});
|
||||
function resetGuestState() {
|
||||
setProfile(null);
|
||||
setBenefitBalance(0);
|
||||
setOrderCounts({});
|
||||
}
|
||||
|
||||
function loadProfile() {
|
||||
if (!loggedIn) return;
|
||||
if (!isLoggedIn()) return;
|
||||
Promise.all([
|
||||
request<UserProfile>('/auth/me'),
|
||||
request<Array<Record<string, unknown>>>('/benefit/coupons').catch(() => []),
|
||||
@@ -52,7 +57,7 @@ export default function MinePage() {
|
||||
),
|
||||
])
|
||||
.then(([me, coupons, ...totals]) => {
|
||||
setProfile(me);
|
||||
setProfile(mergeWxDisplayProfile(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;
|
||||
@@ -67,30 +72,74 @@ export default function MinePage() {
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
syncTabBarSelected(3);
|
||||
const loggedInNow = isLoggedIn();
|
||||
setAuthed(loggedInNow);
|
||||
if (loggedInNow) {
|
||||
loadProfile();
|
||||
} else {
|
||||
resetGuestState();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
request<ClientRuntimeConfig>('/common/client-config')
|
||||
.then((config) => setWxAuthorize(isWxAuthorizeEnabled(config)))
|
||||
.catch(() => setWxAuthorize(true));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadProfile();
|
||||
}, [loggedIn]);
|
||||
|
||||
async function handleAvatarTap() {
|
||||
if (!loggedIn) {
|
||||
if (!isLoggedIn()) {
|
||||
goLogin('/pages/mine/index');
|
||||
return;
|
||||
}
|
||||
if (profile?.hasWechat || !wxAuthorize || process.env.TARO_ENV !== 'weapp') return;
|
||||
if (bindingWx) return;
|
||||
|
||||
let current = profile;
|
||||
if (!current) {
|
||||
try {
|
||||
current = await fetchUserProfile();
|
||||
setProfile(current);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
if (current?.hasWechat) return;
|
||||
|
||||
if (!wxAuthorize) {
|
||||
toast('当前环境未开启微信授权');
|
||||
return;
|
||||
}
|
||||
if (process.env.TARO_ENV !== 'weapp') {
|
||||
toast('请在微信小程序中完成微信授权');
|
||||
return;
|
||||
}
|
||||
|
||||
setBindingWx(true);
|
||||
try {
|
||||
const result = await bindWechatForUser();
|
||||
let wxInfo = null;
|
||||
try {
|
||||
wxInfo = await fetchMiniWechatUserInfo();
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '需要授权微信头像和昵称');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await bindWechatForUser(wxInfo);
|
||||
if (!result.ok && result.needBindPhone) {
|
||||
goLogin('/pages/mine/index', { bindMode: '1', wxSessionKey: result.wxSessionKey });
|
||||
return;
|
||||
}
|
||||
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');
|
||||
}
|
||||
@@ -115,33 +164,46 @@ export default function MinePage() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderAvatarContent(profile: UserProfile | null) {
|
||||
if (profile?.avatarUrl) {
|
||||
return <Image className="mine-avatar-img" src={profile.avatarUrl} mode="aspectFill" />;
|
||||
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" />;
|
||||
}
|
||||
return <Image className="mine-avatar-img" src={BRAND_LOGO_MARK_URL} mode="aspectFit" />;
|
||||
}
|
||||
|
||||
if (!loggedIn) {
|
||||
if (!authed) {
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page">
|
||||
<TabMainHeader title="我的" />
|
||||
<View className="mine-header">
|
||||
<View className="mine-header-texture" />
|
||||
<View className="mine-profile">
|
||||
<View className="mine-avatar">
|
||||
<Image className="mine-avatar-img" src={BRAND_LOGO_MARK_URL} mode="aspectFit" />
|
||||
<View className="mine-avatar-wrap mine-avatar-wrap--action" onClick={() => goLogin('/pages/mine/index')}>
|
||||
<View className="mine-avatar mine-avatar--wx-pending">
|
||||
<Image className="mine-avatar-img" src={BRAND_LOGO_MARK_URL} mode="aspectFit" />
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Text className="mine-profile-name">未登录</Text>
|
||||
<Text className="mine-member-tag">好客会员</Text>
|
||||
<Text className="mine-member-tag">点击头像登录</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
<View className="mine-login-gate u-card">
|
||||
<View className="u-empty">登录后管理订单与个人信息</View>
|
||||
<View className="mine-login-gate">
|
||||
<View className="mine-login-gate-hint">登录后管理订单与个人信息</View>
|
||||
<View
|
||||
className="u-btn u-btn--block"
|
||||
className="mine-login-btn"
|
||||
onClick={() => goLogin('/pages/mine/index')}
|
||||
>
|
||||
<Text>去登录</Text>
|
||||
@@ -152,9 +214,11 @@ export default function MinePage() {
|
||||
);
|
||||
}
|
||||
|
||||
const nickname = profile?.nickname || '用户';
|
||||
const hasWechat = !!profile?.hasWechat;
|
||||
const memberLabel = hasWechat ? '微信会员' : '未授权微信';
|
||||
const canWxBind = wxAuthorize && process.env.TARO_ENV === 'weapp';
|
||||
const display = resolveDisplayProfile(profile, hasWechat);
|
||||
const nickname = display.nickname;
|
||||
const memberLabel = hasWechat ? '好客会员' : canWxBind ? '微信未授权' : '未授权微信';
|
||||
|
||||
return (
|
||||
<PageShell variant="tab" className="mine-page">
|
||||
@@ -162,12 +226,19 @@ export default function MinePage() {
|
||||
<View className="mine-header">
|
||||
<View className="mine-header-texture" />
|
||||
<View className="mine-profile">
|
||||
<View className="mine-avatar-wrap" onClick={() => void handleAvatarTap()}>
|
||||
<View className="mine-avatar">
|
||||
{renderAvatarContent(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' : ''}`}
|
||||
>
|
||||
{renderAvatarContent(display.avatarUrl)}
|
||||
</View>
|
||||
{!hasWechat && wxAuthorize && process.env.TARO_ENV === 'weapp' ? (
|
||||
<Text className="mine-avatar-badge">{bindingWx ? '授权中' : '授权'}</Text>
|
||||
{!hasWechat && canWxBind ? (
|
||||
<View className="mine-avatar-status mine-avatar-status--pending">
|
||||
<Text>{bindingWx ? '授权中' : '去授权'}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
<View>
|
||||
@@ -175,7 +246,7 @@ export default function MinePage() {
|
||||
<Text className={`mine-member-tag${hasWechat ? ' mine-member-tag--wechat' : ''}`}>
|
||||
{memberLabel}
|
||||
</Text>
|
||||
{!hasWechat && wxAuthorize && process.env.TARO_ENV === 'weapp' ? (
|
||||
{!hasWechat && canWxBind ? (
|
||||
<Text className="mine-wechat-hint">点击头像完成微信授权</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user