feat: new channel
This commit is contained in:
@@ -21,11 +21,23 @@ class ChannelController extends Controller
|
||||
{
|
||||
$agents = $this->isAgentPortal()
|
||||
? collect()
|
||||
: User::query()->orderBy('username')->get(['id', 'username']);
|
||||
: User::query()->orderBy('username')->get(['id', 'username', 'comment']);
|
||||
|
||||
$agentOptions = $agents->map(function (User $a) {
|
||||
$name = trim((string) $a->username);
|
||||
$comment = trim((string) ($a->comment ?? ''));
|
||||
|
||||
return [
|
||||
'id' => $a->id,
|
||||
'username' => $name,
|
||||
'label' => $comment !== '' ? $name.'/'.$comment : $name,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return view('admin.channels.index', [
|
||||
'portal' => $this->portal(),
|
||||
'agents' => $agents,
|
||||
'agentOptions' => $agentOptions,
|
||||
'maxPerAgent' => Channel::maxPerAgent(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ class DashboardController extends Controller
|
||||
public function data(Request $request)
|
||||
{
|
||||
$data = $this->stats->collect([
|
||||
'date_from' => trim((string) $request->query('date_from', '')),
|
||||
'date_to' => trim((string) $request->query('date_to', '')),
|
||||
'date_range' => trim((string) $request->query('date_range', '')),
|
||||
'range' => (string) $request->query('range', '30d'),
|
||||
'channel_id' => trim((string) $request->query('channel_id', '')),
|
||||
'agent_user_id' => $this->isAgentPortal()
|
||||
|
||||
@@ -17,6 +17,9 @@ class DashboardStatsService
|
||||
/**
|
||||
* @param array{
|
||||
* range?: string,
|
||||
* date_from?: string,
|
||||
* date_to?: string,
|
||||
* date_range?: string,
|
||||
* channel_id?: string,
|
||||
* channel_exact?: bool,
|
||||
* agent_user_id?: ?int,
|
||||
@@ -44,8 +47,8 @@ class DashboardStatsService
|
||||
*/
|
||||
public function collect(array $filters = []): array
|
||||
{
|
||||
$range = (string) ($filters['range'] ?? '30d');
|
||||
[$from, $to] = $this->rangeBounds($range);
|
||||
[$from, $to] = $this->resolveBounds($filters);
|
||||
$rangeLabel = $from->toDateString().' ~ '.$to->toDateString();
|
||||
|
||||
$channelId = trim((string) ($filters['channel_id'] ?? ''));
|
||||
$channelExact = (bool) ($filters['channel_exact'] ?? false);
|
||||
@@ -68,10 +71,13 @@ class DashboardStatsService
|
||||
}
|
||||
}
|
||||
|
||||
$total = (clone $base)->count();
|
||||
$walletCount = (clone $base)->where('has_wallet', Device::WALLET_YES)->count();
|
||||
$newCount = (clone $base)->whereBetween('created_at', [$from, $to])->count();
|
||||
// Period cohort: devices installed (created) in the selected range.
|
||||
$createdInRange = (clone $base)->whereBetween('created_at', [$from, $to]);
|
||||
$total = (clone $createdInRange)->count();
|
||||
$walletCount = (clone $createdInRange)->where('has_wallet', Device::WALLET_YES)->count();
|
||||
$newCount = $total;
|
||||
$activeCount = (clone $base)->whereBetween('updated_at', [$from, $to])->count();
|
||||
|
||||
$mnemonicCount = $this->countDeviceChildren(
|
||||
WalletMnemonic::query(),
|
||||
'wallet_mnemonics',
|
||||
@@ -79,6 +85,8 @@ class DashboardStatsService
|
||||
$agentUserId,
|
||||
$channelId,
|
||||
$channelExact,
|
||||
$from,
|
||||
$to,
|
||||
);
|
||||
$addressCount = $this->countDeviceChildren(
|
||||
WalletAddress::query(),
|
||||
@@ -87,8 +95,10 @@ class DashboardStatsService
|
||||
$agentUserId,
|
||||
$channelId,
|
||||
$channelExact,
|
||||
$from,
|
||||
$to,
|
||||
);
|
||||
$balances = $this->sumBalances($agent, $agentUserId, $channelId, $channelExact);
|
||||
$balances = $this->sumBalances($agent, $agentUserId, $channelId, $channelExact, $from, $to);
|
||||
|
||||
$visits = PageVisit::query();
|
||||
AgentScope::applyChannelIdScope($visits, $agent);
|
||||
@@ -143,7 +153,7 @@ class DashboardStatsService
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$byDeviceIosVersion = (clone $base)
|
||||
$byDeviceIosVersion = (clone $createdInRange)
|
||||
->select('ios_version')
|
||||
->selectRaw('COUNT(*) as cnt')
|
||||
->groupBy('ios_version')
|
||||
@@ -177,12 +187,49 @@ class DashboardStatsService
|
||||
'by_os' => $byOs,
|
||||
'by_ios_version' => $byIosVersion,
|
||||
'by_device_ios_version' => $byDeviceIosVersion,
|
||||
'range' => $range,
|
||||
'range' => $rangeLabel,
|
||||
'from' => $from->toDateTimeString(),
|
||||
'to' => $to->toDateTimeString(),
|
||||
'date_from' => $from->toDateString(),
|
||||
'date_to' => $to->toDateString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{range?: string, date_from?: string, date_to?: string, date_range?: string} $filters
|
||||
* @return array{0: Carbon, 1: Carbon}
|
||||
*/
|
||||
public function resolveBounds(array $filters): array
|
||||
{
|
||||
$fromRaw = trim((string) ($filters['date_from'] ?? ''));
|
||||
$toRaw = trim((string) ($filters['date_to'] ?? ''));
|
||||
$rangeField = trim((string) ($filters['date_range'] ?? ''));
|
||||
|
||||
if ($fromRaw === '' && $toRaw === '' && $rangeField !== '') {
|
||||
if (preg_match('/^(\d{4}-\d{2}-\d{2})\s*~\s*(\d{4}-\d{2}-\d{2})$/', $rangeField, $m)
|
||||
|| preg_match('/^(\d{4}-\d{2}-\d{2})\s*-\s*(\d{4}-\d{2}-\d{2})$/', $rangeField, $m)) {
|
||||
$fromRaw = $m[1];
|
||||
$toRaw = $m[2];
|
||||
}
|
||||
}
|
||||
|
||||
if ($fromRaw !== '' && $toRaw !== '') {
|
||||
try {
|
||||
$from = Carbon::parse($fromRaw)->startOfDay();
|
||||
$to = Carbon::parse($toRaw)->endOfDay();
|
||||
} catch (\Throwable) {
|
||||
return $this->rangeBounds((string) ($filters['range'] ?? '30d'));
|
||||
}
|
||||
if ($from->gt($to)) {
|
||||
[$from, $to] = [$to->copy()->startOfDay(), $from->copy()->endOfDay()];
|
||||
}
|
||||
|
||||
return [$from, $to];
|
||||
}
|
||||
|
||||
return $this->rangeBounds((string) ($filters['range'] ?? '30d'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a device-child table and apply the same channel / agent scope as device totals.
|
||||
*/
|
||||
@@ -217,19 +264,27 @@ class DashboardStatsService
|
||||
?int $agentUserId,
|
||||
string $channelId,
|
||||
bool $channelExact,
|
||||
Carbon $from,
|
||||
Carbon $to,
|
||||
): int {
|
||||
return $this->scopedDeviceChildren($query, $table, $agent, $agentUserId, $channelId, $channelExact)
|
||||
->whereBetween($table.'.created_at', [$from, $to])
|
||||
->count($table.'.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum coin columns across wallet_addresses for devices in the same scope as dashboard totals.
|
||||
* Not limited by the time range (current holdings).
|
||||
* Sum coin columns for addresses created in the selected range.
|
||||
*
|
||||
* @return array{usdt: string, trx: string, eth: string, btc: string, bnb: string}
|
||||
*/
|
||||
private function sumBalances(?User $agent, ?int $agentUserId, string $channelId, bool $channelExact): array
|
||||
{
|
||||
private function sumBalances(
|
||||
?User $agent,
|
||||
?int $agentUserId,
|
||||
string $channelId,
|
||||
bool $channelExact,
|
||||
Carbon $from,
|
||||
Carbon $to,
|
||||
): array {
|
||||
$q = $this->scopedDeviceChildren(
|
||||
WalletAddress::query(),
|
||||
'wallet_addresses',
|
||||
@@ -237,7 +292,7 @@ class DashboardStatsService
|
||||
$agentUserId,
|
||||
$channelId,
|
||||
$channelExact,
|
||||
);
|
||||
)->whereBetween('wallet_addresses.created_at', [$from, $to]);
|
||||
|
||||
$selects = [];
|
||||
foreach (WalletAddress::COIN_COLUMNS as $col) {
|
||||
|
||||
@@ -85,14 +85,14 @@ class DataCommand
|
||||
'区间: '.$data['from'].' ~ '.$data['to'],
|
||||
'渠道: '.$channelLabel,
|
||||
'',
|
||||
'📦 设备与资产',
|
||||
'📦 设备与资产(区间内)',
|
||||
'总设备: '.$data['wallet_count'].' / '.$data['total'].' (有钱包/全部)',
|
||||
'新增设备: '.$data['new_count'],
|
||||
'活跃设备: '.$data['active_count'],
|
||||
'助记词: '.$data['mnemonic_count'],
|
||||
'钱包地址: '.$data['address_count'],
|
||||
'',
|
||||
'💰 总余额',
|
||||
'💰 总余额(区间内新增地址)',
|
||||
'• USDT '.$data['balances']['usdt'],
|
||||
'• TRX '.$data['balances']['trx'],
|
||||
'• BTC '.$data['balances']['btc'],
|
||||
|
||||
Reference in New Issue
Block a user