| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- <?php
- namespace App\Http\Controllers\Api;
- use App\Http\Controllers\Controller;
- use App\Http\logic\api\RegisterHourlyStatsLogic;
- use Illuminate\Http\Request;
- class RegisterHourlyStatsController extends Controller
- {
- /**
- * 获取按小时分布的注册用户、首充付费人数统计
- *
- * @param Request $request
- * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response
- */
- public function getHourlyStats(Request $request)
- {
- // 获取日期参数,如果没有提供则使用今天
- $date = $request->input('date', date('Y-m-d'));
- $channel = (int)$request->input('channel', 104); // 渠道ID,默认102
- $format = $request->input('format', 'chart'); // json 或 chart
- $logic = new RegisterHourlyStatsLogic();
- $result = $logic->getHourlyStats($date, $channel);
- if ($result === false) {
- if ($format === 'chart') {
- return response('<html><body><h3>错误: ' . htmlspecialchars($logic->getError()) . '</h3></body></html>', 400);
- }
- return apiReturnFail($logic->getError());
- }
- // 如果请求图表格式,返回 HTML 图表页面
- if ($format === 'chart') {
- return $this->renderChart($result, $date, $channel);
- }
- // 默认返回 JSON
- return apiReturnSuc($result);
- }
- /**
- * 渲染 HTML 图表页面
- *
- * @param array $data
- * @param string $date
- * @param int $channel
- * @return \Illuminate\Http\Response
- */
- private function renderChart($data, $date, $channel = 102)
- {
- // 准备图表数据
- $hours = [];
- $registerCounts = [];
- $chargeCounts = [];
- $chargeRates = [];
- foreach ($data as $row) {
- $hours[] = $row['hour_display'];
- $registerCounts[] = $row['register_count'];
- $chargeCounts[] = $row['first_charge_count'];
- $chargeRates[] = $row['charge_rate'];
- }
- $html = '<!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>注册用户按小时统计图表</title>
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
- <style>
- * {
- box-sizing: border-box;
- }
- body {
- font-family: Arial, "Microsoft YaHei", sans-serif;
- margin: 0;
- padding: 20px;
- background-color: #f5f5f5;
- }
- .container {
- max-width: 1400px;
- margin: 0 auto;
- background-color: white;
- padding: 30px;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
- h1 {
- color: #333;
- margin-bottom: 20px;
- text-align: center;
- }
- .filter-section {
- display: flex;
- gap: 20px;
- align-items: center;
- margin-bottom: 30px;
- padding: 20px;
- background-color: #f9f9f9;
- border-radius: 6px;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .filter-item label {
- font-weight: bold;
- color: #555;
- }
- .filter-item input, .filter-item select {
- padding: 8px 12px;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 14px;
- }
- .filter-item button {
- padding: 8px 20px;
- background-color: #4CAF50;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- font-weight: bold;
- }
- .filter-item button:hover {
- background-color: #45a049;
- }
- .chart-container {
- margin-bottom: 40px;
- position: relative;
- height: 400px;
- }
- .chart-title {
- font-size: 18px;
- font-weight: bold;
- margin-bottom: 15px;
- color: #333;
- text-align: center;
- }
- .info {
- color: #666;
- margin-bottom: 20px;
- font-size: 14px;
- text-align: center;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>注册用户按小时统计图表</h1>
- <div class="info">
- <strong>统计日期:</strong> ' . htmlspecialchars($date) . ' |
- <strong>渠道:</strong> ' . htmlspecialchars($channel) . ' |
- <strong>统计时间:</strong> ' . date('Y-m-d H:i:s') . '
- </div>
-
- <div class="filter-section">
- <div class="filter-item">
- <label for="dateInput">选择日期:</label>
- <input type="date" id="dateInput" value="' . htmlspecialchars($date) . '" max="' . date('Y-m-d') . '">
- </div>
- <div class="filter-item">
- <label for="channelInput">渠道:</label>
- <select id="channelInput">
- <option value="104"' . ($channel == 104 ? ' selected' : '') . '>104</option>
- <option value="114"' . ($channel == 114 ? ' selected' : '') . '>114</option>
- </select>
- </div>
- <div class="filter-item">
- <button onclick="loadData()">查询</button>
- </div>
- </div>
- <div class="chart-container">
- <div class="chart-title">注册人数 & 新增付费人数(柱状图)</div>
- <canvas id="barChart"></canvas>
- </div>
- <div class="chart-container">
- <div class="chart-title">新增付费率(折线图)</div>
- <canvas id="lineChart"></canvas>
- </div>
- </div>
- <script>
- const hours = ' . json_encode($hours, JSON_UNESCAPED_UNICODE) . ';
- const registerCounts = ' . json_encode($registerCounts, JSON_UNESCAPED_UNICODE) . ';
- const chargeCounts = ' . json_encode($chargeCounts, JSON_UNESCAPED_UNICODE) . ';
- const chargeRates = ' . json_encode($chargeRates, JSON_UNESCAPED_UNICODE) . ';
- // 柱状图配置
- const barCtx = document.getElementById("barChart").getContext("2d");
- const barChart = new Chart(barCtx, {
- type: "bar",
- data: {
- labels: hours,
- datasets: [
- {
- label: "注册人数",
- data: registerCounts,
- backgroundColor: "rgba(54, 162, 235, 0.6)",
- borderColor: "rgba(54, 162, 235, 1)",
- borderWidth: 1
- },
- {
- label: "新增付费人数",
- data: chargeCounts,
- backgroundColor: "rgba(255, 99, 132, 0.6)",
- borderColor: "rgba(255, 99, 132, 1)",
- borderWidth: 1
- }
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- ticks: {
- stepSize: 1
- }
- }
- },
- plugins: {
- legend: {
- display: true,
- position: "top"
- },
- tooltip: {
- mode: "index",
- intersect: false
- }
- }
- }
- });
- // 折线图配置
- const lineCtx = document.getElementById("lineChart").getContext("2d");
- const lineChart = new Chart(lineCtx, {
- type: "line",
- data: {
- labels: hours,
- datasets: [
- {
- label: "新增付费率 (%)",
- data: chargeRates,
- borderColor: "rgba(75, 192, 192, 1)",
- backgroundColor: "rgba(75, 192, 192, 0.2)",
- borderWidth: 2,
- fill: true,
- tension: 0.4,
- pointRadius: 4,
- pointHoverRadius: 6
- }
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- scales: {
- y: {
- beginAtZero: true,
- max: 100,
- ticks: {
- callback: function(value) {
- return value + "%";
- }
- }
- }
- },
- plugins: {
- legend: {
- display: true,
- position: "top"
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- return "新增付费率: " + context.parsed.y + "%";
- }
- }
- }
- }
- }
- });
- // 加载数据函数
- function loadData() {
- const date = document.getElementById("dateInput").value;
- const channel = document.getElementById("channelInput").value;
-
- if (!date) {
- alert("请选择日期");
- return;
- }
- // 构建URL
- const url = window.location.pathname + "?date=" + date + "&channel=" + channel + "&format=chart";
-
- // 重新加载页面
- window.location.href = url;
- }
- </script>
- </body>
- </html>';
- return response($html, 200)->header('Content-Type', 'text/html; charset=utf-8');
- }
- }
|