5.4 KiB
「今日新增」卡片下钻的列表联动入参(方案 B:语义布尔参数)
Type: spec Status: resolved Related: 02(口径映射表)、03(DTO 与返回契约)
背景 / 缺口
改版后「线索公海」和「线索管理」都有 今日新增 卡片。stats 侧口径已实现
(LeadViewQueryImpl#countStats line 119-130):
applyViewFilters(视图 + 公共筛选) + create_time ∈ [今日00:00, 次日00:00) // 不看 status
其中「今日」= JVM 时区的 LocalDate.now(),用半开区间 ge(create_time, todayStart).lt(create_time, tomorrowStart),
刻意不用 DATE(create_time)=CURRENT_DATE(对齐 JVM 时区、走索引)。
缺口:列表接口 /api/lead/page 的入参 LeadPageParam(含继承的 BaseParam)
没有任何创建时间范围字段,可用下钻字段只有 statusIn + 若干等值筛选 + keyword。
所以前端点「今日新增」卡片时,无参数可把 /page 也过滤成"今天创建的"——
其余按 status 的卡片都能靠 statusIn 联动,唯独今日新增不能。
决策:方案 B —— 加一个语义布尔参数 todayNewOnly
对比方案 A(加通用 createTimeBegin/End)后选 B,理由:
- 前后端用同一个
LocalDate.now():时间窗由后端按LocalDate.now()展开, 前端只传todayNewOnly=true。彻底消除"前端算的今天"与"后端算的今天"跨零点 / 跨时区不一致的风险(方案 A 让前端传绝对时间戳,跨零点点击就可能错开一天)。 - 口径单一可信源:stats 与 page 的今日窗口用同一段展开逻辑(下沉到
applyViewFilters),不会两处各写一遍再漂移。严守 P1-7「统计口径 = 列表口径」。 - 代价:只能查"今天",不能查任意区间。当前需求只有"今日新增"这一个时间卡片, 通用区间是 YAGNI,需要时再加 A。
实现要点
1. LeadPageParam 新增字段
crm-lead/.../domain/param/LeadPageParam.java:
@Schema(description = "仅看今日新增(按后端 JVM 时区 LocalDate.now() 展开 create_time ∈ [今日00:00,次日00:00);用于「今日新增」卡片下钻)")
private Boolean todayNewOnly;
- 类型
Boolean(可空)。null/false都视为不过滤,只有true才追加时间窗。
2. 时间窗展开下沉到 applyViewFilters
LeadViewQueryImpl#applyViewFilters,在公共筛选段追加一条(与现有 statusIn 等
条件并列,条件式追加):
if (Boolean.TRUE.equals(param.getTodayNewOnly())) {
LocalDate today = LocalDate.now();
wrapper.ge(Lead::getCreateTime, today.atStartOfDay())
.lt(Lead::getCreateTime, today.plusDays(1).atStartOfDay());
}
- 下沉到
applyViewFilters后,pageLeads自动生效;且countStats里的 status 分组查询 / total 也会随todayNewOnly=true收窄(前端下钻时通常只调/page, 但 stats 复用同一套 WHERE 意味着口径天然一致,无副作用)。
3. countStats 里的今日新增块可选收敛(非必须)
现有 line 119-130 独立算 todayNew 的那段,其时间窗逻辑与第 2 步完全相同。
可提取一个私有 helper(如 todayCreateTimeRange() 返回 [start,end))给两处复用,
避免同一段 LocalDate.now() 展开写两遍。此为整洁性收敛,不改口径,可在实现时顺手做。
前端联动契约(交付给前端)
点击「今日新增」卡片时,在当前视图的 /page 请求上追加 todayNewOnly:true:
// 线索公海 今日新增
{ "viewType": "PUBLIC_POOL", "todayNewOnly": true, "current": 1, "size": 10 }
// 线索管理 今日新增
{ "viewType": "MANAGE", "todayNewOnly": true, "current": 1, "size": 10 }
- 「今日新增」不看 status,故下钻时 不传
statusIn(传了会叠加收窄,与卡片口径不符)。 - 其它筛选(poolId / channelCode / keyword…)可与
todayNewOnly叠加,语义 = "今天创建的且满足其它筛选",符合列表页保留筛选条件的直觉。 - 其余按 status 的卡片仍走
statusIn,与本 ticket 无关(对照表见下)。
卡片 → 列表下钻传参对照
| 卡片 | 传参 |
|---|---|
| 待领取 | statusIn:[2] |
| 已领取 | statusIn:[3] |
| 跟进中 | statusIn:[4] |
| 销售持有 | statusIn:[3,4] |
| 已转商机 | statusIn:[5] |
| 过期失效 | statusIn:[6] |
| 线索作废 | statusIn:[7] |
| 未分发 | statusIn:[1] |
| 今日新增 | todayNewOnly:true(不传 statusIn) |
测试断言清单(不写实现,只列口径)
pageLeads_todayNewOnly_仅返回今日create_time记录:造 2 条今天 + 1 条昨天,todayNewOnly=true只返回 2 条;todayNewOnly=null返回全部。pageLeads_todayNewOnly_不受status影响:今天创建但 status 各异(1/3/7)均命中。pageLeads_todayNewOnly_叠加其它筛选:todayNewOnly=true+channelCode=X只返回今天且渠道=X。pageLeads_todayNewOnly_与stats一致:同一 param 下pageLeads(todayNewOnly=true).total == countStats(...).todayNew(口径一致性回归)。- 边界:
create_time恰为今日 00:00:00.000 命中;次日 00:00:00.000 不命中(半开区间)。
Out of scope
- 通用创建时间区间查询(方案 A 的
createTimeBegin/End)—— 需要任意区间时再开。 - 其它时间维度卡片(暂无需求)。