This commit is contained in:
xman 2025-04-25 16:22:05 +08:00
parent 0c2629f236
commit 78623550ba
2 changed files with 540 additions and 117 deletions

View File

@ -1,26 +1,336 @@
<style>
.sm-st {
background: #fff;
padding: 20px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
margin-bottom: 20px;
}
.sm-st-icon {
width: 60px;
height: 60px;
display: inline-block;
line-height: 60px;
text-align: center;
font-size: 30px;
background: #eee;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
float: left;
margin-right: 10px;
color: #fff;
}
.sm-st-info {
padding-top: 2px;
}
.sm-st-info span {
display: block;
font-size: 14px;
font-weight: 600;
}
.orange {
background: #fa8564 !important;
}
.tar {
background: #45cf95 !important;
}
.sm-st .green {
background: #86ba41 !important;
}
.pink {
background: #AC75F0 !important;
}
.yellow-b {
background: #fdd752 !important;
}
.stat-elem {
background-color: #fff;
padding: 18px;
border-radius: 40px;
}
.stat-info {
text-align: center;
background-color: #fff;
border-radius: 5px;
margin-top: -5px;
padding: 8px;
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.05);
font-style: italic;
}
.stat-icon {
text-align: center;
margin-bottom: 5px;
}
.st-red {
background-color: #F05050;
}
.st-green {
background-color: #27C24C;
}
.st-violet {
background-color: #7266ba;
}
.st-blue {
background-color: #23b7e5;
}
.stats .stat-icon {
color: #28bb9c;
display: inline-block;
font-size: 26px;
text-align: center;
vertical-align: middle;
width: 50px;
float: left;
}
.stat {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
.stat .value {
font-size: 20px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 500;
}
.stat .name {
overflow: hidden;
text-overflow: ellipsis;
margin: 5px 0;
}
.stat.lg .value {
font-size: 26px;
line-height: 28px;
}
.stat-col {
margin:0 0 10px 0;
}
.stat.lg .name {
font-size: 16px;
}
.stat-col .progress {
height: 2px;
}
.stat-col .progress-bar {
line-height: 2px;
height: 2px;
}
.item {
padding: 30px 0;
}
#statistics .panel {
min-height: 150px;
}
#statistics .panel h5 {
font-size: 14px;
}
</style>
<div class="panel panel-default panel-intro">
<div class="panel-heading">
<!--<div class="panel-lead"><em>多表格Multitable</em>用于展示在一个页面展示多个表格数据,并且每次切换时刷新</div>-->
<ul class="nav nav-tabs">
<li class="active"><a data-val="first" href="#first" data-toggle="tab">统计图表</a></li>
<li><a href="#second" data-val="second" data-toggle="tab">统计列表</a></li>
</ul>
</div>
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
</div>
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('statistics/worker/edit')}"
data-operate-del="{:$auth->check('statistics/worker/del')}"
width="100%">
</table>
</div>
</div>
<div class="panel-body" style="height: 100%">
<div id="myTabContent" class="tab-content" style="height: 100%">
<div class="tab-pane fade active in" style="height: 100%" id="first">
<section class="connectedSortable" style="height: 100%">
<div class="nav-tabs-custom charts-custom">
<!-- Tabs within a box -->
<ul class="nav nav-tabs pull-right">
<li class="active"><a href="#bar-chart" data-toggle="tab">地图分布</a></li>
<!-- <li><a href="#pie-chart" data-toggle="tab">饼图</a></li>-->
<li class="pull-left header"><i class="fa fa-inbox"></i> 师傅统计图</li>
</ul>
<div class="row">
<div class="col-sm-3 col-xs-6">
<div class="sm-st clearfix">
<span class="sm-st-icon st-blue"><i class="fa fa-users"></i></span>
<div class="sm-st-info">
<span>100/10</span>
师傅总数/活跃数
</div>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="sm-st clearfix">
<span class="sm-st-icon st-green"><i class="fa fa-users"></i></span>
<div class="sm-st-info">
<span>100</span>
今日新增
</div>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="sm-st clearfix">
<span class="sm-st-icon st-violet"><i class="fa fa-users"></i></span>
<div class="sm-st-info">
<span>100</span>
师傅总数
</div>
</div>
</div>
<div class="col-sm-3 col-xs-6">
<div class="sm-st clearfix">
<span class="sm-st-icon st-red"><i class="fa fa-users"></i></span>
<div class="sm-st-info">
<span>100</span>
师傅总数
</div>
</div>
</div>
</div>
<div class="tab-content no-padding" style="height: 100%">
<div class="chart tab-pane active" id="bar-chart" style="width: 100%; min-height:600px;height: 100%">
</div>
</div>
</div>
</section>
</div>
<div class="tab-pane fade" id="second">
<table id="table2" class="table table-striped table-bordered table-hover" width="100%">
</table>
</div>
</div>
</div>
</div>
<!-- 先加载 mapv -->
<script src="https://mapv.baidu.com/build/mapv.min.js"></script>
<!-- 再加载 mapvgl -->
<script src="https://code.bdstatic.com/npm/mapvgl@1.0.0-beta.189/dist/mapvgl.min.js"></script>
<!-- 最后加载百度地图并回调 -->
<!-- 加载百度地图 GL -->
<script>
window.initMap = function () {
const map = new BMapGL.Map("bar-chart");
map.centerAndZoom(new BMapGL.Point(104.0665, 30.5728), 7);
map.enableScrollWheelZoom(true);
map.addEventListener('tilesloaded', function () {
const view = new mapvgl.View({ map });
// ✅ 写死的点数据10个城市
const data = [
{ geometry: { type: 'Point', coordinates: [116.4074, 39.9042] } }, // 北京
{ geometry: { type: 'Point', coordinates: [121.4737, 31.2304] } }, // 上海
{ geometry: { type: 'Point', coordinates: [113.2644, 23.1291] } }, // 广州
{ geometry: { type: 'Point', coordinates: [114.0579, 22.5431] } }, // 深圳
{ geometry: { type: 'Point', coordinates: [117.2000, 39.1333] } }, // 天津
{ geometry: { type: 'Point', coordinates: [120.1551, 30.2741] } }, // 杭州
{ geometry: { type: 'Point', coordinates: [104.0665, 30.5728] } }, // 成都
{ geometry: { type: 'Point', coordinates: [106.5516, 29.5630] } }, // 重庆
{ geometry: { type: 'Point', coordinates: [108.9480, 34.2632] } }, // 西安
{ geometry: { type: 'Point', coordinates: [115.8582, 28.6829] } } // 南昌
];
// 根据聚合区域内的点数决定颜色(数量多,颜色深)
const gradient = {
0: 'rgba(162, 180, 73, 0.7)', // 浅绿
0.3: 'rgba(121, 153, 0, 0.8)', // 中绿
0.7: 'rgba(99, 121, 0, 0.9)', // 深绿
1.0: 'rgba(66, 77, 0, 1)' // 更深绿
};
// 定义聚合层
const clusterLayer = new mapvgl.ClusterLayer({
minSize: 30,
maxSize: 50,
clusterRadius: 150,
gradient: gradient, // 设置颜色渐变
showText: true,
textOptions: {
fontSize: 12,
color: 'white',
format: function (count) {
if (count === 0) return ''; // 数量为0时不展示
return count >= 1000 ? Math.round(count / 100) / 10 + 'k' : count; // 格式化数字
}
},
iconOptions: {
fillStyle: 'rgba(162, 180, 73, 0.7)', // 主色调:浅绿色
lineWidth: 1,
strokeStyle: 'rgba(121, 153, 0, 1)', // 边框:绿色
radius: 19, // 圆形点
shadowBlur: 5, // 阴影效果
shadowColor: 'rgba(244, 255, 204, 0.7)', // 阴影颜色
},
// 让每个点有自己的外观样式
icon: function (count) {
// 使用样式文本:点的数量越多,颜色越深
return {
backgroundColor: `rgba(121, 153, 0, 0.7)`, // 点背景颜色
height: '38px', // 高度
width: '38px', // 宽度
border: '1px solid rgb(121, 153, 0)', // 边框
borderRadius: '19px', // 边角半径(圆形)
boxShadow: 'rgb(244, 255, 204) 0px 0px 5px', // 阴影
lineHeight: '38px', // 行高
color: 'rgb(244, 255, 204)', // 字体颜色(淡黄)
fontSize: '14px', // 字体大小
textAlign: 'center', // 文本居中
cursor: 'pointer' // 鼠标样式为指针
};
}
});
// 添加数据并展示
view.addLayer(clusterLayer);
clusterLayer.setData(data);
});
};
</script>
<script src="https://api.map.baidu.com/api?type=webgl&v=1.0&ak=UQjHtbQQoDQRpAE5QLGFvC8exM6rcu69&callback=initMap"></script>

View File

@ -1,113 +1,221 @@
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-theme', 'template','addtabs','moment'], function ($, undefined, Backend, Table, Form,Echarts,undefined,Template,Datatable,Moment) {
var Controller = {
index: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: 'statistics/worker/index' + location.search,
add_url: 'statistics/worker/add',
edit_url: 'statistics/worker/edit',
del_url: 'statistics/worker/del',
multi_url: 'statistics/worker/multi',
import_url: 'statistics/worker/import',
table: 'worker',
}
});
//绑定事件
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var $targetPanel = $($(this).attr("href"));
var tabVal = $(this).data('val');
var table = $("#table");
if (tabVal === 'second') {
// 当切换到“统计列表”时,自动刷新表格
//$targetPanel.find(".btn-refresh").trigger("click");
// 初始化表格参数配置
Table.api.init();
// 表格2
var table2 = $("#table2");
table2.bootstrapTable({
url: 'statistics/worker/index' + location.search,
toolbar: '#toolbar1',
sortName: 'id',
search: false,
commonSearch:true,
visible: false,
showToggle: false,
showColumns: false,
showExport: true,
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: '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},
// 初始化表格
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: 'id',
sortName: 'id',
fixedColumns: true,
fixedRightNumber: 1,
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'admin_id', title: __('Admin_id')},
{field: 'type', title: __('Type')},
{field: 'name', title: __('Name'), operate: 'LIKE'},
{field: 'tel', title: __('Tel'), operate: 'LIKE'},
{field: 'status', title: __('Status'), searchList: {"1":__('Status 1'),"0":__('Status 0')}, formatter: Table.api.formatter.status},
{field: 'area_id', title: __('Area_id')},
{field: 'lng', title: __('Lng'), operate:'BETWEEN'},
{field: 'lat', title: __('Lat'), operate:'BETWEEN'},
{field: 'location_update_time', title: __('Location_update_time'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'deposit_amount', title: __('Deposit_amount'), operate:'BETWEEN'},
{field: 'star', title: __('Star'), operate:'BETWEEN'},
{field: 'create_time', title: __('Create_time'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'update_time', title: __('Update_time'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
]
]
});
{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},
// 为表格绑定事件
Table.api.bindevent(table);
},
recyclebin: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
'dragsort_url': ''
}
});
{field: 'avg_time_diff', title: __('派单时效(天)'), operate: false},
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: 'statistics/worker/recyclebin' + location.search,
pk: 'id',
sortName: 'id',
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},
{field: 'name', title: __('Name'), align: 'left'},
{
field: 'deletetime',
title: __('Deletetime'),
operate: 'RANGE',
addclass: 'datetimerange',
formatter: Table.api.formatter.datetime
},
{
field: 'operate',
width: '140px',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
buttons: [
{
name: 'Restore',
text: __('Restore'),
classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
icon: 'fa fa-rotate-left',
url: 'statistics/worker/restore',
refresh: true
//{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: 'daterange', title: __('时间筛选'), addclass:'datetimerange',
autocomplete:false,
operate: "RANGE",
datetimeFormat: "YYYY-MM-DD",
//defaultValue:today()+' - '+today(),
data:'autocomplete="off" data-local={"format":"YYYY-MM-DD"}',
visible:false,
defaultValue: Config.default_daterange
},
{
name: 'Destroy',
text: __('Destroy'),
classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
icon: 'fa fa-times',
url: 'statistics/worker/destroy',
refresh: 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&dispatch_admin_id='+row.id;
},
extend: 'data-toggle="tooltip" data-container="body"',
classname: 'btn btn-xs btn-default btn-dialog',
visible:function(row){
return true;
}
},
]
}
],
formatter: Table.api.formatter.operate
}
]
]
]
]
});
// 为表格2绑定事件
Table.api.bindevent(table2);
}
});
// 为表格绑定事件
Table.api.bindevent(table);
// 触发 tab 后发起 ajax 获取图表数据
$('ul.nav-tabs li.active a[data-toggle="tab"]').on("shown.bs.tab", function () {
getChartData();
});
function getChartData(){
return;
// 获取单选框选中的值
var timeBy = $('input[name="filter[time_by]"]:checked').val();
// 获取日期范围值
var daterange = $('#daterange').val();
// 构建查询参数
var params = {
'time_by': timeBy,
'daterange': daterange
};
$.ajax({
url: "statistics/dispatcher/chartData", //
type: "POST",
dataType: "json",
data:params,
success: function (response) {
// 数据结构
// response = {
// source: [
// ['产品销售', '2015', '2016', '2017'],
// ['风扇', 43.3, 85.8, 93.7],
// ...
// ]
// }
const headers = response[0];
const units = ['元', '%', '%', '']; // 如果你有单位可以一起拼接
var barChart = Echarts.init(document.getElementById('bar-chart'), 'walden');
var option = {
legend: {},
tooltip: {},
dataset: {
source: response
},
xAxis: {
type: 'category',
axisLabel: {
show: true // 隐藏“系统”
},
axisTick: { show: false },
axisLine: { show: false }
},
yAxis: {},
series: headers.slice(1).map((title, i) => ({
type: 'bar',
label: {
show: true,
position: 'top',
formatter: function (params) {
const value = params.value[i + 1]; // 因为第一列是“派单员”
const unit = units[i] || '';
return `${value} ${unit}`; // 显示数值 + 单位
}
},
itemStyle: {
borderRadius: 4
}
}))
};
barChart.setOption(option);
},
error: function () {
console.error("图表数据加载失败");
}
});
}
var form = $("#chart-filter");
var ranges = {};
ranges[__('Today')] = [Moment().startOf('day'), Moment().endOf('day')];
ranges[__('Yesterday')] = [Moment().subtract(1, 'days').startOf('day'), Moment().subtract(1, 'days').endOf('day')];
ranges[__('Last 7 Days')] = [Moment().subtract(6, 'days').startOf('day'), Moment().endOf('day')];
ranges[__('Last 30 Days')] = [Moment().subtract(29, 'days').startOf('day'), Moment().endOf('day')];
ranges[__('This Month')] = [Moment().startOf('month'), Moment().endOf('month')];
ranges[__('Last Month')] = [Moment().subtract(1, 'month').startOf('month'), Moment().subtract(1, 'month').endOf('month')];
ranges[__('今年')] = [Moment().startOf('year'), Moment().endOf('year')];
var options = {
timePicker: false,
autoUpdateInput: false,
timePickerSeconds: true,
timePicker24Hour: true,
autoApply: true,
locale: {
format: 'YYYY-MM-DD',
customRangeLabel: __("Custom Range"),
applyLabel: __("Apply"),
cancelLabel: __("Clear"),
},
ranges: ranges,
};
var callback = function (start, end) {
$(this.element).val(start.format(options.locale.format) + " - " + end.format(options.locale.format));
};
require(['bootstrap-daterangepicker'], function () {
$(".datetimerange", form).each(function () {
$(this).on('apply.daterangepicker', function (ev, picker) {
callback.call(picker, picker.startDate, picker.endDate);
var label = picker.chosenLabel;
$(picker.element).data('label', label).trigger("change");
});
$(this).on('cancel.daterangepicker', function (ev, picker) {
$(this).val('');
});
$(this).daterangepicker($.extend({}, options), callback);
});
});
// 手动触发一次激活 tab 的 shown.bs.tab
getChartData();
// 绑定查询按钮的点击事件
$('#filter-btn').on('click', function() {
getChartData();
});
},
add: function () {
@ -116,6 +224,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
edit: function () {
Controller.api.bindevent();
},
aftersales: function () {
Controller.api.bindevent();
},
api: {
bindevent: function () {
Form.api.bindevent($("form[role=form]"));