优化积分流水筛选与用户管理样式

This commit is contained in:
2026-04-26 17:54:24 +08:00
parent af772350c9
commit 61cfc2091c
11 changed files with 555 additions and 109 deletions
@@ -17,6 +17,9 @@ use App\Models\UserCurrencyLog;
use Illuminate\Http\Request;
use Illuminate\View\View;
/**
* 类功能:提供后台全局金币/积分流水查询与多条件筛选。
*/
class CurrencyLogController extends Controller
{
/**
@@ -26,6 +29,12 @@ class CurrencyLogController extends Controller
public function index(Request $request): View
{
$query = UserCurrencyLog::query()->with('user');
$allSources = CurrencySource::cases();
$allowedSources = collect($allSources)->map(fn (CurrencySource $source) => $source->value)->all();
$selectedSources = collect($request->array('sources'))
->filter(fn (string $source) => in_array($source, $allowedSources, true))
->values()
->all();
// 查询条件过滤
if ($request->filled('username')) {
@@ -36,8 +45,8 @@ class CurrencyLogController extends Controller
$query->where('currency', $request->input('currency'));
}
if ($request->filled('source')) {
$query->where('source', $request->input('source'));
if ($selectedSources !== []) {
$query->whereIn('source', $selectedSources);
}
if ($request->filled('remark')) {
@@ -63,8 +72,6 @@ class CurrencyLogController extends Controller
// 默认按时间倒序
$logs = $query->latest('id')->paginate(50)->withQueryString();
$allSources = CurrencySource::cases();
return view('admin.currency-logs.index', compact('logs', 'allSources'));
return view('admin.currency-logs.index', compact('logs', 'allSources', 'selectedSources'));
}
}
@@ -14,11 +14,13 @@ namespace App\Http\Controllers\Admin;
use App\Enums\CurrencySource;
use App\Http\Controllers\Controller;
use App\Models\UserCurrencyLog;
use App\Services\UserCurrencyService;
use Illuminate\Http\Request;
use Illuminate\View\View;
/**
* 类功能:展示后台积分流水统计与指定日期净流通数据。
*/
class CurrencyStatsController extends Controller
{
/**
@@ -45,20 +47,7 @@ class CurrencyStatsController extends Controller
);
// 今日净流通量(正向增加 - 负向消耗),可判断通货膨胀
$netFlow = [];
foreach (['exp', 'gold', 'charm'] as $currency) {
$totalIn = UserCurrencyLog::whereDate('created_at', $date)
->where('currency', $currency)->where('amount', '>', 0)
->sum('amount');
$totalOut = UserCurrencyLog::whereDate('created_at', $date)
->where('currency', $currency)->where('amount', '<', 0)
->sum('amount');
$netFlow[$currency] = [
'in' => $totalIn,
'out' => abs($totalOut),
'net' => $totalIn + $totalOut, // 净增量
];
}
$netFlow = $this->currencyService->netFlowStats($date);
// 所有已知来源(供视图展示缺失来源的空行)
$allSources = CurrencySource::cases();
+13 -2
View File
@@ -12,11 +12,15 @@
namespace App\Http\Controllers;
use App\Enums\CurrencySource;
use App\Models\User;
use App\Services\UserCurrencyService;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
/**
* 类功能:展示全站排行榜、今日排行榜与用户个人积分流水记录。
*/
class LeaderboardController extends Controller
{
/**
@@ -133,8 +137,15 @@ class LeaderboardController extends Controller
$user = auth()->user();
$currency = request('currency');
$days = (int) request('days', 7);
$logs = $this->currencyService->userLogs($user->id, $currency ?: null, $days);
$direction = in_array(request('direction'), ['income', 'expense'], true) ? request('direction') : null;
$sourceOptions = CurrencySource::cases();
$allowedSources = collect($sourceOptions)->map(fn (CurrencySource $source) => $source->value)->all();
$selectedSources = collect(request()->array('sources'))
->filter(fn (string $source) => in_array($source, $allowedSources, true))
->values()
->all();
$logs = $this->currencyService->userLogs($user->id, $currency ?: null, $days, $direction, $selectedSources);
return view('leaderboard.my-logs', compact('logs', 'user', 'currency', 'days'));
return view('leaderboard.my-logs', compact('logs', 'user', 'currency', 'days', 'direction', 'sourceOptions', 'selectedSources'));
}
}
+76 -9
View File
@@ -16,7 +16,9 @@ namespace App\Services;
use App\Enums\CurrencySource;
use App\Models\User;
use App\Models\UserCurrencyLog;
use Carbon\CarbonImmutable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
/**
@@ -165,15 +167,75 @@ class UserCurrencyService
*/
public function activityStats(?string $date = null): Collection
{
$date = $date ?? today()->toDateString();
[$date, $rangeStart, $rangeEnd] = $this->statsDateBounds($date);
return UserCurrencyLog::query()
->whereDate('created_at', $date)
->selectRaw('source, currency, SUM(amount) as total_amount, COUNT(DISTINCT user_id) as participant_count')
->groupBy('source', 'currency')
->orderBy('currency')
->orderByRaw('ABS(SUM(amount)) DESC')
->get();
return Cache::remember("currency_stats:activity:{$date}", 300, function () use ($rangeStart, $rangeEnd) {
return UserCurrencyLog::query()
->where('created_at', '>=', $rangeStart)
->where('created_at', '<', $rangeEnd)
->selectRaw('source, currency, SUM(amount) as total_amount, COUNT(DISTINCT user_id) as participant_count')
->groupBy('source', 'currency')
->orderBy('currency')
->orderByRaw('ABS(SUM(amount)) DESC')
->get();
});
}
/**
* 查询某日三种货币的净流通量(流入、流出、净增)。
*
* @param string|null $date 日期字符串如 '2026-02-28',默认今日
* @return array<string, array{in:int, out:int, net:int}>
*/
public function netFlowStats(?string $date = null): array
{
[$date, $rangeStart, $rangeEnd] = $this->statsDateBounds($date);
$rows = Cache::remember("currency_stats:net_flow:{$date}", 300, function () use ($rangeStart, $rangeEnd) {
return UserCurrencyLog::query()
->where('created_at', '>=', $rangeStart)
->where('created_at', '<', $rangeEnd)
->selectRaw('
currency,
SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END) as total_in,
ABS(SUM(CASE WHEN amount < 0 THEN amount ELSE 0 END)) as total_out,
SUM(amount) as net_total
')
->groupBy('currency')
->get()
->keyBy('currency');
});
$netFlow = [];
foreach (['exp', 'gold', 'charm'] as $currency) {
$row = $rows->get($currency);
$netFlow[$currency] = [
'in' => (int) ($row->total_in ?? 0),
'out' => (int) ($row->total_out ?? 0),
'net' => (int) ($row->net_total ?? 0),
];
}
return $netFlow;
}
/**
* 解析统计查询的日期边界,统一复用缓存 key 与时间范围。
*
* @param string|null $date 日期字符串如 '2026-02-28'
* @return array{0:string, 1:CarbonImmutable, 2:CarbonImmutable}
*/
private function statsDateBounds(?string $date = null): array
{
$statsDate = CarbonImmutable::parse($date ?? today()->toDateString())->startOfDay();
return [
$statsDate->toDateString(),
$statsDate,
$statsDate->addDay(),
];
}
/**
@@ -220,12 +282,17 @@ class UserCurrencyService
* @param int $userId 用户 ID
* @param string|null $currency null 时返回所有货币类型
* @param int $days 查询最近多少天
* @param string|null $direction income=收入 / expense=支出 / null=全部
* @param array<int, string> $sources 来源 source 值列表,为空时不过滤
*/
public function userLogs(int $userId, ?string $currency = null, int $days = 7): Collection
public function userLogs(int $userId, ?string $currency = null, int $days = 7, ?string $direction = null, array $sources = []): Collection
{
return UserCurrencyLog::query()
->where('user_id', $userId)
->when($currency, fn ($q) => $q->where('currency', $currency))
->when($direction === 'income', fn ($q) => $q->where('amount', '>', 0))
->when($direction === 'expense', fn ($q) => $q->where('amount', '<', 0))
->when($sources !== [], fn ($q) => $q->whereIn('source', $sources))
->where('created_at', '>=', now()->subDays($days))
->orderByDesc('created_at')
->limit(200)