|
@@ -653,4 +653,516 @@ function renderKeyMetricsCard($kpi_data) {
|
|
|
</div>
|
|
|
</div>
|
|
|
<?php
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取客户价值分布数据
|
|
|
+ *
|
|
|
+ * @param mysqli $conn 数据库连接
|
|
|
+ * @param string $start_date 开始日期
|
|
|
+ * @param string $end_date 结束日期
|
|
|
+ * @return array 客户价值分布数据
|
|
|
+ */
|
|
|
+function getCustomerValueDistribution($conn, $start_date, $end_date) {
|
|
|
+ $sql = "SELECT
|
|
|
+ value_segment,
|
|
|
+ COUNT(customer_id) as customer_count,
|
|
|
+ SUM(total_amount) as total_amount
|
|
|
+ FROM (
|
|
|
+ SELECT
|
|
|
+ o.customer_id,
|
|
|
+ SUM(o.total_amount) as total_amount,
|
|
|
+ CASE
|
|
|
+ WHEN SUM(o.total_amount) > 100000 THEN '高价值客户(>10万)'
|
|
|
+ WHEN SUM(o.total_amount) > 50000 THEN '中高价值客户(5-10万)'
|
|
|
+ WHEN SUM(o.total_amount) > 10000 THEN '中价值客户(1-5万)'
|
|
|
+ WHEN SUM(o.total_amount) > 5000 THEN '低价值客户(5千-1万)'
|
|
|
+ ELSE '微价值客户(<5千)'
|
|
|
+ END as value_segment
|
|
|
+ FROM orders o
|
|
|
+ WHERE o.order_date BETWEEN ? AND ?
|
|
|
+ GROUP BY o.customer_id
|
|
|
+ ) as customer_value
|
|
|
+ GROUP BY value_segment
|
|
|
+ ORDER BY
|
|
|
+ CASE value_segment
|
|
|
+ WHEN '高价值客户(>10万)' THEN 1
|
|
|
+ WHEN '中高价值客户(5-10万)' THEN 2
|
|
|
+ WHEN '中价值客户(1-5万)' THEN 3
|
|
|
+ WHEN '低价值客户(5千-1万)' THEN 4
|
|
|
+ ELSE 5
|
|
|
+ END";
|
|
|
+
|
|
|
+ $stmt = $conn->prepare($sql);
|
|
|
+ $stmt->bind_param("ss", $start_date, $end_date);
|
|
|
+ $stmt->execute();
|
|
|
+ $result = $stmt->get_result();
|
|
|
+
|
|
|
+ $value_segments = [];
|
|
|
+ $customer_counts = [];
|
|
|
+ $total_amounts = [];
|
|
|
+ $total_customers = 0;
|
|
|
+
|
|
|
+ while ($row = $result->fetch_assoc()) {
|
|
|
+ $value_segments[] = $row['value_segment'];
|
|
|
+ $customer_counts[] = $row['customer_count'];
|
|
|
+ $total_amounts[] = $row['total_amount'];
|
|
|
+ $total_customers += $row['customer_count'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'segments' => $value_segments,
|
|
|
+ 'counts' => $customer_counts,
|
|
|
+ 'amounts' => $total_amounts,
|
|
|
+ 'total_customers' => $total_customers
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取客户活跃度分析数据
|
|
|
+ *
|
|
|
+ * @param mysqli $conn 数据库连接
|
|
|
+ * @param string $end_date 截止日期
|
|
|
+ * @return array 客户活跃度分析数据
|
|
|
+ */
|
|
|
+function getCustomerActivityAnalysis($conn, $end_date) {
|
|
|
+ $sql = "SELECT
|
|
|
+ activity_level,
|
|
|
+ COUNT(*) as customer_count
|
|
|
+ FROM (
|
|
|
+ SELECT
|
|
|
+ o.customer_id,
|
|
|
+ CASE
|
|
|
+ WHEN DATEDIFF(?, MAX(o.order_date)) <= 30 THEN '活跃客户(30天内)'
|
|
|
+ WHEN DATEDIFF(?, MAX(o.order_date)) <= 90 THEN '一般活跃(90天内)'
|
|
|
+ WHEN DATEDIFF(?, MAX(o.order_date)) <= 180 THEN '低活跃(180天内)'
|
|
|
+ WHEN DATEDIFF(?, MAX(o.order_date)) <= 365 THEN '沉睡客户(1年内)'
|
|
|
+ ELSE '流失客户(超过1年)'
|
|
|
+ END as activity_level
|
|
|
+ FROM orders o
|
|
|
+ GROUP BY o.customer_id
|
|
|
+ ) as customer_activity
|
|
|
+ GROUP BY activity_level
|
|
|
+ ORDER BY
|
|
|
+ CASE activity_level
|
|
|
+ WHEN '活跃客户(30天内)' THEN 1
|
|
|
+ WHEN '一般活跃(90天内)' THEN 2
|
|
|
+ WHEN '低活跃(180天内)' THEN 3
|
|
|
+ WHEN '沉睡客户(1年内)' THEN 4
|
|
|
+ ELSE 5
|
|
|
+ END";
|
|
|
+
|
|
|
+ $stmt = $conn->prepare($sql);
|
|
|
+ $end_date_formatted = date('Y-m-d', strtotime($end_date));
|
|
|
+ $stmt->bind_param("ssss", $end_date_formatted, $end_date_formatted, $end_date_formatted, $end_date_formatted);
|
|
|
+ $stmt->execute();
|
|
|
+ $result = $stmt->get_result();
|
|
|
+
|
|
|
+ $activity_levels = [];
|
|
|
+ $customer_counts = [];
|
|
|
+
|
|
|
+ while ($row = $result->fetch_assoc()) {
|
|
|
+ $activity_levels[] = $row['activity_level'];
|
|
|
+ $customer_counts[] = $row['customer_count'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'levels' => $activity_levels,
|
|
|
+ 'counts' => $customer_counts
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取客户流失风险分析数据
|
|
|
+ *
|
|
|
+ * @param mysqli $conn 数据库连接
|
|
|
+ * @param string $end_date 截止日期
|
|
|
+ * @return array 客户流失风险分析数据
|
|
|
+ */
|
|
|
+function getCustomerChurnRiskAnalysis($conn, $end_date) {
|
|
|
+ $sql = "SELECT
|
|
|
+ risk_level,
|
|
|
+ COUNT(*) as customer_count
|
|
|
+ FROM (
|
|
|
+ SELECT
|
|
|
+ c.id,
|
|
|
+ CASE
|
|
|
+ WHEN last_order_date IS NULL THEN '从未购买'
|
|
|
+ WHEN DATEDIFF(?, last_order_date) <= 90 THEN '低风险(90天内)'
|
|
|
+ WHEN DATEDIFF(?, last_order_date) <= 180 THEN '中风险(90-180天)'
|
|
|
+ WHEN DATEDIFF(?, last_order_date) <= 365 THEN '高风险(180-365天)'
|
|
|
+ ELSE '极高风险(超过1年)'
|
|
|
+ END as risk_level
|
|
|
+ FROM customer c
|
|
|
+ LEFT JOIN (
|
|
|
+ SELECT customer_id, MAX(order_date) as last_order_date
|
|
|
+ FROM orders
|
|
|
+ GROUP BY customer_id
|
|
|
+ ) o ON c.id = o.customer_id
|
|
|
+ ) as customer_risk
|
|
|
+ GROUP BY risk_level
|
|
|
+ ORDER BY
|
|
|
+ CASE risk_level
|
|
|
+ WHEN '低风险(90天内)' THEN 1
|
|
|
+ WHEN '中风险(90-180天)' THEN 2
|
|
|
+ WHEN '高风险(180-365天)' THEN 3
|
|
|
+ WHEN '极高风险(超过1年)' THEN 4
|
|
|
+ WHEN '从未购买' THEN 5
|
|
|
+ END";
|
|
|
+
|
|
|
+ $stmt = $conn->prepare($sql);
|
|
|
+ $end_date_formatted = date('Y-m-d', strtotime($end_date));
|
|
|
+ $stmt->bind_param("sss", $end_date_formatted, $end_date_formatted, $end_date_formatted);
|
|
|
+ $stmt->execute();
|
|
|
+ $result = $stmt->get_result();
|
|
|
+
|
|
|
+ $risk_levels = [];
|
|
|
+ $customer_counts = [];
|
|
|
+
|
|
|
+ while ($row = $result->fetch_assoc()) {
|
|
|
+ $risk_levels[] = $row['risk_level'];
|
|
|
+ $customer_counts[] = $row['customer_count'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'levels' => $risk_levels,
|
|
|
+ 'counts' => $customer_counts
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 获取客户来源分析数据
|
|
|
+ *
|
|
|
+ * @param mysqli $conn 数据库连接
|
|
|
+ * @return array 客户来源分析数据
|
|
|
+ */
|
|
|
+function getCustomerSourceAnalysis($conn) {
|
|
|
+ // 假设cs_from字段代表客户来源,需要根据实际情况调整SQL
|
|
|
+ $sql = "SELECT
|
|
|
+ source,
|
|
|
+ COUNT(*) as customer_count
|
|
|
+ FROM (
|
|
|
+ SELECT
|
|
|
+ id,
|
|
|
+ CASE
|
|
|
+ WHEN cs_from = 1 THEN '网站注册'
|
|
|
+ WHEN cs_from = 2 THEN '销售开发'
|
|
|
+ WHEN cs_from = 3 THEN '广告引流'
|
|
|
+ WHEN cs_from = 4 THEN '展会获取'
|
|
|
+ WHEN cs_from = 5 THEN '客户推荐'
|
|
|
+ ELSE '其他来源'
|
|
|
+ END as source
|
|
|
+ FROM customer
|
|
|
+ ) as customer_source
|
|
|
+ GROUP BY source
|
|
|
+ ORDER BY customer_count DESC";
|
|
|
+
|
|
|
+ $result = $conn->query($sql);
|
|
|
+
|
|
|
+ $sources = [];
|
|
|
+ $counts = [];
|
|
|
+
|
|
|
+ while ($row = $result->fetch_assoc()) {
|
|
|
+ $sources[] = $row['source'];
|
|
|
+ $counts[] = $row['customer_count'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'sources' => $sources,
|
|
|
+ 'counts' => $counts
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染客户价值分布图表
|
|
|
+ *
|
|
|
+ * @param array $value_data 客户价值分布数据
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+function renderCustomerValueCharts($value_data) {
|
|
|
+ ?>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h2 class="chart-title">客户价值分布</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chart-row">
|
|
|
+ <div class="chart-column">
|
|
|
+ <h3 style="text-align: center; margin-bottom: 15px;">客户价值分布(柱状图)</h3>
|
|
|
+ <canvas id="customerValueBarChart"></canvas>
|
|
|
+ </div>
|
|
|
+ <div class="chart-column">
|
|
|
+ <h3 style="text-align: center; margin-bottom: 15px;">客户价值分布(饼图)</h3>
|
|
|
+ <canvas id="customerValuePieChart"></canvas>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="customer-stats-summary">
|
|
|
+ <div class="stats-row">
|
|
|
+ <?php foreach ($value_data['segments'] as $index => $segment): ?>
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-label"><?php echo $segment; ?>:</span>
|
|
|
+ <span class="stat-value"><?php echo number_format($value_data['counts'][$index]); ?>
|
|
|
+ (<?php echo ($value_data['total_customers'] > 0) ?
|
|
|
+ number_format(($value_data['counts'][$index] / $value_data['total_customers']) * 100, 1) : '0'; ?>%)</span>
|
|
|
+ <span class="stat-sub-value">¥<?php echo number_format($value_data['amounts'][$index], 2); ?></span>
|
|
|
+ </div>
|
|
|
+ <?php endforeach; ?>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ // 客户价值分布柱状图
|
|
|
+ var valueBarCtx = document.getElementById('customerValueBarChart').getContext('2d');
|
|
|
+ var valueBarChart = new Chart(valueBarCtx, {
|
|
|
+ type: 'bar',
|
|
|
+ data: {
|
|
|
+ labels: <?php echo json_encode($value_data['segments']); ?>,
|
|
|
+ datasets: [{
|
|
|
+ label: '客户数量',
|
|
|
+ data: <?php echo json_encode($value_data['counts']); ?>,
|
|
|
+ backgroundColor: [
|
|
|
+ 'rgba(54, 162, 235, 0.7)',
|
|
|
+ 'rgba(75, 192, 192, 0.7)',
|
|
|
+ 'rgba(255, 206, 86, 0.7)',
|
|
|
+ 'rgba(255, 99, 132, 0.7)',
|
|
|
+ 'rgba(153, 102, 255, 0.7)'
|
|
|
+ ],
|
|
|
+ borderWidth: 1
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ scales: {
|
|
|
+ y: {
|
|
|
+ beginAtZero: true,
|
|
|
+ title: {
|
|
|
+ display: true,
|
|
|
+ text: '客户数量'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 客户价值分布饼图
|
|
|
+ var valuePieCtx = document.getElementById('customerValuePieChart').getContext('2d');
|
|
|
+ var valuePieChart = new Chart(valuePieCtx, {
|
|
|
+ type: 'pie',
|
|
|
+ data: {
|
|
|
+ labels: <?php echo json_encode($value_data['segments']); ?>,
|
|
|
+ datasets: [{
|
|
|
+ data: <?php echo json_encode($value_data['counts']); ?>,
|
|
|
+ backgroundColor: [
|
|
|
+ 'rgba(54, 162, 235, 0.7)',
|
|
|
+ 'rgba(75, 192, 192, 0.7)',
|
|
|
+ 'rgba(255, 206, 86, 0.7)',
|
|
|
+ 'rgba(255, 99, 132, 0.7)',
|
|
|
+ 'rgba(153, 102, 255, 0.7)'
|
|
|
+ ],
|
|
|
+ borderWidth: 1
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true
|
|
|
+ }
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ <?php
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染客户活跃度分析图表
|
|
|
+ *
|
|
|
+ * @param array $activity_data 客户活跃度数据
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+function renderCustomerActivityChart($activity_data) {
|
|
|
+ ?>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h2 class="chart-title">客户活跃度分析</h2>
|
|
|
+ </div>
|
|
|
+ <canvas id="customerActivityChart"></canvas>
|
|
|
+
|
|
|
+ <div class="customer-stats-summary">
|
|
|
+ <div class="stats-row">
|
|
|
+ <?php
|
|
|
+ $total_customers = array_sum($activity_data['counts']);
|
|
|
+ foreach ($activity_data['levels'] as $index => $level):
|
|
|
+ ?>
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-label"><?php echo $level; ?>:</span>
|
|
|
+ <span class="stat-value"><?php echo number_format($activity_data['counts'][$index]); ?>
|
|
|
+ (<?php echo ($total_customers > 0) ?
|
|
|
+ number_format(($activity_data['counts'][$index] / $total_customers) * 100, 1) : '0'; ?>%)</span>
|
|
|
+ </div>
|
|
|
+ <?php endforeach; ?>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ // 客户活跃度分析图
|
|
|
+ var activityCtx = document.getElementById('customerActivityChart').getContext('2d');
|
|
|
+ var activityChart = new Chart(activityCtx, {
|
|
|
+ type: 'bar',
|
|
|
+ data: {
|
|
|
+ labels: <?php echo json_encode($activity_data['levels']); ?>,
|
|
|
+ datasets: [{
|
|
|
+ label: '客户数量',
|
|
|
+ data: <?php echo json_encode($activity_data['counts']); ?>,
|
|
|
+ backgroundColor: [
|
|
|
+ 'rgba(54, 162, 235, 0.7)',
|
|
|
+ 'rgba(75, 192, 192, 0.7)',
|
|
|
+ 'rgba(255, 206, 86, 0.7)',
|
|
|
+ 'rgba(255, 99, 132, 0.7)',
|
|
|
+ 'rgba(153, 102, 255, 0.7)'
|
|
|
+ ],
|
|
|
+ borderWidth: 1
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ scales: {
|
|
|
+ y: {
|
|
|
+ beginAtZero: true,
|
|
|
+ title: {
|
|
|
+ display: true,
|
|
|
+ text: '客户数量'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ <?php
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染客户流失风险分析图表
|
|
|
+ *
|
|
|
+ * @param array $risk_data 客户流失风险数据
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+function renderCustomerChurnRiskChart($risk_data) {
|
|
|
+ ?>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h2 class="chart-title">客户流失风险分析</h2>
|
|
|
+ </div>
|
|
|
+ <canvas id="customerRiskChart"></canvas>
|
|
|
+
|
|
|
+ <div class="customer-stats-summary">
|
|
|
+ <div class="stats-row">
|
|
|
+ <?php
|
|
|
+ $total_customers = array_sum($risk_data['counts']);
|
|
|
+ foreach ($risk_data['levels'] as $index => $level):
|
|
|
+ ?>
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-label"><?php echo $level; ?>:</span>
|
|
|
+ <span class="stat-value"><?php echo number_format($risk_data['counts'][$index]); ?>
|
|
|
+ (<?php echo ($total_customers > 0) ?
|
|
|
+ number_format(($risk_data['counts'][$index] / $total_customers) * 100, 1) : '0'; ?>%)</span>
|
|
|
+ </div>
|
|
|
+ <?php endforeach; ?>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ // 客户流失风险分析图
|
|
|
+ var riskCtx = document.getElementById('customerRiskChart').getContext('2d');
|
|
|
+ var riskChart = new Chart(riskCtx, {
|
|
|
+ type: 'doughnut',
|
|
|
+ data: {
|
|
|
+ labels: <?php echo json_encode($risk_data['levels']); ?>,
|
|
|
+ datasets: [{
|
|
|
+ data: <?php echo json_encode($risk_data['counts']); ?>,
|
|
|
+ backgroundColor: [
|
|
|
+ 'rgba(54, 162, 235, 0.7)',
|
|
|
+ 'rgba(75, 192, 192, 0.7)',
|
|
|
+ 'rgba(255, 206, 86, 0.7)',
|
|
|
+ 'rgba(255, 99, 132, 0.7)',
|
|
|
+ 'rgba(153, 102, 255, 0.7)'
|
|
|
+ ],
|
|
|
+ borderWidth: 1
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ plugins: {
|
|
|
+ legend: {
|
|
|
+ position: 'right',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ <?php
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 渲染客户来源分析图表
|
|
|
+ *
|
|
|
+ * @param array $source_data 客户来源数据
|
|
|
+ * @return void
|
|
|
+ */
|
|
|
+function renderCustomerSourceChart($source_data) {
|
|
|
+ ?>
|
|
|
+ <div class="chart-container">
|
|
|
+ <div class="chart-header">
|
|
|
+ <h2 class="chart-title">客户来源分析</h2>
|
|
|
+ </div>
|
|
|
+ <canvas id="customerSourceChart"></canvas>
|
|
|
+
|
|
|
+ <div class="customer-stats-summary">
|
|
|
+ <div class="stats-row">
|
|
|
+ <?php
|
|
|
+ $total_customers = array_sum($source_data['counts']);
|
|
|
+ foreach ($source_data['sources'] as $index => $source):
|
|
|
+ ?>
|
|
|
+ <div class="stat-item">
|
|
|
+ <span class="stat-label"><?php echo $source; ?>:</span>
|
|
|
+ <span class="stat-value"><?php echo number_format($source_data['counts'][$index]); ?>
|
|
|
+ (<?php echo ($total_customers > 0) ?
|
|
|
+ number_format(($source_data['counts'][$index] / $total_customers) * 100, 1) : '0'; ?>%)</span>
|
|
|
+ </div>
|
|
|
+ <?php endforeach; ?>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <script>
|
|
|
+ // 客户来源分析图
|
|
|
+ var sourceCtx = document.getElementById('customerSourceChart').getContext('2d');
|
|
|
+ var sourceChart = new Chart(sourceCtx, {
|
|
|
+ type: 'pie',
|
|
|
+ data: {
|
|
|
+ labels: <?php echo json_encode($source_data['sources']); ?>,
|
|
|
+ datasets: [{
|
|
|
+ data: <?php echo json_encode($source_data['counts']); ?>,
|
|
|
+ backgroundColor: [
|
|
|
+ 'rgba(54, 162, 235, 0.7)',
|
|
|
+ 'rgba(75, 192, 192, 0.7)',
|
|
|
+ 'rgba(255, 206, 86, 0.7)',
|
|
|
+ 'rgba(255, 99, 132, 0.7)',
|
|
|
+ 'rgba(153, 102, 255, 0.7)',
|
|
|
+ 'rgba(255, 159, 64, 0.7)'
|
|
|
+ ],
|
|
|
+ borderWidth: 1
|
|
|
+ }]
|
|
|
+ },
|
|
|
+ options: {
|
|
|
+ responsive: true,
|
|
|
+ plugins: {
|
|
|
+ legend: {
|
|
|
+ position: 'right',
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ <?php
|
|
|
}
|