userDataAnalysis.blade.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. @extends('admin.layouts')
  2. @section('content')
  3. <div class="page-content container">
  4. <div class="card card-shadow">
  5. <div class="card-block p-30">
  6. <form class="form-row">
  7. <div class="form-group col-xxl-1 col-lg-1 col-md-1 col-sm-4">
  8. <input class="form-control" name="uid" type="number" value="{{ Request::query('uid') }}" placeholder="{{ trans('model.user.id') }}" />
  9. </div>
  10. <div class="form-group col-xxl-2 col-lg-3 col-md-3 col-sm-4">
  11. <input class="form-control" name="username" type="text" value="{{ Request::query('username') }}"
  12. placeholder="{{ trans('model.user.username') }}" />
  13. </div>
  14. <div class="form-group col-xxl-1 col-lg-3 col-md-3 col-4 btn-group">
  15. <button class="btn btn-primary" type="submit">{{ trans('common.search') }}</button>
  16. <button class="btn btn-danger" type="button" onclick="resetSearchForm()">{{ trans('common.reset') }}</button>
  17. </div>
  18. </form>
  19. </div>
  20. </div>
  21. @isset($data)
  22. <div class="card card-shadow">
  23. <div class="card-block p-30">
  24. <div class="row pb-20">
  25. <div class="col-md-8 col-sm-6">
  26. <div class="blue-grey-700 font-size-26 font-weight-500">{{ trans('admin.report.hourly_traffic') }}</div>
  27. </div>
  28. </div>
  29. <canvas id="hourlyBar"></canvas>
  30. </div>
  31. </div>
  32. <div class="card card-shadow">
  33. <div class="card-block p-30">
  34. <div class="row pb-20">
  35. <div class="col-md-8 col-sm-6">
  36. <div class="blue-grey-700 font-size-26 font-weight-500">{{ trans('admin.report.daily_traffic') }}</div>
  37. </div>
  38. </div>
  39. <canvas id="dailyBar"></canvas>
  40. </div>
  41. </div>
  42. @endisset
  43. </div>
  44. @endsection
  45. @section('javascript')
  46. @isset($data)
  47. <script src="/assets/global/vendor/chart-js/chart.min.js"></script>
  48. <script src="/assets/global/vendor/chart-js/chartjs-plugin-datalabels.min.js"></script>
  49. <script type="text/javascript">
  50. const userData = @json($data);
  51. const nodeColorMap = generateNodeColorMap(userData.nodes); // 获取所有节点名称并生成颜色映射
  52. function resetSearchForm() {
  53. window.location.href = window.location.href.split('?')[0];
  54. }
  55. function handleFormSubmit() {
  56. $('form').on('submit', function() {
  57. $(this).find('input, select').each(function() {
  58. if (!$(this).val()) {
  59. $(this).remove();
  60. }
  61. });
  62. });
  63. }
  64. function optimizeDatasets(datasets) {
  65. const dataByDate = datasets.reduce((acc, dataset) => {
  66. dataset.data.forEach(item => {
  67. acc[item.time] = acc[item.time] || [];
  68. acc[item.time].push({
  69. id: dataset.label,
  70. total: parseFloat(item.total)
  71. });
  72. });
  73. return acc;
  74. }, {});
  75. const allNodeIds = datasets.map(d => d.label);
  76. const optimizedData = Object.entries(dataByDate).map(([date, dayData]) => {
  77. const total = dayData.reduce((sum, item) => sum + item.total, 0);
  78. const filledData = allNodeIds.map(id => {
  79. const nodeData = dayData.find(item => item.id === id);
  80. return nodeData ? nodeData.total : 0;
  81. });
  82. return {
  83. time: date,
  84. data: filledData,
  85. total
  86. };
  87. });
  88. return datasets.map((dataset, index) => ({
  89. ...dataset,
  90. data: optimizedData.map(day => ({
  91. time: day.time,
  92. total: day.data[index]
  93. }))
  94. }));
  95. }
  96. function createBarChart(elementId, labels, datasets, labelTail, unit = 'MiB') {
  97. const optimizedDatasets = optimizeDatasets(datasets);
  98. new Chart(document.getElementById(elementId), {
  99. type: 'bar',
  100. data: {
  101. labels: optimizedDatasets[0]?.data.map(d => d.time),
  102. datasets: optimizedDatasets
  103. },
  104. plugins: [ChartDataLabels],
  105. options: {
  106. parsing: {
  107. xAxisKey: 'time',
  108. yAxisKey: 'total'
  109. },
  110. scales: {
  111. x: {
  112. stacked: true
  113. },
  114. y: {
  115. stacked: true
  116. }
  117. },
  118. responsive: true,
  119. plugins: {
  120. legend: {
  121. labels: {
  122. padding: 10,
  123. usePointStyle: true,
  124. pointStyle: 'circle',
  125. font: {
  126. size: 14
  127. }
  128. },
  129. },
  130. tooltip: label_callbacks(labelTail, unit),
  131. datalabels: {
  132. display: true,
  133. align: 'end',
  134. anchor: 'end',
  135. formatter: (value, context) => {
  136. if (context.datasetIndex === context.chart.data.datasets.length - 1) {
  137. let total = context.chart.data.datasets.reduce((sum, dataset) => sum + dataset.data[context.dataIndex].total, 0);
  138. return total.toFixed(2) + unit;
  139. }
  140. return null;
  141. },
  142. },
  143. },
  144. },
  145. });
  146. }
  147. function label_callbacks(tail, unit) {
  148. return {
  149. mode: 'index',
  150. intersect: false,
  151. callbacks: {
  152. title: context => `${context[0].label} ${tail}`,
  153. label: context => {
  154. const dataset = context.dataset;
  155. const value = dataset.data[context.dataIndex]?.total || context.parsed.y;
  156. return `${dataset.label || ''}: ${value.toFixed(2)} ${unit}`;
  157. }
  158. }
  159. };
  160. }
  161. function generateNodeColorMap(nodeNames) {
  162. return Object.fromEntries(Object.entries(nodeNames).map(([id, name]) => [id, getRandomColor(name)]));
  163. }
  164. function getRandomColor(name) {
  165. let hash = 0;
  166. for (let i = 0; i < name.length; i++) {
  167. hash = name.charCodeAt(i) + ((hash << 5) - hash);
  168. }
  169. const hue = (hash % 360 + Math.random() * 50) % 360;
  170. const saturation = 50 + (hash % 30);
  171. const lightness = 40 + (hash % 20);
  172. return `hsla(${hue}, ${saturation}%, ${lightness}%, 0.55)`;
  173. }
  174. function generateDatasets(flows) {
  175. const dataByNode = flows.reduce((acc, flow) => {
  176. acc[flow.id] = acc[flow.id] || [];
  177. acc[flow.id].push({
  178. time: flow.time,
  179. total: parseFloat(flow.total),
  180. name: flow.name
  181. });
  182. return acc;
  183. }, {});
  184. return Object.entries(dataByNode).map(([nodeId, data]) => ({
  185. label: data[0].name,
  186. backgroundColor: nodeColorMap[nodeId],
  187. borderColor: nodeColorMap[nodeId],
  188. data,
  189. fill: true,
  190. }));
  191. }
  192. // 创建图表
  193. function initCharts() {
  194. createBarChart('hourlyBar', userData.hours, generateDatasets(userData.hourlyFlows), @json(trans_choice('common.hour', 2)));
  195. createBarChart('dailyBar', userData.days, generateDatasets(userData.dailyFlows), @json(trans_choice('common.days.attribute', 2)));
  196. }
  197. $(document).ready(function() {
  198. handleFormSubmit();
  199. initCharts();
  200. });
  201. </script>
  202. @endisset
  203. @endsection