Merge commit '56dfffd12647e1128ac4360aa9a4044361f0f7ab' into dev_jacy
CI / verify (pull_request) Has been cancelled

This commit is contained in:
2026-07-22 09:05:51 +08:00
5 changed files with 232 additions and 29 deletions
@@ -0,0 +1,50 @@
import { Button, Text } from '@tarojs/components';
import type { ReactNode } from 'react';
export type ContactCsSessionContext = {
orderId?: string;
orderNo?: string;
from?: string;
};
type ContactCsButtonProps = {
className?: string;
children?: ReactNode;
/** 客服会话来源上下文,便于客服后台识别 */
session?: ContactCsSessionContext;
};
const isWeapp = process.env.TARO_ENV === 'weapp';
/** 组装 session-from(微信限制约 1000 字符) */
export function buildCsSessionFrom(session?: ContactCsSessionContext): string {
if (!session) return 'dukang|from=mini-user';
const parts = ['dukang'];
if (session.from) parts.push(`from=${session.from}`);
if (session.orderNo) parts.push(`orderNo=${session.orderNo}`);
if (session.orderId) parts.push(`orderId=${session.orderId}`);
return parts.join('|');
}
/**
* 微信小程序客服入口(open-type=contact)。
* 非 weapp 环境不渲染,由调用方走电话等兜底。
*/
export default function ContactCsButton({
className = '',
children = '联系在线客服',
session,
}: ContactCsButtonProps) {
if (!isWeapp) return null;
return (
<Button
className={className}
openType="contact"
sessionFrom={buildCsSessionFrom(session)}
hoverClass="none"
>
{typeof children === 'string' ? <Text>{children}</Text> : children}
</Button>
);
}
@@ -3,28 +3,42 @@ import Taro from '@tarojs/taro';
import { CUSTOMER_SERVICE_PHONE } from '@dukang/shared-types'; import { CUSTOMER_SERVICE_PHONE } from '@dukang/shared-types';
import PageShell from '../../components/PageShell'; import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader'; import SubPageHeader from '../../components/SubPageHeader';
import ContactCsButton from '../../components/ContactCsButton';
import { toast } from '../../lib/api'; import { toast } from '../../lib/api';
const isWeapp = process.env.TARO_ENV === 'weapp';
const DIAL_PHONE = CUSTOMER_SERVICE_PHONE.replace(/-/g, ''); const DIAL_PHONE = CUSTOMER_SERVICE_PHONE.replace(/-/g, '');
function dialPhone() {
Taro.makePhoneCall({ phoneNumber: DIAL_PHONE }).catch(() => toast('无法拨打电话'));
}
export default function CustomerServicePage() { export default function CustomerServicePage() {
return ( return (
<PageShell variant="sub" className="cs-page"> <PageShell variant="sub" className="cs-page">
<SubPageHeader title="联系客服" /> <SubPageHeader title="联系客服" />
<View className="sub-page-body inset-page cs-body"> <View className="sub-page-body inset-page cs-body">
<Text className="cs-title">线</Text> <Text className="cs-brand"></Text>
<Text className="cs-phone">{CUSTOMER_SERVICE_PHONE}</Text> <Text className="cs-hint">
<Text className="cs-hint">9:00 - 21:00</Text> {isWeapp
<View ? '点击下方按钮,进入小程序在线客服会话'
className="cs-call-btn" : '请在微信小程序内打开以使用在线客服,或拨打客服电话'}
onClick={() => { </Text>
Taro.makePhoneCall({ phoneNumber: DIAL_PHONE }).catch(() => <Text className="cs-hours">9:00 - 21:00</Text>
toast('无法拨打电话'),
); {isWeapp ? (
}} <ContactCsButton className="cs-online-btn" session={{ from: 'customer-service' }} />
> ) : null}
<Text></Text>
<View className={isWeapp ? 'cs-phone-link' : 'cs-call-btn'} onClick={dialPhone}>
<Text>
{isWeapp ? `或拨打客服电话 ${CUSTOMER_SERVICE_PHONE}` : '拨打客服电话'}
</Text>
</View> </View>
{!isWeapp ? (
<Text className="cs-phone-display">{CUSTOMER_SERVICE_PHONE}</Text>
) : null}
</View> </View>
</PageShell> </PageShell>
); );
@@ -5,6 +5,7 @@ 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 ContactCsButton from '../../components/ContactCsButton';
import { request, toast } from '../../lib/api'; import { request, toast } from '../../lib/api';
import { buildPayUrl } from '../../lib/checkout-nav'; import { buildPayUrl } from '../../lib/checkout-nav';
import { maskPhone } from '../../lib/phone'; import { maskPhone } from '../../lib/phone';
@@ -14,6 +15,8 @@ import {
toWeappShareMessage, toWeappShareMessage,
} from '../../lib/wechat-share'; } from '../../lib/wechat-share';
const isWeapp = process.env.TARO_ENV === 'weapp';
type OrderItem = { type OrderItem = {
productName?: string; productName?: string;
productSpec?: string; productSpec?: string;
@@ -104,8 +107,20 @@ export default function OrderDetailPage() {
Taro.navigateTo({ url: buildPayUrl({ orderId: order.id }) }); Taro.navigateTo({ url: buildPayUrl({ orderId: order.id }) });
} }
function goCustomerService() {
Taro.navigateTo({ url: '/pages/customer-service/index' });
}
const pageClass = [
'order-detail-page',
order ? 'order-detail-page--with-actions' : '',
canPay ? 'order-detail-page--with-pay' : '',
]
.filter(Boolean)
.join(' ');
return ( return (
<PageShell variant="sub" className={`order-detail-page${canPay ? ' order-detail-page--with-pay' : ''}`}> <PageShell variant="sub" className={pageClass}>
<WechatShareReady payload={sharePayload} /> <WechatShareReady payload={sharePayload} />
<SubPageHeader <SubPageHeader
title="订单详情" title="订单详情"
@@ -173,8 +188,26 @@ export default function OrderDetailPage() {
)} )}
</View> </View>
{canPay && order && ( {order ? (
<View className="pay-bar order-detail-pay-bar"> <View className={`order-detail-actionbar${canPay ? ' order-detail-actionbar--with-pay' : ''}`}>
{isWeapp ? (
<ContactCsButton
className="order-detail-cs-btn"
session={{
from: 'order-detail',
orderId: order.id,
orderNo: order.orderNo,
}}
>
</ContactCsButton>
) : (
<View className="order-detail-cs-btn" onClick={goCustomerService}>
<Text></Text>
</View>
)}
{canPay ? (
<>
<View className="order-confirm-total"> <View className="order-confirm-total">
<Text className="order-confirm-total-label"></Text> <Text className="order-confirm-total-label"></Text>
<Text className="order-confirm-total-value"> <Text className="order-confirm-total-value">
@@ -184,8 +217,10 @@ export default function OrderDetailPage() {
<View className="order-confirm-submit" onClick={goPay}> <View className="order-confirm-submit" onClick={goPay}>
</View> </View>
</>
) : null}
</View> </View>
)} ) : null}
</PageShell> </PageShell>
); );
} }
+53 -1
View File
@@ -7,7 +7,8 @@
min-height: 100vh; min-height: 100vh;
} }
.order-detail-page--with-pay .sub-page-body { .order-detail-page--with-pay .sub-page-body,
.order-detail-page--with-actions .sub-page-body {
padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px)); padding-bottom: calc(88px + env(safe-area-inset-bottom, 0px));
} }
@@ -16,6 +17,57 @@
gap: 12px; gap: 12px;
} }
.order-detail-actionbar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 50;
display: flex;
align-items: center;
gap: 10px;
padding: 10px var(--space-page) calc(10px + env(safe-area-inset-bottom, 0px));
background: var(--color-card, #fff);
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.06);
box-sizing: border-box;
}
.order-detail-actionbar--with-pay {
justify-content: space-between;
}
.order-detail-cs-btn {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
min-width: 96px;
height: 40px;
padding: 0 14px;
margin: 0;
border: 1px solid var(--color-outline, #c8c4be);
border-radius: 999px;
background: transparent;
color: var(--color-on-surface);
font-size: 14px;
font-weight: 600;
line-height: 1.2;
box-sizing: border-box;
}
.order-detail-cs-btn::after {
border: none;
}
.order-detail-actionbar .order-confirm-total {
flex: 1;
min-width: 0;
}
.order-detail-actionbar .order-confirm-submit {
flex-shrink: 0;
}
.order-card { .order-card {
background: var(--color-card); background: var(--color-card);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
+55 -3
View File
@@ -499,27 +499,70 @@
text-align: center; text-align: center;
} }
.cs-brand {
display: block;
font-family: var(--font-headline);
font-size: 22px;
font-weight: 700;
color: var(--color-on-surface);
margin-bottom: 10px;
}
.cs-title { .cs-title {
font-size: 14px; font-size: 14px;
color: var(--color-subtle-gray); color: var(--color-subtle-gray);
margin-bottom: 12px; margin-bottom: 12px;
} }
.cs-phone { .cs-phone,
.cs-phone-display {
display: block;
font-family: var(--font-headline); font-family: var(--font-headline);
font-size: 32px; font-size: 28px;
font-weight: 700; font-weight: 700;
color: var(--color-heritage-red); color: var(--color-heritage-red);
margin-bottom: 8px; margin-top: 20px;
letter-spacing: 1px; letter-spacing: 1px;
} }
.cs-hint { .cs-hint {
display: block;
font-size: 14px;
color: var(--color-subtle-gray);
line-height: 1.5;
margin-bottom: 8px;
max-width: 280px;
}
.cs-hours {
display: block;
font-size: 12px; font-size: 12px;
color: var(--color-subtle-gray); color: var(--color-subtle-gray);
margin-bottom: 32px; margin-bottom: 32px;
} }
.cs-online-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
max-width: 280px;
padding: 14px 24px;
margin: 0;
border: none;
border-radius: 999px;
background: var(--color-heritage-red);
color: #fff;
font-size: 15px;
font-weight: 600;
line-height: 1.4;
box-sizing: border-box;
}
.cs-online-btn::after {
border: none;
}
.cs-call-btn { .cs-call-btn {
padding: 12px 32px; padding: 12px 32px;
border-radius: 999px; border-radius: 999px;
@@ -528,3 +571,12 @@
font-size: 15px; font-size: 15px;
font-weight: 600; font-weight: 600;
} }
.cs-phone-link {
margin-top: 20px;
padding: 8px 12px;
font-size: 13px;
color: var(--color-subtle-gray);
text-decoration: underline;
text-underline-offset: 3px;
}