一、前端:从 MkhBomData 侧栏抽成独立页面
- 新增 frontend/src/pages/WarehouseShipTimeliness/{index.jsx,style.css},
路由 /planning/operation/warehouse-ship-timeliness,侧栏导航与 BasicLayout 各加 1 行入口。
- 删除 frontend/src/pages/MkhBomData/WarehouseShipTimelinessSidebar.jsx,
并把 MkhBomData/index.jsx、style.css 里相关代码与样式一并摘除(-131 / -277 行)。
二、业务口径(2026-10-20 业务多次纠偏后的定版)
- 轮次归属改用「审核时间」= jst_order_express_details.last_oa_confirm_time
(聚水潭订单列表「审核时间」列,最后一次审核,重审会刷新):
13:00 轮 = 今天 07:00 前审核 + 历史未发;18:00 轮 = 截止 16:00 前审核 + 历史未发。
不再按「创建时间」切自然日窗口,也不再有「满 24 小时」门槛。
- 仓级例外(仅 18:00 轮):山东青岗集仓 / 山东品牌仓 15:00 前审核、河南洛宁仓 13:00 前审核,
核验时刻仍为 18:00(registry 新增 windowEndHourByRound)。
- 明细去掉「创建时间」,改显示「发货时间」(send_time);未发但有运单号的标「已打单 · 未揽收」。
三、已发判定与不考核快递
- 已发 = 状态表 send_time 非空 OR 订单表 status='Sent'(对齐聚水潭销售订单口径)。
不再用运单号反推(已发货与未揽收单都会出现运单号,判不出来)。
- 自提 / 中铁 / 壹米滴答 三家快递不做未发考核,剔除量计入 summary.carrierSkipped。
四、数据源停更必须让用户看见
- freshness 由写死 stale:false 改为实算:快照距今 >90 分钟 → 黄告警;
快照早于本轮核验时刻 → 红告警「数据未覆盖核验时刻,超时单量偏大」。
五、测试
- backend/test/warehouseShipTimeliness.test.js 扩到 17 用例(仓级例外、重审取 last_oa、
快递剔除、Sent 判已发、coversDeadline 告警)。
256 lines
4.5 KiB
CSS
256 lines
4.5 KiB
CSS
.wst-page {
|
||
padding: 16px 20px 24px;
|
||
}
|
||
|
||
.wst-header {
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.wst-header .ant-typography {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.wst-note {
|
||
display: block;
|
||
max-width: 1180px;
|
||
margin-top: 8px;
|
||
padding: 10px 14px;
|
||
/* 页面顶部是深色渐变,直接放浅色小字会看不清,给口径说明一块浅底 */
|
||
background: var(--sycm-bg-container);
|
||
border: 1px solid var(--sycm-border-color);
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
line-height: 1.75;
|
||
}
|
||
|
||
/* ============ 轮次状态条 ============ */
|
||
.wst-status {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.wst-status__row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.wst-status__round {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.wst-status__badge {
|
||
padding: 1px 8px;
|
||
border-radius: 4px;
|
||
background: rgba(22, 119, 255, 0.12);
|
||
color: #0958d9;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.wst-status__badge--idle {
|
||
background: rgba(136, 135, 128, 0.14);
|
||
color: var(--sycm-text-tertiary);
|
||
}
|
||
|
||
.wst-status__label {
|
||
font-size: 13px;
|
||
color: var(--sycm-text-primary);
|
||
}
|
||
|
||
.wst-status__window,
|
||
.wst-status__time {
|
||
font-size: 12px;
|
||
color: var(--sycm-text-tertiary);
|
||
}
|
||
|
||
.wst-alert {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* ============ 统计卡 ============ */
|
||
/* 5 张卡(超时未出库 / 业务挂起 / 涉及仓库 / 最久未出库 / 合计金额)
|
||
用 grid 而不是 Row/Col:24 栅格除不尽 5,Row/Col 会把第 5 张挤到下一行。 */
|
||
.wst-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
@media (max-width: 1500px) {
|
||
.wst-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.wst-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||
}
|
||
|
||
.wst-stat {
|
||
height: 100%;
|
||
}
|
||
|
||
/* 「业务挂起」卡可点开明细 */
|
||
.wst-stat--held {
|
||
cursor: pointer;
|
||
transition: box-shadow 0.2s;
|
||
}
|
||
|
||
.wst-stat--held:hover {
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.wst-stat__more {
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
color: #1677ff;
|
||
}
|
||
|
||
/* ============ 主从两栏 ============ */
|
||
.wst-body {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 14px;
|
||
}
|
||
|
||
.wst-centers {
|
||
flex: none;
|
||
width: 264px;
|
||
}
|
||
|
||
.wst-centers__hint {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.wst-centers__list {
|
||
margin: 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
}
|
||
|
||
.wst-center {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
width: 100%;
|
||
padding: 6px 8px;
|
||
margin-bottom: 2px;
|
||
border: 1px solid transparent;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
font-size: 13px;
|
||
text-align: left;
|
||
color: var(--sycm-text-primary);
|
||
cursor: pointer;
|
||
transition: background 0.12s ease;
|
||
}
|
||
|
||
.wst-center:hover {
|
||
background: var(--sycm-bg-subtle);
|
||
}
|
||
|
||
.wst-center.is-active {
|
||
border-color: rgba(22, 119, 255, 0.45);
|
||
background: rgba(22, 119, 255, 0.08);
|
||
}
|
||
|
||
.wst-center.is-empty .wst-center__name {
|
||
color: var(--sycm-text-tertiary);
|
||
}
|
||
|
||
.wst-center.is-disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.wst-center__name {
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.wst-center__badge {
|
||
flex: none;
|
||
min-width: 24px;
|
||
padding: 0 6px;
|
||
border-radius: 9px;
|
||
background: rgba(136, 135, 128, 0.12);
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
line-height: 17px;
|
||
text-align: center;
|
||
color: var(--sycm-text-tertiary);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.wst-center__badge.is-warn {
|
||
background: rgba(255, 77, 79, 0.16);
|
||
color: #cf1322;
|
||
}
|
||
|
||
.wst-center__badge--none {
|
||
font-weight: 400;
|
||
}
|
||
|
||
.wst-detail {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
}
|
||
|
||
.wst-detail__scope {
|
||
margin-left: 8px;
|
||
font-size: 12px;
|
||
font-weight: 400;
|
||
color: var(--sycm-text-tertiary);
|
||
}
|
||
|
||
.wst-detail__clear {
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
font-size: 12px;
|
||
color: #1677ff;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.wst-detail__overdue {
|
||
color: #cf1322;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* 创建时间没补到、回退成付款时间时的提示色(带 * 号,tooltip 有说明) */
|
||
.wst-detail__fallback {
|
||
color: var(--sycm-text-tertiary);
|
||
font-style: italic;
|
||
}
|
||
|
||
.wst-foot {
|
||
display: block;
|
||
margin-top: 12px;
|
||
font-size: 12px;
|
||
color: var(--sycm-text-tertiary);
|
||
}
|
||
|
||
/* 窄屏:仓库列表改为两列网格,明细整行下移 */
|
||
@media (max-width: 1200px) {
|
||
.wst-body {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.wst-centers {
|
||
width: 100%;
|
||
}
|
||
|
||
.wst-centers__list {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 2px 10px;
|
||
}
|
||
}
|