本文目录导读:

我来详细介绍如何在PHP项目中实现NPS(净推荐值)分析。
数据库设计
-- NPS调查表
CREATE TABLE nps_surveys (
id INT PRIMARY KEY AUTO_INCREMENT,VARCHAR(200) NOT NULL,
description TEXT,
status ENUM('active', 'inactive', 'closed') DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- NPS响应表
CREATE TABLE nps_responses (
id INT PRIMARY KEY AUTO_INCREMENT,
survey_id INT NOT NULL,
user_id INT COMMENT '关联用户ID,匿名可为NULL',
score INT NOT NULL CHECK (score BETWEEN 0 AND 10),
reason TEXT COMMENT '推荐/不推荐原因',
category VARCHAR(50) COMMENT '反馈分类',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (survey_id) REFERENCES nps_surveys(id)
);
-- NPS计算缓存表
CREATE TABLE nps_cache (
id INT PRIMARY KEY AUTO_INCREMENT,
survey_id INT NOT NULL,
promoters INT DEFAULT 0,
passives INT DEFAULT 0,
detractors INT DEFAULT 0,
total_responses INT DEFAULT 0,
nps_score DECIMAL(5,2) DEFAULT 0,
calculated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (survey_id) REFERENCES nps_surveys(id)
);
NPS核心类实现
<?php
// NPSAnalyzer.php
class NPSAnalyzer {
private $db;
public function __construct($dbConnection) {
$this->db = $dbConnection;
}
/**
* 提交NPS评分
*/
public function submitResponse($surveyId, $userId, $score, $reason = '', $category = '') {
if (!is_numeric($score) || $score < 0 || $score > 10) {
throw new InvalidArgumentException('Score must be between 0 and 10');
}
$stmt = $this->db->prepare(
"INSERT INTO nps_responses (survey_id, user_id, score, reason, category)
VALUES (?, ?, ?, ?, ?)"
);
return $stmt->execute([$surveyId, $userId, $score, $reason, $category]);
}
/**
* 计算NPS分数
*/
public function calculateNPS($surveyId) {
$stats = $this->getScoreDistribution($surveyId);
$total = $stats['promoters'] + $stats['passives'] + $stats['detractors'];
if ($total === 0) {
return [
'nps_score' => 0,
'total' => 0,
'distribution' => $stats
];
}
// NPS = 推荐者百分比 - 贬损者百分比
$promoterPercent = ($stats['promoters'] / $total) * 100;
$detractorPercent = ($stats['detractors'] / $total) * 100;
$npsScore = $promoterPercent - $detractorPercent;
// 缓存计算结果
$this->cacheNPS($surveyId, $stats, $npsScore, $total);
return [
'nps_score' => round($npsScore, 2),
'total' => $total,
'distribution' => $stats,
'promoter_percent' => round($promoterPercent, 2),
'detractor_percent' => round($detractorPercent, 2)
];
}
/**
* 获取评分分布
*/
public function getScoreDistribution($surveyId) {
$stmt = $this->db->prepare(
"SELECT
SUM(CASE WHEN score >= 9 THEN 1 ELSE 0 END) as promoters,
SUM(CASE WHEN score BETWEEN 7 AND 8 THEN 1 ELSE 0 END) as passives,
SUM(CASE WHEN score <= 6 THEN 1 ELSE 0 END) as detractors
FROM nps_responses
WHERE survey_id = ?"
);
$stmt->execute([$surveyId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 缓存NPS结果
*/
private function cacheNPS($surveyId, $distribution, $npsScore, $total) {
$stmt = $this->db->prepare(
"INSERT INTO nps_cache (survey_id, promoters, passives, detractors, total_responses, nps_score)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
promoters = VALUES(promoters),
passives = VALUES(passives),
detractors = VALUES(detractors),
total_responses = VALUES(total_responses),
nps_score = VALUES(nps_score),
calculated_at = CURRENT_TIMESTAMP"
);
return $stmt->execute([
$surveyId,
$distribution['promoters'],
$distribution['passives'],
$distribution['detractors'],
$total,
$npsScore
]);
}
/**
* 获取NPS趋势数据
*/
public function getNPSTrend($surveyId, $startDate, $endDate, $interval = 'day') {
$intervals = [
'day' => "DATE(created_at)",
'week' => "WEEK(created_at, 1)",
'month' => "DATE_FORMAT(created_at, '%Y-%m')"
];
$groupBy = $intervals[$interval] ?? $intervals['day'];
$stmt = $this->db->prepare(
"SELECT
{$groupBy} as period,
COUNT(*) as total_responses,
SUM(CASE WHEN score >= 9 THEN 1 ELSE 0 END) as promoters,
SUM(CASE WHEN score BETWEEN 7 AND 8 THEN 1 ELSE 0 END) as passives,
SUM(CASE WHEN score <= 6 THEN 1 ELSE 0 END) as detractors
FROM nps_responses
WHERE survey_id = ?
AND created_at BETWEEN ? AND ?
GROUP BY period
ORDER BY period"
);
$stmt->execute([$surveyId, $startDate, $endDate]);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 计算每个周期的NPS
foreach ($results as &$row) {
$total = $row['total_responses'];
if ($total > 0) {
$row['nps_score'] = round(
(($row['promoters'] / $total) * 100) -
(($row['detractors'] / $total) * 100),
2
);
} else {
$row['nps_score'] = 0;
}
}
return $results;
}
}
控制器实现
<?php
// NPSSurveyController.php
class NPSSurveyController {
private $analyzer;
public function __construct($db) {
$this->analyzer = new NPSAnalyzer($db);
}
/**
* 显示NPS调查页面
*/
public function showSurvey($surveyId) {
// 获取调查信息
$survey = $this->getSurvey($surveyId);
// 渲染调查模板
return $this->render('nps/survey', [
'survey' => $survey
]);
}
/**
* 处理NPS评分提交
*/
public function submitScore($request) {
try {
$result = $this->analyzer->submitResponse(
$request['survey_id'],
$_SESSION['user_id'] ?? null,
(int)$request['score'],
$request['reason'] ?? '',
$request['category'] ?? ''
);
if ($result) {
return [
'success' => true,
'message' => '感谢您的反馈!'
];
}
} catch (Exception $e) {
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* 显示NPS分析仪表盘
*/
public function dashboard($surveyId) {
// 当前NPS
$currentNPS = $this->analyzer->calculateNPS($surveyId);
// 趋势数据(最近30天)
$trend = $this->analyzer->getNPSTrend(
$surveyId,
date('Y-m-d', strtotime('-30 days')),
date('Y-m-d'),
'day'
);
// 获取详细反馈
$feedbacks = $this->getDetailedFeedbacks($surveyId);
return $this->render('nps/dashboard', [
'current_nps' => $currentNPS,
'trend' => $trend,
'feedbacks' => $feedbacks
]);
}
/**
* 获取详细反馈
*/
private function getDetailedFeedbacks($surveyId, $limit = 20) {
$stmt = $this->db->prepare(
"SELECT nr.*, u.name as user_name
FROM nps_responses nr
LEFT JOIN users u ON nr.user_id = u.id
WHERE nr.survey_id = ?
ORDER BY nr.created_at DESC
LIMIT ?"
);
$stmt->execute([$surveyId, $limit]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
前端实现(AJAX交互)
<!-- nps-survey.html -->
<div class="nps-container">
<h2>您向朋友推荐我们的可能性有多大?</h2>
<p>0 = 完全不可能,10 = 非常可能</p>
<form id="npsForm" onsubmit="return submitNPS(event)">
<div class="nps-scale">
<?php for ($i = 0; $i <= 10; $i++): ?>
<label class="nps-option">
<input type="radio" name="score" value="<?= $i ?>" required>
<span class="score-value"><?= $i ?></span>
</label>
<?php endfor; ?>
</div>
<div class="nps-categories">
<label>反馈类别(可选):</label>
<select name="category">
<option value="">选择类别</option>
<option value="product">产品</option>
<option value="service">服务</option>
<option value="support">支持</option>
<option value="price">价格</option>
</select>
</div>
<div class="nps-feedback">
<label>为什么给出这个评分?</label>
<textarea name="reason" rows="3" placeholder="分享您的想法..."></textarea>
</div>
<input type="hidden" name="survey_id" value="<?= $surveyId ?>">
<button type="submit">提交反馈</button>
</form>
</div>
<script>
function submitNPS(event) {
event.preventDefault();
const formData = new FormData(event.target);
fetch('/api/nps/submit', {
method: 'POST',
body: new URLSearchParams(formData)
})
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
event.target.reset();
} else {
showErrorMessage(data.message);
}
})
.catch(error => {
showErrorMessage('提交失败,请稍后重试');
});
return false;
}
</script>
分析仪表盘模板
<!-- nps-dashboard.php -->
<div class="nps-dashboard">
<!-- 当前NPS分数卡片 -->
<div class="nps-card nps-main">
<h3>当前NPS分数</h3>
<div class="nps-score <?= getScoreClass($currentNPS['nps_score']) ?>">
<?= $currentNPS['nps_score'] ?>
</div>
<div class="nps-details">
<span>推荐者: <?= $currentNPS['distribution']['promoters'] ?></span>
<span>被动者: <?= $currentNPS['distribution']['passives'] ?></span>
<span>贬损者: <?= $currentNPS['distribution']['detractors'] ?></span>
</div>
</div>
<!-- NPS趋势图 -->
<div class="nps-card nps-trend">
<h3>NPS趋势(最近30天)</h3>
<canvas id="npsTrendChart"></canvas>
</div>
<!-- 反馈列表 -->
<div class="nps-card nps-feedbacks">
<h3>用户反馈</h3>
<div class="feedback-list">
<?php foreach ($feedbacks as $feedback): ?>
<div class="feedback-item feedback-<?= getFeedbackClass($feedback['score']) ?>">
<div class="feedback-score"><?= $feedback['score'] ?>/10</div>
<div class="feedback-text"><?= htmlspecialchars($feedback['reason']) ?></div>
<div class="feedback-meta">
<?= $feedback['user_name'] ?? '匿名' ?> -
<?= date('Y-m-d H:i', strtotime($feedback['created_at'])) ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php
function getScoreClass($score) {
if ($score >= 50) return 'excellent';
if ($score >= 0) return 'good';
return 'needs-improvement';
}
function getFeedbackClass($score) {
if ($score >= 9) return 'promoter';
if ($score >= 7) return 'passive';
return 'detractor';
}
?>
<!-- NPS趋势图表脚本 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 渲染NPS趋势图
const ctx = document.getElementById('npsTrendChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: <?= json_encode(array_column($trend, 'period')) ?>,
datasets: [{
label: 'NPS分数',
data: <?= json_encode(array_column($trend, 'nps_score')) ?>,
borderColor: '#4CAF50',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
min: -100,
max: 100
}
}
}
});
</script>
API接口
<?php
// api/nps.php
header('Content-Type: application/json');
$db = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
$nps = new NPSAnalyzer($db);
$action = $_GET['action'] ?? '';
switch ($action) {
case 'submit':
$result = $nps->submitResponse(
$_POST['survey_id'],
$_SESSION['user_id'] ?? null,
(int)$_POST['score'],
$_POST['reason'] ?? '',
$_POST['category'] ?? ''
);
echo json_encode([
'success' => $result,
'message' => $result ? '提交成功' : '提交失败'
]);
break;
case 'calculate':
$result = $nps->calculateNPS($_GET['survey_id']);
echo json_encode($result);
break;
case 'trend':
$result = $nps->getNPSTrend(
$_GET['survey_id'],
$_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days')),
$_GET['end_date'] ?? date('Y-m-d'),
$_GET['interval'] ?? 'day'
);
echo json_encode($result);
break;
}
使用示例
<?php
// 使用示例
$db = new PDO('mysql:host=localhost;dbname=your_db', 'user', 'pass');
$nps = new NPSAnalyzer($db);
// 1. 创建调查
$surveyId = 1;
// 2. 提交评分
$nps->submitResponse($surveyId, null, 9, '非常好用', 'product'); // 推荐者
$nps->submitResponse($surveyId, null, 6, '有待改进', 'service'); // 贬损者
// 3. 计算NPS
$result = $nps->calculateNPS($surveyId);
echo "NPS Score: " . $result['nps_score']; // 输出: 50 = (100% - 50%)
// 4. 获取趋势
$trend = $nps->getNPSTrend(
$surveyId,
'2023-01-01',
'2023-12-31',
'month'
);
这个实现包含了NPS分析的核心功能,包括数据收集、计算、缓存、趋势分析和可视化,根据具体需求,可以进一步扩展功能,如用户分组分析、触发条件设置等。