diff --git a/application/admin/controller/statistics/Item.php b/application/admin/controller/statistics/Item.php index 103475d..437cb35 100644 --- a/application/admin/controller/statistics/Item.php +++ b/application/admin/controller/statistics/Item.php @@ -13,6 +13,7 @@ use think\Collection; use think\Exception; use think\exception\DbException; use think\Loader; +use think\Model; use think\response\Json; use function Symfony\Component\Clock\now; @@ -25,7 +26,6 @@ class Item extends Backend { - protected $relationSearch = true; public function _initialize() @@ -38,14 +38,15 @@ class Item extends Backend return $this->fetch('index'); } - public function list(){ - + public function list() + { $build = new Order(); $start = now()->modify('-14 days')->format('Y-m-d'); $end_at = now()->format('Y-m-d 23:29:59'); - $filter ['daterange'] = request()->get('daterange'); + $filter = json_decode(request()->get('filter','[]'),true); + if (!empty($filter['daterange'])) { $arr = explode(' - ', $filter['daterange']); if (trim($arr[0])) { @@ -55,18 +56,134 @@ class Item extends Backend $end_at = trim($arr[1]) . ' 23:29:59'; } } - $build->field([ - 'item_title', - 'sum(total) total', - 'count(id) count', - 'sum(performance) performance' - ])->group('item_title'); + $offset = request()->get('offset',0); + $limit = request()->get('limit',15); + $build->whereBetween('create_time', [$start, $end_at]) + ->field([ + 'item_title name', // 类型 + 'sum(total) total', // 营业额 + 'count(id) count_num', // 单量 + 'count(if(status=60,1,null)) finish_num', // 单量 + 'sum(performance) performance', // 收益 + 'sum(cost) cost', // 收益 + 'sum(material_cost) material_cost', // 收益 + 'sum(refund_amount) refund_amount', // 公司退款 + 'sum(worker_refund_amount) worker_refund_amount', // 工人退款 + ])->group('item_title') + ->order('count_num', 'desc'); +// dd($total); + $res = $build->paginate(); + $total = $res->total(); + $ress = $res->items(); + $data = []; +// dd($res); + foreach ($ress as $re) { + $re = $re->toArray(); +// dd($re); + $cost_total = $re['cost'] + $re['material_cost']; + $name = explode('/', $re['name']); + $re['name'] = str_replace(' ', '', array_pop($name)); + $re['performance_rate'] = $this->mydiv($re['performance'],$re['total']); + $re['trans_rate'] = $this->mydiv($re['finish_num'],$re['count_num']); + $re['cash_value'] = $this->mydiv($re['performance'],$re['count_num']); + $re['total_avg'] = $this->mydiv($re['total'],$re['count_num'],2,false); + $re['performance_avg'] = $this->mydiv($re['performance'],$re['finish_num'],2,false); + + $re['cost_total'] = $cost_total; +// $re['id'] = $re['item_id']; + $data [] = $re; + } + + return [ + 'rows' => $data, + 'total' => $total + ]; - return []; } - public function chartData(){ - return []; + public function chartData() + { + $build = new Order(); + + $start = now()->modify('-14 days')->format('Y-m-d'); + $end_at = now()->format('Y-m-d 23:29:59'); + + $filter ['daterange'] = request()->post('daterange'); + + if (!empty($filter['daterange'])) { + $arr = explode(' - ', $filter['daterange']); + if (trim($arr[0])) { + $start = trim($arr[0]); + } + if (trim($arr[1])) { + $end_at = trim($arr[1]) . ' 23:29:59'; + } + } + + $res = $build->whereBetween('create_time', [$start, $end_at]) + ->where('status', Order::STATUS_FINISHED) + ->field([ + 'item_title name', // 类型 + 'sum(total) total', // 营业额 + 'count(id) count', // 单量 + 'sum(performance) performance', // 收益 + 'sum(refund_amount) refund_amount', // 公司退款 + 'sum(worker_refund_amount) worker_refund_amount', // 工人退款 + ])->group('item_title') + ->order('count', 'desc')->select(); + $data = []; + foreach ($res as $re) { + $re = $re->getData(); + $name = explode('/', $re['name']); + $re['name'] = str_replace(' ', '', array_pop($name)); + $data [] = $re; + } + + $xAxis = []; + $totalPerformance = []; + $conversionRate = []; // 假设转化率 = performance / total(或自定义逻辑) + $profitRate = []; // 假设利润率 = (performance - worker_refund_amount) / performance + $refundRate = []; // 假设退款率 = refund_amount / total + $monetizedValue = []; // 假设变现值 = performance - refund_amount + + foreach ($data as $item) { + $name = $item['name']; + $total = (float)$item['total']; + $performance = (float)$item['performance']; + $refund = (float)($item['refund_amount'] + $item['worker_refund_amount']); + $workerRefund = (float)$item['worker_refund_amount']; + $count = max((int)$item['count'], 1); // 避免除以0 + + $xAxis[] = $name; + $totalPerformance[] = $total; + $conversionRate[] = $total > 0 ? round($performance / $total * 100, 2) : 0; + $profitRate[] = $total > 0 ? round(($total - $workerRefund) / $total * 100, 2) : 0; + $refundRate[] = $total > 0 ? round($refund / $total * 100, 2) : 0; + $monetizedValue[] = $total / $count; + } + $result = [ + 'xAxis' => $xAxis, + 'series' => [ + ['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => $totalPerformance], + ['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $conversionRate], + ['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $profitRate], + ['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $refundRate], + ['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => $monetizedValue], + ] + ]; + + return $result; + } + + private function mydiv($a, $b, int $scale = 4, $is_percent = true): int|string + { + $val = $b > 0 ? bcdiv($a, $b, $scale) : 0; + + if ($is_percent) { + + return bcmul($val, 100, 2); + } + return $val; } } diff --git a/application/admin/view/statistics/item/index.html b/application/admin/view/statistics/item/index.html index b74f52d..749ac6e 100644 --- a/application/admin/view/statistics/item/index.html +++ b/application/admin/view/statistics/item/index.html @@ -24,19 +24,9 @@
- -
- - -
-
- +
diff --git a/public/assets/js/backend/statistics/item.js b/public/assets/js/backend/statistics/item.js index 409a941..97349e2 100644 --- a/public/assets/js/backend/statistics/item.js +++ b/public/assets/js/backend/statistics/item.js @@ -1,4 +1,4 @@ -define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-theme', 'template','addtabs','moment'], function ($, undefined, Backend, Table, Form,Echarts,undefined,Template,Datatable,Moment) { +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-theme', 'template','addtabs','moment'], function ($, undefined, Backend, Table, Form,echarts,undefined,Template,Datatable,Moment) { var Controller = { @@ -28,29 +28,28 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t searchFormVisible:true, columns: [ [ - //{field: 'id', title: __('Id')}, - {field: 'id', title: __('ID'),visible:true,operate: false}, - {field: 'admin_user', title: __('派单员'),operate: "LIKE"}, - {field: 'count_num', title: __('总订单数'),operate: false}, - {field: 'finish_num', title: __('完单数'),operate: false}, + // {field: 'id', title: __('ID'),visible:true,operate: false}, + {field: 'name', title: '项目类型',operate: false}, {field: 'total', title: __('成效额(¥)'), operate: false}, {field: 'performance', title: __('总业绩(¥)'), operate: false}, - {field: 'cost_total', title: __('总成本(¥)'), operate: false}, - {field: 'refund_total', title: __('退款金额(¥)'), operate: false}, - {field: 'refund_count', title: __('退款单数'), operate: false}, + {field: 'count_num', title: __('总订单数'),operate: false}, {field: 'performance_rate', title: __('利润率(%)'), operate: false}, + {field: 'trans_rate', title: __('转化率(%)'), operate: false}, {field: 'cash_value', title: __('变现值'), operate: false}, + {field: 'performance_avg', title: __('客单利润(¥)'), operate: false}, {field: 'total_avg', title: __('客单价(¥)'), operate: false}, {field: 'avg_time_diff', title: __('派单时效(小时)'), operate: false}, - //{field: 'admin_user', title: __('派单员'),operate: "LIKE",visible:false}, - //{field: 'city_name', title: __('城市'),operate: "LIKE",visible:false}, - //{field: 'city_name', title: __('城市'),operate: "LIKE",visible:false}, - {field: 'time_by', title: __('时间维度'), visible:false,searchList: {"1":__('录单时间'),"2":__('派单时间')},defaultValue:1, formatter: Table.api.formatter.normal}, + {field: 'cost_total', title: __('总成本(¥)'), operate: false}, + {field: 'finish_num', title: __('完单数'),operate: false}, + + {field: 'refund_count', title: __('退款单数'), operate: false}, + {field: 'refund_total', title: __('退款金额(¥)'), operate: false}, + {field: 'daterange', title: __('时间筛选'), addclass:'datetimerange', autocomplete:false, operate: "RANGE", @@ -60,24 +59,26 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t visible:false, defaultValue: Config.default_daterange }, - {field: 'operate', title: __('Operate'), table: table2, events: Table.api.events.operate, formatter: Table.api.formatter.operate, - buttons: [ - { - name: 'aftersales', - text:"售后列表", - title:"售后列表", - icon: 'fa fa-list', - url: function(row){ - return 'aftersales/aftersale/index?from=2&man_id='+row.id; - }, - extend: 'data-toggle="tooltip" data-container="body"', - classname: 'btn btn-xs btn-default btn-dialog', - visible:function(row){ - return true; - } - }, - ] - } + // {field: 'operate', title: __('Operate'), table: table2, events: + // Table.api.events.operate, + // formatter: Table.api.formatter.operate, + // buttons: [ + // { + // name: 'aftersales', + // text:"售后列表", + // title:"售后列表", + // icon: 'fa fa-list', + // url: function(row){ + // return 'aftersales/aftersale/index?from=2&man_id='+row.id; + // }, + // extend: 'data-toggle="tooltip" data-container="body"', + // classname: 'btn btn-xs btn-default btn-dialog', + // visible:function(row){ + // return true; + // } + // }, + // ] + // } ] ] }); @@ -87,8 +88,6 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t }); - - // 触发 tab 后发起 ajax 获取图表数据 $('ul.nav-tabs li.active a[data-toggle="tab"]').on("shown.bs.tab", function () { getChartData(); @@ -96,16 +95,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t function getChartData(){ - // 获取单选框选中的值 - var timeBy = $('input[name="filter[time_by]"]:checked').val(); - // 获取日期范围值 var daterange = $('#daterange').val(); // 构建查询参数 var params = { - 'time_by': timeBy, - 'daterange': daterange + 'daterange': daterange, }; $.ajax({ @@ -114,7 +109,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t dataType: "json", data:params, success: function (response) { - + Controller.api.chart(response); }, error: function () { console.error("图表数据加载失败"); @@ -187,6 +182,35 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t api: { bindevent: function () { Form.api.bindevent($("form[role=form]")); + }, + chart(chartData){ + const option = { + tooltip: { + trigger: 'axis', + axisPointer: { type: 'shadow' } + }, + legend: { + data: chartData.series.map(s => s.name) + }, + xAxis: { + type: 'category', + data: chartData.xAxis + }, + yAxis: [ + { type: 'value', name: '金额 (¥)' }, + { type: 'value', name: '比率 (%)' } + ], + series: chartData.series + }; + +// 初始化图表 + const myChart = echarts.init(document.getElementById('bar-chart')); + myChart.setOption(option); + + // 监听窗口大小变化,自动重新绘制图表 + window.addEventListener('resize', function() { + myChart.resize(); + }); } } };