merge(dev_jacy): v3.4.15 admin track and store list
This commit is contained in:
@@ -0,0 +1,185 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Descriptions,
|
||||||
|
Drawer,
|
||||||
|
Empty,
|
||||||
|
Image,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
Timeline,
|
||||||
|
Typography,
|
||||||
|
message,
|
||||||
|
} from 'antd';
|
||||||
|
import type { OrderTrackDto, OrderTrackNodeDto } from '@dukang/shared-types';
|
||||||
|
import { isXfxProviderCode } from '@dukang/shared-types';
|
||||||
|
import { request } from '../lib/api';
|
||||||
|
import { DELIVERY_PROVIDER_LABELS, fmtTime } from '../lib/constants';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
open: boolean;
|
||||||
|
orderId: string | null;
|
||||||
|
orderNo?: string | null;
|
||||||
|
orderStatus?: string | null;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function providerLabel(provider?: string | null, company?: string | null) {
|
||||||
|
if (company) return company;
|
||||||
|
if (!provider) return '—';
|
||||||
|
if (DELIVERY_PROVIDER_LABELS[provider]) return DELIVERY_PROVIDER_LABELS[provider];
|
||||||
|
if (isXfxProviderCode(provider)) return '小飞侠';
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortOldestFirst(nodes: OrderTrackNodeDto[]) {
|
||||||
|
return [...nodes].sort((a, b) => {
|
||||||
|
const ta = new Date(a.createTime).getTime();
|
||||||
|
const tb = new Date(b.createTime).getTime();
|
||||||
|
if (Number.isNaN(ta) && Number.isNaN(tb)) return 0;
|
||||||
|
if (Number.isNaN(ta)) return 1;
|
||||||
|
if (Number.isNaN(tb)) return -1;
|
||||||
|
return ta - tb;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OrderTrackDrawer({
|
||||||
|
open,
|
||||||
|
orderId,
|
||||||
|
orderNo,
|
||||||
|
orderStatus,
|
||||||
|
onClose,
|
||||||
|
}: Props) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [track, setTrack] = useState<OrderTrackDto | null>(null);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
if (!orderId) return;
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await request<OrderTrackDto>(`/admin/orders/${orderId}/track`);
|
||||||
|
setTrack(data);
|
||||||
|
} catch (e) {
|
||||||
|
setTrack(null);
|
||||||
|
message.error(e instanceof Error ? e.message : '加载路由失败');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [orderId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open || !orderId) {
|
||||||
|
setTrack(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void load();
|
||||||
|
}, [open, orderId, load]);
|
||||||
|
|
||||||
|
const nodes = useMemo(
|
||||||
|
() => (track?.nodes?.length ? sortOldestFirst(track.nodes) : []),
|
||||||
|
[track?.nodes],
|
||||||
|
);
|
||||||
|
const timelineComplete = ['PENDING_RECEIVE', 'DELIVERED', 'COMPLETED'].includes(
|
||||||
|
orderStatus || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
title="物流路由"
|
||||||
|
width={520}
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
extra={
|
||||||
|
<Button onClick={() => void load()} loading={loading} disabled={!orderId}>
|
||||||
|
刷新
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
{(orderNo || track?.trackingNo || track?.provider) && (
|
||||||
|
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
||||||
|
{orderNo ? <Descriptions.Item label="订单号">{orderNo}</Descriptions.Item> : null}
|
||||||
|
<Descriptions.Item label="配送方式">
|
||||||
|
{providerLabel(track?.provider, track?.logisticsCompany)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="运单号">{track?.trackingNo || '—'}</Descriptions.Item>
|
||||||
|
{track?.manualQueryUrl ? (
|
||||||
|
<Descriptions.Item label="物流查询">
|
||||||
|
<a href={track.manualQueryUrl} target="_blank" rel="noreferrer">
|
||||||
|
打开物流查询
|
||||||
|
</a>
|
||||||
|
</Descriptions.Item>
|
||||||
|
) : null}
|
||||||
|
{track?.estimatedArrival?.arriveTime ? (
|
||||||
|
<Descriptions.Item label="预计送达">
|
||||||
|
{fmtTime(track.estimatedArrival.arriveTime)}
|
||||||
|
{track.estimatedArrival.siteName
|
||||||
|
? `(${track.estimatedArrival.siteName})`
|
||||||
|
: ''}
|
||||||
|
</Descriptions.Item>
|
||||||
|
) : null}
|
||||||
|
</Descriptions>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{track?.signPhotoUrls && track.signPhotoUrls.length > 0 ? (
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Typography.Title level={5} style={{ marginTop: 0 }}>
|
||||||
|
签收照片
|
||||||
|
</Typography.Title>
|
||||||
|
<Image.PreviewGroup>
|
||||||
|
<Space wrap>
|
||||||
|
{track.signPhotoUrls.map((url) => (
|
||||||
|
<Image
|
||||||
|
key={url}
|
||||||
|
src={url}
|
||||||
|
width={96}
|
||||||
|
height={96}
|
||||||
|
style={{ objectFit: 'cover', borderRadius: 6 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Image.PreviewGroup>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<Typography.Title level={5} style={{ marginTop: 0 }}>
|
||||||
|
物流动态
|
||||||
|
</Typography.Title>
|
||||||
|
{nodes.length > 0 ? (
|
||||||
|
<Timeline
|
||||||
|
items={nodes.map((node, index) => {
|
||||||
|
const isLatest = index === nodes.length - 1;
|
||||||
|
return {
|
||||||
|
color: isLatest ? (timelineComplete ? 'green' : 'blue') : 'gray',
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
{node.statusName ? (
|
||||||
|
<Typography.Text strong style={{ display: 'block' }}>
|
||||||
|
{node.statusName}
|
||||||
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
<Typography.Text style={{ display: 'block', whiteSpace: 'pre-wrap' }}>
|
||||||
|
{node.trackInfo || '—'}
|
||||||
|
</Typography.Text>
|
||||||
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
{fmtTime(node.createTime)}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description={
|
||||||
|
track?.manualQueryUrl
|
||||||
|
? '暂无实时路由节点,可使用上方物流查询链接'
|
||||||
|
: '暂无路由信息,请稍后刷新'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,13 +3,26 @@ import {
|
|||||||
Button, Descriptions, Drawer, Form, Input, Space, Table, Typography, message,
|
Button, Descriptions, Drawer, Form, Input, Space, Table, Typography, message,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import OrderTrackDrawer from '../components/OrderTrackDrawer';
|
||||||
import { request } from '../lib/api';
|
import { request } from '../lib/api';
|
||||||
import { ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
|
import { ORDER_STATUS_LABELS, fmtTime } from '../lib/constants';
|
||||||
import { useAdminList } from '../lib/useAdminList';
|
import { useAdminList } from '../lib/useAdminList';
|
||||||
|
|
||||||
type Row = {
|
type Row = {
|
||||||
id: string; provider: string; trackingNo: string | null; providerOrderNo: string | null; updatedAt: string;
|
id: string;
|
||||||
order?: { orderNo: string; status: string; receiverName: string; receiverPhone: string; deliveryType: string };
|
orderId: string;
|
||||||
|
provider: string;
|
||||||
|
trackingNo: string | null;
|
||||||
|
providerOrderNo: string | null;
|
||||||
|
updatedAt: string;
|
||||||
|
order?: {
|
||||||
|
id: string;
|
||||||
|
orderNo: string;
|
||||||
|
status: string;
|
||||||
|
receiverName: string;
|
||||||
|
receiverPhone: string;
|
||||||
|
deliveryType: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DeliveriesPage() {
|
export default function DeliveriesPage() {
|
||||||
@@ -29,6 +42,22 @@ export default function DeliveriesPage() {
|
|||||||
);
|
);
|
||||||
const [detail, setDetail] = useState<Row | null>(null);
|
const [detail, setDetail] = useState<Row | null>(null);
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [trackOpen, setTrackOpen] = useState(false);
|
||||||
|
const [trackOrderId, setTrackOrderId] = useState<string | null>(null);
|
||||||
|
const [trackOrderNo, setTrackOrderNo] = useState<string | null>(null);
|
||||||
|
const [trackOrderStatus, setTrackOrderStatus] = useState<string | null>(null);
|
||||||
|
|
||||||
|
function openTrack(row: Row) {
|
||||||
|
const orderId = row.orderId || row.order?.id;
|
||||||
|
if (!orderId) {
|
||||||
|
message.warning('缺少关联订单,无法查询路由');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTrackOrderId(orderId);
|
||||||
|
setTrackOrderNo(row.order?.orderNo ?? null);
|
||||||
|
setTrackOrderStatus(row.order?.status ?? null);
|
||||||
|
setTrackOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
const columns: ColumnsType<Row> = [
|
const columns: ColumnsType<Row> = [
|
||||||
{ title: '订单号', dataIndex: ['order', 'orderNo'], width: 170 },
|
{ title: '订单号', dataIndex: ['order', 'orderNo'], width: 170 },
|
||||||
@@ -39,14 +68,17 @@ export default function DeliveriesPage() {
|
|||||||
{ title: '收货人', dataIndex: ['order', 'receiverName'], width: 90 },
|
{ title: '收货人', dataIndex: ['order', 'receiverName'], width: 90 },
|
||||||
{ title: '更新', dataIndex: 'updatedAt', width: 160, render: fmtTime },
|
{ title: '更新', dataIndex: 'updatedAt', width: 160, render: fmtTime },
|
||||||
{
|
{
|
||||||
title: '操作', width: 80,
|
title: '操作', width: 140,
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<Button type="link" size="small" onClick={async () => {
|
<Space size={0}>
|
||||||
const d = await request<Row>(`/admin/deliveries/${row.id}`);
|
<Button type="link" size="small" onClick={() => openTrack(row)}>路由</Button>
|
||||||
setDetail(d);
|
<Button type="link" size="small" onClick={async () => {
|
||||||
editForm.setFieldsValue({ provider: d.provider, trackingNo: d.trackingNo, providerOrderNo: d.providerOrderNo });
|
const d = await request<Row>(`/admin/deliveries/${row.id}`);
|
||||||
setDrawerOpen(true);
|
setDetail(d);
|
||||||
}}>编辑</Button>
|
editForm.setFieldsValue({ provider: d.provider, trackingNo: d.trackingNo, providerOrderNo: d.providerOrderNo });
|
||||||
|
setDrawerOpen(true);
|
||||||
|
}}>编辑</Button>
|
||||||
|
</Space>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -87,9 +119,22 @@ export default function DeliveriesPage() {
|
|||||||
<Form.Item name="providerOrderNo" label="第三方单号"><Input /></Form.Item>
|
<Form.Item name="providerOrderNo" label="第三方单号"><Input /></Form.Item>
|
||||||
<Form.Item name="trackingNo" label="运单号"><Input /></Form.Item>
|
<Form.Item name="trackingNo" label="运单号"><Input /></Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
<Button
|
||||||
|
style={{ marginTop: 8 }}
|
||||||
|
onClick={() => openTrack(detail)}
|
||||||
|
>
|
||||||
|
查看路由
|
||||||
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
<OrderTrackDrawer
|
||||||
|
open={trackOpen}
|
||||||
|
orderId={trackOrderId}
|
||||||
|
orderNo={trackOrderNo}
|
||||||
|
orderStatus={trackOrderStatus}
|
||||||
|
onClose={() => setTrackOpen(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
} from '../lib/constants';
|
} from '../lib/constants';
|
||||||
import { FULFILLMENT_HOLD_REASON_LABELS } from '@dukang/shared-types';
|
import { FULFILLMENT_HOLD_REASON_LABELS } from '@dukang/shared-types';
|
||||||
import ProxyOrderModal from '../components/ProxyOrderModal';
|
import ProxyOrderModal from '../components/ProxyOrderModal';
|
||||||
|
import OrderTrackDrawer from '../components/OrderTrackDrawer';
|
||||||
|
|
||||||
type ShipDefaults = {
|
type ShipDefaults = {
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -186,6 +187,7 @@ export default function OrdersPage() {
|
|||||||
const [shipMode, setShipMode] = useState<ShipMode>('EXPRESS');
|
const [shipMode, setShipMode] = useState<ShipMode>('EXPRESS');
|
||||||
const [warehouses, setWarehouses] = useState<WarehouseOption[]>([]);
|
const [warehouses, setWarehouses] = useState<WarehouseOption[]>([]);
|
||||||
const [proxyOpen, setProxyOpen] = useState(false);
|
const [proxyOpen, setProxyOpen] = useState(false);
|
||||||
|
const [trackOpen, setTrackOpen] = useState(false);
|
||||||
const canDeleteOrders = (profile?.permissionKeys ?? []).includes('orders_delete');
|
const canDeleteOrders = (profile?.permissionKeys ?? []).includes('orders_delete');
|
||||||
const canProxyOrder = (profile?.permissionKeys ?? []).includes('orders');
|
const canProxyOrder = (profile?.permissionKeys ?? []).includes('orders');
|
||||||
|
|
||||||
@@ -859,7 +861,20 @@ export default function OrdersPage() {
|
|||||||
</Descriptions>
|
</Descriptions>
|
||||||
|
|
||||||
{detail.delivery && (
|
{detail.delivery && (
|
||||||
<Descriptions column={1} bordered size="small" title="配送" style={{ marginTop: 16 }}>
|
<Descriptions
|
||||||
|
column={1}
|
||||||
|
bordered
|
||||||
|
size="small"
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<span>配送</span>
|
||||||
|
<Button type="link" size="small" style={{ padding: 0 }} onClick={() => setTrackOpen(true)}>
|
||||||
|
查看路由
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
style={{ marginTop: 16 }}
|
||||||
|
>
|
||||||
<Descriptions.Item label="快递公司">
|
<Descriptions.Item label="快递公司">
|
||||||
{detail.delivery.logisticsCompany ||
|
{detail.delivery.logisticsCompany ||
|
||||||
DELIVERY_PROVIDER_LABELS[detail.delivery.provider] ||
|
DELIVERY_PROVIDER_LABELS[detail.delivery.provider] ||
|
||||||
@@ -1232,6 +1247,14 @@ export default function OrdersPage() {
|
|||||||
void openDetail(order.id);
|
void openDetail(order.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<OrderTrackDrawer
|
||||||
|
open={trackOpen}
|
||||||
|
orderId={detail?.id ?? null}
|
||||||
|
orderNo={detail?.orderNo}
|
||||||
|
orderStatus={detail?.status}
|
||||||
|
onClose={() => setTrackOpen(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dukang/mini-user",
|
"name": "@dukang/mini-user",
|
||||||
"version": "3.4.14",
|
"version": "3.4.15",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "杜康好客 · C 端用户微信小程序(Taro)",
|
"description": "杜康好客 · C 端用户微信小程序(Taro)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -2,7 +2,7 @@ import Taro from '@tarojs/taro';
|
|||||||
import { fetchClientConfig } from './pay-wechat';
|
import { fetchClientConfig } from './pay-wechat';
|
||||||
|
|
||||||
/** 与 package.json version 同步,供服务端 minClientVersion 比对 */
|
/** 与 package.json version 同步,供服务端 minClientVersion 比对 */
|
||||||
export const APP_VERSION = '3.4.14';
|
export const APP_VERSION = '3.4.15';
|
||||||
|
|
||||||
export const APP_VERSION_LABEL = `v${APP_VERSION}`;
|
export const APP_VERSION_LABEL = `v${APP_VERSION}`;
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
toWeappShareMessage,
|
toWeappShareMessage,
|
||||||
toWeappShareTimeline,
|
toWeappShareTimeline,
|
||||||
} from '../../lib/wechat-share';
|
} from '../../lib/wechat-share';
|
||||||
|
import openBadgeImg from '../../assets/icons/store-open-badge.png';
|
||||||
|
|
||||||
type Store = {
|
type Store = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -341,12 +342,12 @@ export default function StoresPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function hoursLines(store: Store): string[] {
|
function hoursText(store: Store): string {
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (store.openTime && store.closeTime) parts.push(`${store.openTime}-${store.closeTime}`);
|
if (store.openTime && store.closeTime) parts.push(`${store.openTime}-${store.closeTime}`);
|
||||||
if (store.openTime2 && store.closeTime2) parts.push(`${store.openTime2}-${store.closeTime2}`);
|
if (store.openTime2 && store.closeTime2) parts.push(`${store.openTime2}-${store.closeTime2}`);
|
||||||
if (!parts.length) parts.push('10:00-22:00');
|
if (!parts.length) parts.push('10:00-22:00');
|
||||||
return parts.map((p, i) => (i === 0 ? `营业时间: ${p}` : p));
|
return `营业时间: ${parts.join(' ')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharePayload = useMemo(
|
const sharePayload = useMemo(
|
||||||
@@ -421,45 +422,38 @@ export default function StoresPage() {
|
|||||||
className="store-card"
|
className="store-card"
|
||||||
onClick={() => Taro.navigateTo({ url: `/pages/store-detail/index?id=${s.id}` })}
|
onClick={() => Taro.navigateTo({ url: `/pages/store-detail/index?id=${s.id}` })}
|
||||||
>
|
>
|
||||||
{s.coverUrl ? (
|
<View className="store-card-cover-wrap">
|
||||||
<Image className="store-card-cover" src={s.coverUrl} mode="aspectFill" />
|
{s.coverUrl ? (
|
||||||
) : (
|
<Image className="store-card-cover" src={s.coverUrl} mode="aspectFill" />
|
||||||
<View className="store-card-cover store-card-cover--empty" />
|
) : (
|
||||||
)}
|
<View className="store-card-cover store-card-cover--empty" />
|
||||||
|
)}
|
||||||
|
<Image
|
||||||
|
className="store-card-open-badge"
|
||||||
|
src={openBadgeImg}
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
<View className="store-card-body">
|
<View className="store-card-body">
|
||||||
{/* 第1行:标题 + 距离 */}
|
{/* 第1行:标题(截断无省略号,顶到最右) */}
|
||||||
<View className="store-card-row store-card-row--head">
|
<View className="store-card-row store-card-row--head">
|
||||||
<Text className="store-card-name" numberOfLines={1}>
|
<Text className="store-card-name">{s.name}</Text>
|
||||||
{s.name}
|
</View>
|
||||||
|
{/* 第2行:地址(最多两行)+ 距离 */}
|
||||||
|
<View className="store-card-row store-card-row--mid">
|
||||||
|
<Text className="store-card-address" numberOfLines={2}>
|
||||||
|
{s.address || (s.district ? `${s.district}` : '地址待完善')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="store-card-distance">
|
<Text className="store-card-distance">
|
||||||
{formatDistanceMeters(s.distanceMeters)}
|
{formatDistanceMeters(s.distanceMeters)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{/* 第2行:营业时间(多段各占一行,居左) */}
|
{/* 第3行:营业时间(同行) */}
|
||||||
<View className="store-card-hours">
|
<View className="store-card-row store-card-row--hours">
|
||||||
{hoursLines(s).map((line) => (
|
<Text className="store-card-hours">{hoursText(s)}</Text>
|
||||||
<Text key={line} className="store-card-hours-line">
|
|
||||||
{line}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
{/* 第3行:地址(最多两行)+ 去核销 */}
|
|
||||||
<View className="store-card-row store-card-row--foot">
|
|
||||||
<Text className="store-card-address" numberOfLines={2}>
|
|
||||||
{s.address || (s.district ? `${s.district}` : '地址待完善')}
|
|
||||||
</Text>
|
|
||||||
<View
|
|
||||||
className="store-card-cta"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
Taro.navigateTo({ url: '/pages/redeem/index' });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text className="store-card-cta-text">去核销</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
<Text className="store-card-arrow">›</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -144,12 +144,12 @@
|
|||||||
padding: 4px var(--space-page) 16px;
|
padding: 4px var(--space-page) 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 左图右文 */
|
/* 左图 + 中间文案 + 右侧箭头 */
|
||||||
.store-card {
|
.store-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: var(--color-card);
|
background: var(--color-card);
|
||||||
@@ -158,11 +158,19 @@
|
|||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-card-cover {
|
.store-card-cover-wrap {
|
||||||
|
position: relative;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 96px;
|
width: 96px;
|
||||||
height: 96px;
|
height: 96px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card-cover {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
background: var(--color-surface-container);
|
background: var(--color-surface-container);
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -171,6 +179,16 @@
|
|||||||
background: var(--color-surface-container);
|
background: var(--color-surface-container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.store-card-open-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.store-card-body {
|
.store-card-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -184,19 +202,19 @@
|
|||||||
|
|
||||||
.store-card-row {
|
.store-card-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 第1行:加粗标题(单行截断)+ 右对齐距离 */
|
/* 第1行:加粗标题(单行截断,不显示 …,宽度顶到最右) */
|
||||||
.store-card-row--head {
|
.store-card-row--head {
|
||||||
gap: 8px;
|
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-card-name {
|
.store-card-name {
|
||||||
flex: 1;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-family: var(--font-headline);
|
font-family: var(--font-headline);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -204,53 +222,23 @@
|
|||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: #1a1a1a;
|
color: #1a1a1a;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: clip;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-card-distance {
|
/* 第2行:地址最多两行 + 右对齐距离 */
|
||||||
flex-shrink: 0;
|
.store-card-row--mid {
|
||||||
max-width: 40%;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 22px;
|
|
||||||
color: #999;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第2行:营业时间独自居左;多段各占一行 */
|
|
||||||
.store-card-hours {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 2px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card-hours-line {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 16px;
|
|
||||||
color: #999;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 第3行:地址最多两行截断 + 右对齐去核销;与按钮垂直居中 */
|
|
||||||
.store-card-row--foot {
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-height: 28px;
|
align-items: flex-start;
|
||||||
align-items: center;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-card-address {
|
.store-card-address {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
font-size: 9px;
|
font-size: 11px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 14px;
|
line-height: 16px;
|
||||||
max-height: 28px;
|
max-height: 32px;
|
||||||
color: #999;
|
color: #999;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
@@ -260,25 +248,44 @@
|
|||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
line-clamp: 2;
|
line-clamp: 2;
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-card-cta {
|
.store-card-distance {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
max-width: 40%;
|
||||||
align-items: center;
|
padding-top: 1px;
|
||||||
justify-content: center;
|
|
||||||
padding: 0 14px;
|
|
||||||
height: 26px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--color-heritage-red, #a61d24);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.store-card-cta-text {
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 400;
|
||||||
line-height: 26px;
|
line-height: 16px;
|
||||||
color: #fff;
|
color: #999;
|
||||||
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 第3行:营业时间同行 */
|
||||||
|
.store-card-row--hours {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card-hours {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: clip;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card-arrow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: center;
|
||||||
|
width: 16px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1;
|
||||||
|
color: #ccc;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export const SYSTEM_CONFIG_FIELDS: SystemConfigFieldMeta[] = [
|
|||||||
group: G.wechat_mini,
|
group: G.wechat_mini,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
requiresRestart: false,
|
requiresRestart: false,
|
||||||
placeholder: '3.4.14',
|
placeholder: '3.4.15',
|
||||||
description: 'semver 格式;客户端低于此版本时提示更新',
|
description: 'semver 格式;客户端低于此版本时提示更新',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ export class AdminOrdersController {
|
|||||||
return this.ordersService.getShipDefaults();
|
return this.ordersService.getShipDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 物流路由(小飞侠实时轨迹,对齐 C 端 GET /trade/orders/:id/track) */
|
||||||
|
@Get(':id/track')
|
||||||
|
track(@Param('id') id: string) {
|
||||||
|
return this.ordersService.getOrderTrack(BigInt(id));
|
||||||
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
detail(@Param('id') id: string) {
|
detail(@Param('id') id: string) {
|
||||||
return this.ordersService.detail(BigInt(id));
|
return this.ordersService.detail(BigInt(id));
|
||||||
|
|||||||
@@ -63,6 +63,15 @@ export class AdminOrdersService {
|
|||||||
return serializeBigInt({ items, total, page, pageSize });
|
return serializeBigInt({ items, total, page, pageSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getOrderTrack(id: bigint) {
|
||||||
|
const order = await this.prisma.order.findUnique({
|
||||||
|
where: { id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!order) throw new NotFoundException('订单不存在');
|
||||||
|
return serializeBigInt(await this.fulfillmentService.getOrderTrack(id));
|
||||||
|
}
|
||||||
|
|
||||||
async detail(id: bigint) {
|
async detail(id: bigint) {
|
||||||
const order = await this.prisma.order.findUnique({
|
const order = await this.prisma.order.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
| 3.4.12 | 08-04 | 退款回滚/财务/批量任务·工单/套餐 imageUrl | [`v3.4.12`](./杜康好客-v3.4.12-工单迭代开发文档.md) |
|
| 3.4.12 | 08-04 | 退款回滚/财务/批量任务·工单/套餐 imageUrl | [`v3.4.12`](./杜康好客-v3.4.12-工单迭代开发文档.md) |
|
||||||
| 3.4.13 | 08-05 | metrics/核销详情/版本联动 PUBLISHED/mini 体验/H5 OAuth | [`v3.4.13`](./杜康好客-v3.4.13-体验优化开发文档.md) |
|
| 3.4.13 | 08-05 | metrics/核销详情/版本联动 PUBLISHED/mini 体验/H5 OAuth | [`v3.4.13`](./杜康好客-v3.4.13-体验优化开发文档.md) |
|
||||||
| 3.4.14 | 08-06 | mini-user 门头/套餐详情;**微信小程序配置可配 Logo·客服·H5·Mock码** | [`v3.4.14`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) |
|
| 3.4.14 | 08-06 | mini-user 门头/套餐详情;**微信小程序配置可配 Logo·客服·H5·Mock码** | [`v3.4.14`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) |
|
||||||
|
| 3.4.15 | 08-06 | mini-user 门店列表卡片:去核销改箭头、地址/距离/营业时间重排、营业中角标 | [`v3.4.15`](./杜康好客-v3.4.15-mini-user门店列表优化.md) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -56,11 +56,13 @@
|
|||||||
| 3.4.12 | [`工单迭代`](./杜康好客-v3.4.12-工单迭代开发文档.md) | ✅ |
|
| 3.4.12 | [`工单迭代`](./杜康好客-v3.4.12-工单迭代开发文档.md) | ✅ |
|
||||||
| 3.4.13 | [`体验优化`](./杜康好客-v3.4.13-体验优化开发文档.md) | ✅ 生产 |
|
| 3.4.13 | [`体验优化`](./杜康好客-v3.4.13-体验优化开发文档.md) | ✅ 生产 |
|
||||||
| 3.4.14 | [`mini-user 门店体验 + 小程序可配置`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) | 🔶 开发中 |
|
| 3.4.14 | [`mini-user 门店体验 + 小程序可配置`](./杜康好客-v3.4.14-mini-user门店体验开发文档.md) | 🔶 开发中 |
|
||||||
|
| 3.4.15 | [`mini-user 门店列表优化`](./杜康好客-v3.4.15-mini-user门店列表优化.md) | 🔶 开发中 |
|
||||||
|
|
||||||
## 5. 变更记录
|
## 5. 变更记录
|
||||||
|
|
||||||
| 日期 | 说明 |
|
| 日期 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
|
| 2026-08-06 | v3.4.15 mini-user 门店列表卡片优化(开发中) |
|
||||||
| 2026-08-06 | v3.4.14 mini-user 门头/套餐详情(开发中) |
|
| 2026-08-06 | v3.4.14 mini-user 门头/套餐详情(开发中) |
|
||||||
| 2026-08-05 | v3.4.13 |
|
| 2026-08-05 | v3.4.13 |
|
||||||
| 2026-08-04 | v3.4.11 / v3.4.12 |
|
| 2026-08-04 | v3.4.11 / v3.4.12 |
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 杜康好客 · v3.4.15 mini-user 门店列表卡片优化
|
||||||
|
|
||||||
|
> **2026-08-06** · **开发中** · mini-user `3.4.15` · **未发版**
|
||||||
|
|
||||||
|
## 更新内容
|
||||||
|
|
||||||
|
- 门店列表卡片去掉「去核销」按钮,整卡最右侧改为小箭头(进入详情)
|
||||||
|
- 第 1 行:门店名单行截断(不显示省略号),宽度顶到文案区最右
|
||||||
|
- 第 2 行:地址最多两行;右侧显示距离
|
||||||
|
- 第 3 行:营业时间同行展示(多段时段空格拼接)
|
||||||
|
- 店铺封面右上角叠加斜角「营业中」标签图
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
| 项 | 交付 |
|
||||||
|
|----|------|
|
||||||
|
| 门店列表卡片 | 布局与交互如上 |
|
||||||
|
| 版本号 | `mini-user` `3.4.15`(`APP_VERSION` / package.json) |
|
||||||
|
|
||||||
|
## ACC
|
||||||
|
|
||||||
|
- [ ] 列表项无「去核销」按钮;右侧有 › 箭头;点击整卡进详情
|
||||||
|
- [ ] 长店名单行截断且无 `…`;标题行无距离
|
||||||
|
- [ ] 地址 ≤2 行,距离在第二行右侧
|
||||||
|
- [ ] 营业时间单行;双时段同行显示
|
||||||
|
- [ ] 封面右上角可见「营业中」斜角标签
|
||||||
Reference in New Issue
Block a user