本文目录导读:

- 第一步:数据库设计(核心是“状态”与“追踪”)
- 第二步:PHP后端实现(状态机与业务逻辑)
- 第三步:数据库查询与统计(生成漏斗数据)
- 第四步:前端可视化(使用 JavaScript 图表库)
- 第五步:进阶增强模块
这是一个非常实际且具有业务价值的PHP项目问题,销售漏斗的核心是追踪客户从初次接触(线索)到最终成交(付款)的整个生命周期状态变化。
在PHP项目中实现销售漏斗,通常需要从 数据模型(数据库)、后端逻辑(状态机)、前端可视化(图表) 三个层面来构建。
以下是一个从零到一的实现方案,包含数据库设计、核心PHP代码逻辑和前端展示思路。
第一步:数据库设计(核心是“状态”与“追踪”)
销售漏斗的每个阶段都是一个“状态”,你需要一个主表来记录每一条“潜在客户”或“商机”。
表结构示例:leads (线索/商机表)
CREATE TABLE `leads` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`company_name` VARCHAR(255) NOT NULL COMMENT '公司名',
`contact_person` VARCHAR(100) COMMENT '联系人',
`contact_phone` VARCHAR(20) COMMENT '电话',
`lead_source` VARCHAR(50) COMMENT '来源:官网/广告/转介绍',
-- 核心漏斗字段
`stage` ENUM('new', 'contacted', 'qualified', 'proposal', 'negotiation', 'won', 'lost')
NOT NULL DEFAULT 'new' COMMENT '当前漏斗阶段',
`stage_duration` INT(11) DEFAULT NULL COMMENT '在当前阶段停留的天数(通过计算得到)',
`estimated_value` DECIMAL(10,2) DEFAULT 0.00 COMMENT '预估成交额',
`lost_reason` TEXT COMMENT '丢单原因',
`assigned_to` INT(11) COMMENT '负责人ID',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_stage` (`stage`),
INDEX `idx_assigned` (`assigned_to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
进阶:阶段变更日志表(用于分析流失率、计算转化时间)
CREATE TABLE `lead_stage_log` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`lead_id` INT(11) NOT NULL,
`from_stage` VARCHAR(50) COMMENT '从哪个阶段来',
`to_stage` VARCHAR(50) COMMENT '进入哪个阶段',
`changed_by` INT(11) COMMENT '操作人ID',
`remarks` TEXT COMMENT '备注',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第二步:PHP后端实现(状态机与业务逻辑)
核心类:LeadFunnel.php
我们需要一个类来处理状态转换,避免直接改数据库的 stage 字段。
<?php
class LeadFunnel {
private $db; // PDO connection
private $allowedTransitions = [
'new' => ['contacted', 'lost'], // 新线索只能联系或放弃
'contacted' => ['qualified', 'lost'],
'qualified' => ['proposal', 'lost'],
'proposal' => ['negotiation', 'lost', 'won'], // 发方案后可能直接赢单或进入谈判
'negotiation' => ['won', 'lost'],
'won' => [], // 已成交,不能后退
'lost' => ['new'], // 丢单可重新激活
];
public function __construct($db) {
$this->db = $db;
}
/**
* 安全地变更阶段
* @param int $leadId
* @param string $newStage
* @param int $userId
* @param string $remarks
* @return bool|string 成功返回true,失败返回错误信息
*/
public function moveToStage($leadId, $newStage, $userId, $remarks = '') {
// 1. 获取当前状态
$stmt = $this->db->prepare("SELECT `stage` FROM `leads` WHERE id = ? FOR UPDATE");
$stmt->execute([$leadId]);
$currentStage = $stmt->fetchColumn();
if (!$currentStage) {
return "线索不存在";
}
// 2. 验证转换是否允许
if (!isset($this->allowedTransitions[$currentStage])
|| !in_array($newStage, $this->allowedTransitions[$currentStage])) {
return "不允许从 '{$currentStage}' 转换到 '{$newStage}'";
}
// 3. 如果是丢单,必须填写原因(业务规则)
if ($newStage === 'lost' && empty($remarks)) {
return "丢单必须填写原因";
}
// 4. 开启事务,原子更新
try {
$this->db->beginTransaction();
// 更新主表
$updateStmt = $this->db->prepare("UPDATE `leads` SET `stage` = ?, `updated_at` = NOW() WHERE id = ?");
$updateStmt->execute([$newStage, $leadId]);
// 写入日志表
$logStmt = $this->db->prepare("INSERT INTO `lead_stage_log` (`lead_id`, `from_stage`, `to_stage`, `changed_by`, `remarks`, `created_at`) VALUES (?, ?, ?, ?, ?, NOW())");
$logStmt->execute([$leadId, $currentStage, $newStage, $userId, $remarks]);
$this->db->commit();
return true;
} catch (Exception $e) {
$this->db->rollBack();
return "操作失败: " . $e->getMessage();
}
}
}
控制器使用示例 (Laravel / ThinkPHP 风格)
// 假设这是一个API接口
public function updateStage(Request $request, $leadId) {
$validator = Validator::make($request->all(), [
'stage' => 'required|in:contacted,qualified,proposal,negotiation, won, lost',
'remarks' => 'nullable|string'
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()], 422);
}
$funnel = new LeadFunnel(DB::connection()->getPdo());
$result = $funnel->moveToStage(
$leadId,
$request->input('stage'),
auth()->id(),
$request->input('remarks', '')
);
if ($result === true) {
return response()->json(['message' => '阶段更新成功']);
} else {
return response()->json(['error' => $result], 400);
}
}
第三步:数据库查询与统计(生成漏斗数据)
当你需要在页面上显示一个“倒三角”漏斗图时,后端需要提供每个阶段的数量和总金额。
<?php
/**
* 获取漏斗统计数据(用于前端图表)
* @param array $filters ['start_date' => '2024-01-01', 'end_date' => '2024-12-31', 'assigned_to' => 1]
* @return array
*/
function getFunnelData($db, $filters = []) {
$stages = ['new', 'contacted', 'qualified', 'proposal', 'negotiation', 'won', 'lost'];
$where = "1=1";
$params = [];
if (!empty($filters['start_date'])) {
$where .= " AND created_at >= ?";
$params[] = $filters['start_date'];
}
if (!empty($filters['end_date'])) {
$where .= " AND created_at <= ?";
$params[] = $filters['end_date'];
}
if (!empty($filters['assigned_to'])) {
$where .= " AND assigned_to = ?";
$params[] = $filters['assigned_to'];
}
$data = [];
foreach ($stages as $stage) {
// 查询当前阶段的数量和总金额
$query = "SELECT COUNT(*) as count, SUM(estimated_value) as total_value
FROM leads
WHERE stage = ? AND {$where}";
$stmt = $db->prepare($query);
$stmt->execute(array_merge([$stage], $params));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$data[$stage] = [
'count' => (int)$row['count'],
'value' => (float)($row['total_value'] ?? 0),
'label' => getStageLabel($stage) // 'new' => '新线索'
];
}
// 计算转化率(从当前阶段到下一个阶段的概率)
// 从 qualified 到 proposal 的转化率
$data['conversion_rates'] = [];
for ($i = 0; $i < count($stages) - 2; $i++) { // 忽略 won 和 lost
$currentCount = $data[$stages[$i]]['count'];
$nextCount = $data[$stages[$i + 1]]['count'];
$data['conversion_rates'][$stages[$i] . '_to_' . $stages[$i+1]] =
$currentCount > 0 ? round(($nextCount / $currentCount) * 100, 2) . '%' : 'N/A';
}
return $data;
}
function getStageLabel($stage) {
$labels = [
'new' => '新线索',
'contacted' => '已联系',
'qualified' => '意向客户',
'proposal' => '方案报价',
'negotiation' => '商务谈判',
'won' => '赢单',
'lost' => '丢单'
];
return $labels[$stage] ?? $stage;
}
第四步:前端可视化(使用 JavaScript 图表库)
有了后端数据,前端可以用 Chart.js 或 ECharts 渲染漏斗图。
使用 ECharts 的示例:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
</head>
<body>
<div id="funnelChart" style="width: 800px; height: 500px;"></div>
<script>
// 假设从后端API获取数据
// fetch('/api/funnel-data').then(response => response.json()).then(data => {
const data = { // 模拟数据
'new': { count: 100, label: '新线索' },
'contacted': { count: 80, label: '已联系' },
'qualified': { count: 50, label: '意向客户' },
'proposal': { count: 30, label: '方案报价' },
'negotiation': { count: 20, label: '商务谈判' },
'won': { count: 10, label: '赢单' },
};
const chart = echarts.init(document.getElementById('funnelChart'));
const option = {
title: { text: '销售漏斗', left: 'center' },
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} 个'
},
series: [
{
name: '销售漏斗',
type: 'funnel',
left: '10%',
top: 60,
bottom: 60,
width: '80%',
min: 0,
max: 100,
minSize: '0%',
maxSize: '100%',
sort: 'descending', // 降序,从大到小
gap: 2,
label: {
show: true,
position: 'inside',
formatter: '{b}:{c}'
},
labelLine: { length: 10, lineStyle: { width: 1, type: 'solid' } },
itemStyle: { borderColor: '#fff', borderWidth: 1 },
emphasis: {
label: { fontSize: 20 }
},
data: Object.keys(data).map(key => ({
value: data[key].count,
name: data[key].label
}))
// 注意:由于漏斗默认按数值排序,顺序可能被改变
// 如果需要固定顺序,可以手动排序数组:
// .sort((a, b) => b.value - a.value)
}
]
};
chart.setOption(option);
// });
</script>
</body>
</html>
第五步:进阶增强模块
一个健壮的销售漏斗系统还可以加入以下功能:
- 自动化触发器:如果线索在某一阶段停留超过3天,自动发送提醒邮件给销售。
- 实现:写一个定时任务(Cron Job),检查
stage_duration。
- 实现:写一个定时任务(Cron Job),检查
- 看板视图(Kanban):模拟 Trello 的拖拽卡片效果。
- 前端:使用
Sortable.js+Vue/React。 - 后端:API 调用
moveToStage()。
- 前端:使用
- 预估收入预测:根据当前漏斗各阶段的数量和历史转化率,乘以平均客单价,预测下个季度的收入。
预测收入 = Sum(当前阶段数量 x 该阶段到赢单的平均转化率 x 平均客单价)
- 权限控制:销售经理能看到全部,普通销售只能看到自己的客户。
用PHP实现销售漏斗的关键在于:
- 状态机模式:控制状态转换的合法性,避免数据混乱。
- 日志追踪:记录每次变动,便于后续做流失分析。
- 聚合查询:按阶段分组统计数量和金额。
- 前端图表:将数据可视化,让“漏斗”的形态直观可见。
你可以根据你的业务复杂度(例如是否涉及合同、付款计划、复购等)在 leads 表的基础上增加更多的关联表。