statistics_sales.php 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. <?php
  2. /**
  3. * 销售统计分析模块
  4. *
  5. * 包含与销售相关的数据分析功能
  6. */
  7. require_once 'statistics_utils.php';
  8. /**
  9. * 获取销售概览数据
  10. *
  11. * @param mysqli $conn 数据库连接
  12. * @param string $start_date 开始日期
  13. * @param string $end_date 结束日期
  14. * @return array 销售概览数据
  15. */
  16. function getSalesOverview($conn, $start_date, $end_date) {
  17. $sql = "SELECT
  18. COUNT(DISTINCT o.id) as total_orders,
  19. SUM(o.total_amount) as total_revenue,
  20. AVG(o.total_amount) as avg_order_value,
  21. COUNT(DISTINCT o.customer_id) as unique_customers,
  22. SUM(oi.quantity) as total_items_sold
  23. FROM orders o
  24. LEFT JOIN order_items oi ON o.id = oi.order_id
  25. WHERE o.order_date BETWEEN ? AND ?
  26. AND o.order_status != 0"; // 排除已取消订单
  27. $stmt = $conn->prepare($sql);
  28. $stmt->bind_param("ss", $start_date, $end_date);
  29. $stmt->execute();
  30. return $stmt->get_result()->fetch_assoc();
  31. }
  32. /**
  33. * 获取订单转化率统计
  34. */
  35. function getOrderConversionStats($conn, $start_date, $end_date) {
  36. $sql = "SELECT
  37. order_status,
  38. COUNT(*) as count,
  39. SUM(total_amount) as amount
  40. FROM orders
  41. WHERE order_date BETWEEN ? AND ?
  42. GROUP BY order_status";
  43. $stmt = $conn->prepare($sql);
  44. $stmt->bind_param("ss", $start_date, $end_date);
  45. $stmt->execute();
  46. return $stmt->get_result();
  47. }
  48. /**
  49. * 获取产品类别销售统计
  50. */
  51. function getProductCategorySales($conn, $start_date, $end_date) {
  52. $sql = "SELECT
  53. pc.name as category_name,
  54. COUNT(DISTINCT o.id) as order_count,
  55. SUM(oi.quantity) as total_quantity,
  56. SUM(oi.total_price) as total_revenue
  57. FROM orders o
  58. JOIN order_items oi ON o.id = oi.order_id
  59. JOIN products p ON oi.product_id = p.id
  60. JOIN product_categories pc ON p.category_id = pc.id
  61. WHERE o.order_date BETWEEN ? AND ?
  62. AND o.order_status != 0
  63. GROUP BY pc.id
  64. ORDER BY total_revenue DESC";
  65. $stmt = $conn->prepare($sql);
  66. $stmt->bind_param("ss", $start_date, $end_date);
  67. $stmt->execute();
  68. return $stmt->get_result();
  69. }
  70. /**
  71. * 获取客户地区分布
  72. */
  73. function getCustomerDistribution($conn, $start_date, $end_date) {
  74. $sql = "SELECT
  75. c.countryName as region,
  76. COUNT(DISTINCT o.customer_id) as customer_count,
  77. COUNT(o.id) as order_count,
  78. SUM(o.total_amount) as total_revenue
  79. FROM orders o
  80. JOIN customer cu ON o.customer_id = cu.id
  81. JOIN country c ON cu.cs_country = c.id
  82. WHERE o.order_date BETWEEN ? AND ?
  83. AND o.order_status != 0
  84. GROUP BY c.id
  85. ORDER BY total_revenue DESC";
  86. $stmt = $conn->prepare($sql);
  87. $stmt->bind_param("ss", $start_date, $end_date);
  88. $stmt->execute();
  89. return $stmt->get_result();
  90. }
  91. /**
  92. * 获取销售员业绩统计
  93. */
  94. function getEmployeePerformance($conn, $start_date, $end_date) {
  95. $sql = "SELECT
  96. e.em_user as employee_name,
  97. COUNT(DISTINCT o.id) as order_count,
  98. COUNT(DISTINCT o.customer_id) as customer_count,
  99. SUM(o.total_amount) as total_revenue,
  100. AVG(o.total_amount) as avg_order_value
  101. FROM orders o
  102. JOIN employee e ON o.employee_id = e.id
  103. WHERE o.order_date BETWEEN ? AND ?
  104. AND o.order_status != 0
  105. GROUP BY e.id
  106. ORDER BY total_revenue DESC";
  107. $stmt = $conn->prepare($sql);
  108. $stmt->bind_param("ss", $start_date, $end_date);
  109. $stmt->execute();
  110. return $stmt->get_result();
  111. }
  112. /**
  113. * 获取支付状态统计
  114. */
  115. function getPaymentStatusStats($conn, $start_date, $end_date) {
  116. $sql = "SELECT
  117. payment_status,
  118. COUNT(*) as count,
  119. SUM(total_amount) as amount
  120. FROM orders
  121. WHERE order_date BETWEEN ? AND ?
  122. AND order_status != 0
  123. GROUP BY payment_status";
  124. $stmt = $conn->prepare($sql);
  125. $stmt->bind_param("ss", $start_date, $end_date);
  126. $stmt->execute();
  127. return $stmt->get_result();
  128. }
  129. /**
  130. * 获取每月销售趋势
  131. *
  132. * @param mysqli $conn 数据库连接
  133. * @param string $start_date 开始日期
  134. * @param string $end_date 结束日期
  135. * @return mysqli_result 月度销售数据结果集
  136. */
  137. function getMonthlySalesTrend($conn, $start_date, $end_date) {
  138. $sql = "SELECT
  139. DATE_FORMAT(order_date, '%Y-%m') as month,
  140. COUNT(DISTINCT id) as orders,
  141. SUM(total_amount) as revenue,
  142. COUNT(DISTINCT customer_id) as unique_customers
  143. FROM orders
  144. WHERE order_date BETWEEN ? AND ?
  145. AND order_status != 0
  146. GROUP BY DATE_FORMAT(order_date, '%Y-%m')
  147. ORDER BY month";
  148. $stmt = $conn->prepare($sql);
  149. $stmt->bind_param("ss", $start_date, $end_date);
  150. $stmt->execute();
  151. return $stmt->get_result();
  152. }
  153. /**
  154. * 获取详细时间段订单趋势
  155. *
  156. * @param mysqli $conn 数据库连接
  157. * @param string $start_date 开始日期
  158. * @param string $end_date 结束日期
  159. * @param string $period 时间粒度 (day/week/month)
  160. * @return mysqli_result 订单趋势数据结果集
  161. */
  162. function getDetailedOrderTrend($conn, $start_date, $end_date, $period = 'day') {
  163. $groupFormat = '%Y-%m-%d';
  164. if ($period == 'week') {
  165. $groupFormat = '%x-W%v';
  166. } else if ($period == 'month') {
  167. $groupFormat = '%Y-%m';
  168. }
  169. $sql = "SELECT
  170. DATE_FORMAT(o.order_date, '$groupFormat') as time_period,
  171. COUNT(DISTINCT o.id) as order_count,
  172. SUM(oi.quantity) as total_quantity,
  173. SUM(o.total_amount) as total_amount,
  174. COUNT(DISTINCT o.customer_id) as unique_customers
  175. FROM orders o
  176. LEFT JOIN order_items oi ON o.id = oi.order_id
  177. WHERE o.order_date BETWEEN ? AND ?
  178. AND o.order_status != 0
  179. GROUP BY time_period
  180. ORDER BY MIN(o.order_date)";
  181. $stmt = $conn->prepare($sql);
  182. $stmt->bind_param("ss", $start_date, $end_date);
  183. $stmt->execute();
  184. return $stmt->get_result();
  185. }
  186. /**
  187. * 渲染销售概览卡片
  188. *
  189. * @param array $sales_overview 销售概览数据
  190. * @return void
  191. */
  192. function renderSalesOverviewCards($sales_overview) {
  193. // 添加空值检查函数
  194. function formatCurrency($value) {
  195. return '¥' . number_format($value ?? 0, 2);
  196. }
  197. ?>
  198. <div class="stats-grid">
  199. <div class="stat-card">
  200. <h3>总订单数</h3>
  201. <div class="stat-value"><?php echo formatNumber($sales_overview['total_orders']); ?></div>
  202. </div>
  203. <div class="stat-card">
  204. <h3>总收入</h3>
  205. <div class="stat-value"><?php echo formatCurrency($sales_overview['total_revenue']); ?></div>
  206. </div>
  207. <div class="stat-card">
  208. <h3>平均订单金额</h3>
  209. <div class="stat-value"><?php echo formatCurrency($sales_overview['avg_order_value']); ?></div>
  210. </div>
  211. <div class="stat-card">
  212. <h3>独立客户数</h3>
  213. <div class="stat-value"><?php echo formatNumber($sales_overview['unique_customers']); ?></div>
  214. </div>
  215. <div class="stat-card">
  216. <h3>总销售数量</h3>
  217. <div class="stat-value"><?php echo formatNumber($sales_overview['total_items_sold']); ?></div>
  218. </div>
  219. </div>
  220. <?php
  221. }
  222. /**
  223. * 渲染订单转化率分析
  224. */
  225. function renderConversionAnalysis($conversion_stats) {
  226. // 添加空值检查函数
  227. $status_names = [
  228. 1 => '待确认',
  229. 2 => '已确认',
  230. 3 => '生产中',
  231. 4 => '已发货',
  232. 5 => '已完成',
  233. 0 => '已取消'
  234. ];
  235. $total_orders = 0;
  236. $data = [];
  237. while ($row = $conversion_stats->fetch_assoc()) {
  238. $total_orders += $row['count'];
  239. $data[$row['order_status']] = $row;
  240. }
  241. ?>
  242. <div class="analysis-grid">
  243. <div>
  244. <canvas id="orderStatusChart"></canvas>
  245. </div>
  246. <div class="table-responsive">
  247. <table class="data-table">
  248. <thead>
  249. <tr>
  250. <th>订单状态</th>
  251. <th>订单数</th>
  252. <th>转化率</th>
  253. <th>金额</th>
  254. </tr>
  255. </thead>
  256. <tbody>
  257. <?php foreach ($status_names as $status_id => $status_name): ?>
  258. <?php if (isset($data[$status_id])): ?>
  259. <tr>
  260. <td><?php echo $status_name; ?></td>
  261. <td><?php echo formatNumber($data[$status_id]['count']); ?></td>
  262. <td><?php echo formatNumber(($data[$status_id]['count'] / ($total_orders ?: 1)) * 100, 1); ?>%</td>
  263. <td><?php echo formatCurrency($data[$status_id]['amount']); ?></td>
  264. </tr>
  265. <?php endif; ?>
  266. <?php endforeach; ?>
  267. </tbody>
  268. </table>
  269. </div>
  270. </div>
  271. <script>
  272. var orderStatusCtx = document.getElementById('orderStatusChart').getContext('2d');
  273. new Chart(orderStatusCtx, {
  274. type: 'doughnut',
  275. data: {
  276. labels: <?php
  277. $labels = [];
  278. $values = [];
  279. foreach ($status_names as $status_id => $status_name) {
  280. if (isset($data[$status_id])) {
  281. $labels[] = $status_name;
  282. $values[] = $data[$status_id]['count'];
  283. }
  284. }
  285. echo json_encode($labels);
  286. ?>,
  287. datasets: [{
  288. data: <?php echo json_encode($values); ?>,
  289. backgroundColor: [
  290. '#FF6384',
  291. '#36A2EB',
  292. '#FFCE56',
  293. '#4BC0C0',
  294. '#9966FF',
  295. '#FF9F40'
  296. ]
  297. }]
  298. },
  299. options: {
  300. responsive: true,
  301. plugins: {
  302. legend: {
  303. position: 'right'
  304. },
  305. title: {
  306. display: true,
  307. text: '订单状态分布'
  308. }
  309. }
  310. }
  311. });
  312. </script>
  313. <?php
  314. }
  315. /**
  316. * 渲染产品类别销售分析图表
  317. */
  318. function renderCategorySalesChart($category_sales) {
  319. $labels = [];
  320. $quantities = [];
  321. $revenues = [];
  322. while ($row = $category_sales->fetch_assoc()) {
  323. $labels[] = $row['category_name'];
  324. $quantities[] = $row['total_quantity'];
  325. $revenues[] = $row['total_revenue'];
  326. }
  327. ?>
  328. <div class="analysis-grid">
  329. <div>
  330. <canvas id="categorySalesChart"></canvas>
  331. </div>
  332. <div class="table-responsive">
  333. <table class="data-table">
  334. <thead>
  335. <tr>
  336. <th>产品类别</th>
  337. <th>订单数</th>
  338. <th>销售数量</th>
  339. <th>销售金额</th>
  340. </tr>
  341. </thead>
  342. <tbody>
  343. <?php
  344. $category_sales->data_seek(0);
  345. while ($row = $category_sales->fetch_assoc()):
  346. ?>
  347. <tr>
  348. <td><?php echo $row['category_name']; ?></td>
  349. <td><?php echo number_format($row['order_count']); ?></td>
  350. <td><?php echo number_format($row['total_quantity']); ?></td>
  351. <td>¥<?php echo number_format($row['total_revenue'], 2); ?></td>
  352. </tr>
  353. <?php endwhile; ?>
  354. </tbody>
  355. </table>
  356. </div>
  357. </div>
  358. <script>
  359. var categorySalesCtx = document.getElementById('categorySalesChart').getContext('2d');
  360. new Chart(categorySalesCtx, {
  361. type: 'bar',
  362. data: {
  363. labels: <?php echo json_encode($labels); ?>,
  364. datasets: [
  365. {
  366. label: '销售数量',
  367. data: <?php echo json_encode($quantities); ?>,
  368. backgroundColor: 'rgba(54, 162, 235, 0.5)',
  369. borderColor: 'rgba(54, 162, 235, 1)',
  370. borderWidth: 1,
  371. yAxisID: 'y-quantity'
  372. },
  373. {
  374. label: '销售金额',
  375. data: <?php echo json_encode($revenues); ?>,
  376. backgroundColor: 'rgba(255, 99, 132, 0.5)',
  377. borderColor: 'rgba(255, 99, 132, 1)',
  378. borderWidth: 1,
  379. yAxisID: 'y-revenue'
  380. }
  381. ]
  382. },
  383. options: {
  384. responsive: true,
  385. scales: {
  386. 'y-quantity': {
  387. type: 'linear',
  388. position: 'left',
  389. title: {
  390. display: true,
  391. text: '销售数量'
  392. }
  393. },
  394. 'y-revenue': {
  395. type: 'linear',
  396. position: 'right',
  397. title: {
  398. display: true,
  399. text: '销售金额'
  400. },
  401. grid: {
  402. drawOnChartArea: false
  403. }
  404. }
  405. }
  406. }
  407. });
  408. </script>
  409. <?php
  410. }
  411. /**
  412. * 渲染客户地区分布图表
  413. */
  414. function renderCustomerDistributionChart($customer_distribution) {
  415. $regions = [];
  416. $customers = [];
  417. $revenues = [];
  418. while ($row = $customer_distribution->fetch_assoc()) {
  419. $regions[] = $row['region'];
  420. $customers[] = $row['customer_count'];
  421. $revenues[] = $row['total_revenue'];
  422. }
  423. ?>
  424. <div class="analysis-grid">
  425. <div>
  426. <canvas id="regionDistributionChart"></canvas>
  427. </div>
  428. <div class="table-responsive">
  429. <table class="data-table">
  430. <thead>
  431. <tr>
  432. <th>地区</th>
  433. <th>客户数</th>
  434. <th>订单数</th>
  435. <th>销售金额</th>
  436. </tr>
  437. </thead>
  438. <tbody>
  439. <?php
  440. $customer_distribution->data_seek(0);
  441. while ($row = $customer_distribution->fetch_assoc()):
  442. ?>
  443. <tr>
  444. <td><?php echo $row['region']; ?></td>
  445. <td><?php echo number_format($row['customer_count']); ?></td>
  446. <td><?php echo number_format($row['order_count']); ?></td>
  447. <td>¥<?php echo number_format($row['total_revenue'], 2); ?></td>
  448. </tr>
  449. <?php endwhile; ?>
  450. </tbody>
  451. </table>
  452. </div>
  453. </div>
  454. <script>
  455. var regionDistributionCtx = document.getElementById('regionDistributionChart').getContext('2d');
  456. new Chart(regionDistributionCtx, {
  457. type: 'bar',
  458. data: {
  459. labels: <?php echo json_encode($regions); ?>,
  460. datasets: [
  461. {
  462. label: '客户数',
  463. data: <?php echo json_encode($customers); ?>,
  464. backgroundColor: 'rgba(75, 192, 192, 0.5)',
  465. borderColor: 'rgba(75, 192, 192, 1)',
  466. borderWidth: 1,
  467. yAxisID: 'y-customers'
  468. },
  469. {
  470. label: '销售金额',
  471. data: <?php echo json_encode($revenues); ?>,
  472. backgroundColor: 'rgba(255, 159, 64, 0.5)',
  473. borderColor: 'rgba(255, 159, 64, 1)',
  474. borderWidth: 1,
  475. yAxisID: 'y-revenue'
  476. }
  477. ]
  478. },
  479. options: {
  480. responsive: true,
  481. scales: {
  482. 'y-customers': {
  483. type: 'linear',
  484. position: 'left',
  485. title: {
  486. display: true,
  487. text: '客户数'
  488. }
  489. },
  490. 'y-revenue': {
  491. type: 'linear',
  492. position: 'right',
  493. title: {
  494. display: true,
  495. text: '销售金额'
  496. },
  497. grid: {
  498. drawOnChartArea: false
  499. }
  500. }
  501. }
  502. }
  503. });
  504. </script>
  505. <?php
  506. }
  507. /**
  508. * 渲染销售员业绩表格
  509. */
  510. function renderEmployeePerformanceTable($employee_performance) {
  511. // 添加空值检查函数
  512. ?>
  513. <div class="table-responsive">
  514. <table class="data-table">
  515. <thead>
  516. <tr>
  517. <th>销售员</th>
  518. <th>订单数</th>
  519. <th>客户数</th>
  520. <th>总销售额</th>
  521. <th>平均订单金额</th>
  522. </tr>
  523. </thead>
  524. <tbody>
  525. <?php while ($row = $employee_performance->fetch_assoc()): ?>
  526. <tr>
  527. <td><?php echo htmlspecialchars($row['employee_name']); ?></td>
  528. <td><?php echo formatNumber($row['order_count']); ?></td>
  529. <td><?php echo formatNumber($row['customer_count']); ?></td>
  530. <td><?php echo formatCurrency($row['total_revenue']); ?></td>
  531. <td><?php echo formatCurrency($row['avg_order_value']); ?></td>
  532. </tr>
  533. <?php endwhile; ?>
  534. </tbody>
  535. </table>
  536. </div>
  537. <?php
  538. }
  539. /**
  540. * 渲染支付状态分析图表
  541. */
  542. function renderPaymentStatusChart($payment_stats) {
  543. $status_names = [
  544. 0 => '未付款',
  545. 1 => '部分付款',
  546. 2 => '已付清'
  547. ];
  548. $data = [];
  549. while ($row = $payment_stats->fetch_assoc()) {
  550. $data[$row['payment_status']] = $row;
  551. }
  552. ?>
  553. <div class="analysis-grid">
  554. <div>
  555. <canvas id="paymentStatusChart"></canvas>
  556. </div>
  557. <div class="table-responsive">
  558. <table class="data-table">
  559. <thead>
  560. <tr>
  561. <th>支付状态</th>
  562. <th>订单数</th>
  563. <th>订单金额</th>
  564. </tr>
  565. </thead>
  566. <tbody>
  567. <?php foreach ($status_names as $status_id => $status_name): ?>
  568. <?php if (isset($data[$status_id])): ?>
  569. <tr>
  570. <td><?php echo $status_name; ?></td>
  571. <td><?php echo number_format($data[$status_id]['count']); ?></td>
  572. <td>¥<?php echo number_format($data[$status_id]['amount'], 2); ?></td>
  573. </tr>
  574. <?php endif; ?>
  575. <?php endforeach; ?>
  576. </tbody>
  577. </table>
  578. </div>
  579. </div>
  580. <script>
  581. var paymentStatusCtx = document.getElementById('paymentStatusChart').getContext('2d');
  582. new Chart(paymentStatusCtx, {
  583. type: 'pie',
  584. data: {
  585. labels: <?php
  586. $labels = [];
  587. $values = [];
  588. foreach ($status_names as $status_id => $status_name) {
  589. if (isset($data[$status_id])) {
  590. $labels[] = $status_name;
  591. $values[] = $data[$status_id]['amount'];
  592. }
  593. }
  594. echo json_encode($labels);
  595. ?>,
  596. datasets: [{
  597. data: <?php echo json_encode($values); ?>,
  598. backgroundColor: [
  599. '#FF6384',
  600. '#36A2EB',
  601. '#4BC0C0'
  602. ]
  603. }]
  604. },
  605. options: {
  606. responsive: true,
  607. plugins: {
  608. legend: {
  609. position: 'right'
  610. },
  611. title: {
  612. display: true,
  613. text: '支付状态分布'
  614. }
  615. }
  616. }
  617. });
  618. </script>
  619. <?php
  620. }
  621. /**
  622. * 渲染月度销售趋势图
  623. *
  624. * @param array $monthly_labels 月份标签
  625. * @param array $monthly_orders 月度订单数量
  626. * @param array $monthly_revenue 月度收入
  627. * @return void
  628. */
  629. function renderMonthlySalesTrendChart($monthly_labels, $monthly_orders, $monthly_revenue) {
  630. ?>
  631. <div class="chart-container">
  632. <div class="chart-header">
  633. <h2 class="chart-title">销售趋势</h2>
  634. </div>
  635. <canvas id="salesTrendChart"></canvas>
  636. </div>
  637. <script>
  638. var salesTrendCtx = document.getElementById('salesTrendChart').getContext('2d');
  639. new Chart(salesTrendCtx, {
  640. type: 'line',
  641. data: {
  642. labels: <?php echo json_encode($monthly_labels); ?>,
  643. datasets: [
  644. {
  645. label: '订单数量',
  646. data: <?php echo json_encode($monthly_orders); ?>,
  647. backgroundColor: 'rgba(54, 162, 235, 0.2)',
  648. borderColor: 'rgba(54, 162, 235, 1)',
  649. borderWidth: 2,
  650. yAxisID: 'y-orders',
  651. tension: 0.1
  652. },
  653. {
  654. label: '销售收入',
  655. data: <?php echo json_encode($monthly_revenue); ?>,
  656. backgroundColor: 'rgba(255, 99, 132, 0.2)',
  657. borderColor: 'rgba(255, 99, 132, 1)',
  658. borderWidth: 2,
  659. yAxisID: 'y-revenue',
  660. tension: 0.1
  661. }
  662. ]
  663. },
  664. options: {
  665. responsive: true,
  666. scales: {
  667. 'y-orders': {
  668. type: 'linear',
  669. position: 'left',
  670. title: {
  671. display: true,
  672. text: '订单数量'
  673. }
  674. },
  675. 'y-revenue': {
  676. type: 'linear',
  677. position: 'right',
  678. title: {
  679. display: true,
  680. text: '销售收入'
  681. },
  682. grid: {
  683. drawOnChartArea: false
  684. }
  685. }
  686. }
  687. }
  688. });
  689. </script>
  690. <?php
  691. }
  692. /**
  693. * 渲染详细订单趋势图
  694. *
  695. * @param array $time_labels 时间标签
  696. * @param array $time_orders 时间段订单数量
  697. * @param array $time_quantities 时间段产品数量
  698. * @param string $period 时间粒度
  699. * @return void
  700. */
  701. function renderDetailedOrderTrendChart($time_labels, $time_orders, $time_quantities, $period = 'day') {
  702. $period_text = $period == 'day' ? '日' : ($period == 'week' ? '周' : '月');
  703. ?>
  704. <div class="chart-container">
  705. <div class="chart-header">
  706. <h2 class="chart-title">详细订单趋势 (<?php echo $period_text; ?>)</h2>
  707. </div>
  708. <canvas id="detailedOrdersChart"></canvas>
  709. </div>
  710. <script>
  711. var detailedOrdersCtx = document.getElementById('detailedOrdersChart').getContext('2d');
  712. new Chart(detailedOrdersCtx, {
  713. type: 'line',
  714. data: {
  715. labels: <?php echo json_encode($time_labels); ?>,
  716. datasets: [
  717. {
  718. label: '订单数量',
  719. data: <?php echo json_encode($time_orders); ?>,
  720. backgroundColor: 'rgba(75, 192, 192, 0.2)',
  721. borderColor: 'rgba(75, 192, 192, 1)',
  722. borderWidth: 2,
  723. yAxisID: 'y-orders',
  724. tension: 0.1
  725. },
  726. {
  727. label: '产品订购数量',
  728. data: <?php echo json_encode($time_quantities); ?>,
  729. backgroundColor: 'rgba(255, 159, 64, 0.2)',
  730. borderColor: 'rgba(255, 159, 64, 1)',
  731. borderWidth: 2,
  732. yAxisID: 'y-quantity',
  733. tension: 0.1
  734. }
  735. ]
  736. },
  737. options: {
  738. responsive: true,
  739. scales: {
  740. x: {
  741. title: {
  742. display: true,
  743. text: '时间'
  744. }
  745. },
  746. 'y-orders': {
  747. type: 'linear',
  748. position: 'left',
  749. title: {
  750. display: true,
  751. text: '订单数量'
  752. },
  753. beginAtZero: true
  754. },
  755. 'y-quantity': {
  756. type: 'linear',
  757. position: 'right',
  758. title: {
  759. display: true,
  760. text: '产品订购数量'
  761. },
  762. beginAtZero: true,
  763. grid: {
  764. drawOnChartArea: false
  765. }
  766. }
  767. }
  768. }
  769. });
  770. </script>
  771. <?php
  772. }
  773. /**
  774. * 获取所有业务员列表
  775. */
  776. function getAllEmployees($conn) {
  777. $sql = "SELECT id, em_user, em_email, em_tel FROM employee ORDER BY em_user";
  778. $result = $conn->query($sql);
  779. return $result->fetch_all(MYSQLI_ASSOC);
  780. }
  781. /**
  782. * 获取业务员详细信息
  783. */
  784. function getEmployeeDetail($conn, $employee_id) {
  785. $sql = "SELECT id, em_user, em_email, em_tel FROM employee WHERE id = ?";
  786. $stmt = $conn->prepare($sql);
  787. $stmt->bind_param("i", $employee_id);
  788. $stmt->execute();
  789. return $stmt->get_result()->fetch_assoc();
  790. }
  791. /**
  792. * 获取业务员统计数据
  793. */
  794. function getEmployeeStats($conn, $employee_id, $start_date, $end_date) {
  795. $sql = "SELECT
  796. COUNT(DISTINCT o.id) as total_orders,
  797. SUM(o.total_amount) as total_revenue,
  798. COUNT(DISTINCT o.customer_id) as customer_count,
  799. AVG(o.total_amount) as avg_order_value,
  800. SUM(CASE WHEN o.order_status = 5 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as completion_rate
  801. FROM orders o
  802. WHERE o.employee_id = ?
  803. AND o.order_date BETWEEN ? AND ?
  804. AND o.order_status != 0";
  805. $stmt = $conn->prepare($sql);
  806. $stmt->bind_param("iss", $employee_id, $start_date, $end_date);
  807. $stmt->execute();
  808. return $stmt->get_result()->fetch_assoc();
  809. }
  810. /**
  811. * 渲染业务员销售趋势
  812. */
  813. function renderEmployeeSalesTrend($conn, $employee_id, $start_date, $end_date) {
  814. $sql = "SELECT
  815. DATE_FORMAT(order_date, '%Y-%m-%d') as date,
  816. COUNT(DISTINCT id) as orders,
  817. SUM(total_amount) as revenue
  818. FROM orders
  819. WHERE employee_id = ?
  820. AND order_date BETWEEN ? AND ?
  821. AND order_status != 0
  822. GROUP BY DATE_FORMAT(order_date, '%Y-%m-%d')
  823. ORDER BY date";
  824. $stmt = $conn->prepare($sql);
  825. $stmt->bind_param("iss", $employee_id, $start_date, $end_date);
  826. $stmt->execute();
  827. $result = $stmt->get_result();
  828. $dates = [];
  829. $orders = [];
  830. $revenues = [];
  831. while ($row = $result->fetch_assoc()) {
  832. $dates[] = $row['date'];
  833. $orders[] = $row['orders'];
  834. $revenues[] = $row['revenue'];
  835. }
  836. ?>
  837. <div class="chart-container">
  838. <div class="chart-header">
  839. <h2 class="chart-title">销售趋势</h2>
  840. </div>
  841. <canvas id="employeeSalesTrendChart"></canvas>
  842. </div>
  843. <script>
  844. var employeeSalesTrendCtx = document.getElementById('employeeSalesTrendChart').getContext('2d');
  845. new Chart(employeeSalesTrendCtx, {
  846. type: 'line',
  847. data: {
  848. labels: <?php echo json_encode($dates); ?>,
  849. datasets: [
  850. {
  851. label: '订单数量',
  852. data: <?php echo json_encode($orders); ?>,
  853. backgroundColor: 'rgba(54, 162, 235, 0.2)',
  854. borderColor: 'rgba(54, 162, 235, 1)',
  855. borderWidth: 2,
  856. yAxisID: 'y-orders',
  857. tension: 0.1
  858. },
  859. {
  860. label: '销售收入',
  861. data: <?php echo json_encode($revenues); ?>,
  862. backgroundColor: 'rgba(255, 99, 132, 0.2)',
  863. borderColor: 'rgba(255, 99, 132, 1)',
  864. borderWidth: 2,
  865. yAxisID: 'y-revenue',
  866. tension: 0.1
  867. }
  868. ]
  869. },
  870. options: {
  871. responsive: true,
  872. scales: {
  873. 'y-orders': {
  874. type: 'linear',
  875. position: 'left',
  876. title: {
  877. display: true,
  878. text: '订单数量'
  879. }
  880. },
  881. 'y-revenue': {
  882. type: 'linear',
  883. position: 'right',
  884. title: {
  885. display: true,
  886. text: '销售收入'
  887. },
  888. grid: {
  889. drawOnChartArea: false
  890. }
  891. }
  892. }
  893. }
  894. });
  895. </script>
  896. <?php
  897. }
  898. /**
  899. * 渲染业务员客户分布
  900. */
  901. function renderEmployeeCustomerDistribution($conn, $employee_id, $start_date, $end_date) {
  902. $sql = "SELECT
  903. c.countryName as region,
  904. COUNT(DISTINCT o.customer_id) as customer_count,
  905. SUM(o.total_amount) as total_revenue
  906. FROM orders o
  907. JOIN customer cu ON o.customer_id = cu.id
  908. JOIN country c ON cu.cs_country = c.id
  909. WHERE o.employee_id = ?
  910. AND o.order_date BETWEEN ? AND ?
  911. AND o.order_status != 0
  912. GROUP BY c.id
  913. ORDER BY total_revenue DESC
  914. LIMIT 10";
  915. $stmt = $conn->prepare($sql);
  916. $stmt->bind_param("iss", $employee_id, $start_date, $end_date);
  917. $stmt->execute();
  918. $result = $stmt->get_result();
  919. $regions = [];
  920. $customers = [];
  921. $revenues = [];
  922. while ($row = $result->fetch_assoc()) {
  923. $regions[] = $row['region'];
  924. $customers[] = $row['customer_count'];
  925. $revenues[] = $row['total_revenue'];
  926. }
  927. ?>
  928. <div class="chart-container">
  929. <div class="chart-header">
  930. <h2 class="chart-title">客户地区分布 (Top 10)</h2>
  931. </div>
  932. <canvas id="employeeCustomerChart"></canvas>
  933. </div>
  934. <script>
  935. var employeeCustomerCtx = document.getElementById('employeeCustomerChart').getContext('2d');
  936. new Chart(employeeCustomerCtx, {
  937. type: 'bar',
  938. data: {
  939. labels: <?php echo json_encode($regions); ?>,
  940. datasets: [
  941. {
  942. label: '客户数',
  943. data: <?php echo json_encode($customers); ?>,
  944. backgroundColor: 'rgba(75, 192, 192, 0.5)',
  945. borderColor: 'rgba(75, 192, 192, 1)',
  946. borderWidth: 1,
  947. yAxisID: 'y-customers'
  948. },
  949. {
  950. label: '销售金额',
  951. data: <?php echo json_encode($revenues); ?>,
  952. backgroundColor: 'rgba(255, 159, 64, 0.5)',
  953. borderColor: 'rgba(255, 159, 64, 1)',
  954. borderWidth: 1,
  955. yAxisID: 'y-revenue'
  956. }
  957. ]
  958. },
  959. options: {
  960. responsive: true,
  961. scales: {
  962. 'y-customers': {
  963. type: 'linear',
  964. position: 'left',
  965. title: {
  966. display: true,
  967. text: '客户数'
  968. }
  969. },
  970. 'y-revenue': {
  971. type: 'linear',
  972. position: 'right',
  973. title: {
  974. display: true,
  975. text: '销售金额'
  976. },
  977. grid: {
  978. drawOnChartArea: false
  979. }
  980. }
  981. }
  982. }
  983. });
  984. </script>
  985. <?php
  986. }
  987. /**
  988. * 渲染业务员产品销售分析
  989. */
  990. function renderEmployeeProductAnalysis($conn, $employee_id, $start_date, $end_date) {
  991. $sql = "SELECT
  992. pc.name as category_name,
  993. COUNT(DISTINCT o.id) as order_count,
  994. SUM(oi.quantity) as total_quantity,
  995. SUM(oi.total_price) as total_revenue
  996. FROM orders o
  997. JOIN order_items oi ON o.id = oi.order_id
  998. JOIN products p ON oi.product_id = p.id
  999. JOIN product_categories pc ON p.category_id = pc.id
  1000. WHERE o.employee_id = ?
  1001. AND o.order_date BETWEEN ? AND ?
  1002. AND o.order_status != 0
  1003. GROUP BY pc.id
  1004. ORDER BY total_revenue DESC";
  1005. $stmt = $conn->prepare($sql);
  1006. $stmt->bind_param("iss", $employee_id, $start_date, $end_date);
  1007. $stmt->execute();
  1008. $result = $stmt->get_result();
  1009. $categories = [];
  1010. $quantities = [];
  1011. $revenues = [];
  1012. while ($row = $result->fetch_assoc()) {
  1013. $categories[] = $row['category_name'];
  1014. $quantities[] = $row['total_quantity'];
  1015. $revenues[] = $row['total_revenue'];
  1016. }
  1017. ?>
  1018. <div class="chart-container">
  1019. <div class="chart-header">
  1020. <h2 class="chart-title">产品类别销售分析</h2>
  1021. </div>
  1022. <canvas id="employeeProductChart"></canvas>
  1023. </div>
  1024. <script>
  1025. var employeeProductCtx = document.getElementById('employeeProductChart').getContext('2d');
  1026. new Chart(employeeProductCtx, {
  1027. type: 'bar',
  1028. data: {
  1029. labels: <?php echo json_encode($categories); ?>,
  1030. datasets: [
  1031. {
  1032. label: '销售数量',
  1033. data: <?php echo json_encode($quantities); ?>,
  1034. backgroundColor: 'rgba(54, 162, 235, 0.5)',
  1035. borderColor: 'rgba(54, 162, 235, 1)',
  1036. borderWidth: 1,
  1037. yAxisID: 'y-quantity'
  1038. },
  1039. {
  1040. label: '销售金额',
  1041. data: <?php echo json_encode($revenues); ?>,
  1042. backgroundColor: 'rgba(255, 99, 132, 0.5)',
  1043. borderColor: 'rgba(255, 99, 132, 1)',
  1044. borderWidth: 1,
  1045. yAxisID: 'y-revenue'
  1046. }
  1047. ]
  1048. },
  1049. options: {
  1050. responsive: true,
  1051. scales: {
  1052. 'y-quantity': {
  1053. type: 'linear',
  1054. position: 'left',
  1055. title: {
  1056. display: true,
  1057. text: '销售数量'
  1058. }
  1059. },
  1060. 'y-revenue': {
  1061. type: 'linear',
  1062. position: 'right',
  1063. title: {
  1064. display: true,
  1065. text: '销售金额'
  1066. },
  1067. grid: {
  1068. drawOnChartArea: false
  1069. }
  1070. }
  1071. }
  1072. }
  1073. });
  1074. </script>
  1075. <?php
  1076. }
  1077. /**
  1078. * 渲染业务员统计卡片
  1079. */
  1080. function renderEmployeeStats($employee_stats) {
  1081. // 添加空值检查函数
  1082. function formatNumber($value, $decimals = 0) {
  1083. return number_format($value ?? 0, $decimals);
  1084. }
  1085. function formatCurrency($value) {
  1086. return '¥' . number_format($value ?? 0, 2);
  1087. }
  1088. ?>
  1089. <div class="performance-grid">
  1090. <div class="performance-card">
  1091. <div class="performance-label">总订单数</div>
  1092. <div class="performance-value"><?php echo formatNumber($employee_stats['total_orders']); ?></div>
  1093. </div>
  1094. <div class="performance-card">
  1095. <div class="performance-label">总销售额</div>
  1096. <div class="performance-value"><?php echo formatCurrency($employee_stats['total_revenue']); ?></div>
  1097. </div>
  1098. <div class="performance-card">
  1099. <div class="performance-label">客户数量</div>
  1100. <div class="performance-value"><?php echo formatNumber($employee_stats['customer_count']); ?></div>
  1101. </div>
  1102. <div class="performance-card">
  1103. <div class="performance-label">平均订单金额</div>
  1104. <div class="performance-value"><?php echo formatCurrency($employee_stats['avg_order_value']); ?></div>
  1105. </div>
  1106. <div class="performance-card">
  1107. <div class="performance-label">订单完成率</div>
  1108. <div class="performance-value"><?php echo formatNumber($employee_stats['completion_rate'], 1); ?>%</div>
  1109. </div>
  1110. </div>
  1111. <?php
  1112. }