diff --git a/apps/mini-user/src/components/ContactCsButton.tsx b/apps/mini-user/src/components/ContactCsButton.tsx
new file mode 100644
index 0000000..f1dfab7
--- /dev/null
+++ b/apps/mini-user/src/components/ContactCsButton.tsx
@@ -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 (
+
+ );
+}
diff --git a/apps/mini-user/src/pages/customer-service/index.tsx b/apps/mini-user/src/pages/customer-service/index.tsx
index 39208be..fe448fd 100644
--- a/apps/mini-user/src/pages/customer-service/index.tsx
+++ b/apps/mini-user/src/pages/customer-service/index.tsx
@@ -3,28 +3,42 @@ import Taro from '@tarojs/taro';
import { CUSTOMER_SERVICE_PHONE } from '@dukang/shared-types';
import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader';
+import ContactCsButton from '../../components/ContactCsButton';
import { toast } from '../../lib/api';
+const isWeapp = process.env.TARO_ENV === 'weapp';
const DIAL_PHONE = CUSTOMER_SERVICE_PHONE.replace(/-/g, '');
+function dialPhone() {
+ Taro.makePhoneCall({ phoneNumber: DIAL_PHONE }).catch(() => toast('无法拨打电话'));
+}
+
export default function CustomerServicePage() {
return (
- 客服热线
- {CUSTOMER_SERVICE_PHONE}
- 工作时间:9:00 - 21:00
- {
- Taro.makePhoneCall({ phoneNumber: DIAL_PHONE }).catch(() =>
- toast('无法拨打电话'),
- );
- }}
- >
- 拨打客服电话
+ 杜康好客客服
+
+ {isWeapp
+ ? '点击下方按钮,进入小程序在线客服会话'
+ : '请在微信小程序内打开以使用在线客服,或拨打客服电话'}
+
+ 工作时间:9:00 - 21:00
+
+ {isWeapp ? (
+
+ ) : null}
+
+
+
+ {isWeapp ? `或拨打客服电话 ${CUSTOMER_SERVICE_PHONE}` : '拨打客服电话'}
+
+
+ {!isWeapp ? (
+ {CUSTOMER_SERVICE_PHONE}
+ ) : null}
);
diff --git a/apps/mini-user/src/pages/order-detail/index.tsx b/apps/mini-user/src/pages/order-detail/index.tsx
index 808fa63..47393a8 100644
--- a/apps/mini-user/src/pages/order-detail/index.tsx
+++ b/apps/mini-user/src/pages/order-detail/index.tsx
@@ -5,6 +5,7 @@ import PageShell from '../../components/PageShell';
import SubPageHeader from '../../components/SubPageHeader';
import ShareNavButton from '../../components/ShareNavButton';
import WechatShareReady from '../../components/WechatShareReady';
+import ContactCsButton from '../../components/ContactCsButton';
import { request, toast } from '../../lib/api';
import { buildPayUrl } from '../../lib/checkout-nav';
import { maskPhone } from '../../lib/phone';
@@ -14,6 +15,8 @@ import {
toWeappShareMessage,
} from '../../lib/wechat-share';
+const isWeapp = process.env.TARO_ENV === 'weapp';
+
type OrderItem = {
productName?: string;
productSpec?: string;
@@ -104,8 +107,20 @@ export default function OrderDetailPage() {
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 (
-
+
- {canPay && order && (
-
-
- 待支付
-
- ¥{Number(order.payAmount ?? 0).toFixed(2)}
-
-
-
- 去付款
-
+ {order ? (
+
+ {isWeapp ? (
+
+ 联系客服
+
+ ) : (
+
+ 联系客服
+
+ )}
+ {canPay ? (
+ <>
+
+ 待支付
+
+ ¥{Number(order.payAmount ?? 0).toFixed(2)}
+
+
+
+ 去付款
+
+ >
+ ) : null}
- )}
+ ) : null}
);
}
diff --git a/apps/mini-user/src/styles/order.css b/apps/mini-user/src/styles/order.css
index 4f64095..6d4432d 100644
--- a/apps/mini-user/src/styles/order.css
+++ b/apps/mini-user/src/styles/order.css
@@ -7,7 +7,8 @@
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));
}
@@ -16,6 +17,57 @@
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 {
background: var(--color-card);
border-radius: var(--radius-lg);
diff --git a/apps/mini-user/src/styles/redeem.css b/apps/mini-user/src/styles/redeem.css
index bd2fe36..63a62a5 100644
--- a/apps/mini-user/src/styles/redeem.css
+++ b/apps/mini-user/src/styles/redeem.css
@@ -499,27 +499,70 @@
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 {
font-size: 14px;
color: var(--color-subtle-gray);
margin-bottom: 12px;
}
-.cs-phone {
+.cs-phone,
+.cs-phone-display {
+ display: block;
font-family: var(--font-headline);
- font-size: 32px;
+ font-size: 28px;
font-weight: 700;
color: var(--color-heritage-red);
- margin-bottom: 8px;
+ margin-top: 20px;
letter-spacing: 1px;
}
.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;
color: var(--color-subtle-gray);
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 {
padding: 12px 32px;
border-radius: 999px;
@@ -528,3 +571,12 @@
font-size: 15px;
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;
+}