主线 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>
325 lines
15 KiB
Plaintext
325 lines
15 KiB
Plaintext
<wxs src="../../utils/format.wxs" module="fmt" />
|
||
<!-- 空态 -->
|
||
<view class="page-empty" wx:if="{{pageState === 'empty'}}">
|
||
<t-icon name="info-circle" size="120rpx" color="#c5c5c5" />
|
||
<text class="empty-text">未找到助教信息</text>
|
||
</view>
|
||
|
||
<!-- 错误态 -->
|
||
<view class="page-error" wx:elif="{{pageState === 'error'}}">
|
||
<t-icon name="close-circle" size="120rpx" color="#e34d59" />
|
||
<text class="error-text">加载失败</text>
|
||
<view class="retry-btn" bindtap="onRetry" hover-class="retry-btn--hover">点击重试</view>
|
||
</view>
|
||
|
||
<!-- 正常态 -->
|
||
<block wx:elif="{{pageState === 'normal'}}">
|
||
<!-- Banner 区域 -->
|
||
<view class="banner-section">
|
||
<image class="banner-bg-img" src="/assets/images/banner-bg-coral-aurora.svg" mode="widthFix" />
|
||
<view class="banner-overlay">
|
||
<view class="coach-header">
|
||
<view class="avatar-box">
|
||
<image class="avatar-img" src="/assets/images/avatar-coach.png" mode="aspectFill" />
|
||
</view>
|
||
<view class="info-middle">
|
||
<view class="name-row">
|
||
<text class="coach-name">{{fmt.safe(detail.name)}}</text>
|
||
<coach-level-tag level="{{detail.level}}" bgColor="{{levelBgColor}}" shadowColor="transparent" />
|
||
</view>
|
||
<view class="skill-row">
|
||
<text class="skill-tag" wx:for="{{detail.skills}}" wx:key="index">{{item}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-right-stats">
|
||
<view class="right-stat">
|
||
<text class="right-stat-label">工龄</text>
|
||
<text class="right-stat-value">{{fmt.safe(detail.workYears)}}</text>
|
||
<text class="right-stat-label">年</text>
|
||
</view>
|
||
<view class="right-stat">
|
||
<text class="right-stat-label">客户</text>
|
||
<text class="right-stat-value">{{fmt.safe(detail.customerCount)}}</text>
|
||
<text class="right-stat-label">人</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 主体内容 -->
|
||
<view class="main-content">
|
||
|
||
<!-- 绩效概览 -->
|
||
<view class="card">
|
||
<view class="card-title-row">
|
||
<text class="section-title title-blue">绩效概览</text>
|
||
</view>
|
||
<view class="perf-grid">
|
||
<view class="perf-card {{item.bgClass}}" wx:for="{{perfCards}}" wx:key="label">
|
||
<text class="perf-label">{{item.label}}</text>
|
||
<view class="perf-value-row">
|
||
<text class="perf-value {{item.valueColor}}">{{item.value}}</text>
|
||
<text class="perf-unit" wx:if="{{item.unit}}">{{item.unit}}</text>
|
||
</view>
|
||
<text class="perf-sub">{{item.sub}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="perf-progress-box">
|
||
<view class="perf-progress-header">
|
||
<text class="perf-progress-label">绩效档位进度</text>
|
||
<text class="perf-progress-hint">距下一档还差 {{fmt.hours(perfGap)}}</text>
|
||
</view>
|
||
<perf-progress-bar
|
||
filledPct="{{pbFilledPct}}"
|
||
clampedSparkPct="{{pbClampedSparkPct}}"
|
||
currentTier="{{pbCurrentTier}}"
|
||
ticks="{{pbTicks}}"
|
||
shineRunning="{{pbShineRunning}}"
|
||
sparkRunning="{{pbSparkRunning}}"
|
||
shineDurMs="{{pbShineDurMs}}"
|
||
sparkDurMs="{{pbSparkDurMs}}"
|
||
style="--ppb-track-bg-color: rgba(59,130,246,0.25); --ppb-divider-color: rgba(255,255,255,1); --ppb-tick-done-color: #60a5fa; --ppb-tick-color: rgba(147,197,253,0.6); --ppb-tick-highlight-color: #3b82f6;"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 收入明细 -->
|
||
<view class="card">
|
||
<view class="card-header">
|
||
<text class="section-title title-green">收入明细</text>
|
||
<view class="income-tabs">
|
||
<view class="income-tab {{incomeTab === 'this' ? 'active' : ''}}" data-tab="this" bindtap="onIncomeTabTap" hover-class="income-tab--hover">
|
||
<text>本月</text>
|
||
<text class="income-tab-est" wx:if="{{incomeTab === 'this'}}">预估</text>
|
||
</view>
|
||
<view class="income-tab {{incomeTab === 'last' ? 'active' : ''}}" data-tab="last" bindtap="onIncomeTabTap" hover-class="income-tab--hover">
|
||
<text>上月</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="income-list">
|
||
<view class="income-item" wx:for="{{currentIncome}}" wx:key="label">
|
||
<view class="income-dot dot-{{item.color}}"></view>
|
||
<text class="income-label">{{item.label}}</text>
|
||
<text class="income-amount">{{fmt.safe(item.amount)}}</text>
|
||
</view>
|
||
<view class="income-total">
|
||
<text class="income-total-label">合计{{incomeTab === 'this' ? '(预估)' : ''}}</text>
|
||
<text class="income-total-value">{{fmt.safe(incomeTotal)}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 任务执行 -->
|
||
<view class="card">
|
||
<view class="card-header">
|
||
<text class="section-title title-orange">任务执行</text>
|
||
<view class="task-summary">
|
||
<text class="task-summary-label">本月完成</text>
|
||
<text class="task-summary-callback">回访<text class="task-summary-num">{{taskStats.callback}}个</text></text>
|
||
<text class="task-summary-recall">召回<text class="task-summary-num">{{taskStats.recall}}个</text></text>
|
||
|
||
</view>
|
||
</view>
|
||
<view class="task-list">
|
||
<view class="task-item task-item-{{item.typeClass}}" wx:for="{{visibleTasks}}" wx:key="index"
|
||
bindtap="onTaskItemTap" data-id="{{item.customerId}}" data-name="{{item.customerName}}" hover-class="task-item--hover">
|
||
<text class="task-tag-text {{item.typeClass}}">{{item.typeLabel}}</text>
|
||
<text class="task-customer-name">{{item.customerName}}</text>
|
||
<view class="task-note-btn" wx:if="{{item.noteCount > 0}}" catchtap="onTaskNoteTap" data-index="{{index}}" hover-class="task-note-btn--hover">
|
||
<t-icon name="chat" size="32rpx" color="#777777" />
|
||
<text class="task-note-count">{{item.noteCount}}</text>
|
||
</view>
|
||
<text class="task-pin" wx:if="{{item.pinned}}">📌</text>
|
||
</view>
|
||
</view>
|
||
<block wx:if="{{tasksExpanded}}">
|
||
<view class="task-list task-list-extra">
|
||
<view class="task-item task-item-{{item.typeClass}}" wx:for="{{hiddenTasks}}" wx:key="index"
|
||
bindtap="onTaskItemTap" data-id="{{item.customerId}}" data-name="{{item.customerName}}" hover-class="task-item--hover">
|
||
<text class="task-tag-text {{item.typeClass}}">{{item.typeLabel}}</text>
|
||
<text class="task-customer-name">{{item.customerName}}</text>
|
||
<view class="task-note-btn" wx:if="{{item.noteCount > 0}}" catchtap="onTaskNoteTap" data-hidden-index="{{index}}" hover-class="task-note-btn--hover">
|
||
<t-icon name="chat" size="32rpx" color="#777777" />
|
||
<text class="task-note-count">{{item.noteCount}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="task-item task-item-abandoned" wx:for="{{abandonedTasks}}" wx:key="index">
|
||
<text class="task-abandoned-name">{{item.customerName}}</text>
|
||
<text class="task-abandoned-reason">{{item.reason}}</text>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
<view class="task-toggle" bindtap="onToggleTasks" hover-class="task-toggle--hover" wx:if="{{hiddenTasks.length > 0 || abandonedTasks.length > 0}}">
|
||
<text>{{tasksExpanded ? '收起 ↑' : '展开全部 ↓'}}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 客户关系 TOP20 -->
|
||
<view class="card">
|
||
<view class="card-header">
|
||
<text class="section-title title-pink">客户关系 TOP20</text>
|
||
<text class="header-hint">近60天</text>
|
||
</view>
|
||
<view class="top-customer-list">
|
||
<view
|
||
class="top-customer-item"
|
||
hover-class="top-customer-item--hover"
|
||
wx:for="{{topCustomers}}"
|
||
wx:key="index"
|
||
wx:if="{{topCustomersExpanded || index < 5}}"
|
||
data-id="{{item.id}}"
|
||
bindtap="onCustomerTap"
|
||
>
|
||
<view class="top-customer-avatar avatar-{{item.avatarGradient}}">
|
||
<text class="top-customer-avatar-text">{{item.initial}}</text>
|
||
</view>
|
||
<view class="top-customer-info">
|
||
<view class="top-customer-name-row">
|
||
<text class="top-customer-name {{item.isScattered ? 'top-customer-name--scattered' : ''}}">{{item.name}}</text>
|
||
<text class="top-customer-heart">{{item.heartEmoji}}</text>
|
||
<text class="top-customer-score top-customer-score-{{item.scoreColor}}">{{fmt.safe(item.score)}}</text>
|
||
</view>
|
||
<view class="top-customer-stats">
|
||
<text class="top-customer-stat">服务 <text class="top-customer-stat-val">{{fmt.count(item.serviceCount, '次')}}</text></text>
|
||
<text class="top-customer-stat">储值 <text class="top-customer-stat-val">{{fmt.money(item.balance)}}</text></text>
|
||
<text class="top-customer-stat">消费 <text class="top-customer-stat-val">{{fmt.money(item.consume)}}</text></text>
|
||
</view>
|
||
</view>
|
||
<t-icon name="chevron-right" size="32rpx" color="#c5c5c5" />
|
||
</view>
|
||
</view>
|
||
<view class="toggle-btn" bindtap="onToggleTopCustomers" hover-class="toggle-btn--hover" wx:if="{{topCustomers.length > 5}}">
|
||
<text>{{topCustomersExpanded ? '收起 ↑' : '查看更多 ↓'}}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 近期服务明细 -->
|
||
<view class="card">
|
||
<view class="card-title-row">
|
||
<text class="section-title title-purple">近期服务明细</text>
|
||
</view>
|
||
<view class="svc-list">
|
||
<view class="svc-card" wx:for="{{serviceRecords}}" wx:key="index" wx:if="{{index < 5}}"
|
||
bindtap="onSvcCardTap" data-id="{{item.customerId}}" hover-class="svc-card--hover">
|
||
<!-- 头像列 -->
|
||
<view class="top-customer-avatar avatar-{{item.avatarGradient}}">
|
||
<text class="top-customer-avatar-text">{{item.initial}}</text>
|
||
</view>
|
||
<!-- 右侧内容列:两行垂直排列 -->
|
||
<view class="svc-content">
|
||
<!-- 第1行:客户名 + 类型标签 + 日期 -->
|
||
<view class="svc-row1">
|
||
<text class="svc-customer {{item.isScattered ? 'svc-customer--scattered' : ''}}">{{item.customerName}}</text>
|
||
<text class="svc-type svc-type-{{item.typeClass}}">{{item.type}}</text>
|
||
<text class="svc-date">{{item.date}}</text>
|
||
</view>
|
||
<!-- 第2行:台号 + 时长 + 绩效 + 收入 -->
|
||
<view class="svc-row2">
|
||
<view class="svc-row2-left">
|
||
<text class="svc-table-tag" wx:if="{{item.table}}">{{item.table}}</text>
|
||
<text class="svc-duration">{{item.duration}}</text>
|
||
<text class="svc-perf" wx:if="{{item.perfHours}}">定档绩效:{{item.perfHours}}</text>
|
||
</view>
|
||
<text class="svc-income">{{fmt.money(item.income)}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="svc-more" bindtap="onViewMoreRecords" hover-class="svc-more--hover">
|
||
<text>查看更多服务记录 →</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 更多信息 -->
|
||
<view class="card">
|
||
<view class="card-title-row">
|
||
<text class="section-title title-teal">更多信息</text>
|
||
</view>
|
||
<view class="more-info-row">
|
||
<text class="more-info-label">入职日期</text>
|
||
<text class="more-info-value">{{fmt.safe(detail.hireDate)}}</text>
|
||
</view>
|
||
<view class="history-table">
|
||
<view class="history-thead">
|
||
<text class="history-th history-th-left">月份</text>
|
||
<text class="history-th">服务客户</text>
|
||
<text class="history-th">访/召完成</text>
|
||
<text class="history-th">业绩时长</text>
|
||
<text class="history-th">工资</text>
|
||
</view>
|
||
<view class="history-row {{index === 0 ? 'history-row-current' : ''}}" wx:for="{{historyMonths}}" wx:key="month">
|
||
<view class="history-td history-td-left">
|
||
<text>{{item.month}}</text>
|
||
<text class="history-est" wx:if="{{item.estimated}}">预估</text>
|
||
</view>
|
||
<text class="history-td">{{fmt.count(item.customers, '人')}}</text>
|
||
<text class="history-td">{{fmt.safe(item.callbackDone)}} | {{fmt.safe(item.recallDone)}}</text>
|
||
<text class="history-td {{index === 0 ? 'text-primary' : ''}}">{{fmt.hoursH(item.hours)}}</text>
|
||
<text class="history-td {{index === 0 ? 'text-success' : ''}}">{{fmt.money(item.salary)}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 备注列表 -->
|
||
<view class="card">
|
||
<view class="card-header">
|
||
<text class="section-title title-orange">备注记录</text>
|
||
<text class="header-hint">共 {{sortedNotes.length}} 条</text>
|
||
</view>
|
||
<view class="note-list" wx:if="{{sortedNotes.length > 0}}">
|
||
<view class="note-item" wx:for="{{sortedNotes}}" wx:key="id">
|
||
<view class="note-top">
|
||
<text class="note-author">{{item.tagLabel}}</text>
|
||
<text class="note-time">{{item.createdAt}}</text>
|
||
</view>
|
||
<text class="note-content">{{item.content}}</text>
|
||
</view>
|
||
</view>
|
||
<view class="note-empty" wx:else>
|
||
<t-icon name="edit-1" size="80rpx" color="#dcdcdc" />
|
||
<text class="empty-hint">暂无备注</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-bar safe-area-bottom">
|
||
<view class="btn-chat" bindtap="onStartChat" hover-class="btn-chat--hover">
|
||
<t-icon name="chat" size="36rpx" color="#ffffff" />
|
||
<text>问问助手</text>
|
||
</view>
|
||
<view class="btn-note" bindtap="onAddNote" hover-class="btn-note--hover">
|
||
<t-icon name="edit-1" size="36rpx" color="#242424" />
|
||
<text>备注</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 备注弹窗 -->
|
||
<note-modal visible="{{noteModalVisible}}" customerName="{{detail.name}}" bind:confirm="onNoteConfirm" bind:cancel="onNoteCancel" />
|
||
|
||
<!-- 备注列表弹窗 -->
|
||
<view class="notes-popup-overlay" wx:if="{{notesPopupVisible}}" catchtap="onHideNotesPopup">
|
||
<view class="notes-popup" catchtap="">
|
||
<view class="notes-popup-header">
|
||
<text class="notes-popup-title">{{notesPopupName}} 的备注</text>
|
||
<view class="notes-popup-close" bindtap="onHideNotesPopup" hover-class="notes-popup-close--hover">
|
||
<t-icon name="close" size="40rpx" color="#8b8b8b" />
|
||
</view>
|
||
</view>
|
||
<view class="notes-popup-list">
|
||
<view class="notes-popup-item" wx:for="{{notesPopupList}}" wx:key="index">
|
||
<view class="notes-popup-item-top">
|
||
<text wx:if="{{item.pinned}}">📌</text>
|
||
<text class="notes-popup-item-date">{{item.date}}</text>
|
||
</view>
|
||
<text class="notes-popup-item-text">{{item.text}}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
|
||
<dev-fab />
|