php项目统计高球传中争顶成功率?

wen PHP项目 5

本文目录导读:

php项目统计高球传中争顶成功率?

  1. 数据库设计
  2. 核心统计类
  3. API 控制器示例
  4. 前端展示示例
  5. 高级统计功能

我来帮你设计一个PHP项目统计高球传中争顶成功率的方案,这个功能通常用于足球数据统计系统。

数据库设计

-- 球员表
CREATE TABLE players (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    team_id INT,
    position VARCHAR(20)
);
-- 传中数据表
CREATE TABLE crosses (
    id INT PRIMARY KEY AUTO_INCREMENT,
    player_id INT,
    match_id INT,
    cross_time INT,           -- 比赛时间(分钟)
    cross_type VARCHAR(20),   -- 传中类型: 'high_ball', 'low_ball', 'cross'
    from_x DECIMAL(5,2),      -- 传中位置X坐标
    from_y DECIMAL(5,2),      -- 传中位置Y坐标
    to_x DECIMAL(5,2),        -- 落点X坐标
    to_y DECIMAL(5,2),        -- 落点Y坐标
    is_successful BOOLEAN,    -- 是否成功
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 争顶数据表
CREATE TABLE headed_duels (
    id INT PRIMARY KEY AUTO_INCREMENT,
    cross_id INT,
    player_id INT,
    is_won BOOLEAN,           -- 是否赢得争顶
    is_clear BOOLEAN,         -- 是否解围
    touch_type VARCHAR(20),   -- 触球类型
    is_goal BOOLEAN,          -- 是否进球
    FOREIGN KEY (cross_id) REFERENCES crosses(id)
);

核心统计类

<?php
class HeadingSuccessRateCalculator {
    private $db;
    public function __construct($dbConnection) {
        $this->db = $dbConnection;
    }
    /**
     * 计算球员传中争顶成功率
     * @param int $playerId 球员ID
     * @param string $startDate 开始日期
     * @param string $endDate 结束日期
     * @return array 统计结果
     */
    public function calculatePlayerHeadingRate($playerId, $startDate = null, $endDate = null) {
        $where = "c.player_id = ?";
        $params = [$playerId];
        if ($startDate && $endDate) {
            $where .= " AND c.create_time BETWEEN ? AND ?";
            $params[] = $startDate;
            $params[] = $endDate;
        }
        // 查询球员的传中数据
        $query = "SELECT 
                    COUNT(*) as total_crosses,
                    SUM(CASE WHEN c.is_successful = 1 THEN 1 ELSE 0 END) as successful_crosses,
                    SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as won_headers,
                    SUM(CASE WHEN hd.is_goal = 1 THEN 1 ELSE 0 END) as header_goals,
                    SUM(CASE WHEN c.is_successful = 1 AND hd.is_won = 1 THEN 1 ELSE 0 END) as successful_headers
                  FROM crosses c
                  LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                  WHERE $where 
                  GROUP BY c.player_id";
        $stmt = $this->db->prepare($query);
        $stmt->execute($params);
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        // 计算各种成功率
        $totalCrosses = $result['total_crosses'] ?? 0;
        $successfulCrosses = $result['successful_crosses'] ?? 0;
        $wonHeaders = $result['won_headers'] ?? 0;
        $headerGoals = $result['header_goals'] ?? 0;
        return [
            'player_id' => $playerId,
            'total_crosses' => $totalCrosses,
            'successful_crosses' => $successfulCrosses,
            'cross_success_rate' => $totalCrosses > 0 ? 
                round(($successfulCrosses / $totalCrosses) * 100, 2) : 0,
            'total_headers' => $totalCrosses,
            'won_headers' => $wonHeaders,
            'heading_success_rate' => $totalCrosses > 0 ? 
                round(($wonHeaders / $totalCrosses) * 100, 2) : 0,
            'header_goals' => $headerGoals,
            'header_goal_rate' => $totalCrosses > 0 ? 
                round(($headerGoals / $totalCrosses) * 100, 2) : 0,
            // 综合效率:争顶成功且传中成功
            'overall_efficiency' => $totalCrosses > 0 ? 
                round((($wonHeaders + $successfulCrosses) / (2 * $totalCrosses)) * 100, 2) : 0,
            // 争顶成功率(在成功传中中的比例)
            'heading_rate_in_successful_crosses' => $successfulCrosses > 0 ? 
                round(($wonHeaders / $successfulCrosses) * 100, 2) : 0
        ];
    }
    /**
     * 计算球队的传中争顶成功率
     */
    public function calculateTeamHeadingRate($teamId, $startDate = null, $endDate = null) {
        $query = "SELECT 
                    p.team_id,
                    COUNT(c.id) as total_crosses,
                    SUM(CASE WHEN c.is_successful = 1 THEN 1 ELSE 0 END) as successful_crosses,
                    SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as won_headers,
                    SUM(CASE WHEN hd.is_goal = 1 THEN 1 ELSE 0 END) as header_goals
                  FROM crosses c
                  INNER JOIN players p ON p.id = c.player_id
                  LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                  WHERE p.team_id = ?";
        if ($startDate && $endDate) {
            $query .= " AND c.create_time BETWEEN ? AND ?";
            $params = [$teamId, $startDate, $endDate];
        } else {
            $params = [$teamId];
        }
        $query .= " GROUP BY p.team_id";
        $stmt = $this->db->prepare($query);
        $stmt->execute($params);
        return $stmt->fetch(PDO::FETCH_ASSOC);
    }
    /**
     * 获取球员争顶热区数据
     */
    public function getPlayerHeadingHeatmap($playerId, $startDate, $endDate) {
        $query = "SELECT 
                    ROUND(c.from_x, 2) as x_coord,
                    ROUND(c.from_y, 2) as y_coord,
                    COUNT(*) as cross_count,
                    SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as heading_wins
                  FROM crosses c
                  LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                  WHERE c.player_id = ? 
                    AND c.create_time BETWEEN ? AND ?
                  GROUP BY c.from_x, c.from_y
                  ORDER BY cross_count DESC
                  LIMIT 20";
        $stmt = $this->db->prepare($query);
        $stmt->execute([$playerId, $startDate, $endDate]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * 比较两名球员的争顶成功率
     */
    public function comparePlayers($player1Id, $player2Id, $startDate = null, $endDate = null) {
        $player1Stats = $this->calculatePlayerHeadingRate($player1Id, $startDate, $endDate);
        $player2Stats = $this->calculatePlayerHeadingRate($player2Id, $startDate, $endDate);
        return [
            'player1' => $player1Stats,
            'player2' => $player2Stats,
            'comparison' => [
                'better_heading_rate' => $player1Stats['heading_success_rate'] > 
                    $player2Stats['heading_success_rate'] ? 'player1' : 'player2',
                'better_cross_rate' => $player1Stats['cross_success_rate'] > 
                    $player2Stats['cross_success_rate'] ? 'player1' : 'player2'
            ]
        ];
    }
}

API 控制器示例

<?php
class HeadingStatsController {
    private $calculator;
    public function __construct($db) {
        $this->calculator = new HeadingSuccessRateCalculator($db);
    }
    // 获取球员统计信息
    public function getPlayerStats($playerId) {
        header('Content-Type: application/json');
        try {
            $startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
            $endDate = $_GET['end_date'] ?? date('Y-m-d');
            $stats = $this->calculator->calculatePlayerHeadingRate(
                $playerId, $startDate, $endDate
            );
            // 获取比赛记录
            $matches = $this->getPlayerMatchHistory($playerId, $startDate, $endDate);
            echo json_encode([
                'status' => 'success',
                'data' => [
                    'basic_stats' => $stats,
                    'match_history' => $matches
                ]
            ]);
        } catch (Exception $e) {
            http_response_code(500);
            echo json_encode([
                'status' => 'error',
                'message' => $e->getMessage()
            ]);
        }
    }
    // 获取比赛历史
    private function getPlayerMatchHistory($playerId, $startDate, $endDate) {
        $query = "SELECT 
                    m.match_date,
                    m.opponent,
                    m.home_away,
                    COUNT(c.id) as crosses,
                    SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as heading_wins,
                    SUM(CASE WHEN hd.is_goal = 1 THEN 1 ELSE 0 END) as goals
                  FROM crosses c
                  INNER JOIN matches m ON m.id = c.match_id
                  LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                  WHERE c.player_id = ?
                    AND m.match_date BETWEEN ? AND ?
                  GROUP BY m.id
                  ORDER BY m.match_date DESC";
        $stmt = $this->db->prepare($query);
        $stmt->execute([$playerId, $startDate, $endDate]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

前端展示示例

<!DOCTYPE html>
<html>
<head>传中争顶成功率统计</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <div class="container">
        <h2>球员争顶成功率统计</h2>
        <!-- 球员选择 -->
        <div class="player-select">
            <select id="playerSelect">
                <option value="">选择球员</option>
            </select>
            <button onclick="loadStats()">查看统计</button>
        </div>
        <!-- 统计卡片 -->
        <div class="stats-grid">
            <div class="stat-card">
                <h3>传中成功率</h3>
                <p id="crossRate">0%</p>
            </div>
            <div class="stat-card">
                <h3>争顶成功率</h3>
                <p id="headingRate">0%</p>
            </div>
            <div class="stat-card">
                <h3>头球进球数</h3>
                <p id="headerGoals">0</p>
            </div>
            <div class="stat-card">
                <h3>综合效率</h3>
                <p id="overallEfficiency">0%</p>
            </div>
        </div>
        <!-- 图表区域 -->
        <canvas id="statsChart" width="400" height="200"></canvas>
        <!-- 数据记录表格 -->
        <table id="matchHistory">
            <thead>
                <tr>
                    <th>比赛日期</th>
                    <th>对手</th>
                    <th>传中数</th>
                    <th>争顶成功</th>
                    <th>进球</th>
                </tr>
            </thead>
            <tbody id="historyBody"></tbody>
        </table>
    </div>
    <script>
        async function loadStats() {
            const playerId = document.getElementById('playerSelect').value;
            if (!playerId) return;
            try {
                const response = await fetch(`/api/heading-stats/player/${playerId}`);
                const data = await response.json();
                if (data.status === 'success') {
                    updateUI(data.data);
                }
            } catch (error) {
                console.error('Error loading stats:', error);
            }
        }
        function updateUI(data) {
            // 更新统计卡片
            document.getElementById('crossRate').textContent = 
                data.basic_stats.cross_success_rate + '%';
            document.getElementById('headingRate').textContent = 
                data.basic_stats.heading_success_rate + '%';
            document.getElementById('headerGoals').textContent = 
                data.basic_stats.header_goals;
            document.getElementById('overallEfficiency').textContent = 
                data.basic_stats.overall_efficiency + '%';
            // 更新图表
            updateChart(data.basic_stats);
            // 更新比赛历史
            updateMatchHistory(data.match_history);
        }
        function updateChart(stats) {
            const ctx = document.getElementById('statsChart').getContext('2d');
            new Chart(ctx, {
                type: 'bar',
                data: {
                    labels: ['传中成功率', '争顶成功率', '头球进球率', '综合效率'],
                    datasets: [{
                        label: '成功率 (%)',
                        data: [
                            stats.cross_success_rate,
                            stats.heading_success_rate,
                            stats.header_goal_rate,
                            stats.overall_efficiency
                        ],
                        backgroundColor: [
                            'rgba(255, 99, 132, 0.2)',
                            'rgba(54, 162, 235, 0.2)',
                            'rgba(255, 206, 86, 0.2)',
                            'rgba(75, 192, 192, 0.2)'
                        ],
                        borderColor: [
                            'rgba(255, 99, 132, 1)',
                            'rgba(54, 162, 235, 1)',
                            'rgba(255, 206, 86, 1)',
                            'rgba(75, 192, 192, 1)'
                        ],
                        borderWidth: 1
                    }]
                },
                options: {
                    scales: {
                        y: {
                            beginAtZero: true,
                            max: 100
                        }
                    }
                }
            });
        }
        function updateMatchHistory(matches) {
            const tbody = document.getElementById('historyBody');
            tbody.innerHTML = '';
            matches.forEach(match => {
                const row = tbody.insertRow();
                row.innerHTML = `
                    <td>${match.match_date}</td>
                    <td>${match.opponent}</td>
                    <td>${match.crosses}</td>
                    <td>${match.heading_wins}</td>
                    <td>${match.goals}</td>
                `;
            });
        }
    </script>
</body>
</html>

高级统计功能

<?php
class AdvancedHeadingStats extends HeadingSuccessRateCalculator {
    /**
     * 计算移动平均成功率(用于趋势分析)
     */
    public function calculateMovingAverage($playerId, $windowSize = 5) {
        $query = "SELECT 
                    c.create_time as match_date,
                    COUNT(*) as crosses,
                    SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as heading_wins
                  FROM crosses c
                  LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                  WHERE c.player_id = ?
                  GROUP BY c.match_id
                  ORDER BY c.create_time
                  LIMIT 50";
        $stmt = $this->db->prepare($query);
        $stmt->execute([$playerId]);
        $matches = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $movingAvgs = [];
        for ($i = 0; $i < count($matches); $i++) {
            $start = max(0, $i - $windowSize + 1);
            $windowData = array_slice($matches, $start, $windowSize);
            $totalCrosses = array_sum(array_column($windowData, 'crosses'));
            $totalWins = array_sum(array_column($windowData, 'heading_wins'));
            $movingAvgs[] = [
                'date' => $matches[$i]['match_date'],
                'moving_avg' => $totalCrosses > 0 ? 
                    round(($totalWins / $totalCrosses) * 100, 2) : 0
            ];
        }
        return $movingAvgs;
    }
    /**
     * 位置区域成功率分析
     */
    public function getZoneSuccessRates($playerId) {
        // 将球场分为多个区域
        $zones = [
            'left_wing' => [0, 33],     // 左路
            'center' => [33, 66],       // 中路
            'right_wing' => [66, 100]   // 右路
        ];
        $results = [];
        foreach ($zones as $zoneName => $zone) {
            $query = "SELECT 
                        COUNT(*) as total,
                        SUM(CASE WHEN hd.is_won = 1 THEN 1 ELSE 0 END) as won
                      FROM crosses c
                      LEFT JOIN headed_duels hd ON hd.cross_id = c.id
                      WHERE c.player_id = ?
                        AND c.from_x BETWEEN ? AND ?";
            $stmt = $this->db->prepare($query);
            $stmt->execute([$playerId, $zone[0], $zone[1]]);
            $result = $stmt->fetch(PDO::FETCH_ASSOC);
            $results[$zoneName] = [
                'total' => $result['total'],
                'won' => $result['won'],
                'rate' => $result['total'] > 0 ? 
                    round(($result['won'] / $result['total']) * 100, 2) : 0
            ];
        }
        return $results;
    }
}

这个系统提供了:

  1. 基础统计:传中成功率、争顶成功率、进球效率
  2. 详细分析:按区域统计、时间趋势、对手比较
  3. 可视化:通过图表展示统计数据
  4. 灵活查询:按时间范围、球员、球队筛选
  5. 扩展性:可添加更多高级分析功能

你可以根据实际需求调整字段和统计逻辑,如果需要更多特定功能的实现,请告诉我。

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