merge(dev_jacy): mini-user logistics preview and detail timeline (v3.4.13)
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
import { isXfxProviderCode } from '@dukang/shared-types';
|
||||||
|
import { formatShanghaiDateTime } from './datetime';
|
||||||
|
import { request } from './api';
|
||||||
|
|
||||||
|
export type OrderDelivery = {
|
||||||
|
provider?: string;
|
||||||
|
trackingNo?: string;
|
||||||
|
logisticsCompany?: string;
|
||||||
|
manualQueryUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTrackNode = {
|
||||||
|
trackInfo?: string;
|
||||||
|
createTime?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
statusName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrderTrackResponse = {
|
||||||
|
nodes?: OrderTrackNode[];
|
||||||
|
manualQueryUrl?: string | null;
|
||||||
|
provider?: string;
|
||||||
|
trackingNo?: string;
|
||||||
|
logisticsCompany?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 订单详情页展示物流摘要的状态(配送中) */
|
||||||
|
export const ORDER_LOGISTICS_PREVIEW_STATUSES = ['SHIPPING', 'SHIPPED'] as const;
|
||||||
|
|
||||||
|
export function deliveryProviderLabel(provider?: string, company?: string) {
|
||||||
|
if (company) return company;
|
||||||
|
if (!provider || provider === 'MOCK' || isXfxProviderCode(provider)) {
|
||||||
|
return '小飞侠配送';
|
||||||
|
}
|
||||||
|
if (provider === 'LOGISTICS') return '快递配送';
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldLoadOrderTrack(delivery?: OrderDelivery | null) {
|
||||||
|
if (!delivery) return false;
|
||||||
|
if (delivery.trackingNo) return true;
|
||||||
|
return isXfxProviderCode(delivery.provider || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTrackNodeTime(node: OrderTrackNode) {
|
||||||
|
return formatShanghaiDateTime(node.createTime || node.createdAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function latestTrackNode(nodes: OrderTrackNode[]) {
|
||||||
|
if (nodes.length === 0) return null;
|
||||||
|
return nodes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchOrderTrack(orderId: string) {
|
||||||
|
return request<OrderTrackResponse>(`/trade/orders/${orderId}/track`);
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@ import WechatShareReady from '../../components/WechatShareReady';
|
|||||||
import ContactCsButton from '../../components/ContactCsButton';
|
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 {
|
||||||
|
fetchOrderTrack,
|
||||||
|
latestTrackNode,
|
||||||
|
ORDER_LOGISTICS_PREVIEW_STATUSES,
|
||||||
|
shouldLoadOrderTrack,
|
||||||
|
type OrderTrackNode,
|
||||||
|
} from '../../lib/order-logistics';
|
||||||
import { maskPhone } from '../../lib/phone';
|
import { maskPhone } from '../../lib/phone';
|
||||||
import { confirmOrderReceive, type WechatConfirmPayload } from '../../lib/wechat-order-confirm';
|
import { confirmOrderReceive, type WechatConfirmPayload } from '../../lib/wechat-order-confirm';
|
||||||
import {
|
import {
|
||||||
@@ -85,11 +92,32 @@ export default function OrderDetailPage() {
|
|||||||
usePageView('order_detail_view', orderId ? { orderId } : undefined);
|
usePageView('order_detail_view', orderId ? { orderId } : undefined);
|
||||||
const [order, setOrder] = useState<OrderDetail | null>(null);
|
const [order, setOrder] = useState<OrderDetail | null>(null);
|
||||||
const [confirming, setConfirming] = useState(false);
|
const [confirming, setConfirming] = useState(false);
|
||||||
|
const [latestTrack, setLatestTrack] = useState<OrderTrackNode | null>(null);
|
||||||
|
const [trackLoading, setTrackLoading] = useState(false);
|
||||||
|
|
||||||
|
async function loadOrderTrack(delivery: OrderDetail['delivery']) {
|
||||||
|
if (!orderId || !shouldLoadOrderTrack(delivery)) {
|
||||||
|
setLatestTrack(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTrackLoading(true);
|
||||||
|
try {
|
||||||
|
const track = await fetchOrderTrack(orderId);
|
||||||
|
setLatestTrack(latestTrackNode(track.nodes ?? []));
|
||||||
|
} catch {
|
||||||
|
setLatestTrack(null);
|
||||||
|
} finally {
|
||||||
|
setTrackLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!orderId) return;
|
if (!orderId) return;
|
||||||
request<OrderDetail>(`/trade/orders/${orderId}`)
|
request<OrderDetail>(`/trade/orders/${orderId}`)
|
||||||
.then(setOrder)
|
.then((data) => {
|
||||||
|
setOrder(data);
|
||||||
|
void loadOrderTrack(data.delivery);
|
||||||
|
})
|
||||||
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
.catch((e) => toast(e instanceof Error ? e.message : '加载失败'));
|
||||||
}, [orderId]);
|
}, [orderId]);
|
||||||
|
|
||||||
@@ -97,7 +125,10 @@ export default function OrderDetailPage() {
|
|||||||
if (!orderId) return;
|
if (!orderId) return;
|
||||||
// 从微信确认收货组件返回后刷新
|
// 从微信确认收货组件返回后刷新
|
||||||
request<OrderDetail>(`/trade/orders/${orderId}`)
|
request<OrderDetail>(`/trade/orders/${orderId}`)
|
||||||
.then(setOrder)
|
.then((data) => {
|
||||||
|
setOrder(data);
|
||||||
|
void loadOrderTrack(data.delivery);
|
||||||
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,6 +144,11 @@ export default function OrderDetailPage() {
|
|||||||
['PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'SHIPPED', 'PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
|
['PENDING_SHIP', 'OUT_WAREHOUSE', 'SHIPPING', 'SHIPPED', 'PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
|
||||||
order.status || '',
|
order.status || '',
|
||||||
);
|
);
|
||||||
|
const showLogisticsPreview =
|
||||||
|
canViewLogistics &&
|
||||||
|
ORDER_LOGISTICS_PREVIEW_STATUSES.includes(
|
||||||
|
(order?.status || '') as (typeof ORDER_LOGISTICS_PREVIEW_STATUSES)[number],
|
||||||
|
);
|
||||||
|
|
||||||
const item = order?.items?.[0];
|
const item = order?.items?.[0];
|
||||||
const productName = item?.productName || order?.productName || '杜康商品';
|
const productName = item?.productName || order?.productName || '杜康商品';
|
||||||
@@ -230,7 +266,22 @@ export default function OrderDetailPage() {
|
|||||||
{isProxy && order.proxyPartnerName ? (
|
{isProxy && order.proxyPartnerName ? (
|
||||||
<Text className="order-proxy-hint">由合伙人 {order.proxyPartnerName} 代下</Text>
|
<Text className="order-proxy-hint">由合伙人 {order.proxyPartnerName} 代下</Text>
|
||||||
) : null}
|
) : null}
|
||||||
{canViewLogistics ? (
|
{showLogisticsPreview ? (
|
||||||
|
<View className="order-logistics-preview" onClick={goLogistics}>
|
||||||
|
<View className="order-logistics-preview-main">
|
||||||
|
{trackLoading ? (
|
||||||
|
<Text className="order-logistics-preview-info u-muted">物流信息加载中…</Text>
|
||||||
|
) : latestTrack?.trackInfo ? (
|
||||||
|
<Text className="order-logistics-preview-info">{latestTrack.trackInfo}</Text>
|
||||||
|
) : order.delivery?.manualQueryUrl ? (
|
||||||
|
<Text className="order-logistics-preview-info u-muted">物流已更新,点击查看详情</Text>
|
||||||
|
) : (
|
||||||
|
<Text className="order-logistics-preview-info u-muted">物流信息待更新,请稍后查看</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text className="order-logistics-preview-link">物流详情</Text>
|
||||||
|
</View>
|
||||||
|
) : canViewLogistics ? (
|
||||||
<Text className="order-logistics-link" onClick={goLogistics}>
|
<Text className="order-logistics-link" onClick={goLogistics}>
|
||||||
查看物流追踪
|
查看物流追踪
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -4,20 +4,16 @@ import Taro, { useRouter } from '@tarojs/taro';
|
|||||||
import PageShell from '../../components/PageShell';
|
import PageShell from '../../components/PageShell';
|
||||||
import SubPageHeader from '../../components/SubPageHeader';
|
import SubPageHeader from '../../components/SubPageHeader';
|
||||||
import { request, toast } from '../../lib/api';
|
import { request, toast } from '../../lib/api';
|
||||||
|
import {
|
||||||
|
deliveryProviderLabel,
|
||||||
|
fetchOrderTrack,
|
||||||
|
formatTrackNodeTime,
|
||||||
|
shouldLoadOrderTrack,
|
||||||
|
type OrderDelivery,
|
||||||
|
type OrderTrackNode,
|
||||||
|
} from '../../lib/order-logistics';
|
||||||
import { usePageView } from '../../lib/usePageView';
|
import { usePageView } from '../../lib/usePageView';
|
||||||
|
|
||||||
type OrderDelivery = {
|
|
||||||
provider?: string;
|
|
||||||
trackingNo?: string;
|
|
||||||
logisticsCompany?: string;
|
|
||||||
manualQueryUrl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TrackNode = {
|
|
||||||
trackInfo?: string;
|
|
||||||
createdAt?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OrderDetail = {
|
type OrderDetail = {
|
||||||
id: string;
|
id: string;
|
||||||
orderNo?: string;
|
orderNo?: string;
|
||||||
@@ -35,25 +31,13 @@ const STATUS_LABELS: Record<string, string> = {
|
|||||||
COMPLETED: '已完成',
|
COMPLETED: '已完成',
|
||||||
};
|
};
|
||||||
|
|
||||||
function deliveryProviderLabel(provider?: string, company?: string) {
|
|
||||||
if (company) return company;
|
|
||||||
if (!provider || provider === 'MOCK' || provider === 'XFX' || provider === 'XIAOFEIXIA') {
|
|
||||||
return '小飞侠配送';
|
|
||||||
}
|
|
||||||
if (provider === 'LOGISTICS') return '快递配送';
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDateTime(value: string) {
|
|
||||||
return String(value).slice(0, 19).replace('T', ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function OrderLogisticsPage() {
|
export default function OrderLogisticsPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const orderId = router.params.id ?? '';
|
const orderId = router.params.id ?? '';
|
||||||
usePageView('order_logistics_view', orderId ? { orderId } : undefined);
|
usePageView('order_logistics_view', orderId ? { orderId } : undefined);
|
||||||
const [order, setOrder] = useState<OrderDetail | null>(null);
|
const [order, setOrder] = useState<OrderDetail | null>(null);
|
||||||
const [trackNodes, setTrackNodes] = useState<TrackNode[]>([]);
|
const [trackNodes, setTrackNodes] = useState<OrderTrackNode[]>([]);
|
||||||
|
const [trackLoading, setTrackLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!orderId) return;
|
if (!orderId) return;
|
||||||
@@ -63,13 +47,18 @@ export default function OrderLogisticsPage() {
|
|||||||
const data = await request<OrderDetail>(`/trade/orders/${orderId}`);
|
const data = await request<OrderDetail>(`/trade/orders/${orderId}`);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
setOrder(data);
|
setOrder(data);
|
||||||
if (data.delivery?.trackingNo || data.delivery?.provider === 'XFX') {
|
if (!shouldLoadOrderTrack(data.delivery)) {
|
||||||
|
setTrackNodes([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTrackLoading(true);
|
||||||
try {
|
try {
|
||||||
const track = await request<{ nodes?: TrackNode[] }>(`/trade/orders/${orderId}/track`);
|
const track = await fetchOrderTrack(orderId);
|
||||||
if (!cancelled) setTrackNodes(track.nodes ?? []);
|
if (!cancelled) setTrackNodes(track.nodes ?? []);
|
||||||
} catch {
|
} catch {
|
||||||
if (!cancelled) setTrackNodes([]);
|
if (!cancelled) setTrackNodes([]);
|
||||||
}
|
} finally {
|
||||||
|
if (!cancelled) setTrackLoading(false);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) toast(e instanceof Error ? e.message : '加载失败');
|
if (!cancelled) toast(e instanceof Error ? e.message : '加载失败');
|
||||||
@@ -99,7 +88,7 @@ export default function OrderLogisticsPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageShell variant="sub" className="order-logistics-page">
|
<PageShell variant="sub" className="order-logistics-page">
|
||||||
<SubPageHeader title="物流追踪" />
|
<SubPageHeader title="物流详情" />
|
||||||
<View className="sub-page-body">
|
<View className="sub-page-body">
|
||||||
{!order ? (
|
{!order ? (
|
||||||
<View className="u-empty">加载中…</View>
|
<View className="u-empty">加载中…</View>
|
||||||
@@ -139,18 +128,37 @@ export default function OrderLogisticsPage() {
|
|||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{trackNodes.length > 0 ? (
|
{trackLoading ? (
|
||||||
<View className="order-card">
|
<View className="order-card">
|
||||||
<Text className="order-card-title">物流动态</Text>
|
<Text className="order-card-title">物流动态</Text>
|
||||||
|
<Text className="u-muted">加载中…</Text>
|
||||||
|
</View>
|
||||||
|
) : trackNodes.length > 0 ? (
|
||||||
|
<View className="order-card">
|
||||||
|
<Text className="order-card-title">物流动态</Text>
|
||||||
|
<View className="order-logistics-timeline">
|
||||||
{trackNodes.map((node, index) => (
|
{trackNodes.map((node, index) => (
|
||||||
<View key={`${node.trackInfo}-${index}`} className="order-logistics-node">
|
<View
|
||||||
<Text className="order-logistics-node-time">
|
key={`${node.trackInfo}-${node.createTime}-${index}`}
|
||||||
{node.createdAt ? formatDateTime(node.createdAt) : '—'}
|
className={`order-logistics-timeline-item${index === 0 ? ' order-logistics-timeline-item--latest' : ''}`}
|
||||||
</Text>
|
>
|
||||||
|
<View className="order-logistics-timeline-dot" />
|
||||||
|
<View className="order-logistics-timeline-body">
|
||||||
|
{node.statusName ? (
|
||||||
|
<Text className="order-logistics-timeline-status">{node.statusName}</Text>
|
||||||
|
) : null}
|
||||||
<Text className="order-logistics-node-info">{node.trackInfo || '—'}</Text>
|
<Text className="order-logistics-node-info">{node.trackInfo || '—'}</Text>
|
||||||
|
<Text className="order-logistics-node-time">{formatTrackNodeTime(node)}</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
|
) : shouldLoadOrderTrack(delivery) ? (
|
||||||
|
<View className="order-card">
|
||||||
|
<Text className="order-card-title">物流动态</Text>
|
||||||
|
<Text className="u-muted">暂无路由信息,请稍后刷新</Text>
|
||||||
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -501,10 +501,101 @@
|
|||||||
color: var(--color-heritage-red);
|
color: var(--color-heritage-red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-logistics-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(166, 29, 36, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-preview-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-preview-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-preview-link {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-heritage-red);
|
||||||
|
}
|
||||||
|
|
||||||
.order-row-value--link {
|
.order-row-value--link {
|
||||||
color: var(--color-heritage-red);
|
color: var(--color-heritage-red);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline {
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-item:last-child {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-item:not(:last-child)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
top: 14px;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background: rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-dot {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
margin-top: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--color-surface-container-high, #e8e4df);
|
||||||
|
border: 2px solid #fff;
|
||||||
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-item--latest .order-logistics-timeline-dot {
|
||||||
|
background: var(--color-heritage-red);
|
||||||
|
box-shadow: 0 0 0 1px var(--color-heritage-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-status {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-on-surface);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-logistics-timeline-item--latest .order-logistics-timeline-status {
|
||||||
|
color: var(--color-heritage-red);
|
||||||
|
}
|
||||||
|
|
||||||
.order-logistics-node {
|
.order-logistics-node {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
| 版本 | 日期 | 说明 |
|
| 版本 | 日期 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| **3.4.13** | 2026-08-05 | 推广码归因统计、核销用户信息、技术支持工单优先级、mini-user 门店/商品/提货/版本/物流体验、H5 登录校验、合伙人微信暂停禁登、**OSS 图片超 10MB 客户端压缩**;开发设计见 [`杜康好客-v3.4.13-体验优化开发文档.md`](./杜康好客-v3.4.13-体验优化开发文档.md) |
|
| **3.4.13** | 2026-08-05 | 推广码归因统计、核销用户信息、技术支持工单优先级、mini-user 门店/商品/提货/版本/**配送中物流摘要与详情时间线**、H5 登录校验、合伙人微信暂停禁登、**OSS 图片超 10MB 客户端压缩**;开发设计见 [`杜康好客-v3.4.13-体验优化开发文档.md`](./杜康好客-v3.4.13-体验优化开发文档.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -341,7 +341,7 @@ C2~C7、C14 见 §1.3。
|
|||||||
| H5 登录前端校验 | ✅ | partner/shop LoginPage |
|
| H5 登录前端校验 | ✅ | partner/shop LoginPage |
|
||||||
| mini-user 门店体验 | ✅ | 电话脱敏/埋点、门头 aspectFit、套餐折叠 |
|
| mini-user 门店体验 | ✅ | 电话脱敏/埋点、门头 aspectFit、套餐折叠 |
|
||||||
| mini-user 商品/提货 | ✅ | 去分享、首图 preview、提货确认弹框 |
|
| mini-user 商品/提货 | ✅ | 去分享、首图 preview、提货确认弹框 |
|
||||||
| mini-user 版本/物流 | ✅ | minClientVersion + UpdateManager;order-logistics |
|
| mini-user 版本/物流 | ✅ | minClientVersion + UpdateManager;配送中最新路由摘要 + 物流详情时间线(小飞侠 `/track`) |
|
||||||
| OSS 大图压缩 | ✅ | shared-ui compressImage;四端 upload + mini 头像 |
|
| OSS 大图压缩 | ✅ | shared-ui compressImage;四端 upload + mini 头像 |
|
||||||
| 文档 | ✅ | PRD §0.5 + v3.4.13 开发文档 |
|
| 文档 | ✅ | PRD §0.5 + v3.4.13 开发文档 |
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
| 模块 | 内容 |
|
| 模块 | 内容 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| admin-web | 推广码 attributionCount;核销记录用户信息;技术支持工单优先级;**大图上传前压缩** |
|
| admin-web | 推广码 attributionCount;核销记录用户信息;技术支持工单优先级;**大图上传前压缩** |
|
||||||
| mini-user | 门店电话脱敏+拨打埋点;门头/套餐展示;商品去分享+首图 preview;提货确认弹框;版本更新提示;物流追踪页;**头像超 10MB 压缩** |
|
| mini-user | 门店电话脱敏+拨打埋点;门头/套餐展示;商品去分享+首图 preview;提货确认弹框;版本更新提示;**订单详情配送中物流摘要+详情页时间线**;**头像超 10MB 压缩** |
|
||||||
| h5-partner / h5-shop / h5-user | 登录 phone/code 前端校验;**OSS 图片超 10MB 自动 canvas 压缩后上传** |
|
| h5-partner / h5-shop / h5-user | 登录 phone/code 前端校验;**OSS 图片超 10MB 自动 canvas 压缩后上传** |
|
||||||
| 后端 | 工单 priority 字段;client-config minClientVersion;合伙人微信登录暂停拦截 |
|
| 后端 | 工单 priority 字段;client-config minClientVersion;合伙人微信登录暂停拦截 |
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
| ST1785901775231811 | 门店套餐遮挡 | ✅ 折叠/行数限制 |
|
| ST1785901775231811 | 门店套餐遮挡 | ✅ 折叠/行数限制 |
|
||||||
| ST1785901711627824 | SHOP login 校验 | ✅ 前端空字段拦截 |
|
| ST1785901711627824 | SHOP login 校验 | ✅ 前端空字段拦截 |
|
||||||
| ST1785901314893145 | 门头照裁剪 | ✅ aspectFit + preview |
|
| ST1785901314893145 | 门头照裁剪 | ✅ aspectFit + preview |
|
||||||
| ST1785939375449985 | 订单物流追踪页 | ✅ order-logistics |
|
| ST1785939375449985 | 订单物流追踪页 | ✅ 配送中摘要 + 物流详情页时间线(小飞侠路由) |
|
||||||
| — | 上传图片超 10MB 先压缩 | ✅ `@dukang/shared-ui/compressImage` + 各端 upload |
|
| — | 上传图片超 10MB 先压缩 | ✅ `@dukang/shared-ui/compressImage` + 各端 upload |
|
||||||
|
|
||||||
## 3. 后端 API / 配置
|
## 3. 后端 API / 配置
|
||||||
@@ -44,23 +44,32 @@
|
|||||||
| GET | `/common/client-config` | 新增 `minClientVersion`(env `MINI_USER_MIN_VERSION`) |
|
| GET | `/common/client-config` | 新增 `minClientVersion`(env `MINI_USER_MIN_VERSION`) |
|
||||||
| — | `CommonSupportTicket.priority` | `LOW \| NORMAL \| HIGH \| URGENT`,默认 NORMAL |
|
| — | `CommonSupportTicket.priority` | `LOW \| NORMAL \| HIGH \| URGENT`,默认 NORMAL |
|
||||||
| — | `loginPartnerWechat` | 非 ACTIVE 账号抛出「合伙人账号已停用」 |
|
| — | `loginPartnerWechat` | 非 ACTIVE 账号抛出「合伙人账号已停用」 |
|
||||||
| GET | `/trade/orders/:id/track` | 物流追踪页复用 |
|
| GET | `/trade/orders/:id/track` | 物流路由;小飞侠(`XFX`/`XIAOFEIXIA`)走小飞侠 TRACK_ROUTE,其他承运商返回 manualQueryUrl |
|
||||||
|
|
||||||
## 4. 数据表
|
### 4. mini-user 物流(ST1785939375449985)
|
||||||
|
|
||||||
|
| 页面 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| 订单详情 | 状态为**配送中**(`SHIPPING`/`SHIPPED`)时,状态下方展示最新一条路由信息;右侧「物流详情」进入详情页 |
|
||||||
|
| 物流详情 | 配送方式、运单号、完整物流动态时间线(状态名 + 描述 + 时间);最新节点高亮 |
|
||||||
|
| 接口 | `GET /trade/orders/:id` + `GET /trade/orders/:id/track`;`shouldLoadOrderTrack`:有运单号或小飞侠 provider 时拉路由 |
|
||||||
|
| 公共模块 | `apps/mini-user/src/lib/order-logistics.ts` |
|
||||||
|
|
||||||
|
## 5. 数据表
|
||||||
|
|
||||||
- `common_support_ticket.priority` ENUM,默认 `NORMAL`
|
- `common_support_ticket.priority` ENUM,默认 `NORMAL`
|
||||||
|
|
||||||
## 5. HQ 开发计划
|
## 6. HQ 开发计划
|
||||||
|
|
||||||
在 admin-web **开发计划 → 版本列表** 创建 `v3.4.13`(状态 `IN_PROGRESS`),审批 ST 后关联 `dev_plan_task`。
|
在 admin-web **开发计划 → 版本列表** 创建 `v3.4.13`(状态 `IN_PROGRESS`),审批 ST 后关联 `dev_plan_task`。
|
||||||
|
|
||||||
## 6. 验收 ACC
|
## 7. 验收 ACC
|
||||||
|
|
||||||
- [ ] 推广码详情展示 attributionCount
|
- [ ] 推广码详情展示 attributionCount
|
||||||
- [ ] 核销记录含 userNo/nickname/phone
|
- [ ] 核销记录含 userNo/nickname/phone
|
||||||
- [ ] 技术支持可创建/筛选/编辑优先级
|
- [ ] 技术支持可创建/筛选/编辑优先级
|
||||||
- [ ] 合伙人/门店登录空字段前端提示;暂停合伙人微信登录被拒
|
- [ ] 合伙人/门店登录空字段前端提示;暂停合伙人微信登录被拒
|
||||||
- [ ] mini-user:电话脱敏、拨打埋点、提货确认、无分享按钮、首图 preview、门头/套餐正常
|
- [ ] mini-user:电话脱敏、拨打埋点、提货确认、无分享按钮、首图 preview、门头/套餐正常
|
||||||
- [ ] mini-user:低版本弹窗、UpdateManager、物流追踪页
|
- [ ] mini-user:低版本弹窗、UpdateManager;配送中展示最新物流 + 物流详情页完整时间线(小飞侠路由)
|
||||||
- [ ] 各端上传 >10MB 图片自动压缩后可上传;仍超限有明确报错
|
- [ ] 各端上传 >10MB 图片自动压缩后可上传;仍超限有明确报错
|
||||||
- [ ] `pnpm lint` 无新增错误
|
- [ ] `pnpm lint` 无新增错误
|
||||||
|
|||||||
Reference in New Issue
Block a user