fix(mini-user): persist bind-phone session and stop login bounce
CI / verify (pull_request) Has been cancelled
CI / verify (pull_request) Has been cancelled
Phone bind discarded the new JWT after account merge, so the next page 401'd back to login. Also harden 401 race, location deny cache, unpaid repay CTA, and finishLoginNavigate.
This commit is contained in:
@@ -76,7 +76,18 @@ function parseBody(data: unknown): { code?: number; message?: string } {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 统一请求:注入 USER_MINI + Bearer,解包 { code, message, data } */
|
function isOnLoginPage(): boolean {
|
||||||
|
try {
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
const cur = pages[pages.length - 1] as { route?: string } | undefined;
|
||||||
|
const route = cur?.route || '';
|
||||||
|
return route.includes('pages/login');
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 统一请求:注入 CLIENT_APP + Bearer,解包 { code, message, data } */
|
||||||
export async function request<T = unknown>(path: string, options: ReqOptions = {}): Promise<T> {
|
export async function request<T = unknown>(path: string, options: ReqOptions = {}): Promise<T> {
|
||||||
const header: Record<string, string> = {
|
const header: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -96,8 +107,12 @@ export async function request<T = unknown>(path: string, options: ReqOptions = {
|
|||||||
const body = parseBody(res.data);
|
const body = parseBody(res.data);
|
||||||
|
|
||||||
if (status === 401 || body?.code === 401) {
|
if (status === 401 || body?.code === 401) {
|
||||||
clearAuth();
|
// 仅清掉「发起本请求时」仍在使用的 token,避免登录页旧 /auth/me 竞态清掉刚写入的新 token
|
||||||
redirectToLogin();
|
const stillCurrent = !!token && getToken() === token;
|
||||||
|
if (stillCurrent) {
|
||||||
|
clearAuth();
|
||||||
|
if (!isOnLoginPage()) redirectToLogin();
|
||||||
|
}
|
||||||
throw new Error(body?.message || '登录已过期,请重新登录');
|
throw new Error(body?.message || '登录已过期,请重新登录');
|
||||||
}
|
}
|
||||||
if (status === 404 && body.code === undefined) {
|
if (status === 404 && body.code === undefined) {
|
||||||
|
|||||||
@@ -42,24 +42,31 @@ export function goLogin(returnPath?: string, extras?: Record<string, string>) {
|
|||||||
/** 登录成功后回到 return 页,或回退 / 首页 */
|
/** 登录成功后回到 return 页,或回退 / 首页 */
|
||||||
export function finishLoginNavigate(returnTo?: string) {
|
export function finishLoginNavigate(returnTo?: string) {
|
||||||
const raw = (returnTo || '').trim();
|
const raw = (returnTo || '').trim();
|
||||||
const target = raw ? decodeURIComponent(raw) : '';
|
let target = '';
|
||||||
|
try {
|
||||||
|
target = raw ? decodeURIComponent(raw) : '';
|
||||||
|
} catch {
|
||||||
|
target = raw;
|
||||||
|
}
|
||||||
|
// 防止 return 仍指向登录页造成死循环
|
||||||
const pathOnly = target.split('?')[0];
|
const pathOnly = target.split('?')[0];
|
||||||
|
if (!pathOnly || pathOnly.includes('/pages/login')) {
|
||||||
if (pathOnly && TAB_PAGES.has(pathOnly)) {
|
Taro.reLaunch({ url: '/pages/home/index' });
|
||||||
Taro.switchTab({ url: pathOnly });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pathOnly && pathOnly.startsWith('/pages/')) {
|
|
||||||
|
if (TAB_PAGES.has(pathOnly)) {
|
||||||
|
Taro.switchTab({ url: pathOnly }).catch(() => {
|
||||||
|
Taro.reLaunch({ url: pathOnly });
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (pathOnly.startsWith('/pages/')) {
|
||||||
Taro.redirectTo({ url: target }).catch(() => {
|
Taro.redirectTo({ url: target }).catch(() => {
|
||||||
Taro.reLaunch({ url: pathOnly });
|
Taro.reLaunch({ url: pathOnly });
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pages = Taro.getCurrentPages();
|
Taro.reLaunch({ url: '/pages/home/index' });
|
||||||
if (pages.length > 1) {
|
|
||||||
Taro.navigateBack();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Taro.switchTab({ url: '/pages/home/index' });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,12 @@ export async function ensurePayReady(returnPath: string): Promise<boolean> {
|
|||||||
|
|
||||||
goLogin(returnPath, { needWechat: '1' });
|
goLogin(returnPath, { needWechat: '1' });
|
||||||
return false;
|
return false;
|
||||||
} catch {
|
} catch (e) {
|
||||||
goLogin(returnPath);
|
// 仅会话失效时踢回登录;网络/业务错误不误清登录态
|
||||||
|
const msg = e instanceof Error ? e.message : '';
|
||||||
|
if (/登录已过期|重新登录|401/.test(msg) || !isLoggedIn()) {
|
||||||
|
goLogin(returnPath);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { DEFAULT_REGION, regionFromGeo, type RegionSelection } from './region-da
|
|||||||
import { FALLBACK_CITY_CODE } from './product-images';
|
import { FALLBACK_CITY_CODE } from './product-images';
|
||||||
|
|
||||||
export const GPS_CITY_STORAGE_KEY = 'dukang_gps_city';
|
export const GPS_CITY_STORAGE_KEY = 'dukang_gps_city';
|
||||||
|
/** 用户拒绝定位后持久化,避免首页/门店每次 useDidShow 再弹授权 */
|
||||||
|
const LOCATION_DENIED_KEY = 'dukang_location_denied';
|
||||||
|
|
||||||
export type ResolvedUserCity = {
|
export type ResolvedUserCity = {
|
||||||
province: string;
|
province: string;
|
||||||
@@ -30,7 +32,35 @@ const FALLBACK_CITY: ResolvedUserCity = {
|
|||||||
displayCity: '郑州市',
|
displayCity: '郑州市',
|
||||||
};
|
};
|
||||||
|
|
||||||
let locationPrompted = false;
|
function isLocationDenied(): boolean {
|
||||||
|
try {
|
||||||
|
return Taro.getStorageSync(LOCATION_DENIED_KEY) === '1';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function markLocationDenied() {
|
||||||
|
try {
|
||||||
|
Taro.setStorageSync(LOCATION_DENIED_KEY, '1');
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLocationDenied() {
|
||||||
|
try {
|
||||||
|
Taro.removeStorageSync(LOCATION_DENIED_KEY);
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDenyMessage(errMsg?: string): boolean {
|
||||||
|
return /auth deny|authorize|permission|denied|拒绝|用户拒绝|getLocation:fail/i.test(
|
||||||
|
errMsg || '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function readCache(): GpsCityCache | null {
|
function readCache(): GpsCityCache | null {
|
||||||
try {
|
try {
|
||||||
@@ -55,6 +85,12 @@ function writeCache(data: ResolvedUserCity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 拒绝或失败后写入兜底城市,避免短时间内反复调起定位 */
|
||||||
|
function cacheFallbackAndMaybeDeny(denied: boolean) {
|
||||||
|
if (denied) markLocationDenied();
|
||||||
|
writeCache(FALLBACK_CITY);
|
||||||
|
}
|
||||||
|
|
||||||
async function reportLocationToServer(payload: {
|
async function reportLocationToServer(payload: {
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
@@ -98,14 +134,12 @@ function toResolved(data: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptLocationAuth() {
|
async function promptLocationAuthOnce() {
|
||||||
if (locationPrompted) return;
|
|
||||||
locationPrompted = true;
|
|
||||||
await Taro.showModal({
|
await Taro.showModal({
|
||||||
title: '位置授权',
|
title: '位置授权',
|
||||||
content: '需要获取您的位置以展示所在城市的商品与门店',
|
content: '需要获取您的位置以展示所在城市的商品与门店。拒绝后将默认使用郑州市,不会再次弹窗。',
|
||||||
confirmText: '去授权',
|
confirmText: '知道了',
|
||||||
showCancel: true,
|
showCancel: false,
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,11 +161,14 @@ async function resolveViaH5Jssdk(): Promise<ResolvedUserCity | null> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!outcome.location) {
|
if (!outcome.location) {
|
||||||
|
const denied = isDenyMessage(outcome.errMsg);
|
||||||
await reportLocationToServer({
|
await reportLocationToServer({
|
||||||
sdk: outcome.sdk,
|
sdk: outcome.sdk,
|
||||||
status: 'fail',
|
status: 'fail',
|
||||||
errMsg: outcome.errMsg,
|
errMsg: outcome.errMsg,
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
// 失败一律缓存兜底,避免首页/门店每次进入再次调起微信定位弹窗
|
||||||
|
cacheFallbackAndMaybeDeny(denied);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,9 +180,13 @@ async function resolveViaH5Jssdk(): Promise<ResolvedUserCity | null> {
|
|||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
const resolved = toResolved(data);
|
const resolved = toResolved(data);
|
||||||
if (resolved) writeCache(resolved);
|
if (resolved) {
|
||||||
|
clearLocationDenied();
|
||||||
|
writeCache(resolved);
|
||||||
|
}
|
||||||
return resolved;
|
return resolved;
|
||||||
} catch {
|
} catch {
|
||||||
|
cacheFallbackAndMaybeDeny(false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,6 +194,10 @@ async function resolveViaH5Jssdk(): Promise<ResolvedUserCity | null> {
|
|||||||
/** 获取并解析用户当前城市;失败返回郑州市兜底 */
|
/** 获取并解析用户当前城市;失败返回郑州市兜底 */
|
||||||
export async function resolveUserCity(force = false): Promise<ResolvedUserCity> {
|
export async function resolveUserCity(force = false): Promise<ResolvedUserCity> {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
|
if (isLocationDenied()) {
|
||||||
|
const cached = readCache();
|
||||||
|
return cached ?? FALLBACK_CITY;
|
||||||
|
}
|
||||||
const cached = readCache();
|
const cached = readCache();
|
||||||
if (cached) return cached;
|
if (cached) return cached;
|
||||||
}
|
}
|
||||||
@@ -176,20 +221,22 @@ export async function resolveUserCity(force = false): Promise<ResolvedUserCity>
|
|||||||
});
|
});
|
||||||
const resolved = toResolved(data);
|
const resolved = toResolved(data);
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
|
clearLocationDenied();
|
||||||
writeCache(resolved);
|
writeCache(resolved);
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = err instanceof Error ? err.message : String(err);
|
const errMsg = err instanceof Error ? err.message : String(err);
|
||||||
const denied = /auth deny|authorize|permission|拒绝/i.test(errMsg);
|
const denied = isDenyMessage(errMsg);
|
||||||
if (denied) {
|
if (denied && !isLocationDenied()) {
|
||||||
await promptLocationAuth();
|
await promptLocationAuthOnce();
|
||||||
}
|
}
|
||||||
await reportLocationToServer({
|
await reportLocationToServer({
|
||||||
sdk: 'jssdk',
|
sdk: 'jssdk',
|
||||||
status: 'fail',
|
status: 'fail',
|
||||||
errMsg: errMsg.slice(0, 200),
|
errMsg: errMsg.slice(0, 200),
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
cacheFallbackAndMaybeDeny(denied);
|
||||||
}
|
}
|
||||||
|
|
||||||
return FALLBACK_CITY;
|
return FALLBACK_CITY;
|
||||||
|
|||||||
@@ -66,8 +66,15 @@ export default function LoginPage() {
|
|||||||
setCompleteMode(null);
|
setCompleteMode(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 完善资料场景才拉 profile;普通登录勿抢跑 /auth/me,避免旧 token 401 与短信登录竞态
|
||||||
|
if (!needPhone && !needWechat) {
|
||||||
|
setCompleteMode(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cancelled = false;
|
||||||
fetchUserProfile()
|
fetchUserProfile()
|
||||||
.then((me) => {
|
.then((me) => {
|
||||||
|
if (cancelled) return;
|
||||||
if (needPhone && !me.phoneVerified) {
|
if (needPhone && !me.phoneVerified) {
|
||||||
setCompleteMode('phone');
|
setCompleteMode('phone');
|
||||||
return;
|
return;
|
||||||
@@ -76,13 +83,14 @@ export default function LoginPage() {
|
|||||||
setCompleteMode('wechat');
|
setCompleteMode('wechat');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (needPhone || needWechat) {
|
finishLoginNavigate(returnTo);
|
||||||
finishLoginNavigate(returnTo);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCompleteMode(null);
|
|
||||||
})
|
})
|
||||||
.catch(() => setCompleteMode(null));
|
.catch(() => {
|
||||||
|
if (!cancelled) setCompleteMode(null);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
}, [needPhone, needWechat, returnTo]);
|
}, [needPhone, needWechat, returnTo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -103,6 +111,7 @@ export default function LoginPage() {
|
|||||||
data: SessionPayload | WechatLoginResult,
|
data: SessionPayload | WechatLoginResult,
|
||||||
phone?: string,
|
phone?: string,
|
||||||
wxInfo?: MiniWechatProfile | null,
|
wxInfo?: MiniWechatProfile | null,
|
||||||
|
successToast = '登录成功',
|
||||||
) {
|
) {
|
||||||
if (!data.accessToken) return;
|
if (!data.accessToken) return;
|
||||||
if (phone) saveUserPhone(phone);
|
if (phone) saveUserPhone(phone);
|
||||||
@@ -116,7 +125,7 @@ export default function LoginPage() {
|
|||||||
.then((me) => resolveDefaultUserPhone(me))
|
.then((me) => resolveDefaultUserPhone(me))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
toast('登录成功', 'success');
|
toast(successToast, 'success');
|
||||||
finishLoginNavigate(returnTo);
|
finishLoginNavigate(returnTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,19 +197,26 @@ export default function LoginPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (completeMode === 'phone' && isLoggedIn()) {
|
if (completeMode === 'phone' && isLoggedIn()) {
|
||||||
await request('/auth/phone/bind', {
|
// bind 返回新 session(合并账号后旧 guest JWT 立刻失效),必须落盘后再离开
|
||||||
|
const data = await request<SessionPayload>('/auth/phone/bind', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { phone: normalized, code: code.trim() },
|
data: { phone: normalized, code: code.trim() },
|
||||||
});
|
});
|
||||||
saveUserPhone(normalized);
|
if (!data?.accessToken) {
|
||||||
toast('手机号验证成功', 'success');
|
setMsg('手机号验证成功但会话未返回,请重新登录');
|
||||||
finishLoginNavigate(returnTo);
|
return;
|
||||||
|
}
|
||||||
|
applySessionAndLeave(data, normalized, null, '手机号验证成功');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = await request<SessionPayload>('/auth/login/sms', {
|
const data = await request<SessionPayload>('/auth/login/sms', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
data: { phone: normalized, code: code.trim() },
|
data: { phone: normalized, code: code.trim() },
|
||||||
});
|
});
|
||||||
|
if (!data?.accessToken) {
|
||||||
|
setMsg('登录成功但未返回令牌,请重试');
|
||||||
|
return;
|
||||||
|
}
|
||||||
applySessionAndLeave(data, normalized);
|
applySessionAndLeave(data, normalized);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setMsg(e instanceof Error ? e.message : '登录失败');
|
setMsg(e instanceof Error ? e.message : '登录失败');
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { View, Text } from '@tarojs/components';
|
import { View, Text } from '@tarojs/components';
|
||||||
import { useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
import Taro, { useRouter, useShareAppMessage, useShareTimeline } from '@tarojs/taro';
|
||||||
import PageShell from '../../components/PageShell';
|
import PageShell from '../../components/PageShell';
|
||||||
import SubPageHeader from '../../components/SubPageHeader';
|
import SubPageHeader from '../../components/SubPageHeader';
|
||||||
import ShareNavButton from '../../components/ShareNavButton';
|
import ShareNavButton from '../../components/ShareNavButton';
|
||||||
import WechatShareReady from '../../components/WechatShareReady';
|
import WechatShareReady from '../../components/WechatShareReady';
|
||||||
import { request, toast } from '../../lib/api';
|
import { request, toast } from '../../lib/api';
|
||||||
|
import { buildPayUrl } from '../../lib/checkout-nav';
|
||||||
import {
|
import {
|
||||||
DEFAULT_SHARE_DESC,
|
DEFAULT_SHARE_DESC,
|
||||||
DEFAULT_SHARE_TITLE,
|
DEFAULT_SHARE_TITLE,
|
||||||
@@ -21,6 +22,19 @@ type OrderDetail = {
|
|||||||
qty?: number;
|
qty?: number;
|
||||||
addressSnapshot?: string;
|
addressSnapshot?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
|
originOrderId?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const STATUS_LABELS: Record<string, string> = {
|
||||||
|
PENDING_PAY: '待付款',
|
||||||
|
PENDING_SHIP: '待发货',
|
||||||
|
OUT_WAREHOUSE: '出库中',
|
||||||
|
SHIPPING: '配送中',
|
||||||
|
PENDING_RECEIVE: '待签收',
|
||||||
|
COMPLETED: '已完成',
|
||||||
|
CANCELLED: '已取消',
|
||||||
|
REFUNDING: '退款中',
|
||||||
|
REFUNDED: '已退款',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function OrderDetailPage() {
|
export default function OrderDetailPage() {
|
||||||
@@ -35,6 +49,8 @@ export default function OrderDetailPage() {
|
|||||||
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
||||||
}, [orderId]);
|
}, [orderId]);
|
||||||
|
|
||||||
|
const canPay = !!order && order.status === 'PENDING_PAY' && !order.originOrderId;
|
||||||
|
|
||||||
const sharePayload = useMemo(
|
const sharePayload = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
title: order?.productName
|
title: order?.productName
|
||||||
@@ -52,8 +68,13 @@ export default function OrderDetailPage() {
|
|||||||
query: orderId ? `id=${orderId}` : '',
|
query: orderId ? `id=${orderId}` : '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function goPay() {
|
||||||
|
if (!order) return;
|
||||||
|
Taro.navigateTo({ url: buildPayUrl({ orderId: order.id }) });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell variant="sub" className="order-detail-page">
|
<PageShell variant="sub" className={`order-detail-page${canPay ? ' order-detail-page--with-pay' : ''}`}>
|
||||||
<WechatShareReady payload={sharePayload} />
|
<WechatShareReady payload={sharePayload} />
|
||||||
<SubPageHeader
|
<SubPageHeader
|
||||||
title="订单详情"
|
title="订单详情"
|
||||||
@@ -66,7 +87,9 @@ export default function OrderDetailPage() {
|
|||||||
<>
|
<>
|
||||||
<View className="order-card">
|
<View className="order-card">
|
||||||
<Text className="order-card-title">订单状态</Text>
|
<Text className="order-card-title">订单状态</Text>
|
||||||
<Text className="order-list-status">{order.status || '处理中'}</Text>
|
<Text className="order-list-status">
|
||||||
|
{STATUS_LABELS[order.status || ''] || order.status || '处理中'}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className="order-card">
|
<View className="order-card">
|
||||||
<Text className="order-card-title">商品信息</Text>
|
<Text className="order-card-title">商品信息</Text>
|
||||||
@@ -103,6 +126,20 @@ export default function OrderDetailPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{canPay && order && (
|
||||||
|
<View className="pay-bar order-detail-pay-bar">
|
||||||
|
<View className="order-confirm-total">
|
||||||
|
<Text className="order-confirm-total-label">待支付</Text>
|
||||||
|
<Text className="order-confirm-total-value">
|
||||||
|
¥{Number(order.payAmount ?? 0).toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="order-confirm-submit" onClick={goPay}>
|
||||||
|
去付款
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</PageShell>
|
</PageShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,15 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-detail-page--with-pay .sub-page-body {
|
||||||
|
padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-pay-bar {
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.order-card {
|
.order-card {
|
||||||
background: var(--color-card);
|
background: var(--color-card);
|
||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
|
|||||||
Reference in New Issue
Block a user