statistics_customers.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <?php
  2. /**
  3. * 客户统计分析模块
  4. *
  5. * 包含与客户相关的数据分析功能
  6. */
  7. require_once 'statistics_utils.php';
  8. /**
  9. * 获取客户类型分布
  10. *
  11. * @param mysqli $conn 数据库连接
  12. * @return mysqli_result 客户类型分布数据结果集
  13. */
  14. function getCustomerTypeDistribution($conn) {
  15. $sql = "SELECT
  16. ct.businessType,
  17. COUNT(c.id) as customer_count
  18. FROM customer c
  19. JOIN clienttype ct ON c.cs_type = ct.id
  20. GROUP BY c.cs_type";
  21. return $conn->query($sql);
  22. }
  23. /**
  24. * 获取成交阶段分布
  25. *
  26. * @param mysqli $conn 数据库连接
  27. * @return mysqli_result 成交阶段分布数据结果集
  28. */
  29. function getDealStageDistribution($conn) {
  30. $sql = "SELECT
  31. cs_deal,
  32. CASE
  33. WHEN cs_deal = 1 THEN '背景调查'
  34. WHEN cs_deal = 2 THEN '明确需求'
  35. WHEN cs_deal = 3 THEN '已成交'
  36. ELSE '其他'
  37. END as stage_name,
  38. COUNT(id) as customer_count
  39. FROM customer
  40. GROUP BY cs_deal";
  41. return $conn->query($sql);
  42. }
  43. /**
  44. * 获取客户增长趋势
  45. *
  46. * @param mysqli $conn 数据库连接
  47. * @param int $months 获取多少个月的数据,默认12个月
  48. * @return mysqli_result 客户增长趋势数据结果集
  49. */
  50. function getCustomerGrowthTrend($conn, $months = 12) {
  51. $sql = "SELECT
  52. DATE_FORMAT(cs_addtime, '%Y-%m') as month,
  53. COUNT(id) as new_customers
  54. FROM customer
  55. WHERE cs_addtime >= DATE_SUB(CURDATE(), INTERVAL ? MONTH)
  56. GROUP BY DATE_FORMAT(cs_addtime, '%Y-%m')
  57. ORDER BY month";
  58. $stmt = $conn->prepare($sql);
  59. $stmt->bind_param("i", $months);
  60. $stmt->execute();
  61. return $stmt->get_result();
  62. }
  63. /**
  64. * 获取新老客户订单分析
  65. *
  66. * @param mysqli $conn 数据库连接
  67. * @param string $start_date 开始日期
  68. * @param string $end_date 结束日期
  69. * @return array 新老客户订单分析数据
  70. */
  71. function getNewVsReturningCustomerOrders($conn, $start_date, $end_date) {
  72. // 获取选定日期范围内的订单
  73. $sql = "SELECT
  74. o.customer_id,
  75. COUNT(o.id) as order_count,
  76. SUM(o.total_amount) as total_amount,
  77. MIN(o.order_date) as first_order_date,
  78. MAX(c.cs_addtime) as customer_addtime
  79. FROM orders o
  80. JOIN customer c ON o.customer_id = c.id
  81. WHERE o.order_date BETWEEN ? AND ?
  82. GROUP BY o.customer_id";
  83. $stmt = $conn->prepare($sql);
  84. $stmt->bind_param("ss", $start_date, $end_date);
  85. $stmt->execute();
  86. $result = $stmt->get_result();
  87. $new_customers = 0;
  88. $returning_customers = 0;
  89. $new_customer_amount = 0;
  90. $returning_customer_amount = 0;
  91. while ($row = $result->fetch_assoc()) {
  92. // 查找之前是否有订单
  93. $prev_sql = "SELECT id FROM orders
  94. WHERE customer_id = ?
  95. AND order_date < ?
  96. LIMIT 1";
  97. $prev_stmt = $conn->prepare($prev_sql);
  98. $prev_stmt->bind_param("is", $row['customer_id'], $start_date);
  99. $prev_stmt->execute();
  100. $prev_result = $prev_stmt->get_result();
  101. if ($prev_result->num_rows > 0) {
  102. // 老客户
  103. $returning_customers++;
  104. $returning_customer_amount += $row['total_amount'];
  105. } else {
  106. // 新客户
  107. $new_customers++;
  108. $new_customer_amount += $row['total_amount'];
  109. }
  110. }
  111. return [
  112. 'new_customers' => $new_customers,
  113. 'returning_customers' => $returning_customers,
  114. 'new_customer_amount' => $new_customer_amount,
  115. 'returning_customer_amount' => $returning_customer_amount,
  116. 'total_customers' => $new_customers + $returning_customers,
  117. 'total_amount' => $new_customer_amount + $returning_customer_amount
  118. ];
  119. }
  120. /**
  121. * 渲染客户类型分布图
  122. *
  123. * @param array $type_labels 类型标签
  124. * @param array $type_data 类型数据
  125. * @return void
  126. */
  127. function renderCustomerTypeChart($type_labels, $type_data) {
  128. ?>
  129. <div class="chart-container">
  130. <div class="chart-header">
  131. <h2 class="chart-title">客户类型分布</h2>
  132. </div>
  133. <canvas id="customerTypeChart"></canvas>
  134. </div>
  135. <script>
  136. // 客户类型分布图
  137. var customerTypeCtx = document.getElementById('customerTypeChart').getContext('2d');
  138. var customerTypeChart = new Chart(customerTypeCtx, {
  139. type: 'doughnut',
  140. data: {
  141. labels: <?php echo json_encode($type_labels); ?>,
  142. datasets: [{
  143. data: <?php echo json_encode($type_data); ?>,
  144. backgroundColor: [
  145. 'rgba(54, 162, 235, 0.7)',
  146. 'rgba(255, 99, 132, 0.7)',
  147. 'rgba(255, 206, 86, 0.7)',
  148. 'rgba(75, 192, 192, 0.7)',
  149. 'rgba(153, 102, 255, 0.7)'
  150. ],
  151. borderWidth: 1
  152. }]
  153. },
  154. options: {
  155. responsive: true,
  156. plugins: {
  157. legend: {
  158. position: 'right',
  159. }
  160. }
  161. }
  162. });
  163. </script>
  164. <?php
  165. }
  166. /**
  167. * 渲染成交阶段分布图
  168. *
  169. * @param array $stage_labels 阶段标签
  170. * @param array $stage_data 阶段数据
  171. * @return void
  172. */
  173. function renderDealStageChart($stage_labels, $stage_data) {
  174. ?>
  175. <div class="chart-container">
  176. <div class="chart-header">
  177. <h2 class="chart-title">成交阶段分布</h2>
  178. </div>
  179. <canvas id="dealStageChart"></canvas>
  180. </div>
  181. <script>
  182. // 成交阶段分布图
  183. var dealStageCtx = document.getElementById('dealStageChart').getContext('2d');
  184. var dealStageChart = new Chart(dealStageCtx, {
  185. type: 'bar',
  186. data: {
  187. labels: <?php echo json_encode($stage_labels); ?>,
  188. datasets: [{
  189. label: '客户数量',
  190. data: <?php echo json_encode($stage_data); ?>,
  191. backgroundColor: [
  192. 'rgba(255, 206, 86, 0.7)',
  193. 'rgba(54, 162, 235, 0.7)',
  194. 'rgba(255, 99, 132, 0.7)'
  195. ],
  196. borderWidth: 1
  197. }]
  198. },
  199. options: {
  200. responsive: true,
  201. scales: {
  202. y: {
  203. beginAtZero: true,
  204. title: {
  205. display: true,
  206. text: '客户数量'
  207. }
  208. }
  209. }
  210. }
  211. });
  212. </script>
  213. <?php
  214. }
  215. /**
  216. * 渲染客户增长趋势图
  217. *
  218. * @param array $growth_labels 增长标签
  219. * @param array $growth_data 增长数据
  220. * @return void
  221. */
  222. function renderCustomerGrowthChart($growth_labels, $growth_data) {
  223. ?>
  224. <div class="chart-container">
  225. <div class="chart-header">
  226. <h2 class="chart-title">客户增长趋势</h2>
  227. </div>
  228. <canvas id="customerGrowthChart"></canvas>
  229. </div>
  230. <script>
  231. // 客户增长趋势图
  232. var customerGrowthCtx = document.getElementById('customerGrowthChart').getContext('2d');
  233. var customerGrowthChart = new Chart(customerGrowthCtx, {
  234. type: 'line',
  235. data: {
  236. labels: <?php echo json_encode($growth_labels); ?>,
  237. datasets: [{
  238. label: '新增客户',
  239. data: <?php echo json_encode($growth_data); ?>,
  240. backgroundColor: 'rgba(75, 192, 192, 0.2)',
  241. borderColor: 'rgba(75, 192, 192, 1)',
  242. borderWidth: 2,
  243. tension: 0.1
  244. }]
  245. },
  246. options: {
  247. responsive: true,
  248. scales: {
  249. y: {
  250. beginAtZero: true,
  251. title: {
  252. display: true,
  253. text: '客户数量'
  254. }
  255. }
  256. }
  257. }
  258. });
  259. </script>
  260. <?php
  261. }
  262. /**
  263. * 渲染新老客户分析图
  264. *
  265. * @param array $new_vs_returning 新老客户数据
  266. * @return void
  267. */
  268. function renderNewVsReturningCustomersChart($new_vs_returning) {
  269. ?>
  270. <div class="chart-container">
  271. <div class="chart-header">
  272. <h2 class="chart-title">新老客户分析</h2>
  273. </div>
  274. <style>
  275. .pie-charts-container {
  276. display: flex;
  277. flex-direction: row;
  278. justify-content: space-between;
  279. margin-bottom: 20px;
  280. }
  281. .pie-chart-wrapper {
  282. flex: 0 0 48%;
  283. max-width: 48%;
  284. }
  285. .customer-stats-summary {
  286. margin-top: 20px;
  287. padding: 15px;
  288. background-color: #f9f9f9;
  289. border-radius: 5px;
  290. }
  291. .stats-row {
  292. display: flex;
  293. margin-bottom: 15px;
  294. }
  295. .stat-item {
  296. flex: 1;
  297. padding: 0 10px;
  298. }
  299. .stat-label {
  300. display: block;
  301. font-weight: bold;
  302. margin-bottom: 5px;
  303. color: #555;
  304. }
  305. .stat-value {
  306. font-size: 16px;
  307. color: #333;
  308. }
  309. </style>
  310. <div class="pie-charts-container">
  311. <div class="pie-chart-wrapper">
  312. <h3 style="text-align: center; margin-bottom: 15px;">客户数量分布</h3>
  313. <canvas id="newVsReturningCustomersChart"></canvas>
  314. </div>
  315. <div class="pie-chart-wrapper">
  316. <h3 style="text-align: center; margin-bottom: 15px;">销售额分布</h3>
  317. <canvas id="newVsReturningAmountChart"></canvas>
  318. </div>
  319. </div>
  320. <div class="customer-stats-summary">
  321. <div class="stats-row">
  322. <div class="stat-item">
  323. <span class="stat-label">总客户数:</span>
  324. <span class="stat-value"><?php echo number_format($new_vs_returning['total_customers']); ?></span>
  325. </div>
  326. <div class="stat-item">
  327. <span class="stat-label">新客户:</span>
  328. <span class="stat-value"><?php echo number_format($new_vs_returning['new_customers']); ?>
  329. (<?php echo ($new_vs_returning['total_customers'] > 0) ? number_format(($new_vs_returning['new_customers'] / $new_vs_returning['total_customers']) * 100, 1) : '0'; ?>%)</span>
  330. </div>
  331. <div class="stat-item">
  332. <span class="stat-label">老客户:</span>
  333. <span class="stat-value"><?php echo number_format($new_vs_returning['returning_customers']); ?>
  334. (<?php echo ($new_vs_returning['total_customers'] > 0) ? number_format(($new_vs_returning['returning_customers'] / $new_vs_returning['total_customers']) * 100, 1) : '0'; ?>%)</span>
  335. </div>
  336. </div>
  337. <div class="stats-row">
  338. <div class="stat-item">
  339. <span class="stat-label">总销售额:</span>
  340. <span class="stat-value">¥<?php echo number_format($new_vs_returning['total_amount'], 2); ?></span>
  341. </div>
  342. <div class="stat-item">
  343. <span class="stat-label">新客户销售额:</span>
  344. <span class="stat-value">¥<?php echo number_format($new_vs_returning['new_customer_amount'], 2); ?>
  345. (<?php echo ($new_vs_returning['total_amount'] > 0) ? number_format(($new_vs_returning['new_customer_amount'] / $new_vs_returning['total_amount']) * 100, 1) : '0'; ?>%)</span>
  346. </div>
  347. <div class="stat-item">
  348. <span class="stat-label">老客户销售额:</span>
  349. <span class="stat-value">¥<?php echo number_format($new_vs_returning['returning_customer_amount'], 2); ?>
  350. (<?php echo ($new_vs_returning['total_amount'] > 0) ? number_format(($new_vs_returning['returning_customer_amount'] / $new_vs_returning['total_amount']) * 100, 1) : '0'; ?>%)</span>
  351. </div>
  352. </div>
  353. </div>
  354. </div>
  355. <script>
  356. // 新老客户数量图
  357. var newVsReturningCtx = document.getElementById('newVsReturningCustomersChart').getContext('2d');
  358. var newVsReturningChart = new Chart(newVsReturningCtx, {
  359. type: 'pie',
  360. data: {
  361. labels: ['新客户', '老客户'],
  362. datasets: [{
  363. data: [
  364. <?php echo $new_vs_returning['new_customers']; ?>,
  365. <?php echo $new_vs_returning['returning_customers']; ?>
  366. ],
  367. backgroundColor: [
  368. 'rgba(54, 162, 235, 0.7)',
  369. 'rgba(255, 99, 132, 0.7)'
  370. ],
  371. borderWidth: 1
  372. }]
  373. },
  374. options: {
  375. responsive: true,
  376. maintainAspectRatio: true,
  377. plugins: {
  378. legend: {
  379. position: 'bottom',
  380. }
  381. }
  382. }
  383. });
  384. // 新老客户销售额图
  385. var amountCtx = document.getElementById('newVsReturningAmountChart').getContext('2d');
  386. var amountChart = new Chart(amountCtx, {
  387. type: 'pie',
  388. data: {
  389. labels: ['新客户销售额', '老客户销售额'],
  390. datasets: [{
  391. data: [
  392. <?php echo $new_vs_returning['new_customer_amount']; ?>,
  393. <?php echo $new_vs_returning['returning_customer_amount']; ?>
  394. ],
  395. backgroundColor: [
  396. 'rgba(54, 162, 235, 0.7)',
  397. 'rgba(255, 99, 132, 0.7)'
  398. ],
  399. borderWidth: 1
  400. }]
  401. },
  402. options: {
  403. responsive: true,
  404. maintainAspectRatio: true,
  405. plugins: {
  406. legend: {
  407. position: 'bottom',
  408. }
  409. }
  410. }
  411. });
  412. </script>
  413. <?php
  414. }
  415. /**
  416. * 获取客户总数
  417. *
  418. * @param mysqli $conn 数据库连接
  419. * @return int 客户总数
  420. */
  421. function getTotalCustomers($conn) {
  422. $sql = "SELECT COUNT(id) as total FROM customer";
  423. $result = $conn->query($sql);
  424. $row = $result->fetch_assoc();
  425. return $row['total'];
  426. }
  427. /**
  428. * 获取指定时间段内新增客户数
  429. *
  430. * @param mysqli $conn 数据库连接
  431. * @param string $start_date 开始日期
  432. * @param string $end_date 结束日期
  433. * @return int 新增客户数
  434. */
  435. function getNewCustomers($conn, $start_date, $end_date) {
  436. $sql = "SELECT COUNT(id) as new_count
  437. FROM customer
  438. WHERE cs_addtime BETWEEN ? AND ?";
  439. $stmt = $conn->prepare($sql);
  440. $stmt->bind_param("ss", $start_date, $end_date);
  441. $stmt->execute();
  442. $result = $stmt->get_result();
  443. $row = $result->fetch_assoc();
  444. return $row['new_count'];
  445. }
  446. /**
  447. * 计算平均客户价值(客户平均订单金额)
  448. *
  449. * @param mysqli $conn 数据库连接
  450. * @param string $start_date 开始日期
  451. * @param string $end_date 结束日期
  452. * @return float 平均客户价值
  453. */
  454. function getAverageCustomerValue($conn, $start_date, $end_date) {
  455. $sql = "SELECT AVG(customer_value) as avg_value FROM (
  456. SELECT
  457. o.customer_id,
  458. SUM(o.total_amount) as customer_value
  459. FROM orders o
  460. WHERE o.order_date BETWEEN ? AND ?
  461. GROUP BY o.customer_id
  462. ) as customer_values";
  463. $stmt = $conn->prepare($sql);
  464. $stmt->bind_param("ss", $start_date, $end_date);
  465. $stmt->execute();
  466. $result = $stmt->get_result();
  467. $row = $result->fetch_assoc();
  468. return $row['avg_value'] ? $row['avg_value'] : 0;
  469. }
  470. /**
  471. * 计算客户留存率
  472. *
  473. * @param mysqli $conn 数据库连接
  474. * @param string $start_date 开始日期
  475. * @param string $end_date 结束日期
  476. * @return array 客户留存率数据
  477. */
  478. function getCustomerRetentionRate($conn, $start_date, $end_date) {
  479. // 获取之前时间段的客户
  480. $previous_start = date('Y-m-d', strtotime('-1 year', strtotime($start_date)));
  481. $previous_end = date('Y-m-d', strtotime('-1 day', strtotime($start_date)));
  482. // 之前时间段的客户ID
  483. $prev_sql = "SELECT DISTINCT customer_id
  484. FROM orders
  485. WHERE order_date BETWEEN ? AND ?";
  486. $prev_stmt = $conn->prepare($prev_sql);
  487. $prev_stmt->bind_param("ss", $previous_start, $previous_end);
  488. $prev_stmt->execute();
  489. $prev_result = $prev_stmt->get_result();
  490. $previous_customers = [];
  491. while ($row = $prev_result->fetch_assoc()) {
  492. $previous_customers[] = $row['customer_id'];
  493. }
  494. $previous_count = count($previous_customers);
  495. // 如果没有之前的客户,返回0
  496. if ($previous_count == 0) {
  497. return [
  498. 'retained_count' => 0,
  499. 'total_previous' => 0,
  500. 'retention_rate' => 0
  501. ];
  502. }
  503. // 查询当前时间段内,之前客户中再次购买的客户数
  504. $current_sql = "SELECT COUNT(DISTINCT customer_id) as retained_count
  505. FROM orders
  506. WHERE order_date BETWEEN ? AND ?
  507. AND customer_id IN (" . implode(',', $previous_customers) . ")";
  508. $current_stmt = $conn->prepare($current_sql);
  509. $current_stmt->bind_param("ss", $start_date, $end_date);
  510. $current_stmt->execute();
  511. $current_result = $current_stmt->get_result();
  512. $row = $current_result->fetch_assoc();
  513. $retained_count = $row['retained_count'];
  514. $retention_rate = ($retained_count / $previous_count) * 100;
  515. return [
  516. 'retained_count' => $retained_count,
  517. 'total_previous' => $previous_count,
  518. 'retention_rate' => $retention_rate
  519. ];
  520. }
  521. /**
  522. * 计算下单转换率
  523. *
  524. * @param mysqli $conn 数据库连接
  525. * @param string $start_date 开始日期
  526. * @param string $end_date 结束日期
  527. * @return array 下单转换率数据
  528. */
  529. function getOrderConversionRate($conn, $start_date, $end_date) {
  530. // 获取指定时间段内总客户数
  531. $total_sql = "SELECT COUNT(DISTINCT id) as total_count FROM customer WHERE cs_addtime <= ?";
  532. $total_stmt = $conn->prepare($total_sql);
  533. $total_stmt->bind_param("s", $end_date);
  534. $total_stmt->execute();
  535. $total_result = $total_stmt->get_result();
  536. $total_row = $total_result->fetch_assoc();
  537. $total_customers = $total_row['total_count'];
  538. // 获取有订单的客户数
  539. $order_sql = "SELECT COUNT(DISTINCT customer_id) as order_count
  540. FROM orders
  541. WHERE order_date BETWEEN ? AND ?";
  542. $order_stmt = $conn->prepare($order_sql);
  543. $order_stmt->bind_param("ss", $start_date, $end_date);
  544. $order_stmt->execute();
  545. $order_result = $order_stmt->get_result();
  546. $order_row = $order_result->fetch_assoc();
  547. $customers_with_orders = $order_row['order_count'];
  548. // 计算转换率
  549. $conversion_rate = ($total_customers > 0) ? ($customers_with_orders / $total_customers) * 100 : 0;
  550. return [
  551. 'total_customers' => $total_customers,
  552. 'customers_with_orders' => $customers_with_orders,
  553. 'conversion_rate' => $conversion_rate
  554. ];
  555. }
  556. /**
  557. * 渲染关键指标仪表板
  558. *
  559. * @param array $kpi_data 关键指标数据
  560. * @return void
  561. */
  562. function renderKeyMetricsCard($kpi_data) {
  563. ?>
  564. <div class="stats-card-container">
  565. <div class="stats-card">
  566. <div class="stats-card-header">
  567. <h3>客户总数</h3>
  568. </div>
  569. <div class="stats-card-body">
  570. <div class="stats-card-value"><?php echo number_format($kpi_data['total_customers']); ?></div>
  571. </div>
  572. </div>
  573. <div class="stats-card">
  574. <div class="stats-card-header">
  575. <h3>新增客户</h3>
  576. </div>
  577. <div class="stats-card-body">
  578. <div class="stats-card-value"><?php echo number_format($kpi_data['new_customers']); ?></div>
  579. </div>
  580. </div>
  581. <div class="stats-card">
  582. <div class="stats-card-header">
  583. <h3>平均客户价值</h3>
  584. </div>
  585. <div class="stats-card-body">
  586. <div class="stats-card-value">¥<?php echo number_format($kpi_data['avg_customer_value'], 2); ?></div>
  587. </div>
  588. </div>
  589. <div class="stats-card">
  590. <div class="stats-card-header">
  591. <h3>客户留存率</h3>
  592. </div>
  593. <div class="stats-card-body">
  594. <div class="stats-card-value"><?php echo number_format($kpi_data['retention_rate'], 1); ?>%</div>
  595. <div class="stats-card-subtitle"><?php echo number_format($kpi_data['retained_count']); ?> / <?php echo number_format($kpi_data['total_previous']); ?></div>
  596. </div>
  597. </div>
  598. <div class="stats-card">
  599. <div class="stats-card-header">
  600. <h3>下单转换率</h3>
  601. </div>
  602. <div class="stats-card-body">
  603. <div class="stats-card-value"><?php echo number_format($kpi_data['conversion_rate'], 1); ?>%</div>
  604. <div class="stats-card-subtitle"><?php echo number_format($kpi_data['customers_with_orders']); ?> / <?php echo number_format($kpi_data['total_customers']); ?></div>
  605. </div>
  606. </div>
  607. </div>
  608. <?php
  609. }