diff --git a/src/TelegramPanel.Web/Components/Dialogs/OnlineDevicesDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/OnlineDevicesDialog.razor
new file mode 100644
index 0000000..d36e938
--- /dev/null
+++ b/src/TelegramPanel.Web/Components/Dialogs/OnlineDevicesDialog.razor
@@ -0,0 +1,192 @@
+@namespace TelegramPanel.Web.Components.Dialogs
+@inject AccountTelegramToolsService AccountTelegramTools
+@inject IDialogService DialogService
+@inject ISnackbar Snackbar
+
+
+
+
+ 账号:@Phone(ID:@AccountId)
+
+
+ 这里显示账号当前在线设备/会话,可对“非当前设备”执行踢出登录。
+
+
+
+
+ 刷新
+
+
+ 踢出所有其他设备
+
+
+
+ @if (loading)
+ {
+
+ }
+ else if (devices.Count == 0)
+ {
+ 暂无设备信息
+ }
+ else
+ {
+
+
+ 当前
+ 应用/设备
+ 系统
+ IP/地区
+ 最近活跃
+ 操作
+
+
+
+
+ @(context.Current ? "是" : "否")
+
+
+
+ @context.Title
+ ApiId=@context.ApiId @context.AppVersion
+
+
+ @($"{context.Platform} {context.SystemVersion}".Trim())
+
+
+ @context.Ip
+ @($"{context.Country} {context.Region}".Trim())
+
+
+ @(context.LastActiveAtUtc?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? "-")
+
+
+
+ 踢出
+
+
+
+
+ }
+
+
+
+ 关闭
+
+
+
+@code
+{
+ [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
+ [Parameter] public int AccountId { get; set; }
+ [Parameter] public string Phone { get; set; } = "";
+
+ private bool loading;
+ private List devices = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadDevices();
+ }
+
+ private async Task LoadDevices()
+ {
+ loading = true;
+ try
+ {
+ var list = await AccountTelegramTools.GetAuthorizationsAsync(AccountId);
+ devices = list.ToList();
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"加载设备失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private async Task KickOne(TelegramAuthorizationInfo auth)
+ {
+ if (auth.Current)
+ {
+ Snackbar.Add("不能踢出当前设备", Severity.Warning);
+ return;
+ }
+
+ bool? result = await DialogService.ShowMessageBox(
+ "确认踢出",
+ $"确定要踢出该设备登录吗?\n{auth.Title}\nIP:{auth.Ip}\n最近活跃:{auth.LastActiveAtUtc?.ToLocalTime():yyyy-MM-dd HH:mm:ss}",
+ yesText: "踢出", cancelText: "取消");
+
+ if (result != true)
+ return;
+
+ loading = true;
+ try
+ {
+ var ok = await AccountTelegramTools.KickAuthorizationAsync(AccountId, auth.Hash);
+ if (ok)
+ {
+ Snackbar.Add("已踢出该设备", Severity.Success);
+ await LoadDevices();
+ }
+ else
+ {
+ Snackbar.Add("踢出失败(返回 false)", Severity.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"踢出失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private async Task KickAllOthers()
+ {
+ bool? result = await DialogService.ShowMessageBox(
+ "确认踢出",
+ "确定要踢出所有其他设备吗?(当前设备会保留)",
+ yesText: "踢出", cancelText: "取消");
+
+ if (result != true)
+ return;
+
+ loading = true;
+ try
+ {
+ var ok = await AccountTelegramTools.KickAllOtherAuthorizationsAsync(AccountId);
+ if (ok)
+ {
+ Snackbar.Add("已踢出所有其他设备", Severity.Success);
+ await LoadDevices();
+ }
+ else
+ {
+ Snackbar.Add("踢出失败(返回 false)", Severity.Error);
+ }
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"踢出失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private void Close()
+ {
+ MudDialog.Close();
+ }
+}
+
diff --git a/src/TelegramPanel.Web/Components/Dialogs/SystemInboxDialog.razor b/src/TelegramPanel.Web/Components/Dialogs/SystemInboxDialog.razor
new file mode 100644
index 0000000..a79a149
--- /dev/null
+++ b/src/TelegramPanel.Web/Components/Dialogs/SystemInboxDialog.razor
@@ -0,0 +1,90 @@
+@namespace TelegramPanel.Web.Components.Dialogs
+@inject AccountTelegramToolsService AccountTelegramTools
+@inject ISnackbar Snackbar
+
+
+
+
+ 账号:@Phone(ID:@AccountId)
+
+
+ 这里显示 Telegram 系统通知(通常用于接收登录验证码)。若列表为空,可能是该账号从未在 Telegram 内收到系统消息。
+
+
+
+
+ 刷新
+
+ @if (lastLoadedAtUtc.HasValue)
+ {
+ 最后刷新:@lastLoadedAtUtc.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")
+ }
+
+
+ @if (loading)
+ {
+
+ }
+ else if (messages.Count == 0)
+ {
+ 暂无系统通知
+ }
+ else
+ {
+
+ @foreach (var m in messages)
+ {
+
+ @((m.DateUtc ?? DateTime.MinValue).ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"))
+ @m.Text
+
+ }
+
+ }
+
+
+
+ 关闭
+
+
+
+@code
+{
+ [CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
+ [Parameter] public int AccountId { get; set; }
+ [Parameter] public string Phone { get; set; } = "";
+
+ private bool loading;
+ private DateTime? lastLoadedAtUtc;
+ private List messages = new();
+
+ protected override async Task OnInitializedAsync()
+ {
+ await LoadMessages();
+ }
+
+ private async Task LoadMessages()
+ {
+ loading = true;
+ try
+ {
+ var list = await AccountTelegramTools.GetLatestSystemMessagesAsync(AccountId, limit: 30);
+ messages = list.ToList();
+ lastLoadedAtUtc = DateTime.UtcNow;
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"加载系统通知失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private void Close()
+ {
+ MudDialog.Close();
+ }
+}
diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor
index a599350..c6cd22d 100644
--- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor
+++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor
@@ -1,6 +1,7 @@
@page "/accounts"
@inject AccountManagementService AccountManagement
@inject AccountCategoryManagementService CategoryManagement
+@inject AccountTelegramToolsService AccountTelegramTools
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@using TelegramPanel.Data.Entities
@@ -25,6 +26,10 @@
+
+ 批量刷新状态
+
刷新
@@ -36,6 +41,7 @@
分类
频道/群组
状态
+ Telegram 状态
最后同步
操作
@@ -64,10 +70,28 @@
@(context.IsActive ? "活跃" : "停用")
+
+ @if (telegramStatus.TryGetValue(context.Id, out var s))
+ {
+
+ @s.Summary
+
+ }
+ else
+ {
+ 未检测
+ }
+
@context.LastSyncAt.ToString("yyyy-MM-dd HH:mm")
+
+
+
telegramStatus = new();
private IEnumerable filteredAccounts =>
accounts.Where(a =>
@@ -134,6 +159,10 @@
{
var accountList = await AccountManagement.GetAllAccountsAsync();
accounts = accountList.ToList();
+ // 刷新账号列表后,清理不存在账号的状态缓存
+ var ids = new HashSet(accounts.Select(a => a.Id));
+ foreach (var key in telegramStatus.Keys.Where(k => !ids.Contains(k)).ToList())
+ telegramStatus.Remove(key);
await LoadCategories();
}
catch (Exception ex)
@@ -153,6 +182,99 @@
await DialogService.ShowMessageBox("账号详情", message, "关闭");
}
+ private async Task RefreshTelegramStatus(Account account)
+ {
+ try
+ {
+ loading = true;
+ var result = await AccountTelegramTools.RefreshAccountStatusAsync(account.Id);
+ telegramStatus[account.Id] = result;
+
+ // 资料刷新后重新加载(昵称/用户名/用户ID 可能更新)
+ await LoadAccounts();
+
+ Snackbar.Add($"账号 {account.Phone} 状态:{result.Summary}", result.Ok ? Severity.Success : Severity.Warning);
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"刷新失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private async Task BatchRefreshTelegramStatus()
+ {
+ if (accounts.Count == 0)
+ {
+ Snackbar.Add("暂无账号可刷新", Severity.Info);
+ return;
+ }
+
+ loading = true;
+ try
+ {
+ var targets = filteredAccounts.ToList();
+ foreach (var a in targets)
+ {
+ try
+ {
+ var result = await AccountTelegramTools.RefreshAccountStatusAsync(a.Id);
+ telegramStatus[a.Id] = result;
+ }
+ catch (Exception ex)
+ {
+ telegramStatus[a.Id] = new TelegramAccountStatusResult(
+ Ok: false,
+ Summary: "刷新失败",
+ Details: ex.Message,
+ CheckedAtUtc: DateTime.UtcNow);
+ }
+
+ StateHasChanged();
+ }
+
+ await LoadAccounts();
+ Snackbar.Add($"批量刷新完成:{targets.Count} 个账号", Severity.Success);
+ }
+ catch (Exception ex)
+ {
+ Snackbar.Add($"批量刷新失败:{ex.Message}", Severity.Error);
+ }
+ finally
+ {
+ loading = false;
+ }
+ }
+
+ private async Task OpenSystemInbox(Account account)
+ {
+ var parameters = new DialogParameters
+ {
+ ["AccountId"] = account.Id,
+ ["Phone"] = account.Phone
+ };
+
+ var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
+ DialogService.Show("系统通知(验证码)", parameters, options);
+ await Task.CompletedTask;
+ }
+
+ private async Task OpenDevices(Account account)
+ {
+ var parameters = new DialogParameters
+ {
+ ["AccountId"] = account.Id,
+ ["Phone"] = account.Phone
+ };
+
+ var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
+ DialogService.Show("在线设备", parameters, options);
+ await Task.CompletedTask;
+ }
+
private async Task ToggleActive(Account account)
{
try
diff --git a/src/TelegramPanel.Web/Components/_Imports.razor b/src/TelegramPanel.Web/Components/_Imports.razor
index e08bc5a..ab1c6e5 100644
--- a/src/TelegramPanel.Web/Components/_Imports.razor
+++ b/src/TelegramPanel.Web/Components/_Imports.razor
@@ -13,5 +13,6 @@
@using TelegramPanel.Core.Interfaces
@using TelegramPanel.Core.Services
@using TelegramPanel.Core.Services.Telegram
+@using TelegramPanel.Core.Models
@using TelegramPanel.Web.Components
@using TelegramPanel.Web.Components.Layout