Files
Neo-ZQYY/apps/miniprogram/miniprogram/utils/perf-progress.ts
Neo 2a7a5d68aa feat: 2026-04-15~04-20 累积变更基线 — 多主线合流
主线 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>
2026-04-20 06:32:07 +08:00

125 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 绩效进度条共用模块 -- 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_caseCamelModel 可能已转 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,
}
}