提交代码

This commit is contained in:
youda 2025-04-19 16:17:04 +08:00
parent e81b954d0a
commit 3bc11c8d0e
5 changed files with 782 additions and 401 deletions

View File

@ -43,22 +43,39 @@ class Statistics extends Base
if ($type) {
$where[] = ['type', '=', $type];
}
return View::fetch("Statistics/profit");
}
/**
* 盒子利润统计数据接口(用于前端分离模式)
* @param Request $request
* @return \think\response\Json
*/
public function profitData(Request $request)
{
$goodId = trim(input('get.goodId'));
$title = trim(input('get.title'));
$status = trim(input('get.status'));
$type = trim(input('get.type'));
$addtime = trim(input('get.addtime'));
$page = intval(input('page', 1));
$limit = intval(input('limit', 20));
// 解析时间范围
$startTime = 0;
$endTime = time();
if ($addtime) {
$times = explode(' - ', $addtime);
$startTime = strtotime($times[0]);
$endTime = strtotime($times[1]);
// 构建查询条件
$where = [['delete_time', '=', null]];
if ($goodId) {
$where[] = ['id', '=', $goodId];
}
if ($title) {
$where[] = ['title', 'like', '%' . $title . '%'];
}
if ($status) {
$where[] = ['status', '=', $status];
}
if ($type) {
$where[] = ['type', '=', $type];
}
// 获取测试用户ID
$testUsers = User::where('istest', '>', 0)->column('id');
$testUserIds = empty($testUsers) ? [0] : $testUsers;
$testUserIdsStr = implode(',', $testUserIds);
// 构建SQL查询
// 构建SQL查询只返回基本信息
$query = Db::name('goods')->alias('goods')
->field([
'goods.id',
@ -67,54 +84,70 @@ class Statistics extends Base
'goods.price',
'goods.stock',
'goods.status',
'goods.type',
'(SELECT COALESCE(sum(use_money), 0) + COALESCE(sum(price), 0) FROM `order` WHERE status=1 AND (price>0 OR use_money>0) AND pay_time > ' . $startTime . ' and pay_time < ' . $endTime . ' AND goods_id=goods.id AND status=1 AND user_id NOT IN (' . $testUserIdsStr . ')) AS use_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS sc_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(recovery_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS re_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(send_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS fh_money',
'(SELECT count(1) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ' and parent_goods_list_id=0 ) AS cj_count'
'goods.type'
])
->where($where)
->order(['goods.id' => 'desc']);
// 获取列表数据
$list = $query->select()->toArray();
// 获取总数量
$total = $query->count();
// 分页
$list = $query->page($page, $limit)->select()->toArray();
// 计算总金额
$totalIncome = 0;
$totalCost = 0;
$totalProfit = 0;
$totalReMoney = 0;
$totalFhMoney = 0;
foreach ($list as &$item) {
// 计算单个盒子的利润和利润率
$item['profit'] = $item['use_money'] - $item['sc_money'];
$item['profit_rate'] = $item['use_money'] > 0 ? round(($item['profit'] / $item['use_money']) * 100, 2) : 0;
$item['is_negative'] = $item['profit'] < 0;
// 计算总金额
$totalIncome += $item['use_money'];
$totalCost += $item['sc_money'];
$totalReMoney += $item['re_money'];
$totalFhMoney += $item['fh_money'];
// 获取盒子类型名称
$typesList = $this->getGoodsTypes();
$typesMap = [];
foreach ($typesList as $item) {
$typesMap[$item['value']] = $item['fl_name'];
}
$totalProfit = $totalIncome - $totalCost;
// 初始化统计数据为0
foreach ($list as &$item) {
// 添加类型名称
$item['type_name'] = isset($typesMap[$item['type']]) ? $typesMap[$item['type']] : '未知类型';
// 初始化统计字段为0后续会异步加载
$item['use_money'] = 0;
$item['sc_money'] = 0;
$item['re_money'] = 0;
$item['fh_money'] = 0;
$item['cj_count'] = 0;
$item['profit'] = 0;
$item['profit_rate'] = 0;
$item['is_negative'] = false;
$item['loaded'] = false; // 标记是否已加载统计数据
}
// 传递数据给视图
View::assign([
'list' => $list,
'totalIncome' => $totalIncome,
'totalCost' => $totalCost,
'totalProfit' => $totalProfit,
'totalReMoney' => $totalReMoney,
'totalFhMoney' => $totalFhMoney,
// 返回JSON数据
return json([
'code' => 0,
'msg' => '获取数据成功',
'count' => $total,
'data' => $list,
'summary' => [
'totalIncome' => 0,
'totalCost' => 0,
'totalProfit' => 0,
'totalReMoney' => 0,
'totalFhMoney' => 0,
]
]);
return View::fetch("Statistics/profit");
}
/**
* 获取盒子类型列表用于API调用
*/
private function getGoodsTypes()
{
$types = Db::name('goods_type')
->field('value, fl_name, remark')
->where('is_fenlei', 1)
->order('sort_order asc')
->select()
->toArray();
return $types;
}
/**
* 解析时间范围
*/
@ -616,4 +649,63 @@ class Statistics extends Base
return View::fetch('Statistics/productsOverview');
}
/**
* 获取单个盒子的统计数据
* @param Request $request
* @return \think\response\Json
*/
public function getBoxStatistics(Request $request)
{
$goodId = intval(input('get.goods_id', 0));
$addtime = trim(input('get.addtime', ''));
if ($goodId <= 0) {
return json(['code' => 1, 'msg' => '参数错误']);
}
// 解析时间范围
$startTime = 0;
$endTime = time();
if ($addtime) {
$times = explode(' - ', $addtime);
$startTime = strtotime($times[0]);
$endTime = strtotime($times[1]);
}
// 获取测试用户ID
$testUsers = User::where('istest', '>', 0)->column('id');
$testUserIds = empty($testUsers) ? [0] : $testUsers;
$testUserIdsStr = implode(',', $testUserIds);
// 查询单个盒子的统计数据
$data = Db::name('goods')->alias('goods')
->field([
'goods.id',
'(SELECT COALESCE(sum(use_money), 0) + COALESCE(sum(price), 0) FROM `order` WHERE status=1 AND (price>0 OR use_money>0) AND pay_time > ' . $startTime . ' and pay_time < ' . $endTime . ' AND goods_id=goods.id AND status=1 AND user_id NOT IN (' . $testUserIdsStr . ')) AS use_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS sc_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(recovery_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS re_money',
'(SELECT COALESCE(sum(goodslist_money), 0) FROM `order_list` WHERE goods_id=goods.id AND LENGTH(send_num)>0 AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ') AS fh_money',
'(SELECT count(1) FROM `order_list` WHERE goods_id=goods.id AND user_id NOT IN (' . $testUserIdsStr . ') AND addtime > ' . $startTime . ' and addtime < ' . $endTime . ' and parent_goods_list_id=0 ) AS cj_count'
])
->where('goods.id', '=', $goodId)
->find();
if (!$data) {
return json(['code' => 1, 'msg' => '盒子不存在']);
}
// 处理数值,确保为浮点型
$data['use_money'] = floatval($data['use_money']);
$data['sc_money'] = floatval($data['sc_money']);
$data['re_money'] = floatval($data['re_money']);
$data['fh_money'] = floatval($data['fh_money']);
// 计算利润和利润率
$data['profit'] = $data['use_money'] - $data['sc_money'];
$data['profit_rate'] = $data['use_money'] > 0 ? round(($data['profit'] / $data['use_money']) * 100, 2) : 0;
$data['is_negative'] = $data['profit'] < 0;
return json(['code' => 0, 'msg' => '获取成功', 'data' => $data]);
}
}

View File

@ -415,4 +415,15 @@ Route::get('sign_config_coupons', 'SignConfig/getCoupons');
#UserRank.php用户排行榜
#============================
Route::rule('user_rank', 'UserRank/index', 'GET');
Route::rule('user_rank_data', 'UserRank/getRankData', 'GET');
Route::rule('user_rank_data', 'UserRank/getRankData', 'GET');
#============================
#Statistics.php统计管理
#============================
Route::rule('statistics_profit', 'Statistics/profit', 'GET');
Route::rule('statistics/profitData', 'Statistics/profitData', 'GET');
Route::rule('statistics_order', 'Statistics/orderList', 'GET');
Route::rule('statistics_orderList', 'Statistics/goodsList', 'GET');
Route::rule('statistics_exchangeList', 'Statistics/exchangeList', 'GET');
Route::rule('statistics_shipmentList', 'Statistics/shipmentList', 'GET');
Route::rule('statistics_productsOverview', 'Statistics/productsOverview', 'GET');

View File

@ -4,27 +4,25 @@
<div class="layui-fluid">
<div class="layui-card">
<!-- 搜索条件区域 -->
<form method="get" class="layui-form layui-card-header layuiadmin-card-header-auto">
<form class="layui-form layui-card-header layuiadmin-card-header-auto">
<div class="layui-form-item">
<div class="layui-inline">
<div class="layui-input-inline" style="width:150px;margin-left: 0px">
<input type="text" name="goodId" value="{$Request.get.goodId}" placeholder="请输入盒子Id"
autocomplete="off" class="layui-input">
<input type="text" name="goodId" id="goodId" placeholder="请输入盒子Id" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width:150px;margin-left: 0px">
<input type="text" name="title" value="{$Request.get.title}" placeholder="请输入盒子名称"
autocomplete="off" class="layui-input">
<input type="text" name="title" id="title" placeholder="请输入盒子名称" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width: 180px;margin-left: 0px">
<select name="status" style="width:100%">
<select name="status" id="status" style="width:100%">
<option value="">--请选择盒子状态--</option>
<option value="1" {if condition="$Request.get.status eq 1" }selected{/if}>上架</option>
<option value="2" {if condition="$Request.get.status eq 2" }selected{/if}>下架</option>
<option value="3" {if condition="$Request.get.status eq 3" }selected{/if}>售罄</option>
<option value="1">上架</option>
<option value="2">下架</option>
<option value="3">售罄</option>
</select>
</div>
</div>
@ -37,13 +35,12 @@
</div>
<div class="layui-inline">
<div class="layui-input-inline" style="width:300px;margin-left: 0px">
<input type="text" id="addtime" name="addtime" value="{$Request.get.addtime}"
class="layui-input" placeholder="选择时间" autocomplete="off">
<input type="text" id="addtime" name="addtime" class="layui-input" placeholder="选择时间" autocomplete="off">
</div>
</div>
<div class="layui-inline">
<button class="layui-btn layuiadmin-btn-useradmin mmm" lay-submit lay-filter="LAY-user-front-search">
<button class="layui-btn layuiadmin-btn-useradmin" id="searchBtn" type="button">
<i class="layui-icon layui-icon-search layuiadmin-button-btn"></i>
</button>
</div>
@ -52,12 +49,12 @@
<!-- 统计摘要区域 -->
<div class="layui-card-body">
<div class="layui-row layui-col-space15">
<div class="layui-row layui-col-space15" id="statisticsSummary">
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">总收入</div>
<div class="layui-card-body" style="font-size: 24px; color: #01AAED;">
¥ {$totalIncome|default="0"|round=2}
¥ <span id="totalIncome">0.00</span>
</div>
</div>
</div>
@ -65,15 +62,15 @@
<div class="layui-card">
<div class="layui-card-header">总出货价值</div>
<div class="layui-card-body" style="font-size: 24px; color: #FFB800;">
¥ {$totalCost|default="0"|round=2}
¥ <span id="totalCost">0.00</span>
</div>
</div>
</div>
<div class="layui-col-md3">
<div class="layui-card">
<div class="layui-card-header">总利润</div>
<div class="layui-card-body" style="font-size: 24px; color: {$totalProfit >= 0 ? '#5FB878' : '#FF5722'};">
¥ {$totalProfit|default="0"|round=2}
<div class="layui-card-body" style="font-size: 24px;">
¥ <span id="totalProfit" class="profit-value">0.00</span>
</div>
</div>
</div>
@ -81,88 +78,33 @@
<div class="layui-card">
<div class="layui-card-header">总兑换/发货价值</div>
<div class="layui-card-body" style="font-size: 24px;">
<span style="color: #FF9800;">¥ {$totalReMoney|default="0"|round=2}</span> /
<span style="color: #673AB7;">¥ {$totalFhMoney|default="0"|round=2}</span>
<span style="color: #FF9800;">¥ <span id="totalReMoney">0.00</span></span> /
<span style="color: #673AB7;">¥ <span id="totalFhMoney">0.00</span></span>
</div>
</div>
</div>
</div>
<!-- 数据表格区域 -->
<table class="layui-table">
<thead>
<tr>
<th>盒子ID</th>
<th>盒子名称</th>
<th>盒子类型/状态</th>
<th>盒子单价</th>
<th>抽奖次数</th>
<th>收入</th>
<th>出货价值</th>
<th>兑换价值</th>
<th>发货价值</th>
<th>利润</th>
<th>利润率</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{volist name="list" id="vo"}
<tr {if condition="$vo.is_negative"}style="background-color: #ffebee;"{/if}>
<td>{$vo.id}</td>
<td>{$vo.title}</td>
<td>
<button class="layui-btn layui-btn-normal layui-btn-radius layui-btn-xs" data-type="{$vo.type}">加载中...</button>
/
{if condition="$vo.status eq 1"}
<span class="layui-badge layui-bg-green">上架</span>
{elseif condition="$vo.status eq 2"}
<span class="layui-badge layui-bg-gray">下架</span>
{elseif condition="$vo.status eq 3"}
<span class="layui-badge layui-bg-orange">售罄</span>
{/if}
</td>
<td>¥ {$vo.price}</td>
<td>{$vo.cj_count}</td>
<td>
<span class="layui-badge layui-bg-blue">¥ {$vo.use_money|default="0"|round=2}</span>
</td>
<td>
<span class="layui-badge layui-bg-orange">¥ {$vo.sc_money|default="0"|round=2}</span>
</td>
<td>
<span class="layui-badge" style="background-color: #FF9800;">¥ {$vo.re_money|default="0"|round=2}</span>
</td>
<td>
<span class="layui-badge" style="background-color: #673AB7;">¥ {$vo.fh_money|default="0"|round=2}</span>
</td>
<td>
<span class="layui-badge {if condition="$vo.profit >= 0"}layui-bg-green{else}layui-bg-red{/if}">
¥ {$vo.profit|default="0"|round=2}
</span>
</td>
<td>
<span class="layui-badge {if condition="$vo.profit_rate >= 0"}layui-bg-green{else}layui-bg-red{/if}">
{$vo.profit_rate|default="0"|round=2}%
</span>
</td>
<td>
<button class="layui-btn layui-btn-danger layui-btn-xs" onclick="viewProductsOverview({$vo.id})">出货概览</button>
<!-- <button class="layui-btn layui-btn-xs" onclick="viewOrders({$vo.id})">支付订单</button>
<button class="layui-btn layui-btn-normal layui-btn-xs" onclick="viewOrderLists({$vo.id})">出货明细</button>
<button class="layui-btn layui-btn-warm layui-btn-xs" onclick="viewExchangeList({$vo.id})">兑换明细</button>
<button class="layui-btn layui-btn-primary layui-btn-xs" onclick="viewShipmentList({$vo.id})">发货明细</button> -->
</td>
</tr>
{/volist}
{if condition="empty($list)"}
<tr>
<td colspan="11" style="text-align:center;">暂时没有数据!</td>
</tr>
{/if}
</tbody>
</table>
<table id="profitTable" lay-filter="profitTable"></table>
<!-- 表格操作栏模板 -->
<script type="text/html" id="operationTpl">
<button class="layui-btn layui-btn-danger layui-btn-xs" lay-event="viewProductsOverview">出货概览</button>
</script>
<!-- 利润和利润率模板 -->
<script type="text/html" id="profitTpl">
<span class="layui-badge {{d.profit >= 0 ? 'layui-bg-green' : 'layui-bg-red'}}">
¥ {{d.profit.toFixed(2)}}
</span>
</script>
<script type="text/html" id="profitRateTpl">
<span class="layui-badge {{d.profit_rate >= 0 ? 'layui-bg-green' : 'layui-bg-red'}}">
{{d.profit_rate.toFixed(2)}}%
</span>
</script>
</div>
</div>
</div>
@ -173,6 +115,7 @@
var layer = layui.layer;
var laydate = layui.laydate;
var form = layui.form;
var table = layui.table;
// 日期时间范围
laydate.render({
@ -180,7 +123,7 @@
type: 'datetime',
range: true
});
// 加载盒子类型数据
function loadGoodsTypes() {
$.ajax({
@ -192,102 +135,294 @@
var html = '<option value="">--盒子类型--</option>';
$.each(res.data, function (index, item) {
if(item.value == '{$Request.get.type}'){
html += '<option value="' + item.value + '" title="' + item.remark + '" selected>' + item.fl_name + '</option>';
} else {
html += '<option value="' + item.value + '" title="' + item.remark + '">' + item.fl_name + '</option>';
}
html += '<option value="' + item.value + '" title="' + item.remark + '">' + item.fl_name + '</option>';
});
$('#goodsType').html(html);
form.render('select');
// 更新表格中的类型显示
$('button[data-type]').each(function() {
var type = $(this).data('type');
var typeInfo = res.data.find(function(item) {
return item.value == type;
});
if (typeInfo) {
$(this).text(typeInfo.fl_name);
} else {
$(this).text('未知类型');
}
});
}
}
});
}
// 全局变量,存储汇总数据
var summaryData = {
totalIncome: 0,
totalCost: 0,
totalProfit: 0,
totalReMoney: 0,
totalFhMoney: 0
};
// 全局变量,用于控制异步加载
var loadingQueue = [];
var isLoading = false;
var maxConcurrentRequests = 3; // 最大并发请求数
// 初始化表格
var profitTable = table.render({
elem: '#profitTable',
url: '{:url("/admin/statistics/profitData")}',
method: 'get',
page: true,
limit: 20,
where: getSearchParams(),
parseData: function(res) {
if (res.code === 0) {
// 清空汇总数据
summaryData = {
totalIncome: 0,
totalCost: 0,
totalProfit: 0,
totalReMoney: 0,
totalFhMoney: 0
};
// 更新统计摘要数据为0
updateStatisticsSummary(summaryData);
return {
"code": 0,
"msg": res.msg,
"count": res.count,
"data": res.data
};
}
return {
"code": res.code,
"msg": res.msg,
"count": 0,
"data": []
};
},
cols: [[
{field: 'id', title: '盒子ID', width: 80, sort: true, fixed: 'left'},
{field: 'title', title: '盒子名称', width: 180},
{templet: function(d) {
var statusHtml = '';
if(d.status == 1) {
statusHtml = '<span class="layui-badge layui-bg-green">上架</span>';
} else if(d.status == 2) {
statusHtml = '<span class="layui-badge layui-bg-gray">下架</span>';
} else if(d.status == 3) {
statusHtml = '<span class="layui-badge layui-bg-orange">售罄</span>';
}
return '<div><button class="layui-btn layui-btn-normal layui-btn-radius layui-btn-xs type-btn" data-type="'+d.type+'">'+d.type_name+'</button> / ' + statusHtml + '</div>';
}, title: '盒子类型/状态', width: 160},
{field: 'price', title: '盒子单价', width: 100, templet: '<div>¥ {{d.price}}</div>'},
{field: 'cj_count', title: '抽奖次数', width: 100, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
return '<div>' + d.cj_count + '</div>';
}},
{field: 'use_money', title: '收入', width: 110, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
return '<div><span class="layui-badge layui-bg-blue">¥ ' + d.use_money.toFixed(2) + '</span></div>';
}},
{field: 'sc_money', title: '出货价值', width: 110, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
return '<div><span class="layui-badge layui-bg-orange">¥ ' + d.sc_money.toFixed(2) + '</span></div>';
}},
{field: 're_money', title: '兑换价值', width: 110, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
return '<div><span class="layui-badge" style="background-color: #FF9800;">¥ ' + d.re_money.toFixed(2) + '</span></div>';
}},
{field: 'fh_money', title: '发货价值', width: 110, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
return '<div><span class="layui-badge" style="background-color: #673AB7;">¥ ' + d.fh_money.toFixed(2) + '</span></div>';
}},
{field: 'profit', title: '利润', width: 110, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
var colorClass = d.profit >= 0 ? 'layui-bg-green' : 'layui-bg-red';
return '<div><span class="layui-badge ' + colorClass + '">¥ ' + d.profit.toFixed(2) + '</span></div>';
}},
{field: 'profit_rate', title: '利润率', width: 100, templet: function(d) {
if (!d.loaded) {
return '<div><i class="layui-icon layui-icon-loading layui-anim layui-anim-rotate layui-anim-loop"></i></div>';
}
var colorClass = d.profit_rate >= 0 ? 'layui-bg-green' : 'layui-bg-red';
return '<div><span class="layui-badge ' + colorClass + '">' + d.profit_rate.toFixed(2) + '%</span></div>';
}},
{title: '操作', width: 110, toolbar: '#operationTpl', fixed: 'right'}
]],
done: function(res) {
// 清空加载队列
loadingQueue = [];
isLoading = false;
// 将所有数据项添加到加载队列中
var tableData = table.cache.profitTable || [];
for (var i = 0; i < tableData.length; i++) {
if (!tableData[i].loaded) {
loadingQueue.push(tableData[i]);
}
}
// 开始加载统计数据
processLoadingQueue();
}
});
// 处理加载队列
function processLoadingQueue() {
if (isLoading || loadingQueue.length === 0) return;
isLoading = true;
var requests = 0;
var activeRequests = 0;
// 获取当前需要处理的项目
while (requests < maxConcurrentRequests && loadingQueue.length > 0) {
var item = loadingQueue.shift();
requests++;
activeRequests++;
loadBoxStatistics(item, function() {
activeRequests--;
if (activeRequests === 0) {
isLoading = false;
processLoadingQueue();
}
});
}
}
// 加载单个盒子的统计数据
function loadBoxStatistics(item, callback) {
var params = getSearchParams();
$.ajax({
url: '{:url("/admin/statistics/getBoxStatistics")}',
type: 'GET',
data: {
goods_id: item.id,
addtime: params.addtime
},
success: function(res) {
if (res.code === 0) {
var data = res.data;
// 更新表格中的数据
var tableData = table.cache.profitTable;
for (var i = 0; i < tableData.length; i++) {
if (tableData[i].id === item.id) {
tableData[i].use_money = data.use_money;
tableData[i].sc_money = data.sc_money;
tableData[i].re_money = data.re_money;
tableData[i].fh_money = data.fh_money;
tableData[i].cj_count = data.cj_count;
tableData[i].profit = data.profit;
tableData[i].profit_rate = data.profit_rate;
tableData[i].is_negative = data.is_negative;
tableData[i].loaded = true;
// 更新汇总数据
summaryData.totalIncome += data.use_money;
summaryData.totalCost += data.sc_money;
summaryData.totalReMoney += data.re_money;
summaryData.totalFhMoney += data.fh_money;
summaryData.totalProfit = summaryData.totalIncome - summaryData.totalCost;
// 更新统计摘要
updateStatisticsSummary(summaryData);
// 设置背景色
if (data.is_negative) {
$('tr[data-index="' + i + '"]').css('background-color', '#ffebee');
}
break;
}
}
// 重新渲染表格
table.render({
elem: '#profitTable',
data: tableData,
page: false,
cols: profitTable.config.cols
});
}
if (callback) callback();
},
error: function() {
if (callback) callback();
}
});
}
// 更新统计摘要
function updateStatisticsSummary(summary) {
if (!summary) return;
$('#totalIncome').text(summary.totalIncome.toFixed(2));
$('#totalCost').text(summary.totalCost.toFixed(2));
$('#totalProfit').text(summary.totalProfit.toFixed(2));
$('#totalReMoney').text(summary.totalReMoney.toFixed(2));
$('#totalFhMoney').text(summary.totalFhMoney.toFixed(2));
// 设置利润颜色
var profitElem = $('#totalProfit');
if (summary.totalProfit >= 0) {
profitElem.parent().css('color', '#5FB878');
} else {
profitElem.parent().css('color', '#FF5722');
}
}
// 获取搜索参数
function getSearchParams() {
return {
goodId: $('#goodId').val(),
title: $('#title').val(),
status: $('#status').val(),
type: $('#goodsType').val(),
addtime: $('#addtime').val()
};
}
// 搜索按钮点击事件
$('#searchBtn').on('click', function() {
profitTable.reload({
where: getSearchParams(),
page: {
curr: 1
}
});
});
// 监听表格工具条事件
table.on('tool(profitTable)', function(obj) {
var data = obj.data;
if (obj.event === 'viewProductsOverview') {
// 查看出货概览
layer.open({
type: 2,
title: '出货概览',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: '{:url("/admin/statistics_productsOverview")}?goods_id=' + data.id
});
}
});
// 页面加载完成后执行
$(function() {
loadGoodsTypes();
});
});
// 查看支付订单列表
function viewOrders(goodsId) {
var url = "{:url('/admin/statistics_order')}?goods_id=" + goodsId;
layer.open({
type: 2,
title: '支付订单列表',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: url
});
}
// 查看出货列表
function viewOrderLists(goodsId) {
var url = "{:url('/admin/statistics_orderList')}?goods_id=" + goodsId;
layer.open({
type: 2,
title: '出货列表',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: url
});
}
// 查看兑换列表
function viewExchangeList(goodsId) {
var url = "{:url('/admin/statistics_exchangeList')}?goods_id=" + goodsId;
layer.open({
type: 2,
title: '兑换列表',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: url
});
}
// 查看发货列表
function viewShipmentList(goodsId) {
var url = "{:url('/admin/statistics_shipmentList')}?goods_id=" + goodsId;
layer.open({
type: 2,
title: '发货列表',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: url
});
}
// 查看出货概览
function viewProductsOverview(goodsId) {
var url = "{:url('/admin/statistics_productsOverview')}?goods_id=" + goodsId;
layer.open({
type: 2,
title: '出货概览',
shadeClose: false,
shade: 0.3,
area: ['90%', '90%'],
content: url
});
}
</script>
</body>
</html>

View File

@ -26,7 +26,7 @@ class Config extends Base
return $this->renderSuccess('获取成功', [
'good_type' => $goodsTypeList,
'app_setting' => $app_setting,
'version' => '100'
'version' => '101'
]);
}

View File

@ -183,12 +183,18 @@ class Login extends Base
*/
public function login()
{
// 初始化日志收集变量
$logMessages = [];
try {
$code = request()->param("code", '');
if (empty($code)) {
$logMessages[] = '用户未获取到code:' . $code;
Log::error(end($logMessages));
return $this->renderError('请求参数错误');
}
$logMessages[] = '用户开始登录: ' . $code;
$click_id = request()->header('clickid');
$wxServer = new \app\common\server\Wx($this->app);
$user_base = $wxServer->getOpenid($code);
@ -241,52 +247,75 @@ class Login extends Base
} else {
}
$res[] = UserAccount::where(['user_id' => $user['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
// 检查UserAccount是否存在
$userAccount = UserAccount::where(['user_id' => $user['id']])->find();
if ($userAccount) {
// 存在则更新
$res[] = UserAccount::where(['user_id' => $user['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
} else {
// 不存在则新增
$res[] = UserAccount::insert([
'user_id' => $user['id'],
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
}
// 记录用户登录日志(每天只记录一次)
UserLoginLog::recordLogin(
$user['id'],
'wechat',
$last_login_ip1,
$ip_province . $ip_city
''//$ip_province . $ip_city
);
$logMessages[] = '用户登录成功: ' . $code . ' 用户ID: ' . $user['id'] . '用户手机号' . $user['mobile'];
// 输出收集的所有日志
Log::info(implode("==》", $logMessages));
return $this->renderSuccess("登录成功", $account_token);
} else {
$nickname = request()->param('nickname', '');
$headimg = request()->param('headimg', '');
if (!$nickname) {
return $this->renderError('请求参数错误!');
}
// if (!$nickname) {
// return $this->renderError('请求参数错误!');
// }
$pid = 0;
$pid_pid = request()->param('pid', 0);
if ($pid_pid > 0) {
log::info("获取推荐人id" . $pid_pid);
}
$randx = rand(1000, 9999);
if ($nickname == "微信用户") {
$nickname = $nickname . $randx;
}
$nickname = "微信用户" . $randx;
$logMessages[] = $nickname;
$randx = rand(10000, 99999);
$identicon = new \Identicon\Identicon();
$imageData = $identicon->getImageData($openid . $nickname);
$uploadResult = $this->uploader->uploadFile($imageData, "storage/users/icon/default/" . $randx . ".png");
$headimg = $uploadResult['full_url'];
if ($pid_pid) {
$logMessages[] = "尝试获取推荐人ID: " . $pid_pid;
$pid_info = User::where('id', '=', $pid_pid)->value("id");
if ($pid_info) {
log::info("获取推荐人id" . $pid_info);
$logMessages[] = "获取推荐人ID成功: " . $pid_info;
$pid = $pid_info;
}
}
@ -319,12 +348,10 @@ class Login extends Base
$redis = (new RedisHelper())->getRedis();
$lockKey = 'user:beta_reward:' . $user_id;
if ($redis->set($lockKey, 1, ['nx', 'ex' => 60])) {
try {
$res[] = User::changeMoney($user_id, 50000, 8, '内测免费送');
} finally {
// 释放锁
$redis->del($lockKey);
}
$res[] = User::changeMoney($user_id, 50000, 8, '内测免费送');
$logMessages[] = '赠送钻石: 50000';
// 释放锁
$redis->del($lockKey);
}
}
}
@ -380,20 +407,32 @@ class Login extends Base
$user_id,
'wechat:v1.0.0',
$last_login_ip1,
$ip_province . $ip_city
''//$ip_province . $ip_city
);
$logMessages[] = '==》用户注册成功: ' . $code . ' 用户ID: ' . $user_id;
// 输出收集的所有日志
Log::info(implode("==>", $logMessages));
Db::commit();
return $this->renderSuccess("登录成功", $account_token);
} else {
Db::rollback();
$logMessages[] = '==》用户注册失败: ' . $code . ' 用户ID: ' . $user_id;
// 输出收集的所有日志
Log::info(implode("==>", $logMessages));
return $this->renderError("登录失败");
}
}
} catch (\Exception $e) {
Log::error('错误信息' . $e->getMessage());
Log::error('错误行数' . $e->getLine());
$logMessages[] = '登录失败->错误信息: ' . $e->getMessage();
$logMessages[] = '登录失败->错误行数: ' . $e->getLine();
// 输出收集的错误日志
Log::error(implode("==>", $logMessages));
return $this->renderError("登录失败");
}
}
@ -403,13 +442,24 @@ class Login extends Base
*/
public function h5login()
{
// 获取请求参数
$code = request()->param("code", '');
if (empty($code)) {
return $this->renderError('请求参数错误');
}
$click_id = request()->header('clickid');
// 使用Redis全局锁防止并发请求
$redis = (new RedisHelper())->getRedis();
$lockKey = 'global:h5login:lock:' . md5($code . $click_id);
// 尝试获取锁设置过期时间为30秒
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 30])) {
// 如果获取锁失败,表示有并发请求正在处理
return $this->renderError('登录请求处理中,请稍后再试');
}
try {
$code = request()->param("code", '');
if (empty($code)) {
return $this->renderError('请求参数错误');
}
$click_id = request()->header('clickid');
$wxServer = new \app\common\server\WechatOfficialAccount($this->app);
$user_base = $wxServer->getAccessToken($code);
// $user_base_info = $wxServer->getUserInfo($user_base);
@ -460,17 +510,37 @@ class Login extends Base
}
$res[] = UserAccount::where(['user_id' => $user['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
// 检查UserAccount是否存在
$userAccount = UserAccount::where(['user_id' => $user['id']])->find();
if ($userAccount) {
// 存在则更新
$res[] = UserAccount::where(['user_id' => $user['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
} else {
// 不存在则新增
$res[] = UserAccount::insert([
'user_id' => $user['id'],
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $time,
'last_login_ip' => ip2long($last_login_ip1),
'last_login_ip1' => $last_login_ip1,
'ip_adcode' => $ip_adcode,
'ip_province' => $ip_province,
'ip_city' => $ip_city,
]);
}
// 记录用户登录日志(每天只记录一次)
UserLoginLog::recordLogin(
@ -515,6 +585,13 @@ class Login extends Base
'click_id' => $click_id,
'uid' => '',
]);
// 生成用户uid
$uid = $this->generateUid($user_id);
if ($uid) {
User::where('id', $user_id)->update(['uid' => $uid]);
}
$time = time();
#token字符串
$token_num = getRandStr(10);
@ -581,6 +658,9 @@ class Login extends Base
Log::error('错误信息' . $e->getMessage());
Log::error('错误行数' . $e->getLine());
return $this->renderError('非法请求');
} finally {
// 确保释放锁
$redis->del($lockKey);
}
}
@ -613,62 +693,89 @@ class Login extends Base
public function login_bind_mobile()
{
$user = $this->getUser();
$user_id = $user['id'];
$code = request()->param("code", '');
$wxServer = new \app\common\server\Wx($this->app);
$mobile = $wxServer->getMobile($code);
// return $this->renderError($mobile,[$mobile,$code]);
Db::startTrans();
$res = [];
// $res[] = User::where(['id' => $user['id']])->update([
// 'mobile' => $mobile,
// 'update_time' => time(),
// ]);
$data = [];
$user_mobile = User::where(['mobile' => $mobile])->find();
if ($user_mobile) {
$old_user_account = UserAccount::where(['user_id' => $user_id])->find();
#修改openid
$res[] = User::where(['id' => $user_mobile['id']])
->update([
'openid' => $user['openid'],
// 'nickname' => $user['nickname'],
// 'headimg' => $user['headimg'],
]);
$time = time();
#token字符串
$token_num = getRandStr(10);
#加密token
$account_token = user_md5($user_mobile['id'] . $token_num . $time);
#修改token
$res[] = UserAccount::where(['user_id' => $user_mobile['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $old_user_account['last_login_time'],
'last_login_ip' => $old_user_account['last_login_ip'],
]);
#修改
$res[] = User::where(['id' => $user['id']])->delete();
$res[] = UserAccount::where(['user_id' => $user_id])->delete();
$data['token'] = $account_token;
// $res[] = UserAccount::where(['user_id' => $user['id']])->update([
// 'token_time' => time(),
// ]);
} else {
$res[] = User::where(['id' => $user['id']])->update([
'mobile' => $mobile,
'update_time' => time(),
]);
}
if (resCheck($res)) {
Db::commit();
return $this->renderSuccess("绑定成功2", $data);
} else {
Db::rollback();
return $this->renderSuccess("绑定成功3");
if (empty($user)) {
return $this->renderError('用户不存在');
}
$user_id = $user['id'];
$code = request()->param("code", '');
if (empty($code)) {
return $this->renderError('参数错误缺少code');
}
// 使用Redis全局锁防止并发绑定请求
$redis = (new RedisHelper())->getRedis();
$lockKey = 'global:bind_mobile:lock:' . $user_id;
// 尝试获取锁设置过期时间为20秒
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 20])) {
// 如果获取锁失败,表示有并发请求正在处理
return $this->renderError('绑定请求处理中,请稍后再试');
}
try {
$wxServer = new \app\common\server\Wx($this->app);
$mobile = $wxServer->getMobile($code);
// return $this->renderError($mobile,[$mobile,$code]);
Db::startTrans();
$res = [];
// $res[] = User::where(['id' => $user['id']])->update([
// 'mobile' => $mobile,
// 'update_time' => time(),
// ]);
$data = [];
$user_mobile = User::where(['mobile' => $mobile])->find();
if ($user_mobile) {
$old_user_account = UserAccount::where(['user_id' => $user_id])->find();
#修改openid
$res[] = User::where(['id' => $user_mobile['id']])
->update([
'openid' => $user['openid'],
// 'nickname' => $user['nickname'],
// 'headimg' => $user['headimg'],
]);
$time = time();
#token字符串
$token_num = getRandStr(10);
#加密token
$account_token = user_md5($user_mobile['id'] . $token_num . $time);
#修改token
$res[] = UserAccount::where(['user_id' => $user_mobile['id']])->update([
'account_token' => $account_token,
'token_num' => $token_num,
'token_time' => $time,
'last_login_time' => $old_user_account['last_login_time'],
'last_login_ip' => $old_user_account['last_login_ip'],
]);
#修改
$res[] = User::where(['id' => $user['id']])->delete();
$res[] = UserAccount::where(['user_id' => $user_id])->delete();
$data['token'] = $account_token;
// $res[] = UserAccount::where(['user_id' => $user['id']])->update([
// 'token_time' => time(),
// ]);
} else {
$res[] = User::where(['id' => $user['id']])->update([
'mobile' => $mobile,
'update_time' => time(),
]);
}
if (resCheck($res)) {
Db::commit();
return $this->renderSuccess("绑定成功", $data);
} else {
Db::rollback();
return $this->renderError("绑定失败");
}
} catch (\Exception $e) {
Db::rollback();
Log::error('绑定手机号错误: ' . $e->getMessage());
Log::error('错误行数: ' . $e->getLine());
return $this->renderError('绑定失败: ' . $e->getMessage());
} finally {
// 确保释放锁
$redis->del($lockKey);
}
}
@ -678,22 +785,49 @@ class Login extends Base
public function login_bind_mobile_h5()
{
$user = $this->getUser();
$user_id = $user['id'];
$mobile = request()->param("mobile", '');
Db::startTrans();
$res = [];
$res[] = User::where(['id' => $user_id])->update([
'mobile' => $mobile,
'update_time' => time(),
]);
if (resCheck($res)) {
Db::commit();
return $this->renderSuccess("绑定成功2");
} else {
Db::rollback();
return $this->renderSuccess("绑定成功3");
if (empty($user)) {
return $this->renderError('用户不存在');
}
$user_id = $user['id'];
$mobile = request()->param("mobile", '');
if (empty($mobile)) {
return $this->renderError('请输入手机号');
}
// 使用Redis全局锁防止并发绑定请求
$redis = (new RedisHelper())->getRedis();
$lockKey = 'global:bind_mobile_h5:lock:' . $user_id;
// 尝试获取锁设置过期时间为10秒
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 10])) {
// 如果获取锁失败,表示有并发请求正在处理
return $this->renderError('绑定请求处理中,请稍后再试');
}
try {
Db::startTrans();
$res = [];
$res[] = User::where(['id' => $user_id])->update([
'mobile' => $mobile,
'update_time' => time(),
]);
if (resCheck($res)) {
Db::commit();
return $this->renderSuccess("绑定成功");
} else {
Db::rollback();
return $this->renderError("绑定失败");
}
} catch (\Exception $e) {
Db::rollback();
Log::error('绑定手机号H5错误: ' . $e->getMessage());
Log::error('错误行数: ' . $e->getLine());
return $this->renderError('绑定失败: ' . $e->getMessage());
} finally {
// 确保释放锁
$redis->del($lockKey);
}
}
/**
@ -718,77 +852,86 @@ class Login extends Base
public function recordLogin()
{
try {
// $user_id = $this->getUserid1();
// //去redis中查询一下如果存在今日的数据则不在往下执行
// $redis = (new RedisHelper())->getRedis();
// $today = date('Y-m-d');
// $redis_key = "login_record:" . $today . ":" . $user_id;
// $redis_data = $redis->get($redis_key);
// if ($redis_data) {
// return $this->renderSuccess('登录记录成功');
// }
// 获取用户信息
$user = $this->getUser();
if (empty($user)) {
return $this->renderError('用户不存在');
}
// 获取设备信息
$device = request()->param('device', ''); // 设备类型
$device_info = request()->param('device_info', ''); // 设备详细信息
// 获取IP和地理位置信息
$ip = $this->getRealIp();
$location = '';
$ip_province = '';
$ip_city = '';
$ip_adcode = '';
$result = $this->ip_location($ip);
if ($result) {
$ip_province = $result['province'];
$ip_city = $result['city'];
$ip_adcode = $result['adcode'];
$location = $ip_province . $ip_city;
}
$user_id = $user['id'];
$isTest = \app\common\helper\ConfigHelper::getSystemTestKey("enable_test");
if ($isTest == "1") {
// 使用Redis锁防止重复获取
$redis = (new RedisHelper())->getRedis();
$lockKey = 'user:beta_reward:' . $user_id;
if ($redis->set($lockKey, 1, ['nx', 'ex' => 60])) {
$userCount = ProfitMoney::where('user_id', $user_id)
->where('type', 8)
->where('content', '=', '内测免费送')
->count();
if ($userCount == 0) {
// 使用Redis全局锁防止并发请求
$redis = (new RedisHelper())->getRedis();
$lockKey = 'global:record_login:lock:' . $user_id;
// 尝试获取锁设置过期时间为10秒
if (!$redis->set($lockKey, 1, ['nx', 'ex' => 10])) {
// 如果获取锁失败,直接返回成功,避免用户等待
return $this->renderSuccess('登录成功', [
'uid' => $user['uid'] ?: $user['id'],
'nickname' => $user['nickname'],
'headimg' => imageUrl($user['headimg'])
]);
}
try {
// 获取设备信息
$device = request()->param('device', ''); // 设备类型
$device_info = request()->param('device_info', ''); // 设备详细信息
// 获取IP和地理位置信息
$ip = $this->getRealIp();
$location = '';
$ip_province = '';
$ip_city = '';
$ip_adcode = '';
$result = $this->ip_location($ip);
if ($result) {
$ip_province = $result['province'];
$ip_city = $result['city'];
$ip_adcode = $result['adcode'];
$location = $ip_province . $ip_city;
}
$isTest = \app\common\helper\ConfigHelper::getSystemTestKey("enable_test");
if ($isTest == "1") {
// 使用全局 Redis 锁防止重复获取内测奖励
$bonusLockKey = 'global:bonus:lock:' . $user_id;
if ($redis->set($bonusLockKey, 1, ['nx', 'ex' => 60])) {
try {
$res[] = User::changeMoney($user_id, 50000, 8, '内测免费送');
$userCount = ProfitMoney::where('user_id', $user_id)
->where('type', 8)
->where('content', '=', '内测免费送')
->count();
if ($userCount == 0) {
$res[] = User::changeMoney($user_id, 50000, 8, '内测免费送');
}
} finally {
// 释放锁
$redis->del($lockKey);
// 释放内测奖励
$redis->del($bonusLockKey);
}
}
}
// 记录登录日志
UserLoginLog::recordLogin(
$user['id'],
$device,
$ip,
$location
);
return $this->renderSuccess('登录成功', [
'uid' => $user['uid'] ?: $user['id'],
'nickname' => $user['nickname'],
'headimg' => imageUrl($user['headimg'])
]);
} finally {
// 确保释放锁
$redis->del($lockKey);
}
// 记录登录日志
UserLoginLog::recordLogin(
$user['id'],
$device,
$ip,
$location
);
// //将数据写入redis,过期时间为当天剩余的时间
// $redis->set($redis_key, json_encode($user), 86400 - time() % 86400);
return $this->renderSuccess('登录成功', [
'uid' => $user['uid'] ?: $user['id'],
'nickname' => $user['nickname'],
'headimg' => imageUrl($user['headimg'])
]);
} catch (\Exception $e) {
Log::error('记录登录错误: ' . $e->getMessage());
Log::error('错误行数: ' . $e->getLine());