Files
Neo-ZQYY/apps/miniprogram/miniprogram/pages/task-detail/task-detail.wxml
Neo 2dfc926f96 feat(ai): W1-AI-CLOSURE 超级 Sprint — 9 APP 全链路收口 + chat 上下文真激活
Phase 2.3 chat 上下文捕获链路从未真正激活到完整工作:
- 14 处 ai-float-button 补 sourcePage,chat.ts 三分支同步设 pageFilters.contextId
- 后端 page_context 4 层 BUG 修(列名错位 + RLS site_id 未重设)
- xcx_chat filters.pop 破坏 body.page_context 引用 — dict() 浅拷贝隔离
- chat 流式 markdown 实时解析(表格/标题/列表/加粗 + KPI 富卡)
- reference_card KPI 富卡接入 SSE 路径,db 真写入
- 维客线索 source 显示规则:AI 来源用机器人 icon 替代长文字

数据库:
- public.member_retention_clue 加 emoji + runtime_mode + sandbox_instance_id
- biz.ai_run_logs 加 assistant_id + 复合索引
- chk_ai_cache_type CHECK 约束 8 类应用名
- cache_type / app_type 命名统一(app6_note / app7_customer / app8_consolidation)
- 历史 emoji 抽取脚本 44/44 成功

后端 silent failure 修:
- cleanup_service WHERE app_type → cache_type(90 天清理 + 20K 上限重新生效)
- _build_ai_insight 字段错位修复(app4 → app7 + 字段对齐 prompt schema)
- task_manager talkingPoints 改 app5_tactics + tactics 字段
- task_manager aiSuggestion 改取 one_line_summary
- cache_service.CACHE_EXPIRY_DAYS 加 app2a_finance_area
- WS /ws/ai-cache 加 token + JWT + site_id 校验(P0 信息泄露漏洞)
- internal_ai token 改 hmac.compare_digest

工具/文档:
- main.py 加 RotatingFileHandler logs/backend.log + uvicorn /health 过滤
- 新建 utils/clue_category.py(VI 6 类配色 + emoji fallback + source 显示规则)
- 新建 utils/markdown.ts(轻量 md 转 rich-text 解析 + streaming 容错)
- audit + 数据库变更说明 + backlog §七 #14 收口 + #15-#38 残余子任务
- backlog 追加 §十一 App1 参数/MCP/沙箱审计 + §十二 百炼/SQL MCP 主任务线

实地 MCP 走查:14 入口数据层 + 5 代表入口 sourcePage 注入 + customer-detail 全模块 + chat md 渲染 + reference_card 富卡 都已验证。9 项预先 BUG/UX 登记 §七 #29-#38 后续修复。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:39:07 +08:00

284 lines
12 KiB
Plaintext
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.
<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 区域SVG 背景)-->
<view class="banner-area">
<image src="{{bannerBgSvg}}" class="banner-bg-svg" mode="scaleToFill" />
<view class="banner-content">
<!-- 放弃/取消放弃按钮 - 右上角 -->
<view class="banner-abandon-btn" bindtap="onAbandon" hover-class="banner-abandon-btn--hover">
<text class="banner-abandon-text">{{detail.status === 'abandoned' ? '取消放弃' : '放弃'}}</text>
</view>
<view class="customer-info">
<view class="avatar-box">
<text class="avatar-text">{{detail.customerName[0] || '?'}}</text>
</view>
<view class="info-right">
<view class="name-row">
<text class="customer-name">{{fmt.safe(detail.customerName)}}</text>
<text class="task-type-tag">{{detail.taskTypeLabel || '高优先召回'}}</text>
</view>
<view class="sub-info">
<text class="phone">{{phoneVisible ? fmt.safe(detail.customerPhone) : fmt.maskPhone(detail.customerPhone)}}</text>
<view class="phone-toggle-btn" bindtap="{{phoneVisible ? 'onCopyPhone' : 'onTogglePhone'}}" hover-class="phone-toggle-btn--hover">
<text class="phone-toggle-text">{{phoneVisible ? '复制' : '查看'}}</text>
</view>
<text class="storage-level-text">💰 储值 {{storageLevel}}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 主体内容 -->
<view class="main-content">
<!-- 与我的关系(动态颜色)-->
<view class="card">
<view class="card-header">
<text class="section-title title-pink">与我的关系</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<view class="relationship-row">
<view class="rel-tag rel-level-{{relationLevel}}">
<heart-icon score="{{detail.heartScore}}" />
<text class="rel-tag-text">{{relationLevelText}}</text>
</view>
<view class="rel-bar">
<view class="rel-bar-fill rel-fill-{{relationLevel}}" style="width: {{detail.heartScore * 10}}%; background: {{relationColor}};"></view>
</view>
<text class="rel-score" style="color: {{relationColor}};">{{fmt.toFixed(detail.heartScore, 1)}}</text>
</view>
<view class="card-desc-wrap">
<text class="card-desc">{{fmt.safe(detail.aiAnalysis.summary)}}</text>
</view>
</view>
<!-- 任务建议 -->
<view class="card">
<view class="card-header">
<text class="section-title title-orange">任务建议</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<view class="suggestion-box">
<view class="suggestion-header">
<text class="suggestion-icon-text">💡 建议执行</text>
</view>
<view class="suggestion-body">
<view class="suggestion-intro-wrap">
<text class="suggestion-intro">{{detail.aiAnalysis.summary || '暂无AI分析'}}</text>
</view>
<view class="suggestion-item" wx:for="{{detail.aiAnalysis.suggestions}}" wx:key="index">
<text class="suggestion-item-text">• {{item}}</text>
</view>
</view>
</view>
<view class="speech-section">
<text class="speech-title">💬 话术参考</text>
<view class="speech-list">
<view class="speech-bubble" wx:for="{{talkingPoints}}" wx:key="index">
<view class="speech-text-wrap">
<ai-inline-icon color="{{aiColor}}" />
<text class="speech-text">{{item.scenario ? item.scenario + '' + item.script : item.script}}</text>
</view>
<view class="speech-copy-row">
<view class="copy-btn" bindtap="onCopySpeech" data-index="{{index}}" hover-class="copy-btn--hover">
<text class="copy-btn-text">{{copiedIndex === index ? '✓ 已复制' : '复制'}}</text>
</view>
</view>
<view class="speech-arrow"></view>
</view>
</view>
</view>
</view>
<!-- 维客线索 -->
<view class="card">
<view class="card-header">
<text class="section-title title-green">维客线索</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<view class="clue-list">
<clue-card
wx:for="{{retentionClues}}"
wx:key="index"
tag="{{item.tag}}"
category="{{item.tagColor}}"
emoji="{{item.emoji}}"
title="{{item.text}}"
source="{{item.source}}"
content="{{item.desc || ''}}"
/>
</view>
</view>
<!-- T4.3 行动建议AI 生成) -->
<view class="card" wx:if="{{detail.actionSuggestions && detail.actionSuggestions.length > 0}}">
<view class="card-header">
<text class="section-title title-green">行动建议</text>
<ai-title-badge color="{{aiColor}}" />
</view>
<view class="action-suggestions-list">
<view class="action-suggestion-card" wx:for="{{detail.actionSuggestions}}" wx:key="index">
<text class="action-suggestion-index">{{index + 1}}</text>
<text class="action-suggestion-text">{{item}}</text>
</view>
</view>
</view>
<!-- 我给TA的备注 -->
<view class="card">
<view class="card-header">
<text class="section-title title-blue">我给TA的备注</text>
<text class="note-count">{{sortedNotes.length}} 条备注</text>
</view>
<block wx:if="{{sortedNotes.length > 0}}">
<view class="note-item" wx:for="{{sortedNotes}}" wx:key="id">
<view class="note-top">
<view class="note-date-wrap">
<text class="note-date">{{fmt.safe(item.timeLabel || item.createdAt)}}</text>
</view>
<view class="note-top-right">
<!-- CHANGE 2026-03-27 | 备注联调:无评分时不显示星星 -->
<star-rating wx:if="{{item.score}}" score="{{item.score}}" size="28rpx" readonly="{{true}}" />
<view class="note-delete-btn" catchtap="onDeleteNote" data-id="{{item.id}}" hover-class="note-delete-btn--hover">
<t-icon name="delete" size="32rpx" color="#a6a6a6" />
</view>
</view>
</view>
<view class="note-content-wrap">
<text class="note-content">{{fmt.safe(item.content)}}</text>
</view>
</view>
</block>
<view class="note-empty" wx:else>
<t-icon name="edit-1" size="80rpx" color="#dcdcdc" />
<text class="empty-hint">暂无备注</text>
</view>
</view>
<!-- 60天内服务记录 -->
<view class="card">
<view class="card-header">
<text class="section-title title-blue">60天内服务记录</text>
<text class="note-count">共 {{serviceRecords.length}} 次</text>
</view>
<!-- 汇总统计 -->
<view class="svc-summary">
<view class="svc-summary-item svc-summary-blue">
<view class="svc-summary-value-row">
<text class="svc-summary-value svc-val-blue">{{fmt.hoursH(serviceSummary.totalHours)}}</text>
</view>
<text class="svc-summary-label">总时长</text>
</view>
<view class="svc-summary-item svc-summary-green">
<text class="svc-summary-value svc-val-green">{{fmt.money(serviceSummary.totalIncome)}}</text>
<text class="svc-summary-label">总收入</text>
</view>
<view class="svc-summary-item svc-summary-orange">
<text class="svc-summary-value svc-val-orange">{{fmt.money(serviceSummary.avgIncome)}}</text>
<text class="svc-summary-label">场均</text>
</view>
</view>
<!-- 记录列表(使用 service-record-card 组件)-->
<view class="svc-list">
<service-record-card
wx:for="{{serviceRecords}}"
wx:key="index"
time="{{item.date}}"
course-label="{{item.type}}"
type-class="{{item.typeClass}}"
type="{{item.recordType || 'course'}}"
table-no="{{item.table}}"
hours="{{item.duration}}"
hours-raw="{{item.durationRaw}}"
drinks="{{item.drinks}}"
income="{{item.income}}"
is-estimate="{{item.isEstimate}}"
/>
</view>
<view class="svc-view-all" bindtap="onViewAllRecords" hover-class="svc-view-all--hover">
<text class="svc-view-all-text">查看全部服务记录 →</text>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar safe-area-bottom">
<view class="btn-ask" bindtap="onAskAssistant" hover-class="btn-ask--hover">
<t-icon name="chat" size="36rpx" color="#ffffff" />
<text class="btn-text">问问助手</text>
</view>
<view class="btn-note" bindtap="onAddNote" hover-class="btn-note--hover">
<t-icon name="edit-1" size="36rpx" color="#242424" />
<text class="btn-text-dark">备注</text>
</view>
</view>
<!-- 备注弹窗 -->
<note-modal visible="{{noteModalVisible}}" customerName="{{detail.customerName || ''}}" initialScore="{{0}}" initialContent="" showExpandBtn="{{detail.taskType !== 'callback'}}" showRating="{{true}}" bind:confirm="onNoteConfirm" bind:cancel="onNoteCancel" />
<!-- 放弃弹窗 -->
<abandon-modal
visible="{{abandonModalVisible}}"
customerName="{{detail.customerName || ''}}"
bind:confirm="onAbandonConfirm"
bind:cancel="onAbandonCancel"
/>
<!-- 页面调试工具 -->
<view class="debug-panel" wx:if="{{showDebugPanel}}">
<view class="debug-header">
<text class="debug-title">🔧 页面调试</text>
<view class="debug-close" bindtap="toggleDebugPanel" hover-class="debug-close--hover">
<t-icon name="close" size="32rpx" color="#5e5e5e" />
</view>
</view>
<view class="debug-section">
<text class="debug-label">任务类型:</text>
<view class="debug-btn-group">
<view class="debug-btn {{debugTaskType === 'high_priority' ? 'debug-btn--active' : ''}}" bindtap="onDebugTaskType" data-type="high_priority">高优先召回</view>
<view class="debug-btn {{debugTaskType === 'priority_recall' ? 'debug-btn--active' : ''}}" bindtap="onDebugTaskType" data-type="priority_recall">优先召回</view>
<view class="debug-btn {{debugTaskType === 'relationship' ? 'debug-btn--active' : ''}}" bindtap="onDebugTaskType" data-type="relationship">关系构建</view>
<view class="debug-btn {{debugTaskType === 'callback' ? 'debug-btn--active' : ''}}" bindtap="onDebugTaskType" data-type="callback">客户回访</view>
</view>
</view>
<view class="debug-section">
<text class="debug-label">关系数值:{{debugHeartScore}}</text>
<slider class="debug-slider" min="0" max="10" step="0.5" value="{{debugHeartScore}}" bindchange="onDebugHeartScore" activeColor="#ec4899" backgroundColor="#f3f3f3" block-size="24" />
<view class="debug-hint">
<text>0-3.5: 待发展</text>
<text>3.5-6: 一般</text>
<text>6-8.5: 良好</text>
<text>>8.5: 很好</text>
</view>
</view>
</view>
<!-- 调试面板触发按钮 -->
<view class="debug-trigger" bindtap="toggleDebugPanel" hover-class="debug-trigger--hover">
<text>🔧</text>
</view>
<!-- AI 悬浮按钮(W1-AI-CLOSURE 组 6:补 task-detail 整页缺失的入口 + sourcePage) -->
<ai-float-button taskId="{{detail.id}}" sourcePage="task-detail" />
</block>