PHP项目用户行为与漏斗

wen PHP项目 4

本文目录导读:

PHP项目用户行为与漏斗

  1. 数据采集方案
  2. 漏斗分析实现
  3. 前端集成与展示
  4. 数据报表与API
  5. 使用示例

我来为你提供一份关于PHP项目中用户行为追踪与漏斗分析的完整解决方案。

数据采集方案

数据库表结构设计

-- 用户行为日志表
CREATE TABLE `user_behavior_logs` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
    `user_id` int(11) NOT NULL COMMENT '用户ID',
    `session_id` varchar(64) NOT NULL COMMENT '会话ID',
    `event_type` varchar(50) NOT NULL COMMENT '事件类型(visit/click/purchase等)',
    `event_name` varchar(100) NOT NULL COMMENT '事件名称',
    `event_value` text COMMENT '事件参数(JSON格式)',
    `page_url` varchar(500) NOT NULL COMMENT '页面URL',
    `referrer_url` varchar(500) DEFAULT NULL COMMENT '来源URL',
    `ip` varchar(45) DEFAULT NULL COMMENT 'IP地址',
    `user_agent` text COMMENT '浏览器UA',
    `device_type` varchar(20) DEFAULT NULL COMMENT '设备类型',
    `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    KEY `idx_user_id` (`user_id`),
    KEY `idx_event_type` (`event_type`),
    KEY `idx_created_at` (`created_at`),
    KEY `idx_session_id` (`session_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户会话表(用于追踪完整流程)
CREATE TABLE `user_sessions` (
    `id` varchar(64) NOT NULL,
    `user_id` int(11) NOT NULL,
    `start_time` timestamp NOT NULL,
    `end_time` timestamp NULL,
    `first_page` varchar(500) DEFAULT NULL,
    `last_page` varchar(500) DEFAULT NULL,
    `page_views` int(11) DEFAULT 1,
    `device_info` text,
    `ip` varchar(45) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_user_id` (`user_id`),
    KEY `idx_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP数据采集实现

<?php
class UserBehaviorTracker {
    private $db;
    private $userId;
    private $sessionId;
    public function __construct($db) {
        $this->db = $db;
        $this->userId = $this->getCurrentUserId();
        $this->sessionId = $this->getOrCreateSessionId();
    }
    /**
     * 记录用户行为事件
     */
    public function track($eventType, $eventName, $eventValue = []) {
        $data = [
            'user_id' => $this->userId,
            'session_id' => $this->sessionId,
            'event_type' => $eventType,
            'event_name' => $eventName,
            'event_value' => json_encode($eventValue, JSON_UNESCAPED_UNICODE),
            'page_url' => $_SERVER['REQUEST_URI'] ?? '',
            'referrer_url' => $_SERVER['HTTP_REFERER'] ?? '',
            'ip' => $this->getClientIp(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'device_type' => $this->detectDeviceType($_SERVER['HTTP_USER_AGENT'] ?? ''),
            'created_at' => date('Y-m-d H:i:s')
        ];
        $this->saveToDatabase($data);
        $this->updateSession();
    }
    /**
     * 记录页面访问
     */
    public function trackPageView($pageName = '') {
        $this->track('visit', $pageName ?: 'page_view', ['page_name' => $pageName]);
    }
    /**
     * 记录按钮点击
     */
    public function trackClick($buttonName, $extraData = []) {
        $this->track('click', $buttonName, $extraData);
    }
    /**
     * 记录用户转化
     */
    public function trackConversion($conversionType, $value = null) {
        $this->track('conversion', $conversionType, ['value' => $value]);
    }
    private function getCurrentUserId() {
        return $_SESSION['user_id'] ?? 0;
    }
    private function getOrCreateSessionId() {
        if (!isset($_SESSION['tracking_session_id'])) {
            $_SESSION['tracking_session_id'] = uniqid('sess_', true);
        }
        return $_SESSION['tracking_session_id'];
    }
    private function saveToDatabase($data) {
        $sql = "INSERT INTO user_behavior_logs 
                (user_id, session_id, event_type, event_name, event_value, 
                 page_url, referrer_url, ip, user_agent, device_type, created_at)
                VALUES 
                (:user_id, :session_id, :event_type, :event_name, :event_value,
                 :page_url, :referrer_url, :ip, :user_agent, :device_type, :created_at)";
        $stmt = $this->db->prepare($sql);
        $stmt->execute($data);
    }
    private function updateSession() {
        // 更新会话信息
        $sql = "INSERT INTO user_sessions (id, user_id, start_time, first_page, ip)
                VALUES (:id, :user_id, NOW(), :page_url, :ip)
                ON DUPLICATE KEY UPDATE 
                end_time = NOW(), 
                page_views = page_views + 1,
                last_page = :page_url";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([
            'id' => $this->sessionId,
            'user_id' => $this->userId,
            'page_url' => $_SERVER['REQUEST_URI'] ?? '',
            'ip' => $this->getClientIp()
        ]);
    }
    private function getClientIp() {
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
            return trim($ips[0]);
        }
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }
    private function detectDeviceType($userAgent) {
        if (preg_match('/Mobile|Android|iPhone|iPad/i', $userAgent)) {
            return 'mobile';
        } elseif (preg_match('/Tablet|iPad/i', $userAgent)) {
            return 'tablet';
        }
        return 'desktop';
    }
}

漏斗分析实现

漏斗模型类

<?php
class FunnelAnalyzer {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 创建漏斗分析
     * @param array $steps 步骤定义 [['event_type'=>'visit','event_name'=>'homepage'], ...]
     * @param string $startDate 开始日期
     * @param string $endDate 结束日期
     * @param array $filters 过滤条件
     */
    public function analyzeFunnel($steps, $startDate, $endDate, $filters = []) {
        $results = [];
        $totalUsers = 0;
        foreach ($steps as $index => $step) {
            $stepData = $this->getStepData($step, $startDate, $endDate, $filters, $index);
            $results[] = $stepData;
            if ($index == 0) {
                $totalUsers = $stepData['unique_users'];
            }
        }
        // 计算漏斗转化率
        return $this->calculateConversionRates($results, $totalUsers);
    }
    private function getStepData($step, $startDate, $endDate, $filters, $stepIndex) {
        $sql = "SELECT COUNT(DISTINCT user_id) as unique_users,
                       COUNT(*) as total_events
                FROM user_behavior_logs 
                WHERE event_type = :event_type
                AND event_name = :event_name
                AND created_at BETWEEN :start_date AND :end_date";
        // 如果是后续步骤,需要限定用户之前已完成前面步骤
        if ($stepIndex > 0) {
            $sql .= " AND user_id IN (
                SELECT DISTINCT user_id FROM user_behavior_logs 
                WHERE event_type = :prev_event_type
                AND event_name = :prev_event_name
                AND created_at BETWEEN :start_date AND :end_date
            )";
        }
        // 添加额外过滤条件
        $params = [
            'event_type' => $step['event_type'],
            'event_name' => $step['event_name'],
            'start_date' => $startDate,
            'end_date' => $endDate
        ];
        if (!empty($filters)) {
            $sql .= $this->buildFilterConditions($filters, $params);
        }
        $stmt = $this->db->prepare($sql);
        $stmt->execute($params);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        return [
            'step_name' => $step['name'] ?? $step['event_name'],
            'event_type' => $step['event_type'],
            'event_name' => $step['event_name'],
            'unique_users' => (int)$result['unique_users'],
            'total_events' => (int)$result['total_events']
        ];
    }
    private function calculateConversionRates($steps, $totalUsers) {
        $funnelData = [];
        foreach ($steps as $index => $step) {
            $currentUsers = $step['unique_users'];
            // 计算转化率
            $overallRate = $totalUsers > 0 ? round(($currentUsers / $totalUsers) * 100, 2) : 0;
            $stepRate = $index > 0 ? 
                ($steps[$index - 1]['unique_users'] > 0 ? 
                    round(($currentUsers / $steps[$index - 1]['unique_users']) * 100, 2) : 0) : 100;
            $funnelData[] = [
                'step' => $step['step_name'],
                'users' => $currentUsers,
                'events' => $step['total_events'],
                'step_conversion_rate' => $stepRate,
                'overall_conversion_rate' => $overallRate,
                'drop_off' => $index > 0 ? $steps[$index - 1]['unique_users'] - $currentUsers : 0
            ];
        }
        return $funnelData;
    }
    private function buildFilterConditions($filters, &$params) {
        $conditions = '';
        foreach ($filters as $field => $value) {
            if (is_array($value)) {
                $placeholders = [];
                foreach ($value as $i => $v) {
                    $key = "filter_{$field}_{$i}";
                    $placeholders[] = ":$key";
                    $params[$key] = $v;
                }
                $conditions .= " AND $field IN (" . implode(',', $placeholders) . ")";
            } else {
                $key = "filter_{$field}";
                $conditions .= " AND $field = :$key";
                $params[$key] = $value;
            }
        }
        return $conditions;
    }
}

时间序列分析

<?php
class TimeSeriesFunnelAnalyzer extends FunnelAnalyzer {
    /**
     * 按天分析漏斗趋势
     */
    public function analyzeDailyFunnel($steps, $startDate, $endDate, $filters = []) {
        $dailyData = [];
        $current = strtotime($startDate);
        $end = strtotime($endDate);
        while ($current <= $end) {
            $date = date('Y-m-d', $current);
            $dayStart = $date . ' 00:00:00';
            $dayEnd = $date . ' 23:59:59';
            $funnel = $this->analyzeFunnel($steps, $dayStart, $dayEnd, $filters);
            $dailyData[$date] = $funnel;
            $current = strtotime('+1 day', $current);
        }
        return $dailyData;
    }
    /**
     * 获取趋势数据用于图表
     */
    public function getTrendData($steps, $startDate, $endDate, $filters = []) {
        $dailyFunnel = $this->analyzeDailyFunnel($steps, $startDate, $endDate, $filters);
        $trendData = [
            'dates' => [],
            'steps' => []
        ];
        foreach ($steps as $step) {
            $trendData['steps'][$step['name'] ?? $step['event_name']] = [];
        }
        foreach ($dailyFunnel as $date => $funnel) {
            $trendData['dates'][] = $date;
            foreach ($funnel as $stepData) {
                $stepName = $stepData['step'];
                $trendData['steps'][$stepName][] = $stepData['users'];
            }
        }
        return $trendData;
    }
}

高级漏斗分析功能

<?php
class AdvancedFunnelAnalyzer extends FunnelAnalyzer {
    /**
     * 分段分析(按设备、渠道等)
     */
    public function segmentFunnel($steps, $startDate, $endDate, $segmentBy = 'device_type') {
        $segments = $this->getDistinctValues($segmentBy, $startDate, $endDate);
        $segmentedResults = [];
        foreach ($segments as $segment) {
            $filters = [$segmentBy => $segment];
            $funnel = $this->analyzeFunnel($steps, $startDate, $endDate, $filters);
            $segmentedResults[$segment] = $funnel;
        }
        return $segmentedResults;
    }
    /**
     * 用户路径分析
     */
    public function getUserPathAnalysis($userId, $limit = 20) {
        $sql = "SELECT event_type, event_name, page_url, created_at 
                FROM user_behavior_logs 
                WHERE user_id = :user_id 
                ORDER BY created_at 
                LIMIT :limit";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['user_id' => $userId, 'limit' => $limit]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * 流失用户分析
     */
    public function analyzeDropOffUsers($steps, $startDate, $endDate) {
        $droppedAtStep = [];
        foreach ($steps as $index => $step) {
            if ($index > 0) {
                $sql = "SELECT DISTINCT l1.user_id 
                        FROM user_behavior_logs l1
                        WHERE l1.event_type = :prev_type
                        AND l1.event_name = :prev_name
                        AND l1.created_at BETWEEN :start AND :end
                        AND NOT EXISTS (
                            SELECT 1 FROM user_behavior_logs l2
                            WHERE l2.user_id = l1.user_id
                            AND l2.event_type = :curr_type
                            AND l2.event_name = :curr_name
                            AND l2.created_at BETWEEN :start AND :end
                        )";
                $stmt = $this->db->prepare($sql);
                $stmt->execute([
                    'prev_type' => $steps[$index - 1]['event_type'],
                    'prev_name' => $steps[$index - 1]['event_name'],
                    'curr_type' => $step['event_type'],
                    'curr_name' => $step['event_name'],
                    'start' => $startDate,
                    'end' => $endDate
                ]);
                $droppedAtStep[$step['name'] ?? $step['event_name']] = [
                    'count' => $stmt->rowCount(),
                    'user_ids' => $stmt->fetchAll(PDO::FETCH_COLUMN)
                ];
            }
        }
        return $droppedAtStep;
    }
    private function getDistinctValues($field, $startDate, $endDate) {
        $sql = "SELECT DISTINCT $field FROM user_behavior_logs 
                WHERE $field IS NOT NULL 
                AND created_at BETWEEN :start AND :end";
        $stmt = $this->db->prepare($sql);
        $stmt->execute(['start' => $startDate, 'end' => $endDate]);
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    }
}

前端集成与展示

JavaScript跟踪代码

// tracking.js
class UserBehaviorTracker {
    constructor(config) {
        this.config = {
            trackPageViews: true,
            trackClicks: true,
            trackFormSubmissions: true,
            scrollTracking: false,
            serverUrl: '/api/track',
            ...config
        };
        this.init();
    }
    init() {
        if (this.config.trackPageViews) {
            this.trackPageView();
        }
        if (this.config.trackClicks) {
            this.bindClickTracking();
        }
        if (this.config.trackFormSubmissions) {
            this.bindFormTracking();
        }
        if (this.config.scrollTracking) {
            this.initScrollTracking();
        }
    }
    trackPageView() {
        this.sendEvent('visit', 'page_view', {
            page: window.location.pathname,
            title: document.title,
            referrer: document.referrer
        });
    }
    trackEvent(eventType, eventName, data = {}) {
        this.sendEvent(eventType, eventName, data);
    }
    bindClickTracking() {
        document.addEventListener('click', (e) => {
            const target = e.target;
            const dataTrack = target.getAttribute('data-track');
            const dataTrackValue = target.getAttribute('data-track-value');
            if (dataTrack) {
                this.sendEvent('click', dataTrack, {
                    value: dataTrackValue || target.innerText || target.href,
                    element: target.tagName,
                    id: target.id,
                    className: target.className
                });
            }
        }, true);
    }
    bindFormTracking() {
        document.addEventListener('submit', (e) => {
            const form = e.target;
            const formName = form.getAttribute('name') || form.id || 'unknown_form';
            this.sendEvent('form_submit', formName, {
                fields: this.getFormFields(form)
            });
        });
    }
    initScrollTracking() {
        let scrollTimeout;
        window.addEventListener('scroll', () => {
            clearTimeout(scrollTimeout);
            scrollTimeout = setTimeout(() => {
                const scrollPercent = Math.round(
                    (window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
                );
                if (scrollPercent >= 25 && scrollPercent < 50) {
                    this.sendEvent('scroll', '25_percent');
                } else if (scrollPercent >= 50 && scrollPercent < 75) {
                    this.sendEvent('scroll', '50_percent');
                } else if (scrollPercent >= 75 && scrollPercent < 100) {
                    this.sendEvent('scroll', '75_percent');
                } else if (scrollPercent >= 100) {
                    this.sendEvent('scroll', '100_percent');
                }
            }, 500);
        });
    }
    sendEvent(eventType, eventName, data = {}) {
        const payload = {
            event_type: eventType,
            event_name: eventName,
            event_value: data,
            timestamp: new Date().toISOString(),
            page_url: window.location.href,
            user_agent: navigator.userAgent
        };
        // 使用Beacon API或fetch发送数据
        if (navigator.sendBeacon) {
            navigator.sendBeacon(this.config.serverUrl, JSON.stringify(payload));
        } else {
            fetch(this.config.serverUrl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(payload)
            });
        }
    }
    getFormFields(form) {
        const fields = {};
        const formData = new FormData(form);
        for (let [key, value] of formData.entries()) {
            fields[key] = value;
        }
        return fields;
    }
}
// 使用示例
const tracker = new UserBehaviorTracker({
    trackPageViews: true,
    trackClicks: true,
    trackFormSubmissions: true,
    scrollTracking: true,
    serverUrl: '/api/track-event'
});

PHP后端接收接口

<?php
// api/track-event.php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../includes/UserBehaviorTracker.php';
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    echo json_encode(['error' => 'Method not allowed']);
    exit;
}
try {
    $input = json_decode(file_get_contents('php://input'), true);
    if (!$input || !isset($input['event_type']) || !isset($input['event_name'])) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid input']);
        exit;
    }
    $db = Database::getConnection();
    $tracker = new UserBehaviorTracker($db);
    $tracker->track(
        $input['event_type'],
        $input['event_name'],
        $input['event_value'] ?? []
    );
    http_response_code(200);
    echo json_encode(['success' => true]);
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => $e->getMessage()]);
}

数据报表与API

漏斗分析API

<?php
// api/funnel-analysis.php
require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../analytics/FunnelAnalyzer.php';
require_once __DIR__ . '/../analytics/AdvancedFunnelAnalyzer.php';
class FunnelAPI {
    private $db;
    private $analyzer;
    public function __construct() {
        $this->db = Database::getConnection();
    }
    public function handleRequest() {
        $action = $_GET['action'] ?? 'funnel';
        switch ($action) {
            case 'funnel':
                $this->getFunnelData();
                break;
            case 'trend':
                $this->getTrendData();
                break;
            case 'segment':
                $this->getSegmentedData();
                break;
            case 'dropoff':
                $this->getDropOffUsers();
                break;
            default:
                $this->response(['error' => 'Invalid action'], 400);
        }
    }
    private function getFunnelData() {
        $steps = $this->getRequestSteps();
        $startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
        $endDate = $_GET['end_date'] ?? date('Y-m-d');
        $analyzer = new FunnelAnalyzer($this->db);
        $data = $analyzer->analyzeFunnel($steps, $startDate, $endDate);
        $this->response([
            'success' => true,
            'data' => $data,
            'metadata' => [
                'start_date' => $startDate,
                'end_date' => $endDate,
                'total_steps' => count($steps)
            ]
        ]);
    }
    private function getTrendData() {
        $steps = $this->getRequestSteps();
        $startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-7 days'));
        $endDate = $_GET['end_date'] ?? date('Y-m-d');
        $analyzer = new TimeSeriesFunnelAnalyzer($this->db);
        $data = $analyzer->getTrendData($steps, $startDate, $endDate);
        $this->response([
            'success' => true,
            'data' => $data
        ]);
    }
    private function getSegmentedData() {
        $steps = $this->getRequestSteps();
        $startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
        $endDate = $_GET['end_date'] ?? date('Y-m-d');
        $segmentBy = $_GET['segment_by'] ?? 'device_type';
        $analyzer = new AdvancedFunnelAnalyzer($this->db);
        $data = $analyzer->segmentFunnel($steps, $startDate, $endDate, $segmentBy);
        $this->response([
            'success' => true,
            'data' => $data,
            'segment_by' => $segmentBy
        ]);
    }
    private function getDropOffUsers() {
        $steps = $this->getRequestSteps();
        $startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
        $endDate = $_GET['end_date'] ?? date('Y-m-d');
        $analyzer = new AdvancedFunnelAnalyzer($this->db);
        $data = $analyzer->analyzeDropOffUsers($steps, $startDate, $endDate);
        $this->response([
            'success' => true,
            'data' => $data
        ]);
    }
    private function getRequestSteps() {
        $stepsJson = $_GET['steps'] ?? '[]';
        $steps = json_decode($stepsJson, true);
        if (empty($steps)) {
            // 默认漏斗步骤
            $steps = [
                ['event_type' => 'visit', 'event_name' => 'page_view', 'name' => '访问首页'],
                ['event_type' => 'visit', 'event_name' => 'product_page', 'name' => '浏览商品'],
                ['event_type' => 'click', 'event_name' => 'add_to_cart', 'name' => '加入购物车'],
                ['event_type' => 'click', 'event_name' => 'checkout', 'name' => '开始结算'],
                ['event_type' => 'conversion', 'event_name' => 'purchase', 'name' => '完成购买']
            ];
        }
        return $steps;
    }
    private function response($data, $statusCode = 200) {
        http_response_code($statusCode);
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
        exit;
    }
}
// 处理请求
$api = new FunnelAPI();
$api->handleRequest();

使用示例

在前端页面中使用

<!-- 在页面中添加跟踪代码 -->
<script src="/js/tracking.js"></script>
<script>
    // 初始化跟踪器
    const tracker = new UserBehaviorTracker({
        trackPageViews: true,
        trackClicks: true,
        trackFormSubmissions: true,
        serverUrl: '/api/track-event'
    });
    // 手动跟踪特定事件
    function trackAddToCart(productId, productName) {
        tracker.trackEvent('click', 'add_to_cart', {
            product_id: productId,
            product_name: productName
        });
    }
    function trackPurchase(orderId, total) {
        tracker.trackEvent('conversion', 'purchase', {
            order_id: orderId,
            total: total
        });
    }
</script>
<!-- 使用data-track属性自动跟踪点击 -->
<button data-track="cta_button" data-track-value="立即购买">
    立即购买
</button>
<a href="/product/123" data-track="product_click" data-track-value="商品123">
    查看商品
</a>

PHP后端调用

<?php
// 在控制器中使用
class OrderController {
    public function placeOrder() {
        // 处理订单逻辑
        // 跟踪购买事件
        $tracker = new UserBehaviorTracker($db);
        $tracker->trackConversion('purchase', [
            'order_id' => $orderId,
            'total' => $totalAmount,
            'product_count' => $productCount
        ]);
        // 重定向到成功页面
        header('Location: /order/success/' . $orderId);
    }
}

前端调用API获取漏斗数据

// 获取漏斗数据并渲染图表
async function renderFunnelChart() {
    const steps = [
        { event_type: 'visit', event_name: 'page_view', name: '访问首页' },
        { event_type: 'visit', event_name: 'product_page', name: '浏览商品' },
        { event_type: 'click', event_name: 'add_to_cart', name: '加入购物车' },
        { event_type: 'click', event_name: 'checkout', name: '开始结算' },
        { event_type: 'conversion', event_name: 'purchase', name: '完成购买' }
    ];
    const response = await fetch(`/api/funnel-analysis.php?action=funnel&steps=${JSON.stringify(steps)}&start_date=2024-01-01&end_date=2024-01-31`);
    const result = await response.json();
    if (result.success) {
        createFunnelChart(result.data);
    }
}
function createFunnelChart(funnelData) {
    // 使用Chart.js或其他图表库渲染漏斗图
    const ctx = document.getElementById('funnelChart').getContext('2d');
    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: funnelData.map(d => d.step),
            datasets: [{
                label: '用户数',
                data: funnelData.map(d => d.users),
                backgroundColor: [
                    'rgba(54, 162, 235, 0.8)',
                    'rgba(75, 192, 192, 0.8)',
                    'rgba(255, 206, 86, 0.8)',
                    'rgba(255, 159, 64, 0.8)',
                    'rgba(255, 99, 132, 0.8)'
                ],
                borderWidth: 1
            }]
        },
        options: {
            indexAxis: 'y',
            plugins: {
                tooltip: {
                    callbacks: {
                        afterLabel: function(context) {
                            const data = funnelData[context.dataIndex];
                            return [
                                `转化率: ${data.step_conversion_rate}%`,
                                `整体转化率: ${data.overall_conversion_rate}%`,
                                `流失用户: ${data.drop_off}`
                            ];
                        }
                    }
                }
            },
            scales: {
                x: {
                    beginAtZero: true
                }
            }
        }
    });
}

这个完整的PHP用户行为与漏斗分析系统包含了数据采集、存储、分析、API和前端展示的完整解决方案,你可以根据实际需求进行调整和扩展。

抱歉,评论功能暂时关闭!