主线 1: rns1-customer-coach-api + 04-miniapp-core-business 后端实施
- 新增 GET /xcx/coaches/{id}/banner 轻量接口
- performance/records 加 coach_id 参数 + view_board_coach 权限分流
- coach/customer/performance/board/task 服务层重构
- fdw_queries 结算单粒度聚合 + consumption_summary 视图统一
- task_generator 回访宽限 72h + UPSERT 替代策略 + Step 5 保底清理
- recall_detector settle_type=3 双重限制 + 门店级 resolved
主线 2: 小程序权限分流 + 新增 coach-service-records 管理者视角业绩明细页
- perf-progress 共享模块去重 task-list/coach-detail 动画逻辑
- isScattered 散客标记端到端
- foodDetail/phoneFull/creator* 字段透传
主线 3: P19 指数回测框架 Phase 1+2
- 3 个指数表 stat_date 日快照模式
- 新增 DWS_INDEX_BACKFILL / DWS_TASK_SIMULATION 工具任务
- task_engine 升级 HTTP 实时 + 推演回测双模式
主线 4: Core 维度层启用
- 新增 CORE_DIM_SYNC 任务(DWD → core 4 维度表)
- 修复 app 视图空查询问题
主线 5: member_project_tag 改为 LAST_30_VISITS 消费次数窗口
主线 6: 2 个迁移 SQL 已执行(stat_date + member_project_tag 新窗口)
- schema 基线与 DDL 快照同步
主线 7: 开发机路径迁移 C:\NeoZQYY → C:\Project\NeoZQYY(约 95% 改动量)
附带: 新建运维脚本(churned_customer_report / simulate_historical_tasks /
backfill_index_snapshots)+ tools/task-analysis/ 任务分析工具
合计 157 文件。未包含中间产物(tmp/ .playwright-mcp/ inspect-* excel/sheet 分析 txt)。
审计记录见下一个 commit。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
125 lines
5.7 KiB
TypeScript
125 lines
5.7 KiB
TypeScript
/**
|
||
* 绩效进度条共用模块 -- task-list / coach-detail 统一使用
|
||
*
|
||
* 包含:动画参数、刻度计算、高光时长计算、类型定义
|
||
*/
|
||
|
||
/* ======================================================
|
||
* 进度条动画参数 -- 在此调节
|
||
* ======================================================
|
||
*
|
||
* 动画由 JS 状态机驱动,每轮循环重新读取实际进度条宽度:
|
||
*
|
||
* +-------------+ SPARK_DELAY_MS +-------------+ NEXT_LOOP_DELAY_MS +-------------+
|
||
* | 高光匀速扫过 | --------------> | 火花迸发 | ------------------> | 下一轮 |
|
||
* | 时长由速度决定| | SPARK_DUR_MS| |(重新读进度) |
|
||
* +-------------+ +-------------+ +-------------+
|
||
*
|
||
* SHINE_SPEED : 高光移动速度,范围 1~100
|
||
* 1 = 最慢,最宽进度条(100%)下 5 秒走完
|
||
* 100 = 最快,最宽进度条(100%)下 0.05 秒走完
|
||
* 实际时长 = 基准时长 x (filledPct/100)
|
||
* 基准时长 = 5s - (SHINE_SPEED-1)/(100-1) x (5-0.05)s
|
||
*
|
||
* SPARK_DELAY_MS : 高光到达最右端后,光柱点亮+火花迸发的延迟(毫秒)
|
||
* 正数 = 高光结束后停顿再点亮
|
||
* 负数 = 高光还未结束,提前点亮(高光末尾与火花重叠)
|
||
*
|
||
* SPARK_DUR_MS : 火花迸发到完全消散的时长(毫秒)
|
||
*
|
||
* NEXT_LOOP_DELAY_MS: 火花消散后到下一轮高光启动的延迟(毫秒)
|
||
* 正数 = 停顿一段时间
|
||
* 负数 = 火花还未消散完,高光已从左端启动
|
||
*
|
||
* 修改说明见 apps/miniprogram/doc/progress-bar-animation.md
|
||
*/
|
||
export const SHINE_SPEED = 70 // 1~100,速度值
|
||
export const SPARK_DELAY_MS = -200 // 毫秒,高光结束->光柱点亮+火花(负=提前)
|
||
export const SPARK_DUR_MS = 1400 // 毫秒,火花持续时长
|
||
export const NEXT_LOOP_DELAY_MS = 400 // 毫秒,火花结束->下轮高光(负=提前)
|
||
|
||
/* 高光宽度固定(SHINE_WIDTH_RPX),需要走过的距离 = 填充条宽度 + 高光宽度
|
||
* 轨道宽度约 634rpx(750 - 左右padding各58rpx),高光宽度约占轨道 19%
|
||
* 时长正比于需要走过的总距离,保证视觉速度恒定
|
||
*
|
||
* 速度1 -> baseDur=5000ms(最慢),速度100 -> baseDur=50ms(最快)
|
||
* shineDurMs = baseDur x (filledPct + SHINE_WIDTH_PCT) / (100 + SHINE_WIDTH_PCT)
|
||
*/
|
||
export const SHINE_WIDTH_RPX = 120 // rpx,需与 WXSS 的 --shine-width 保持一致
|
||
export const TRACK_WIDTH_RPX = 634 // rpx,进度条轨道宽度(750 - padding 116rpx)
|
||
export const SHINE_WIDTH_PCT = (SHINE_WIDTH_RPX / TRACK_WIDTH_RPX) * 100 // ~19%
|
||
|
||
// ── 类型定义 ────────────────────────────────────────────────
|
||
|
||
/** 刻度项(传给 perf-progress-bar 组件的 ticks 属性) */
|
||
export interface TickItem {
|
||
value: number // 刻度数值(如 100)
|
||
label: string // 显示文字(如 '100')
|
||
left: string // 百分比位置字符串(如 '45.45%'),首尾项忽略此字段
|
||
highlight: boolean // 是否加粗高亮
|
||
}
|
||
|
||
// ── 工具函数 ────────────────────────────────────────────────
|
||
|
||
/**
|
||
* 根据速度值和进度百分比计算高光扫过时长。
|
||
*
|
||
* 速度 1 -> baseDur=5000ms(最慢),速度 100 -> baseDur=50ms(最快)
|
||
* 实际时长 = baseDur x (filledPct + SHINE_WIDTH_PCT) / (100 + SHINE_WIDTH_PCT)
|
||
*/
|
||
export function calcShineDur(filledPct: number): number {
|
||
const t = (SHINE_SPEED - 1) / 99 // 0(最慢) ~ 1(最快)
|
||
const baseDur = 5000 - t * (5000 - 50)
|
||
const distRatio = (filledPct + SHINE_WIDTH_PCT) / (100 + SHINE_WIDTH_PCT)
|
||
return Math.max(50, Math.round(baseDur * distRatio))
|
||
}
|
||
|
||
/**
|
||
* 根据档位节点数组生成刻度数据(供 perf-progress-bar 组件渲染)。
|
||
*/
|
||
export function buildTicks(tierNodes: number[], maxHours: number): TickItem[] {
|
||
return tierNodes.map((v, i) => ({
|
||
value: v,
|
||
label: String(v),
|
||
left: `${Math.round((v / maxHours) * 10000) / 100}%`,
|
||
highlight: false,
|
||
}))
|
||
}
|
||
|
||
/**
|
||
* 从后端返回的 performance 对象中计算进度条所需的全部数据。
|
||
*
|
||
* 两个页面统一调用此函数,确保进度百分比、当前档位、刻度等计算逻辑一致。
|
||
*/
|
||
export function buildProgressBarData(perf: Record<string, any>) {
|
||
// 后端返回 snake_case(CamelModel 可能已转 camelCase),兼容两种命名
|
||
const totalHours = perf.totalHours ?? perf.total_hours ?? 0
|
||
const tierNodes: number[] = perf.tierNodes ?? perf.tier_nodes ?? [0]
|
||
const currentTier = perf.currentTier ?? perf.current_tier ?? 0
|
||
const nextTierHours = perf.nextTierHours ?? perf.next_tier_hours ?? 0
|
||
|
||
const maxHours = tierNodes.length > 0 ? tierNodes[tierNodes.length - 1] : 0
|
||
const filledPct = maxHours > 0 ? Math.min(100, Math.round((totalHours / maxHours) * 1000) / 10) : 0
|
||
const clampedSparkPct = Math.max(0, Math.min(100, filledPct))
|
||
const remainHours = Math.max(0, nextTierHours - totalHours)
|
||
const ticks = buildTicks(tierNodes, maxHours)
|
||
|
||
// 段内进度(当前档位内的百分比)
|
||
const segStart = tierNodes[currentTier] ?? 0
|
||
const segEnd = tierNodes[currentTier + 1] ?? maxHours
|
||
const tierProgress = segEnd > segStart
|
||
? Math.min(100, Math.round(((totalHours - segStart) / (segEnd - segStart)) * 100))
|
||
: 100
|
||
|
||
return {
|
||
filledPct,
|
||
clampedSparkPct,
|
||
currentTier,
|
||
ticks,
|
||
remainHours,
|
||
tierProgress,
|
||
shineDurMs: calcShineDur(filledPct),
|
||
sparkDurMs: SPARK_DUR_MS,
|
||
}
|
||
}
|