微信小程序UV与转化漏斗:从浏览到下单的完整链路

179 阅读18分钟

一、微信小程序转化漏斗的定义

1. 与传统App漏斗的区别

微信小程序的转化漏斗设计,需要充分考虑其"轻量、即用即走"的产品特性,这与传统App有本质差异:

入口差异

  • App:用户需要下载、安装、注册后才能进入漏斗,转化起点是"打开App"
  • 小程序:扫码或点击即打开,无需安装,转化起点是"首次启动"

路径差异

  • App:用户可能在App内停留较长时间,漏斗步骤可以更细致
  • 小程序:用户会话时长通常在3-10分钟,漏斗必须精简

支付差异

  • App:需要跳转第三方支付或内置钱包
  • 小程序:微信支付原生集成,支付流程更顺畅

留存差异

  • App:图标常驻桌面,复访容易
  • 小程序:用户需要回忆来源,复访依赖触达(公众号、群聊、搜索)

2. 微信小程序核心漏斗模型

code复制

完整转化漏斗:

启动(100%) → 浏览关键页(70%) → 加购(40%) → 下单(25%) → 支付(20%) → 完成(18%)

核心指标说明:
- 启动UV:打开小程序的独立用户数(场景值1001、1011等)
- 浏览UV:查看商品详情页或核心服务页的用户
- 加购UV:点击"加入购物车""立即购买"的用户
- 下单UV:进入订单确认页并点击提交的用户
- 支付UV:唤起微信支付并完成支付的用户
- 完成UV:支付成功并完成订单的用户

javascript复制

// 漏斗步骤定义
const funnelSteps = [
  { 
    id: 'launch', 
    name: '启动', 
    event: 'app_launch',
    required: true 
  },
  { 
    id: 'browse', 
    name: '浏览关键页', 
    event: 'view_product',
    required: true 
  },
  { 
    id: 'add_cart', 
    name: '加购', 
    event: 'add_to_cart',
    required: false 
  },
  { 
    id: 'place_order', 
    name: '下单', 
    event: 'submit_order',
    required: false 
  },
  { 
    id: 'pay', 
    name: '支付', 
    event: 'payment_complete',
    required: false 
  },
  { 
    id: 'complete', 
    name: '完成', 
    event: 'order_complete',
    required: false 
  }
];

二、关键漏斗设计与实现

1. 基于微信API的漏斗埋点

javascript复制

// app.js - 应用启动埋点
App({
  onLaunch(options) {
    this.trackEvent('app_launch', {
      scene: options.scene,
      path: options.path,
      query: options.query,
      visitorId: this.getVisitorId(),
      timestamp: Date.now()
    });
  },
  
  getVisitorId() {
    let visitorId = wx.getStorageSync('visitor_id');
    if (!visitorId) {
      visitorId = 'v_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
      wx.setStorageSync('visitor_id', visitorId);
    }
    return visitorId;
  },
  
  trackEvent(eventName, data) {
    const event = {
      ...data,
      event: eventName,
      sessionId: this.getSessionId()
    };
    
    // 上报到云数据库
    wx.cloud.database().collection('funnel_events').add({ data: event });
    
    // 同时上报微信后台分析
    wx.reportAnalytics(eventName.replace(/_/g, '_'), data);
  },
  
  getSessionId() {
    let sessionId = wx.getStorageSync('session_id');
    const lastActivity = wx.getStorageSync('last_activity') || 0;
    const now = Date.now();
    
    // 30分钟无活动则新开session
    if (!sessionId || now - lastActivity > 30 * 60 * 1000) {
      sessionId = 's_' + Date.now();
    }
    
    wx.setStorageSync('last_activity', now);
    wx.setStorageSync('session_id', sessionId);
    return sessionId;
  }
});

2. 商品页浏览埋点

javascript复制

// pages/product/product.js
Page({
  onLoad(options) {
    this.productId = options.id;
    this.viewStartTime = Date.now();
    
    // 浏览埋点
    this.trackFunnelEvent('view_product', {
      productId: this.productId,
      fromScene: options.scene,
      referrer: options.referrer || 'direct'
    });
  },
  
  onUnload() {
    // 计算浏览时长
    const viewDuration = Date.now() - this.viewStartTime;
    
    // 上报浏览完成事件(停留超过3秒算有效浏览)
    if (viewDuration > 3000) {
      this.trackFunnelEvent('product_view_complete', {
        productId: this.productId,
        viewDuration: Math.round(viewDuration / 1000)
      });
    }
  },
  
  trackFunnelEvent(event, data) {
    const app = getApp();
    app.trackEvent(event, {
      ...data,
      visitorId: app.getVisitorId(),
      timestamp: Date.now()
    });
  }
});

3. 加购与下单埋点

javascript复制

// 加购埋点
function trackAddToCart(product, sku, quantity) {
  const app = getApp();
  
  app.trackEvent('add_to_cart', {
    productId: product.id,
    productName: product.name,
    productPrice: product.price,
    skuId: sku.id,
    skuName: sku.name,
    quantity: quantity,
    cartTotal: getCartTotal() + quantity,
    visitorId: app.getVisitorId(),
    timestamp: Date.now()
  });
}

// 下单埋点(订单提交)
function trackPlaceOrder(order) {
  const app = getApp();
  
  app.trackEvent('submit_order', {
    orderId: order.id,
    totalAmount: order.totalAmount,
    itemCount: order.items.length,
    paymentMethod: order.paymentMethod || 'wxpay',
    visitorId: app.getVisitorId(),
    timestamp: Date.now()
  });
}

4. 支付完成埋点(关键步骤)

javascript复制

// 唤起微信支付并追踪支付结果
async function processPayment(order) {
  try {
    // 上报支付开始事件
    trackFunnelEvent('payment_start', { orderId: order.id });
    
    // 唤起微信支付
    const payResult = await wx.requestPayment({
      timeStamp: order.payment.timeStamp,
      nonceStr: order.payment.nonceStr,
      package: order.payment.package,
      signType: order.payment.signType || 'RSA',
      paySign: order.payment.paySign
    });
    
    // 支付成功埋点
    trackFunnelEvent('payment_complete', {
      orderId: order.id,
      amount: order.totalAmount,
      paymentMethod: 'wechat',
      success: true
    });
    
    // 更新订单状态
    await updateOrderStatus(order.id, 'paid');
    
    return { success: true, order };
    
  } catch (error) {
    // 支付失败埋点(重要!用于分析流失)
    trackFunnelEvent('payment_failed', {
      orderId: order.id,
      errorCode: error.errMsg,
      errorMessage: error.message || 'User cancelled',
      failStage: 'payment_request'
    });
    
    // 区分用户取消和支付失败
    if (error.errMsg.includes('cancel')) {
      trackFunnelEvent('payment_cancelled', { orderId: order.id });
    }
    
    return { success: false, error };
  }
}

function trackFunnelEvent(event, data) {
  const app = getApp();
  app.trackEvent(event, {
    ...data,
    visitorId: app.getVisitorId(),
    sessionId: app.getSessionId(),
    timestamp: Date.now()
  });
}

三、漏斗计算SQL实现

1. 基础漏斗UV统计

sql复制

-- 计算各步骤UV及转化率
WITH funnel_steps AS (
  SELECT 
    visitor_id,
    MIN(CASE WHEN event = 'app_launch' THEN event_date END) AS launch_date,
    MIN(CASE WHEN event = 'view_product' THEN event_date END) AS browse_date,
    MIN(CASE WHEN event = 'add_to_cart' THEN event_date END) AS add_cart_date,
    MIN(CASE WHEN event = 'submit_order' THEN event_date END) AS order_date,
    MIN(CASE WHEN event = 'payment_complete' THEN event_date END) AS pay_date,
    MIN(CASE WHEN event = 'order_complete' THEN event_date END) AS complete_date
  FROM funnel_events
  WHERE event_date BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL 30 DAY) AND CURRENT_DATE
  GROUP BY visitor_id
)
SELECT 
  '启动' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  100.00 AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL

UNION ALL

SELECT 
  '浏览关键页' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  ROUND(COUNT(DISTINCT CASE WHEN browse_date IS NOT NULL THEN visitor_id END) * 100.0 / 
    NULLIF(COUNT(DISTINCT visitor_id), 0), 2) AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL

UNION ALL

SELECT 
  '加购' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  ROUND(COUNT(DISTINCT CASE WHEN add_cart_date IS NOT NULL THEN visitor_id END) * 100.0 / 
    NULLIF(COUNT(DISTINCT visitor_id), 0), 2) AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL

UNION ALL

SELECT 
  '下单' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  ROUND(COUNT(DISTINCT CASE WHEN order_date IS NOT NULL THEN visitor_id END) * 100.0 / 
    NULLIF(COUNT(DISTINCT visitor_id), 0), 2) AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL

UNION ALL

SELECT 
  '支付' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  ROUND(COUNT(DISTINCT CASE WHEN pay_date IS NOT NULL THEN visitor_id END) * 100.0 / 
    NULLIF(COUNT(DISTINCT visitor_id), 0), 2) AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL

UNION ALL

SELECT 
  '完成' AS step,
  COUNT(DISTINCT visitor_id) AS uv,
  ROUND(COUNT(DISTINCT CASE WHEN complete_date IS NOT NULL THEN visitor_id END) * 100.0 / 
    NULLIF(COUNT(DISTINCT visitor_id), 0), 2) AS conversion_rate
FROM funnel_steps
WHERE launch_date IS NOT NULL;

2. 步骤间转化率(更直观)

sql复制

-- 计算相邻步骤的转化率
WITH step_uv AS (
  SELECT 
    COUNT(DISTINCT CASE WHEN event = 'app_launch' THEN visitor_id END) AS launch_uv,
    COUNT(DISTINCT CASE WHEN event = 'view_product' THEN visitor_id END) AS browse_uv,
    COUNT(DISTINCT CASE WHEN event = 'add_to_cart' THEN visitor_id END) AS add_cart_uv,
    COUNT(DISTINCT CASE WHEN event = 'submit_order' THEN visitor_id END) AS order_uv,
    COUNT(DISTINCT CASE WHEN event = 'payment_complete' THEN visitor_id END) AS pay_uv,
    COUNT(DISTINCT CASE WHEN event = 'order_complete' THEN visitor_id END) AS complete_uv
  FROM funnel_events
  WHERE event_date = CURRENT_DATE
)
SELECT 
  '启动→浏览' AS step_transition,
  launch_uv AS from_uv,
  browse_uv AS to_uv,
  ROUND(browse_uv * 100.0 / NULLIF(launch_uv, 0), 2) AS conversion_rate,
  launch_uv - browse_uv AS lost_uv
FROM step_uv

UNION ALL

SELECT 
  '浏览→加购' AS step_transition,
  browse_uv AS from_uv,
  add_cart_uv AS to_uv,
  ROUND(add_cart_uv * 100.0 / NULLIF(browse_uv, 0), 2) AS conversion_rate,
  browse_uv - add_cart_uv AS lost_uv
FROM step_uv

UNION ALL

SELECT 
  '加购→下单' AS step_transition,
  add_cart_uv AS from_uv,
  order_uv AS to_uv,
  ROUND(order_uv * 100.0 / NULLIF(add_cart_uv, 0), 2) AS conversion_rate,
  add_cart_uv - order_uv AS lost_uv
FROM step_uv

UNION ALL

SELECT 
  '下单→支付' AS step_transition,
  order_uv AS from_uv,
  pay_uv AS to_uv,
  ROUND(pay_uv * 100.0 / NULLIF(order_uv, 0), 2) AS conversion_rate,
  order_uv - pay_uv AS lost_uv
FROM step_uv

UNION ALL

SELECT 
  '支付→完成' AS step_transition,
  pay_uv AS from_uv,
  complete_uv AS to_uv,
  ROUND(complete_uv * 100.0 / NULLIF(pay_uv, 0), 2) AS conversion_rate,
  pay_uv - complete_uv AS lost_uv
FROM step_uv;

3. 时间序列漏斗分析

sql复制

-- 按日计算漏斗转化趋势
WITH daily_funnel AS (
  SELECT 
    event_date,
    COUNT(DISTINCT CASE WHEN event = 'app_launch' THEN visitor_id END) AS launch_uv,
    COUNT(DISTINCT CASE WHEN event = 'view_product' THEN visitor_id END) AS browse_uv,
    COUNT(DISTINCT CASE WHEN event = 'add_to_cart' THEN visitor_id END) AS add_cart_uv,
    COUNT(DISTINCT CASE WHEN event = 'submit_order' THEN visitor_id END) AS order_uv,
    COUNT(DISTINCT CASE WHEN event = 'payment_complete' THEN visitor_id END) AS pay_uv,
    COUNT(DISTINCT CASE WHEN event = 'order_complete' THEN visitor_id END) AS complete_uv
  FROM funnel_events
  WHERE event_date BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL 30 DAY) AND CURRENT_DATE
  GROUP BY event_date
)
SELECT 
  event_date,
  launch_uv,
  browse_uv,
  add_cart_uv,
  order_uv,
  pay_uv,
  complete_uv,
  ROUND(complete_uv * 100.0 / NULLIF(launch_uv, 0), 2) AS overall_conversion_rate,
  ROUND(pay_uv * 100.0 / NULLIF(order_uv, 0), 2) AS payment_conversion_rate
FROM daily_funnel
ORDER BY event_date;

四、分场景值漏斗分析

不同场景值来源的用户,其行为模式和转化路径存在显著差异。

1. 场景值漏斗对比

javascript复制

class SceneFunnelAnalyzer {
  constructor() {
    this.funnelData = new Map();
  }
  
  // 按场景值聚合漏斗数据
  aggregateByScene(events) {
    events.forEach(event => {
      const scene = event.scene;
      if (!this.funnelData.has(scene)) {
        this.funnelData.set(scene, {
          launch: new Set(),
          browse: new Set(),
          addCart: new Set(),
          order: new Set(),
          pay: new Set(),
          complete: new Set()
        });
      }
      
      const sceneData = this.funnelData.get(scene);
      const visitorId = event.visitorId;
      
      switch (event.event) {
        case 'app_launch':
          sceneData.launch.add(visitorId);
          break;
        case 'view_product':
          sceneData.browse.add(visitorId);
          break;
        case 'add_to_cart':
          sceneData.addCart.add(visitorId);
          break;
        case 'submit_order':
          sceneData.order.add(visitorId);
          break;
        case 'payment_complete':
          sceneData.pay.add(visitorId);
          break;
        case 'order_complete':
          sceneData.complete.add(visitorId);
          break;
      }
    });
  }
  
  // 生成场景值漏斗对比报告
  generateComparisonReport() {
    const report = [];
    
    this.funnelData.forEach((data, scene) => {
      const launchUV = data.launch.size;
      const completeUV = data.complete.size;
      
      if (launchUV > 10) { // 过滤样本量过小的场景
        report.push({
          scene,
          sceneName: getSceneName(scene),
          category: categorizeScene(scene).categoryName,
          launchUV,
          browseUV: data.browse.size,
          addCartUV: data.addCart.size,
          orderUV: data.order.size,
          payUV: data.pay.size,
          completeUV,
          overallConversion: ((completeUV / launchUV) * 100).toFixed(2),
          browseRate: ((data.browse.size / launchUV) * 100).toFixed(2),
          payRate: launchUV > 0 && data.order.size > 0 ? 
            ((data.pay.size / data.order.size) * 100).toFixed(2) : 'N/A',
          avgOrderValue: this.calculateAvgOrderValue(scene)
        });
      }
    });
    
    return report.sort((a, b) => b.overallConversion - a.overallConversion);
  }
  
  calculateAvgOrderValue(scene) {
    // 简化实现,实际需要查询订单数据
    const avgValueMap = {
      1011: 285, // 扫码用户客单价通常更高
      1007: 168,
      1008: 152, // 群分享客单价略低
      1035: 198,
      1129: 175  // 搜一搜
    };
    return avgValueMap[scene] || 150;
  }
}

2. 典型场景值转化差异

场景值1011(扫二维码)

  • 用户意图强,线下场景明确(如餐厅点餐、商场导购)
  • 浏览→加购转化率:35-45%
  • 下单→支付转化率:85-92%
  • 客单价:250-350元(线下消费能力信号)

场景值1008(群聊分享)

  • 流量脉冲式,群消息生命周期短(通常2-4小时)
  • 浏览→加购转化率:25-35%
  • 下单→支付转化率:70-80%
  • 客单价:120-180元(价格敏感型用户)

场景值1129(搜一搜)

  • 用户主动搜索,意向明确
  • 浏览→加购转化率:30-40%
  • 下单→支付转化率:80-88%
  • 客单价:150-220元

场景值1035(公众号菜单)

  • 用户来自私域,信任度高
  • 浏览→加购转化率:40-50%
  • 下单→支付转化率:88-95%
  • 客单价:180-260元

sql复制

-- 场景值漏斗对比SQL
WITH scene_funnel AS (
  SELECT 
    scene,
    COUNT(DISTINCT CASE WHEN event = 'app_launch' THEN visitor_id END) AS launch_uv,
    COUNT(DISTINCT CASE WHEN event = 'view_product' THEN visitor_id END) AS browse_uv,
    COUNT(DISTINCT CASE WHEN event = 'add_to_cart' THEN visitor_id END) AS add_cart_uv,
    COUNT(DISTINCT CASE WHEN event = 'submit_order' THEN visitor_id END) AS order_uv,
    COUNT(DISTINCT CASE WHEN event = 'payment_complete' THEN visitor_id END) AS pay_uv,
    COUNT(DISTINCT CASE WHEN event = 'order_complete' THEN visitor_id END) AS complete_uv
  FROM funnel_events
  WHERE event_date BETWEEN DATE_SUB(CURRENT_DATE, INTERVAL 7 DAY) AND CURRENT_DATE
  GROUP BY scene
  HAVING launch_uv >= 100  -- 样本量筛选
)
SELECT 
  scene,
  launch_uv,
  browse_uv,
  add_cart_uv,
  order_uv,
  pay_uv,
  complete_uv,
  ROUND(complete_uv * 100.0 / NULLIF(launch_uv, 0), 2) AS overall_conv_rate,
  ROUND(pay_uv * 100.0 / NULLIF(order_uv, 0), 2) AS pay_conv_rate
FROM scene_funnel
ORDER BY overall_conv_rate DESC;

五、漏斗流失分析

1. 流失原因诊断框架

javascript复制

class FunnelDropOffAnalyzer {
  constructor() {
    this.dropOffReasons = {
      'launch_to_browse': {
        reasons: [
          { name: '页面加载慢', weight: 0.25 },
          { name: '内容不匹配预期', weight: 0.30 },
          { name: '导航不清晰', weight: 0.20 },
          { name: '闪退或报错', weight: 0.15 },
          { name: '其他', weight: 0.10 }
        ],
        indicators: ['load_time > 3000ms', 'bounce_within_5s', 'error_logs']
      },
      'browse_to_add_cart': {
        reasons: [
          { name: '价格超出预期', weight: 0.35 },
          { name: '商品信息不全', weight: 0.20 },
          { name: '缺少尺码/规格', weight: 0.15 },
          { name: '评价不好', weight: 0.15 },
          { name: '其他', weight: 0.15 }
        ],
        indicators: ['price_check_count', 'gallery_view_count', 'review_read']
      },
      'add_cart_to_order': {
        reasons: [
          { name: '运费过高', weight: 0.30 },
          { name: '没有优惠券', weight: 0.25 },
          { name: '凑单未完成', weight: 0.20 },
          { name: '购物车清空', weight: 0.15 },
          { name: '其他', weight: 0.10 }
        ],
        indicators: ['shipping_fee_check', 'coupon_attempt', 'cart_abandon']
      },
      'order_to_pay': {
        reasons: [
          { name: '支付失败', weight: 0.25 },
          { name: '用户取消支付', weight: 0.35 },
          { name: '余额不足', weight: 0.15 },
          { name: '支付超时', weight: 0.15 },
          { name: '其他', weight: 0.10 }
        ],
        indicators: ['payment_error_code', 'cancel_timestamp', 'timeout']
      }
    };
  }
  
  // 分析特定步骤的流失
  analyzeDropOff(stepFrom, stepTo, userData) {
    const dropOffKey = `${stepFrom}_to_${stepTo}`;
    const reasons = this.dropOffReasons[dropOffKey];
    
    if (!reasons) {
      return { error: 'Unknown drop-off step' };
    }
    
    // 根据用户行为数据推断流失原因
    const inferredReasons = this.inferReasons(userData, reasons);
    
    return {
      stepTransition: `${stepFrom}${stepTo}`,
      dropOffRate: this.calculateDropOffRate(stepFrom, stepTo, userData),
      topReasons: inferredReasons.slice(0, 3),
      recommendations: this.generateRecommendations(inferredReasons),
      sampleSize: userData.length
    };
  }
  
  inferReasons(userData, reasonTemplate) {
    // 根据实际数据推断原因分布
    return reasonTemplate.reasons.map(reason => ({
      ...reason,
      estimatedPercentage: reason.weight * 100 + (Math.random() * 10 - 5), // 加入噪声模拟真实分布
      evidence: this.findEvidence(userData, reason.name)
    })).sort((a, b) => b.estimatedPercentage - a.estimatedPercentage);
  }
  
  findEvidence(userData, reasonName) {
    // 查找支持该原因的证据
    const evidenceMap = {
      '页面加载慢': '平均加载时间 > 3秒',
      '价格超出预期': '多次查看价格详情',
      '运费过高': '运费信息查看 > 2次',
      '支付失败': '微信支付返回错误码'
    };
    return evidenceMap[reasonName] || '用户行为推断';
  }
  
  generateRecommendations(topReasons) {
    const recommendationMap = {
      '页面加载慢': '优化首屏渲染,使用分包加载,减少图片体积',
      '价格超出预期': '展示价格优势,突出性价比,提供分期选项',
      '运费过高': '设置免邮门槛提示,提供运费券',
      '支付失败': '增加支付方式提示,优化支付重试流程'
    };
    
    return topReasons.slice(0, 2).map(r => ({
      issue: r.name,
      solution: recommendationMap[r.name] || '需要进一步分析'
    }));
  }
  
  calculateDropOffRate(stepFrom, stepTo, userData) {
    // 简化实现
    const baseDropOffs = {
      'launch_to_browse': 30,
      'browse_to_add_cart': 45,
      'add_cart_to_order': 40,
      'order_to_pay': 25
    };
    return baseDropOffs[`${stepFrom}_to_${stepTo}`] || 20;
  }
}

2. 流失用户画像

sql复制

-- 分析流失用户的特征
WITH churned_users AS (
  -- 启动但未浏览的用户
  SELECT 
    visitor_id,
    'launch_to_browse' AS churn_stage,
    scene,
    device_model,
    EXTRACT(HOUR FROM event_time) AS visit_hour
  FROM funnel_events
  WHERE event = 'app_launch'
    AND event_date = CURRENT_DATE
    AND visitor_id NOT IN (
      SELECT DISTINCT visitor_id 
      FROM funnel_events 
      WHERE event = 'view_product' 
        AND event_date = CURRENT_DATE
    )
  
  UNION ALL
  
  -- 加购但未下单的用户
  SELECT 
    visitor_id,
    'add_cart_to_order' AS churn_stage,
    scene,
    device_model,
    EXTRACT(HOUR FROM event_time) AS visit_hour
  FROM funnel_events
  WHERE event = 'add_to_cart'
    AND event_date = CURRENT_DATE
    AND visitor_id NOT IN (
      SELECT DISTINCT visitor_id 
      FROM funnel_events 
      WHERE event = 'submit_order' 
        AND event_date = CURRENT_DATE
    )
)
SELECT 
  churn_stage,
  COUNT(DISTINCT visitor_id) AS churned_uv,
  MODE() WITHIN GROUP (ORDER BY scene) AS top_scene,
  MODE() WITHIN GROUP (ORDER BY visit_hour) AS peak_hour,
  COUNT(CASE WHEN device_model LIKE '%iPhone%' THEN 1 END) * 100.0 / 
    COUNT(*) AS ios_percentage
FROM churned_users
GROUP BY churn_stage;

3. 流失挽回策略

javascript复制

class ChurnRecoveryStrategy {
  constructor() {
    this.strategies = {
      'launch_to_browse': {
        immediate: ['优化首页加载速度', 'A/B测试落地页内容'],
        followUp: ['订阅消息推送(需用户授权)', '公众号内容触达'],
        timeline: '24小时内'
      },
      'browse_to_add_cart': {
        immediate: ['商品详情页优化', '价格展示优化'],
        followUp: ['购物车提醒', '优惠券发放'],
        timeline: '48小时内'
      },
      'add_cart_to_order': {
        immediate: ['运费策略调整', '凑单推荐'],
        followUp: ['购物车召回消息', '限时优惠'],
        timeline: '24小时内'
      },
      'order_to_pay': {
        immediate: ['支付流程简化', '错误处理优化'],
        followUp: ['订单超时取消提醒', '支付方式引导'],
        timeline: '15分钟内'
      }
    };
  }
  
  getRecoveryPlan(churnStage, userContext) {
    const strategy = this.strategies[churnStage];
    if (!strategy) {
      return { error: 'Unknown churn stage' };
    }
    
    return {
      churnStage,
      userSegment: this.segmentUser(userContext),
      immediateActions: strategy.immediate,
      followUpActions: strategy.followUp,
      optimalTiming: this.calculateOptimalTiming(churnStage, userContext),
      expectedRecoveryRate: this.estimateRecoveryRate(churnStage),
      messageTemplate: this.generateMessageTemplate(churnStage, userContext)
    };
  }
  
  segmentUser(userContext) {
    // 用户分群
    if (userContext.scene === 1011) return '线下扫码用户';
    if (userContext.scene === 1008) return '群分享用户';
    if (userContext.scene === 1129) return '搜一搜用户';
    if (userContext.isNewUser) return '新用户';
    return '常规用户';
  }
  
  calculateOptimalTiming(churnStage, userContext) {
    // 根据用户活跃时间计算最佳触达时机
    const baseHours = {
      'launch_to_browse': 2,
      'browse_to_add_cart': 4,
      'add_cart_to_order': 1,
      'order_to_pay': 0.25
    };
    
    const hours = baseHours[churnStage] || 2;
    const optimalTime = new Date(Date.now() + hours * 60 * 60 * 1000);
    
    // 调整到用户活跃时段
    if (userContext.peakActiveHour) {
      optimalTime.setHours(userContext.peakActiveHour);
    }
    
    return optimalTime.toISOString();
  }
  
  estimateRecoveryRate(churnStage) {
    const recoveryRates = {
      'launch_to_browse': '5-10%',
      'browse_to_add_cart': '15-25%',
      'add_cart_to_order': '20-35%',
      'order_to_pay': '40-60%'
    };
    return recoveryRates[churnStage] || '10-20%';
  }
  
  generateMessageTemplate(churnStage, userContext) {
    const templates = {
      'add_cart_to_order': {
        subscribeMessage: {
          templateId: 'your_template_id',
          data: {
            thing1: { value: '您的购物车还有商品' },
            thing2: { value: '限时8折优惠' },
            time3: { value: '24小时内有效' }
          }
        },
        pushTitle: '购物车提醒',
        pushContent: '您购物车的商品即将售罄,快来下单吧~'
      }
    };
    
    return templates[churnStage] || null;
  }
}

六、微信支付集成对漏斗的影响

1. 支付成功率优化

微信支付的原生集成大幅提升了支付环节的转化率,但仍需优化细节:

javascript复制

// 优化的支付流程
class PaymentOptimizer {
  constructor() {
    this.retryConfig = {
      maxRetries: 3,
      retryDelay: 1000,
      backoffFactor: 2
    };
  }
  
  async processOrderPayment(order) {
    const paymentStart = Date.now();
    
    try {
      // 预检查支付条件
      await this.preCheckPayment(order);
      
      // 获取支付参数
      const paymentParams = await this.getPaymentParams(order.id);
      
      // 唤起支付
      const result = await this.invokeWxPay(paymentParams);
      
      // 支付成功处理
      await this.handlePaymentSuccess(order, result, paymentStart);
      
      return { success: true, orderId: order.id };
      
    } catch (error) {
      await this.handlePaymentError(order, error, paymentStart);
      return { success: false, error: error.message };
    }
  }
  
  async invokeWxPay(params) {
    try {
      const result = await wx.requestPayment({
        timeStamp: params.timeStamp,
        nonceStr: params.nonceStr,
        package: params.package,
        signType: params.signType || 'RSA',
        paySign: params.paySign
      });
      
      // 支付成功埋点
      this.trackPaymentEvent('payment_success', {
        orderId: params.orderId,
        duration: Date.now() - params.startTime,
        method: 'wechat'
      });
      
      return result;
      
    } catch (error) {
      // 区分错误类型
      if (error.errMsg.includes('cancel')) {
        this.trackPaymentEvent('payment_cancel', {
          orderId: params.orderId,
          reason: 'user_cancelled'
        });
      } else if (error.errMsg.includes('fail')) {
        this.trackPaymentEvent('payment_fail', {
          orderId: params.orderId,
          errorCode: error.errCode,
          errorMsg: error.errMsg
        });
      }
      
      throw error;
    }
  }
  
  trackPaymentEvent(eventName, data) {
    const app = getApp();
    app.trackEvent(eventName, {
      ...data,
      visitorId: app.getVisitorId(),
      timestamp: Date.now()
    });
  }
  
  // 支付失败后的重试策略
  async handleRetry(order, error) {
    if (this.shouldRetry(error) && this.retryCount < this.retryConfig.maxRetries) {
      this.retryCount++;
      const delay = this.retryConfig.retryDelay * 
        Math.pow(this.retryConfig.backoffFactor, this.retryCount - 1);
      
      await new Promise(resolve => setTimeout(resolve, delay));
      
      // 重新获取支付参数(可能过期)
      const newParams = await this.getPaymentParams(order.id);
      return this.invokeWxPay(newParams);
    }
    
    throw error;
  }
  
  shouldRetry(error) {
    // 网络错误或支付超时可重试
    const retryableErrors = [
      'requestPayment:fail',
      'requestPayment:fail (detail message)',
      'timeout'
    ];
    return retryableErrors.some(e => error.errMsg.includes(e));
  }
}

2. 支付成功率监控

sql复制

-- 按小时监控支付成功率
SELECT 
  DATE_TRUNC('hour', event_time) AS hour_bucket,
  COUNT(CASE WHEN event = 'payment_start' THEN 1 END) AS payment_attempts,
  COUNT(CASE WHEN event = 'payment_success' THEN 1 END) AS payment_successes,
  COUNT(CASE WHEN event = 'payment_cancel' THEN 1 END) AS payment_cancels,
  COUNT(CASE WHEN event = 'payment_fail' THEN 1 END) AS payment_fails,
  ROUND(COUNT(CASE WHEN event = 'payment_success' THEN 1 END) * 100.0 / 
    NULLIF(COUNT(CASE WHEN event = 'payment_start' THEN 1 END), 0), 2) AS success_rate
FROM funnel_events
WHERE event_date = CURRENT_DATE
  AND event IN ('payment_start', 'payment_success', 'payment_cancel', 'payment_fail')
GROUP BY hour_bucket
ORDER BY hour_bucket DESC;

七、订阅消息对复购漏斗的提升

微信小程序的订阅消息是提升复购的关键工具,但需用户主动授权。

1. 订阅消息授权管理

javascript复制

class SubscribeMessageManager {
  constructor() {
    this.templateIds = {
      orderConfirm: 'your_template_id_1',
      shipNotice: 'your_template_id_2',
      arrivalNotice: 'your_template_id_3',
      promotionNotice: 'your_template_id_4'
    };
    
    this.userSubscriptions = new Map();
  }
  
  // 请求订阅权限(在关键节点弹出)
  async requestSubscription(context) {
    const { page, action, userConsentHistory } = context;
    
    // 智能选择请求时机
    if (!this.shouldRequestNow(page, action, userConsentHistory)) {
      return { requested: false };
    }
    
    try {
      const result = await wx.requestSubscribeMessage({
        tmplIds: this.getRelevantTemplates(page)
      });
      
      // 记录用户选择
      this.recordSubscriptionResult(result);
      
      return {
        requested: true,
        accepted: Object.values(result).filter(v => v === 'accept').length,
        rejected: Object.values(result).filter(v => v === 'reject').length
      };
      
    } catch (error) {
      console.error('订阅请求失败:', error);
      return { requested: true, error: error.message };
    }
  }
  
  // 判断是否应该现在请求
  shouldRequestNow(page, action, userConsentHistory) {
    // 用户刚完成支付,适合请求
    if (action === 'payment_success') {
      return true;
    }
    
    // 用户添加购物车时,可请求促销通知
    if (action === 'add_to_cart' && page === 'product') {
      return true;
    }
    
    // 检查历史拒绝记录,避免频繁骚扰
    const lastRejectTime = userConsentHistory?.lastRejectTime;
    if (lastRejectTime) {
      const daysSinceReject = (Date.now() - lastRejectTime) / (1000 * 60 * 60 * 24);
      if (daysSinceReject < 7) return false; // 一周内不再请求
    }
    
    return false;
  }
  
  getRelevantTemplates(page) {
    const pageTemplates = {
      'order_confirm': [this.templateIds.orderConfirm, this.templateIds.shipNotice],
      'product': [this.templateIds.promotionNotice],
      'cart': [this.templateIds.promotionNotice]
    };
    
    return pageTemplates[page] || [];
  }
  
  recordSubscriptionResult(result) {
    const app = getApp();
    
    app.trackEvent('subscription_request', {
      ...result,
      visitorId: app.getVisitorId(),
      timestamp: Date.now()
    });
  }
}

// 在支付成功页请求订阅
Page({
  onLoad() {
    // 支付成功后请求订阅
    setTimeout(() => {
      this.requestSubscription();
    }, 1500); // 延迟请求,避免打扰用户
  },
  
  async requestSubscription() {
    const manager = new SubscribeMessageManager();
    
    const result = await manager.requestSubscription({
      page: 'order_confirm',
      action: 'payment_success',
      userConsentHistory: wx.getStorageSync('subscription_history')
    });
    
    if (result.accepted > 0) {
      console.log('用户接受了', result.accepted, '个订阅');
    }
  }
});

2. 复购漏斗设计

javascript复制

class RepurchaseFunnel {
  constructor() {
    this.stages = [
      { id: 'order_complete', name: '首次购买完成' },
      { id: 'subscribe', name: '订阅消息授权' },
      { id: 'message_sent', name: '触达消息发送' },
      { id: 'message_click', name: '消息点击打开' },
      { id: 'repurchase', name: '复购下单' }
    ];
  }
  
  // 计算复购漏斗转化
  calculateRepurchaseFunnel(userData) {
    const funnel = {};
    
    // 首次购买用户数
    funnel.order_complete = userData.filter(u => u.hasOrdered).length;
    
    // 订阅授权用户数
    funnel.subscribe = userData.filter(u => u.hasSubscribed).length;
    funnel.subscribeRate = (funnel.subscribe / funnel.order_complete * 100).toFixed(2);
    
    // 消息触达用户数(实际发送成功)
    funnel.message_sent = userData.filter(u => u.messageSent).length;
    funnel.messageSentRate = (funnel.message_sent / funnel.subscribe * 100).toFixed(2);
    
    // 消息点击用户数
    funnel.message_click = userData.filter(u => u.messageClicked).length;
    funnel.messageClickRate = (funnel.message_click / funnel.message_sent * 100).toFixed(2);
    
    // 复购用户数
    funnel.repurchase = userData.filter(u => u.hasRepurchased).length;
    funnel.repurchaseRate = (funnel.repurchase / funnel.message_click * 100).toFixed(2);
    
    // 整体复购率
    funnel.overallRepurchaseRate = (funnel.repurchase / funnel.order_complete * 100).toFixed(2);
    
    return funnel;
  }
  
  // A/B测试:订阅消息对复购的影响
  compareWithNonSubscribers(userData) {
    const subscribers = userData.filter(u => u.hasSubscribed);
    const nonSubscribers = userData.filter(u => !u.hasSubscribed);
    
    return {
      subscribers: {
        count: subscribers.length,
        repurchaseCount: subscribers.filter(u => u.hasRepurchased).length,
        repurchaseRate: (subscribers.filter(u => u.hasRepurchased).length / 
          subscribers.length * 100).toFixed(2),
        avgRepurchaseTime: this.calculateAvgTime(subscribers, 'firstOrder', 'repurchase'),
        avgOrderValue: this.calculateAvg(subscribers, 'orderValue')
      },
      nonSubscribers: {
        count: nonSubscribers.length,
        repurchaseCount: nonSubscribers.filter(u => u.hasRepurchased).length,
        repurchaseRate: (nonSubscribers.filter(u => u.hasRepurchased).length / 
          nonSubscribers.length * 100).toFixed(2),
        avgRepurchaseTime: this.calculateAvgTime(nonSubscribers, 'firstOrder', 'repurchase'),
        avgOrderValue: this.calculateAvg(nonSubscribers, 'orderValue')
      },
      lift: {
        repurchaseRateLift: this.calculateLift(
          subscribers.filter(u => u.hasRepurchased).length / subscribers.length,
          nonSubscribers.filter(u => u.hasRepurchased).length / nonSubscribers.length
        )
      }
    };
  }
  
  calculateLift(treatment, control) {
    return ((treatment - control) / control * 100).toFixed(2);
  }
  
  calculateAvgTime(users, startEvent, endEvent) {
    // 简化实现
    return Math.round(Math.random() * 7 + 7); // 模拟7-14天
  }
  
  calculateAvg(users, field) {
    return Math.round(users.reduce((sum, u) => sum + (u[field] || 0), 0) / users.length);
  }
}

订阅消息效果参考值

指标订阅用户非订阅用户提升幅度
30天复购率35-45%15-22%+80-120%
平均复购周期7-10天14-21天-40-50%
客单价180元160元+12%
用户LTV520元320元+62%

八、漏斗可视化与监控

1. 实时漏斗监控看板

javascript复制

class FunnelDashboard {
  constructor() {
    this.refreshInterval = 60000; // 1分钟刷新
    this.alertThresholds = {
      overallConversion: { low: 10, critical: 5 },
      paymentConversion: { low: 70, critical: 60 },
      bounceRate: { high: 40, critical: 50 }
    };
  }
  
  // 计算实时漏斗数据
  async getRealtimeFunnel() {
    const db = wx.cloud.database();
    const _ = db.command;
    
    const now = new Date();
    const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    
    // 获取今日实时数据
    const events = await db.collection('funnel_events')
      .where({
        timestamp: _.gte(todayStart.getTime())
      })
      .get();
    
    return this.calculateFunnel(events.data);
  }
  
  calculateFunnel(events) {
    const uniqueVisitors = {
      launch: new Set(),
      browse: new Set(),
      addCart: new Set(),
      order: new Set(),
      pay: new Set(),
      complete: new Set()
    };
    
    events.forEach(e => {
      uniqueVisitors[e.event]?.add(e.visitorId);
    });
    
    const funnel = {};
    Object.entries(uniqueVisitors).forEach(([stage, visitors]) => {
      funnel[stage] = visitors.size;
    });
    
    // 计算转化率
    funnel.browseRate = (funnel.browse / funnel.launch * 100).toFixed(2);
    funnel.addCartRate = (funnel.addCart / funnel.browse * 100).toFixed(2);
    funnel.orderRate = (funnel.order / funnel.addCart * 100).toFixed(2);
    funnel.payRate = (funnel.pay / funnel.order * 100).toFixed(2);
    funnel.completeRate = (funnel.complete / funnel.pay * 100).toFixed(2);
    funnel.overallRate = (funnel.complete / funnel.launch * 100).toFixed(2);
    
    return funnel;
  }
  
  // 异常告警
  checkAlerts(funnel) {
    const alerts = [];
    
    if (funnel.overallRate < this.alertThresholds.overallConversion.low) {
      alerts.push({
        level: funnel.overallRate < this.alertThresholds.overallConversion.critical 
          ? 'critical' : 'warning',
        metric: '整体转化率',
        value: funnel.overallRate,
        threshold: this.alertThresholds.overallConversion.low,
        message: '整体转化率低于阈值,请检查是否有异常'
      });
    }
    
    if (funnel.payRate < this.alertThresholds.paymentConversion.low) {
      alerts.push({
        level: 'warning',
        metric: '支付转化率',
        value: funnel.payRate,
        threshold: this.alertThresholds.paymentConversion.low,
        message: '支付转化率下降,请检查微信支付是否正常'
      });
    }
    
    return alerts;
  }
}

2. 漏斗可视化组件

javascript复制

// 小程序端漏斗可视化
Component({
  properties: {
    funnelData: {
      type: Object,
      value: {}
    }
  },
  
  data: {
    chartData: []
  },
  
  observers: {
    'funnelData': function(data) {
      if (data && Object.keys(data).length > 0) {
        this.renderFunnelChart(data);
      }
    }
  },
  
  methods: {
    renderFunnelChart(data) {
      const stages = [
        { key: 'launch', name: '启动', color: '#1890ff' },
        { key: 'browse', name: '浏览', color: '#52c41a' },
        { key: 'addCart', name: '加购', color: '#faad14' },
        { key: 'order', name: '下单', color: '#eb2f96' },
        { key: 'pay', name: '支付', color: '#722ed1' },
        { key: 'complete', name: '完成', color: '#13c2c2' }
      ];
      
      const maxUV = data.launch || 1;
      
      const chartData = stages.map((stage, index) => ({
        ...stage,
        uv: data[stage.key] || 0,
        rate: index === 0 ? '100%' : 
          ((data[stage.key] || 0) / maxUV * 100).toFixed(1) + '%',
        width: Math.max(20, (data[stage.key] || 0) / maxUV * 100)
      }));
      
      this.setData({ chartData });
    }
  }
});

九、最佳实践总结

1. 漏斗设计原则

  • 步骤精简:小程序用户耐心有限,核心漏斗控制在5步以内
  • 关键节点埋点:每个步骤至少记录UV、时间戳、来源场景
  • 支付流程优化:微信支付集成要处理超时、取消、失败三种情况
  • 订阅消息策略:在用户情绪正面时请求授权(如支付成功后)

2. 数据分析要点

  • 分场景值分析:不同入口用户转化差异大,应分别优化
  • 流失原因诊断:结合用户行为数据和问卷调查推断流失原因
  • 时间序列监控:每日监控转化率波动,及时发现异常

3. 持续优化闭环

code复制

数据采集 → 漏斗分析 → 识别瓶颈 → 优化方案 → A/B测试 → 效果评估 → 数据采集

通过科学的转化漏斗设计和持续的数据驱动优化,微信小程序可以实现从流量到交易的高效转化,将UV价值最大化。