feat(platform): add delivery time presets
This commit is contained in:
@@ -125,6 +125,25 @@ function previousDateString(value: string) {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function offsetDateString(value: string, offsetDays: number) {
|
||||||
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
||||||
|
if (!match) return value;
|
||||||
|
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]) + offsetDays);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekStartDateString(value: string) {
|
||||||
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
||||||
|
if (!match) return value;
|
||||||
|
const date = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
|
||||||
|
const day = date.getDay();
|
||||||
|
const mondayOffset = day === 0 ? -6 : 1 - day;
|
||||||
|
return offsetDateString(value, mondayOffset);
|
||||||
|
}
|
||||||
|
|
||||||
function dayRangeText(dateFrom?: string, dateTo?: string) {
|
function dayRangeText(dateFrom?: string, dateTo?: string) {
|
||||||
if (!dateFrom && !dateTo) return '未限定时间';
|
if (!dateFrom && !dateTo) return '未限定时间';
|
||||||
if (!dateFrom || !dateTo) return '单边时间窗';
|
if (!dateFrom || !dateTo) return '单边时间窗';
|
||||||
@@ -2002,6 +2021,29 @@ export function Dashboard({
|
|||||||
onClick: openTimeMonitorRaw
|
onClick: openTimeMonitorRaw
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const deliveryToday = timeMonitorScopeValue.dateTo || todayDateString();
|
||||||
|
const customerTimeRangePresets = [
|
||||||
|
{
|
||||||
|
label: '最近一天',
|
||||||
|
range: `${previousDateString(deliveryToday)} 至 ${deliveryToday}`,
|
||||||
|
filters: { ...timeMonitorScopeValue, dateFrom: previousDateString(deliveryToday), dateTo: deliveryToday }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '最近三天',
|
||||||
|
range: `${offsetDateString(deliveryToday, -3)} 至 ${deliveryToday}`,
|
||||||
|
filters: { ...timeMonitorScopeValue, dateFrom: offsetDateString(deliveryToday, -3), dateTo: deliveryToday }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '本周',
|
||||||
|
range: `${weekStartDateString(deliveryToday)} 至 ${deliveryToday}`,
|
||||||
|
filters: { ...timeMonitorScopeValue, dateFrom: weekStartDateString(deliveryToday), dateTo: deliveryToday }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '自定义复盘',
|
||||||
|
range: '当前范围',
|
||||||
|
filters: timeMonitorScopeValue
|
||||||
|
}
|
||||||
|
];
|
||||||
const deliveryScopeText = `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorScopeValue.dateFrom || '-'} 至 ${timeMonitorScopeValue.dateTo || '-'}`;
|
const deliveryScopeText = `${timeMonitorScopeValue.keyword || '全部车辆'} / ${timeMonitorScopeValue.protocol || '全部数据通道'} / ${timeMonitorScopeValue.dateFrom || '-'} 至 ${timeMonitorScopeValue.dateTo || '-'}`;
|
||||||
const customerDeliveryWorkbenchItems = [
|
const customerDeliveryWorkbenchItems = [
|
||||||
{
|
{
|
||||||
@@ -2140,6 +2182,22 @@ export function Dashboard({
|
|||||||
<span>当前交付范围</span>
|
<span>当前交付范围</span>
|
||||||
<strong>{deliveryScopeText}</strong>
|
<strong>{deliveryScopeText}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="vp-customer-time-presets">
|
||||||
|
<span>常用时间范围</span>
|
||||||
|
<div>
|
||||||
|
{customerTimeRangePresets.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.label}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setTimeMonitorFilters(item.filters)}
|
||||||
|
aria-label={`客户常用时间范围 ${item.label} ${item.range} 应用`}
|
||||||
|
>
|
||||||
|
<strong>{item.label}</strong>
|
||||||
|
<small>{item.range}</small>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="vp-customer-delivery-workbench-grid">
|
<div className="vp-customer-delivery-workbench-grid">
|
||||||
{customerDeliveryWorkbenchItems.map((item) => (
|
{customerDeliveryWorkbenchItems.map((item) => (
|
||||||
|
|||||||
@@ -1425,6 +1425,59 @@ body {
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets > span {
|
||||||
|
color: var(--vp-text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets button {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid var(--vp-border);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
text-align: left;
|
||||||
|
display: grid;
|
||||||
|
gap: 3px;
|
||||||
|
transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets button:hover,
|
||||||
|
.vp-customer-time-presets button:focus-visible {
|
||||||
|
border-color: rgba(22, 100, 255, 0.42);
|
||||||
|
background: #f7fbff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(22, 100, 255, 0.08);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets strong {
|
||||||
|
color: var(--vp-text);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-customer-time-presets small {
|
||||||
|
color: var(--vp-text-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.vp-customer-delivery-workbench-grid {
|
.vp-customer-delivery-workbench-grid {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -910,6 +910,11 @@ test('dashboard frames history query and export as a customer delivery workbench
|
|||||||
expect(screen.getByText('先锁定车辆和时间窗,再选择轨迹、里程、历史明细或告警说明,最终交付一份客户能看懂的数据包。')).toBeInTheDocument();
|
expect(screen.getByText('先锁定车辆和时间窗,再选择轨迹、里程、历史明细或告警说明,最终交付一份客户能看懂的数据包。')).toBeInTheDocument();
|
||||||
expect(screen.getByText('当前交付范围')).toBeInTheDocument();
|
expect(screen.getByText('当前交付范围')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('全部车辆 / 全部数据通道 / 2026-07-04 至 2026-07-05').length).toBeGreaterThan(0);
|
expect(screen.getAllByText('全部车辆 / 全部数据通道 / 2026-07-04 至 2026-07-05').length).toBeGreaterThan(0);
|
||||||
|
expect(screen.getByText('常用时间范围')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '客户常用时间范围 最近一天 2026-07-04 至 2026-07-05 应用' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '客户常用时间范围 最近三天 2026-07-02 至 2026-07-05 应用' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '客户常用时间范围 本周 2026-06-29 至 2026-07-05 应用' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: '客户常用时间范围 自定义复盘 当前范围 应用' })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: '客户查询导出交付台 轨迹回放 4 活跃 回放轨迹' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: '客户查询导出交付台 轨迹回放 4 活跃 回放轨迹' })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: '客户查询导出交付台 里程统计 1,033 车辆 统计查询' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: '客户查询导出交付台 里程统计 1,033 车辆 统计查询' })).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: '客户查询导出交付台 历史明细 1,286,320 帧 历史导出' })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: '客户查询导出交付台 历史明细 1,286,320 帧 历史导出' })).toBeInTheDocument();
|
||||||
|
|||||||
Reference in New Issue
Block a user