You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

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)—— 需要任意区间时再开。
  • 其它时间维度卡片(暂无需求)。