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.
118 lines
5.4 KiB
118 lines
5.4 KiB
|
1 week ago
|
<!-- wayfinder:spec -->
|
||
|
|
# 「今日新增」卡片下钻的列表联动入参(方案 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`:
|
||
|
|
|
||
|
|
```java
|
||
|
|
@Schema(description = "仅看今日新增(按后端 JVM 时区 LocalDate.now() 展开 create_time ∈ [今日00:00,次日00:00);用于「今日新增」卡片下钻)")
|
||
|
|
private Boolean todayNewOnly;
|
||
|
|
```
|
||
|
|
|
||
|
|
- 类型 `Boolean`(可空)。`null` / `false` 都视为不过滤,只有 `true` 才追加时间窗。
|
||
|
|
|
||
|
|
### 2. 时间窗展开下沉到 `applyViewFilters`
|
||
|
|
|
||
|
|
`LeadViewQueryImpl#applyViewFilters`,在公共筛选段追加一条(与现有 `statusIn` 等
|
||
|
|
条件并列,条件式追加):
|
||
|
|
|
||
|
|
```java
|
||
|
|
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`:
|
||
|
|
|
||
|
|
```json
|
||
|
|
// 线索公海 今日新增
|
||
|
|
{ "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`)—— 需要任意区间时再开。
|
||
|
|
- 其它时间维度卡片(暂无需求)。
|