综合PHP项目:边路传中成功率对比系统
下面给你一个完整的PHP项目方案,用于统计和对比足球比赛中"边路传中成功率"(可按球员/球队/左右路/比赛维度对比)。

需求分析
核心指标:
- 传中总次数(Crosses Attempted)
- 成功传中次数(Successful Crosses,即找到队友)
- 传中成功率 = 成功次数 / 总次数 × 100%
- 分维度:球队 / 球员 / 左路 vs 右路 / 主客场 / 时间段
对比对象示例: | 球员 | 总传中 | 成功 | 成功率 | |------|--------|------|--------| | 德布劳内 | 120 | 42 | 35.0% | | 阿诺德 | 150 | 45 | 30.0% |
数据库设计(MySQL)
CREATE DATABASE football_stats DEFAULT CHARSET utf8mb4;
USE football_stats;
-- 球队表
CREATE TABLE teams (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL
);
-- 球员表
CREATE TABLE players (
id INT PRIMARY KEY AUTO_INCREMENT,
team_id INT,
name VARCHAR(50) NOT NULL,
position VARCHAR(20),
FOREIGN KEY (team_id) REFERENCES teams(id)
);
-- 比赛表
CREATE TABLE matches (
id INT PRIMARY KEY AUTO_INCREMENT,
home_team_id INT,
away_team_id INT,
match_date DATE
);
-- 传中记录表(核心)
CREATE TABLE crosses (
id INT PRIMARY KEY AUTO_INCREMENT,
match_id INT,
player_id INT,
minute INT,
side ENUM('left','right') NOT NULL, -- 左路/右路
is_success TINYINT(1) DEFAULT 0, -- 1成功 0失败
FOREIGN KEY (match_id) REFERENCES matches(id),
FOREIGN KEY (player_id) REFERENCES players(id),
INDEX idx_player (player_id),
INDEX idx_side (side)
);
示例数据:
INSERT INTO teams (name) VALUES ('曼城'),('利物浦');
INSERT INTO players (team_id,name,position) VALUES
(1,'德布劳内','MF'),(1,'福登','MF'),
(2,'阿诺德','DF'),(2,'萨拉赫','FW');
INSERT INTO matches (home_team_id,away_team_id,match_date) VALUES
(1,2,'2024-03-10'),(2,1,'2024-04-05');
INSERT INTO crosses (match_id,player_id,minute,side,is_success) VALUES
(1,1,12,'right',1),(1,1,30,'right',0),(1,1,55,'left',1),
(1,2,20,'left',0),(1,2,66,'left',1),
(2,3,10,'right',1),(2,3,40,'right',1),(2,3,70,'left',0),
(2,4,15,'right',0),(2,4,50,'right',1),(2,4,88,'right',1);
PHP 项目结构
cross-analysis/
├── config/
│ └── db.php
├── includes/
│ └── functions.php
├── api/
│ ├── by_player.php
│ ├── by_team.php
│ └── by_side.php
├── index.php # 可视化对比页
└── assets/
└── style.css
核心代码
config/db.php
<?php
$host = '127.0.0.1';
$db = 'football_stats';
$user = 'root';
$pass = 'root';
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
die('DB连接失败: '.$e->getMessage());
}
includes/functions.php — 通用统计函数
<?php
require_once __DIR__.'/../config/db.php';
/**
* 按球员统计传中成功率
*/
function getCrossStatsByPlayer(PDO $pdo, ?int $teamId = null): array {
$sql = "SELECT p.id, p.name AS player_name, t.name AS team_name,
COUNT(c.id) AS total,
SUM(c.is_success) AS success,
ROUND(SUM(c.is_success)/COUNT(c.id)*100, 2) AS rate
FROM players p
JOIN teams t ON p.team_id = t.id
JOIN crosses c ON c.player_id = p.id";
if ($teamId) {
$sql .= " WHERE p.team_id = :tid";
}
$sql .= " GROUP BY p.id ORDER BY rate DESC";
$stmt = $pdo->prepare($sql);
if ($teamId) $stmt->bindValue(':tid', $teamId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
/**
* 按球队统计
*/
function getCrossStatsByTeam(PDO $pdo): array {
$sql = "SELECT t.id, t.name AS team_name,
COUNT(c.id) AS total,
SUM(c.is_success) AS success,
ROUND(SUM(c.is_success)/COUNT(c.id)*100, 2) AS rate
FROM teams t
JOIN players p ON p.team_id = t.id
JOIN crosses c ON c.player_id = p.id
GROUP BY t.id
ORDER BY rate DESC";
return $pdo->query($sql)->fetchAll();
}
/**
* 按左右路统计(可指定球员)
*/
function getCrossStatsBySide(PDO $pdo, ?int $playerId = null): array {
$sql = "SELECT side,
COUNT(*) AS total,
SUM(is_success) AS success,
ROUND(SUM(is_success)/COUNT(*)*100, 2) AS rate
FROM crosses";
if ($playerId) $sql .= " WHERE player_id = :pid";
$sql .= " GROUP BY side";
$stmt = $pdo->prepare($sql);
if ($playerId) $stmt->bindValue(':pid', $playerId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
/**
* 两两对比(任意两名球员)
*/
function compareTwoPlayers(PDO $pdo, int $p1, int $p2): array {
$sql = "SELECT p.id, p.name,
COUNT(c.id) AS total,
SUM(c.is_success) AS success,
ROUND(SUM(c.is_success)/COUNT(c.id)*100,2) AS rate
FROM players p
JOIN crosses c ON c.player_id = p.id
WHERE p.id IN (:a, :b)
GROUP BY p.id";
$stmt = $pdo->prepare($sql);
$stmt->execute([':a'=>$p1, ':b'=>$p2]);
return $stmt->fetchAll();
}
api/by_player.php — JSON接口
<?php
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__.'/../includes/functions.php';
$teamId = isset($_GET['team_id']) ? (int)$_GET['team_id'] : null;
try {
$data = getCrossStatsByPlayer($pdo, $teamId);
echo json_encode(['code'=>0,'data'=>$data], JSON_UNESCAPED_UNICODE);
} catch (Exception $e) {
echo json_encode(['code'=>1,'msg'=>$e->getMessage()]);
}
index.php — 对比可视化页
<?php
require_once __DIR__.'/includes/functions.php';
$teamStats = getCrossStatsByTeam($pdo);
$playerStats = getCrossStatsByPlayer($pdo);
$sideStats = getCrossStatsBySide($pdo);
// 简化:取前两名球员做对比
$top2 = array_slice($playerStats, 0, 2);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">边路传中成功率对比</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body{font-family:system-ui,sans-serif;max-width:1000px;margin:20px auto;padding:0 15px;}
table{width:100%;border-collapse:collapse;margin:15px 0;}
th,td{border:1px solid #ddd;padding:8px;text-align:center;}
th{background:#2c3e50;color:#fff;}
.rate{font-weight:bold;color:#27ae60;}
</style>
</head>
<body>
<h1>⚽ 边路传中成功率对比</h1>
<h2>📊 球队对比</h2>
<table>
<tr><th>球队</th><th>传中总数</th><th>成功</th><th>成功率</th></tr>
<?php foreach($teamStats as $r): ?>
<tr>
<td><?=htmlspecialchars($r['team_name'])?></td>
<td><?=$r['total']?></td>
<td><?=$r['success']?></td>
<td class="rate"><?=$r['rate']?>%</td>
</tr>
<?php endforeach; ?>
</table>
<h2>👤 球员对比</h2>
<table>
<tr><th>球员</th><th>球队</th><th>传中</th><th>成功</th><th>成功率</th></tr>
<?php foreach($playerStats as $r): ?>
<tr>
<td><?=htmlspecialchars($r['player_name'])?></td>
<td><?=htmlspecialchars($r['team_name'])?></td>
<td><?=$r['total']?></td>
<td><?=$r['success']?></td>
<td class="rate"><?=$r['rate']?>%</td>
</tr>
<?php endforeach; ?>
</table>
<h2>↔️ 左右路对比</h2>
<table>
<tr><th>边路</th><th>传中</th><th>成功</th><th>成功率</th></tr>
<?php foreach($sideStats as $r): ?>
<tr>
<td><?=$r['side']==='left'?'左路':'右路'?></td>
<td><?=$r['total']?></td>
<td><?=$r['success']?></td>
<td class="rate"><?=$r['rate']?>%</td>
</tr>
<?php endforeach; ?>
</table>
<h2>📈 球员成功率可视化</h2>
<canvas id="chart" height="100"></canvas>
<script>
new Chart(document.getElementById('chart'), {
type: 'bar',
data: {
labels: <?=json_encode(array_column($playerStats,'player_name'), JSON_UNESCAPED_UNICODE)?>,
datasets: [{
label: '传中成功率 (%)',
data: <?=json_encode(array_column($playerStats,'rate'))?>,
backgroundColor: ['#3498db','#e74c3c','#2ecc71','#f39c12','#9b59b6']
}]
},
options: { scales: { y: { beginAtZero:true, max:100 } } }
});
</script>
</body>
</html>
扩展功能建议
| 功能 | 说明 |
|---|---|
| 分时间段 | 0-15、15-30…分钟传中效率,看体能影响 |
| 主客场对比 | 加一列 is_home 判断 |
| 对阵强度 | 关联对手排名,做加权分析 |
| 雷达对比图 | Chart.js radar,直观呈现两球员多维能力 |
| 数据导入 | 支持从 Opta/StatsBomb CSV 批量导入 |
| 缓存 | 用 Redis 缓存统计结果,减少聚合查询压力 |
关键设计要点
- 一次传中一条记录,用
is_success布尔字段存储,聚合灵活。 side用 ENUM,方便按边路筛选。- 接口与视图分离:
functions.php做统计,api/*输出 JSON,index.php渲染。 - 百分比用 SQL 直接算(
SUM/COUNT*100),避免 PHP 端二次计算。 - 索引优化:
player_id、side、match_id建索引,聚合查询更快。
如果你需要我进一步扩展为:
- 完整前后端(含 Vue/React 前端)
- 采集真实比赛数据(API)自动写入
- 多名球员横向雷达图对比
告诉我具体方向,我可以继续帮你完善代码。