feat(store): 门店套餐 v3.4.10 全端实现
新增 StorePackage 与变更审核流,覆盖总部直存、合伙/门店提审、C 端展示与套餐异议工单;同步 PRD/开发文档与 REQ 索引。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { View, Text, Image } from '@tarojs/components';
|
||||
import { View, Text, Image, Textarea } from '@tarojs/components';
|
||||
import Taro, {
|
||||
useDidShow,
|
||||
useLoad,
|
||||
@@ -28,6 +28,15 @@ type StoreMedia = {
|
||||
mediaType?: string | null;
|
||||
};
|
||||
|
||||
type StorePackage = {
|
||||
name: string;
|
||||
price: string | number;
|
||||
dishes: string;
|
||||
usableTime?: string | null;
|
||||
otherNotes?: string | null;
|
||||
sortOrder?: number;
|
||||
};
|
||||
|
||||
type Store = {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -42,6 +51,7 @@ type Store = {
|
||||
coverUrl?: string | null;
|
||||
carouselUrls?: string[] | null;
|
||||
media?: StoreMedia[] | null;
|
||||
packages?: StorePackage[] | null;
|
||||
openTime?: string | null;
|
||||
closeTime?: string | null;
|
||||
openTime2?: string | null;
|
||||
@@ -142,6 +152,9 @@ export default function StoreDetailPage() {
|
||||
const [recentRedeems, setRecentRedeems] = useState<RecentRedeem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadError, setLoadError] = useState('');
|
||||
const [disputeOpen, setDisputeOpen] = useState(false);
|
||||
const [disputeRemark, setDisputeRemark] = useState('');
|
||||
const [disputeSubmitting, setDisputeSubmitting] = useState(false);
|
||||
const [headerSolid, setHeaderSolid] = useState(false);
|
||||
const storeRef = useRef<Store | null>(null);
|
||||
storeRef.current = store;
|
||||
@@ -263,6 +276,27 @@ export default function StoreDetailPage() {
|
||||
Taro.makePhoneCall({ phoneNumber: store.phone }).catch(() => toast('无法拨打电话'));
|
||||
}
|
||||
|
||||
async function submitPackageDispute() {
|
||||
if (!storeId) return;
|
||||
setDisputeSubmitting(true);
|
||||
try {
|
||||
await request('/trade/package-disputes', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
storeId,
|
||||
remark: disputeRemark.trim() || '用户对门店套餐有异议',
|
||||
}),
|
||||
});
|
||||
toast('已提交套餐异议');
|
||||
setDisputeOpen(false);
|
||||
setDisputeRemark('');
|
||||
} catch (e) {
|
||||
toast(e instanceof Error ? e.message : '提交失败');
|
||||
} finally {
|
||||
setDisputeSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
function openMap() {
|
||||
if (!store) return;
|
||||
const lat = store.latitude != null ? Number(store.latitude) : NaN;
|
||||
@@ -389,6 +423,29 @@ export default function StoreDetailPage() {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{store.packages && store.packages.length > 0 ? (
|
||||
<View className="store-detail-section">
|
||||
<Text className="store-detail-section-title">门店套餐</Text>
|
||||
{store.packages.map((pkg, index) => (
|
||||
<View key={`${pkg.name}-${index}`} className="store-detail-package-card">
|
||||
<Text className="store-detail-package-name">{pkg.name}</Text>
|
||||
<Text className="store-detail-package-body">
|
||||
{formatRedeemAmountYuan(pkg.price)} 元 · {pkg.dishes}
|
||||
</Text>
|
||||
{pkg.usableTime ? (
|
||||
<Text className="store-detail-package-meta">使用时间:{pkg.usableTime}</Text>
|
||||
) : null}
|
||||
{pkg.otherNotes ? (
|
||||
<Text className="store-detail-package-meta">说明:{pkg.otherNotes}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
))}
|
||||
<Text className="store-detail-package-dispute" onClick={() => setDisputeOpen(true)}>
|
||||
对套餐有异议?提交反馈
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{intro ? (
|
||||
<View className="store-detail-section">
|
||||
<Text className="store-detail-section-title">门店详情</Text>
|
||||
@@ -420,6 +477,33 @@ export default function StoreDetailPage() {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{disputeOpen ? (
|
||||
<View className="store-detail-dispute-mask" onClick={() => !disputeSubmitting && setDisputeOpen(false)}>
|
||||
<View className="store-detail-dispute-panel" onClick={(e) => e.stopPropagation()}>
|
||||
<Text className="store-detail-section-title">套餐异议</Text>
|
||||
<Text className="store-detail-package-meta">请描述您对门店套餐内容的异议,客服将跟进处理。</Text>
|
||||
<Textarea
|
||||
className="store-detail-dispute-input"
|
||||
value={disputeRemark}
|
||||
maxlength={500}
|
||||
placeholder="请填写异议说明(选填)"
|
||||
onInput={(e) => setDisputeRemark(e.detail.value)}
|
||||
/>
|
||||
<View className="store-detail-dispute-actions">
|
||||
<View className="u-btn u-btn--ghost" onClick={() => setDisputeOpen(false)}>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View
|
||||
className="u-btn"
|
||||
onClick={() => !disputeSubmitting && void submitPackageDispute()}
|
||||
>
|
||||
<Text>{disputeSubmitting ? '提交中…' : '提交'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
<View className="store-detail-bar">
|
||||
<View
|
||||
className="u-btn u-btn--block"
|
||||
|
||||
Reference in New Issue
Block a user