依据:小冈 docs/plans/2026-09-24-C端v3完整架构方案.md 第五章(客服线索承接)+ 第七章(最小埋点集)
定位:只做后端设施 + A 端台账/漏斗页。C 端前端(v3 域:小齐/小冈)不动,对接时按本文第 5 节埋点 schema 上报即可。
原则:不关心具体商户身份——所有表merchant_id参数化 +is_test隔离,沿用现有惯例。
| 项 | 本设计负责 | 不负责 |
|---|---|---|
线索台账 crm_leads + SLA | ✅ 后端表 + 接口 + A 端页 | — |
埋点 analytics_events | ⚠️ 复用现有,不新建表 | C 端采集代码(v3 域) |
| 转化漏斗 | ✅ 后端聚合接口 + A 端页 | — |
is_test 治理 | ✅ 写入带标记、查询默认隔离 | 既有 hotel stats 的 is_test 过滤(后续补) |
不碰:具体商户是谁、MVP 商户定死(这是数据/运营决策,与系统实现无关)。
探查发现后端已有完整埋点基础设施,C 端 v3 的埋点层直接复用,不新建:
analytics_events 表(sql/056_admin_backend_skeleton.sql):event_name / event_category / user_id(可空) / session_id / page_path / element_id / properties JSONB / is_test / created_at + 索引齐全POST /api/admin/track/event(admin_backend.py:555):匿名可写(user 可空),写入 analytics_eventsGET /api/admin/stats/overview:已有按 category/event_name 聚合properties JSONB 自由扩展完美承载 5 类事件的差异化字段,is_test 已支持。结论:不新建 events 表,不新建埋点写入接口。
线索台账与现有 merchant_stall_leads(B 端摊位留资:name/phone/note 三字段)场景不同(C 端转化首页 CTA 意图预选 → 客服承接,带意图/来源/SLA/结果),故新建 crm_leads,不合并。
sql/082_crm_leads.sql)crm_leads(消费者 → 客服 线索)CREATE TABLE IF NOT EXISTS crm_leads (
id BIGSERIAL PRIMARY KEY,
merchant_id BIGINT NOT NULL REFERENCES merchants(id),
scene VARCHAR(32) NOT NULL DEFAULT 'unknown', -- 来源点位 front/room/restaurant/platform/qr
channel VARCHAR(32) NOT NULL DEFAULT 'merchant', -- 双入口 platform(平台码)/merchant(商户码)
intent VARCHAR(32), -- 住宿/准备/行程/未定
page_id VARCHAR(64), -- 触发页面
contact TEXT, -- 联系方式(可选)
status VARCHAR(16) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','replied','accepted','invalid')),
first_reply_at TIMESTAMPTZ, -- SLA 计时锚点
outcome VARCHAR(16) CHECK (outcome IS NULL OR outcome IN ('accepted','invalid','follow_up')),
operator_id BIGINT, -- 承接客服
is_test BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 索引:merchant_id+created_at / status+created_at / channel+created_at / is_test
crm_sla_config(SLA 配置,商户参数化,运营参数后台可配)CREATE TABLE IF NOT EXISTS crm_sla_config (
id BIGSERIAL PRIMARY KEY,
merchant_id BIGINT NOT NULL REFERENCES merchants(id),
first_reply_minutes INTEGER NOT NULL DEFAULT 10, -- 未响应提醒时限
escalate_minutes INTEGER NOT NULL DEFAULT 120, -- 超时升级时限
escalate_to VARCHAR(64), -- 升级提醒对象(后台配置)
is_test BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (merchant_id)
);
SLA 引擎:MVP 用台账页查询时实时计算(now()-created_at 比对配置阈值),不加 cron / 定时任务,降复杂度。后续要主动推送再上定时任务。
app/routers/crm.py,注册于 main.py)| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /api/miniapp/crm/leads | 匿名可写(user 可空) | C 端创建线索,body 带 merchant_id/scene/channel/intent/page_id/contact/is_test |
| GET | /api/admin/crm/leads | staff/admin | A 端台账,筛选 merchant_id/status/channel/include_test,返回含 sla_state 实时状态(normal/overdue/escalated/done) |
| PATCH | /api/admin/crm/leads/{id} | staff/admin | 标记 status/outcome/operator_id/first_reply_at;状态进入 replied/accepted 且原无首响则自动记 first_reply_at=now() |
| GET | /api/admin/crm/sla-config?merchant_id= | staff/admin | 读 SLA,未配置返回默认值(default:true) |
| POST | /api/admin/crm/sla-config | admin | upsert SLA(运营参数) |
| GET | /api/admin/crm/funnel?days=&scene=&channel=&merchant_id=&include_test= | staff/admin | 转化漏斗,基于 analytics_events 聚合 5 类事件 + 来源分布 |
响应体:沿用全局 ResponseWrapperMiddleware({ok,data})。安全:全部 SQL 参数化(asyncpg $1),创建/配置前校验 merchant_id 存在。
POST /api/admin/track/event)接口已存在(匿名可写)。前端只需按约定 event_name + properties 上报,后端零改动:
| event_name | properties 字段 | 对应指标 |
|---|---|---|
page_enter | scene, channel, page_id | 双入口流量分布 |
intent_select | intent(stay/prepare/trip/unknown) | 意图选择分布 |
cs_click | screen, chapter_id | 北极星:首屏→客服点击率 |
chapter_view | chapter_id, dwell | 章节流失点 |
merchant_enter | merchant_id, src | 商户页进入率 |
admin-console)LeadsLedger.tsx(线索台账):列表 + 筛选(商户 ID/状态/入口/含测试)+ SLA 状态色标(normal 灰 / overdue 橙 / escalated 红 / done 绿)+ 一键标记(已响应/承接/无效)。复用 ResourcePage.css 样式。ConversionFunnel.tsx(转化漏斗):漏斗条形(相对 page_enter 比例)+ 北极星点击率卡 + 来源分布表。天数 7/14/30 切换。App.tsx 路由 /crm-leads /crm-funnel + menu.ts 菜单项(scope B)。sql/082_crm_leads.sql(幂等 IF NOT EXISTS)kailash-app(staging + prod)——app/routers/crm.py 新模块,main.py 已注册admin-console npm run build → 同步 dist 到 a.kailashi.cn(nginx /srv/kailash/nginx/html/adminconsole)crm_leads / crm_sla_config 表存在 ✅(082 迁移;prod 新执行 14/跳过 77,staging 已记录)POST /api/miniapp/crm/leads 匿名创建返回 ok(is_test 隔离)✅ —— 实测匿名无 token POST 返回 ok(修复前误用 get_current_user 曾 401)GET /api/admin/crm/leads 带 staff token 返回含 sla_state ✅ —— 实测含 merchant_name + sla_state + 默认 SLA 10/120 分钟now()-created_at vs 配置阈值,pending 分级 normal/overdue/escalated,非 pending=done ✅(无 cron,MVP 简化)PATCH 标记 + 自动首响时间 ✅ —— 状态进入 replied/accepted 且无首响时自动 first_reply_at=now()GET /api/admin/crm/funnel 聚合 analytics_events 5 类事件 ✅ —— page_enter/intent_select/chapter_view/cs_click/merchant_enter + scene_dista.kailashi.cn 渲染真实数据、零控制台错误 ✅ —— verify-live 真机浏览器验收 PASS /crm-leads、/crm-funnel(CONSOLE_ERRORS=0)