连接前端页面与后端服务(第一部分)
本次更新内容: - 完善Accounts账号列表页面,连接AccountManagementService - 实现真实数据加载、搜索过滤、启用/停用、删除等功能 - 显示账号分类、频道/群组统计信息 - 完善AccountLogin手机号登录页面,实现完整登录流程 - 多步骤登录(API配置→验证码→两步验证密码) - 登录成功后自动保存到数据库 - 支持已有账号更新和新账号创建 - 新增AccountCategory和ChannelGroup的Repository和Service - IAccountCategoryRepository / AccountCategoryRepository - IChannelGroupRepository / ChannelGroupRepository - AccountCategoryManagementService - ChannelGroupManagementService - 更新服务注册,添加新的Repository和Service 页面功能: - Accounts页面:列表展示、搜索、详情查看、启用/停用、删除 - AccountLogin页面:完整的三步登录流程,自动保存到数据库 下一步计划: - 继续连接Channels、Groups、BatchTasks等页面 - 完善系统设置页面 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
02c10cf2bb
commit
28546a614b
10 files changed
+392
-46
No files matched your search
@@ -26,7 +26,9 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// 注册数据管理服务
|
||||
services.AddScoped<AccountManagementService>();
|
||||
services.AddScoped<AccountCategoryManagementService>();
|
||||
services.AddScoped<ChannelManagementService>();
|
||||
services.AddScoped<ChannelGroupManagementService>();
|
||||
services.AddScoped<GroupManagementService>();
|
||||
services.AddScoped<BatchTaskManagementService>();
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using TelegramPanel.Data.Entities;
|
||||
using TelegramPanel.Data.Repositories;
|
||||
|
||||
namespace TelegramPanel.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 账号分类管理服务
|
||||
/// </summary>
|
||||
public class AccountCategoryManagementService
|
||||
{
|
||||
private readonly IAccountCategoryRepository _categoryRepository;
|
||||
|
||||
public AccountCategoryManagementService(IAccountCategoryRepository categoryRepository)
|
||||
{
|
||||
_categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AccountCategory>> GetAllCategoriesAsync()
|
||||
{
|
||||
return await _categoryRepository.GetAllAsync();
|
||||
}
|
||||
|
||||
public async Task<AccountCategory?> GetCategoryAsync(int id)
|
||||
{
|
||||
return await _categoryRepository.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task<AccountCategory?> GetCategoryByNameAsync(string name)
|
||||
{
|
||||
return await _categoryRepository.GetByNameAsync(name);
|
||||
}
|
||||
|
||||
public async Task<AccountCategory> CreateCategoryAsync(AccountCategory category)
|
||||
{
|
||||
return await _categoryRepository.AddAsync(category);
|
||||
}
|
||||
|
||||
public async Task UpdateCategoryAsync(AccountCategory category)
|
||||
{
|
||||
await _categoryRepository.UpdateAsync(category);
|
||||
}
|
||||
|
||||
public async Task DeleteCategoryAsync(int id)
|
||||
{
|
||||
var category = await _categoryRepository.GetByIdAsync(id);
|
||||
if (category != null)
|
||||
{
|
||||
await _categoryRepository.DeleteAsync(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using TelegramPanel.Data.Entities;
|
||||
using TelegramPanel.Data.Repositories;
|
||||
|
||||
namespace TelegramPanel.Core.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 频道分组管理服务
|
||||
/// </summary>
|
||||
public class ChannelGroupManagementService
|
||||
{
|
||||
private readonly IChannelGroupRepository _groupRepository;
|
||||
|
||||
public ChannelGroupManagementService(IChannelGroupRepository groupRepository)
|
||||
{
|
||||
_groupRepository = groupRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ChannelGroup>> GetAllGroupsAsync()
|
||||
{
|
||||
return await _groupRepository.GetAllAsync();
|
||||
}
|
||||
|
||||
public async Task<ChannelGroup?> GetGroupAsync(int id)
|
||||
{
|
||||
return await _groupRepository.GetByIdAsync(id);
|
||||
}
|
||||
|
||||
public async Task<ChannelGroup?> GetGroupByNameAsync(string name)
|
||||
{
|
||||
return await _groupRepository.GetByNameAsync(name);
|
||||
}
|
||||
|
||||
public async Task<ChannelGroup> CreateGroupAsync(ChannelGroup group)
|
||||
{
|
||||
return await _groupRepository.AddAsync(group);
|
||||
}
|
||||
|
||||
public async Task UpdateGroupAsync(ChannelGroup group)
|
||||
{
|
||||
await _groupRepository.UpdateAsync(group);
|
||||
}
|
||||
|
||||
public async Task DeleteGroupAsync(int id)
|
||||
{
|
||||
var group = await _groupRepository.GetByIdAsync(id);
|
||||
if (group != null)
|
||||
{
|
||||
await _groupRepository.DeleteAsync(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TelegramPanel.Data.Entities;
|
||||
|
||||
namespace TelegramPanel.Data.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 账号分类仓储实现
|
||||
/// </summary>
|
||||
public class AccountCategoryRepository : Repository<AccountCategory>, IAccountCategoryRepository
|
||||
{
|
||||
public AccountCategoryRepository(AppDbContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<AccountCategory>> GetAllAsync()
|
||||
{
|
||||
return await _dbSet
|
||||
.Include(c => c.Accounts)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<AccountCategory?> GetByNameAsync(string name)
|
||||
{
|
||||
return await _dbSet
|
||||
.Include(c => c.Accounts)
|
||||
.FirstOrDefaultAsync(c => c.Name == name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TelegramPanel.Data.Entities;
|
||||
|
||||
namespace TelegramPanel.Data.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 频道分组仓储实现
|
||||
/// </summary>
|
||||
public class ChannelGroupRepository : Repository<ChannelGroup>, IChannelGroupRepository
|
||||
{
|
||||
public ChannelGroupRepository(AppDbContext context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<IEnumerable<ChannelGroup>> GetAllAsync()
|
||||
{
|
||||
return await _dbSet
|
||||
.Include(g => g.Channels)
|
||||
.OrderBy(g => g.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ChannelGroup?> GetByNameAsync(string name)
|
||||
{
|
||||
return await _dbSet
|
||||
.Include(g => g.Channels)
|
||||
.FirstOrDefaultAsync(g => g.Name == name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using TelegramPanel.Data.Entities;
|
||||
|
||||
namespace TelegramPanel.Data.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 账号分类仓储接口
|
||||
/// </summary>
|
||||
public interface IAccountCategoryRepository : IRepository<AccountCategory>
|
||||
{
|
||||
Task<AccountCategory?> GetByNameAsync(string name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using TelegramPanel.Data.Entities;
|
||||
|
||||
namespace TelegramPanel.Data.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// 频道分组仓储接口
|
||||
/// </summary>
|
||||
public interface IChannelGroupRepository : IRepository<ChannelGroup>
|
||||
{
|
||||
Task<ChannelGroup?> GetByNameAsync(string name);
|
||||
}
|
||||
@@ -17,7 +17,9 @@ public static class ServiceCollectionExtensions
|
||||
|
||||
// 注册所有Repository
|
||||
services.AddScoped<IAccountRepository, AccountRepository>();
|
||||
services.AddScoped<IAccountCategoryRepository, AccountCategoryRepository>();
|
||||
services.AddScoped<IChannelRepository, ChannelRepository>();
|
||||
services.AddScoped<IChannelGroupRepository, ChannelGroupRepository>();
|
||||
services.AddScoped<IGroupRepository, GroupRepository>();
|
||||
services.AddScoped<IBatchTaskRepository, BatchTaskRepository>();
|
||||
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
@page "/accounts/login"
|
||||
@inject IAccountService AccountService
|
||||
@inject AccountManagementService AccountManagement
|
||||
@inject ISnackbar Snackbar
|
||||
@inject NavigationManager Navigation
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<PageTitle>手机号登录 - Telegram Panel</PageTitle>
|
||||
|
||||
@@ -16,6 +19,9 @@
|
||||
<MudTextField @bind-Value="apiHash" Label="API Hash" Variant="Variant.Outlined" Required="true" Class="mt-3" />
|
||||
<MudTextField @bind-Value="phoneNumber" Label="手机号" Variant="Variant.Outlined" Required="true"
|
||||
Placeholder="+86xxxxxxxxxx" Class="mt-3" />
|
||||
<MudText Typo="Typo.caption" Class="mt-2" Color="Color.Info">
|
||||
* 手机号需包含国家代码,例如:+8613800138000
|
||||
</MudText>
|
||||
</MudStep>
|
||||
|
||||
<MudStep Title="验证码" IsResultStep="false">
|
||||
@@ -24,6 +30,9 @@
|
||||
</MudText>
|
||||
<MudTextField @bind-Value="verificationCode" Label="验证码" Variant="Variant.Outlined" Required="true"
|
||||
Placeholder="12345" />
|
||||
<MudText Typo="Typo.caption" Class="mt-2" Color="Color.Info">
|
||||
请在Telegram客户端查收验证码
|
||||
</MudText>
|
||||
</MudStep>
|
||||
|
||||
<MudStep Title="密码验证" Optional="true">
|
||||
@@ -36,10 +45,14 @@
|
||||
|
||||
<MudStep Title="完成" IsResultStep="true">
|
||||
<MudAlert Severity="Severity.Success" Class="mb-3">
|
||||
<MudText Typo="Typo.body1">登录成功!</MudText>
|
||||
<MudText Typo="Typo.body1">登录成功并保存到数据库!</MudText>
|
||||
</MudAlert>
|
||||
<MudText Typo="Typo.body2">用户名:@username</MudText>
|
||||
<MudText Typo="Typo.body2">用户ID:@userId</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>手机号:</strong>@phoneNumber</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>用户名:</strong>@(username ?? "无")</MudText>
|
||||
<MudText Typo="Typo.body2"><strong>用户ID:</strong>@userId</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Href="/accounts" Class="mt-4">
|
||||
查看账号列表
|
||||
</MudButton>
|
||||
</MudStep>
|
||||
</MudStepper>
|
||||
</MudCardContent>
|
||||
@@ -48,7 +61,7 @@
|
||||
重置
|
||||
</MudButton>
|
||||
<MudSpacer />
|
||||
@if (stepper.GetActiveIndex() > 0)
|
||||
@if (stepper.GetActiveIndex() > 0 && !stepper.IsAllStepsCompleted())
|
||||
{
|
||||
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(() => stepper.Previous())">
|
||||
上一步
|
||||
@@ -84,62 +97,96 @@
|
||||
private string verificationCode = "";
|
||||
private string password = "";
|
||||
|
||||
private string username = "";
|
||||
private string? username = "";
|
||||
private long userId = 0;
|
||||
private int accountId = 0;
|
||||
|
||||
private async Task NextStep()
|
||||
{
|
||||
var currentStep = stepper.GetActiveIndex();
|
||||
|
||||
logging = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (currentStep == 0)
|
||||
{
|
||||
// 验证 API 配置并发送验证码
|
||||
if (!int.TryParse(apiId, out _))
|
||||
// 步骤1:验证 API 配置并发送验证码
|
||||
if (!int.TryParse(apiId, out int parsedApiId))
|
||||
{
|
||||
Snackbar.Add("API ID 格式错误", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 调用 API 发送验证码
|
||||
Snackbar.Add($"验证码已发送到 {phoneNumber}", Severity.Info);
|
||||
stepper.Next();
|
||||
if (string.IsNullOrWhiteSpace(apiHash) || string.IsNullOrWhiteSpace(phoneNumber))
|
||||
{
|
||||
Snackbar.Add("请填写完整信息", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成临时账号ID(用于跟踪登录过程)
|
||||
accountId = DateTime.UtcNow.GetHashCode();
|
||||
|
||||
var result = await AccountService.StartLoginAsync(accountId, phoneNumber);
|
||||
|
||||
if (result.NextStep == "code")
|
||||
{
|
||||
Snackbar.Add($"验证码已发送到 {phoneNumber}", Severity.Info);
|
||||
stepper.Next();
|
||||
}
|
||||
else if (result.NextStep == "signup")
|
||||
{
|
||||
Snackbar.Add("此号码未注册Telegram,请先注册", Severity.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(result.Message ?? "发送验证码失败", Severity.Error);
|
||||
}
|
||||
}
|
||||
else if (currentStep == 1)
|
||||
{
|
||||
// 验证验证码
|
||||
if (string.IsNullOrEmpty(verificationCode))
|
||||
// 步骤2:验证验证码
|
||||
if (string.IsNullOrWhiteSpace(verificationCode))
|
||||
{
|
||||
Snackbar.Add("请输入验证码", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: 验证验证码,检查是否需要密码
|
||||
bool needsPassword = false; // 模拟
|
||||
var result = await AccountService.SubmitCodeAsync(accountId, verificationCode);
|
||||
|
||||
if (needsPassword)
|
||||
if (result.Success)
|
||||
{
|
||||
// 登录成功,保存到数据库
|
||||
await SaveAccount(result.Account!);
|
||||
}
|
||||
else if (result.NextStep == "password")
|
||||
{
|
||||
Snackbar.Add("需要两步验证密码", Severity.Info);
|
||||
stepper.Next();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接完成登录
|
||||
await CompleteLogin();
|
||||
Snackbar.Add(result.Message ?? "验证码错误", Severity.Error);
|
||||
}
|
||||
}
|
||||
else if (currentStep == 2)
|
||||
{
|
||||
// 验证密码并完成登录
|
||||
if (string.IsNullOrEmpty(password))
|
||||
// 步骤3:验证密码
|
||||
if (string.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
Snackbar.Add("请输入密码", Severity.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
await CompleteLogin();
|
||||
var result = await AccountService.SubmitPasswordAsync(accountId, password);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
await SaveAccount(result.Account!);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(result.Message ?? "密码错误", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -152,13 +199,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CompleteLogin()
|
||||
private async Task SaveAccount(TelegramPanel.Core.Models.AccountInfo accountInfo)
|
||||
{
|
||||
// TODO: 完成登录并保存 session
|
||||
username = "TestUser";
|
||||
userId = 123456789;
|
||||
try
|
||||
{
|
||||
// 检查账号是否已存在
|
||||
var existingAccount = await AccountManagement.GetAccountByPhoneAsync(accountInfo.Phone!);
|
||||
|
||||
Snackbar.Add("登录成功!", Severity.Success);
|
||||
stepper.Next();
|
||||
if (existingAccount != null)
|
||||
{
|
||||
// 更新现有账号
|
||||
existingAccount.Username = accountInfo.Username;
|
||||
existingAccount.IsActive = true;
|
||||
existingAccount.LastSyncAt = DateTime.UtcNow;
|
||||
await AccountManagement.UpdateAccountAsync(existingAccount);
|
||||
|
||||
Snackbar.Add("账号信息已更新", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 创建新账号
|
||||
var account = new Account
|
||||
{
|
||||
Phone = accountInfo.Phone!,
|
||||
UserId = accountInfo.TelegramUserId,
|
||||
Username = accountInfo.Username,
|
||||
SessionPath = $"sessions/{accountInfo.Phone}.session",
|
||||
ApiId = int.Parse(apiId),
|
||||
ApiHash = apiHash,
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
LastSyncAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await AccountManagement.CreateAccountAsync(account);
|
||||
Snackbar.Add("账号已保存到数据库", Severity.Success);
|
||||
}
|
||||
|
||||
// 设置显示信息
|
||||
username = accountInfo.Username;
|
||||
userId = accountInfo.TelegramUserId;
|
||||
|
||||
// 跳转到完成步骤
|
||||
stepper.Next();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"保存账号失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
@page "/accounts"
|
||||
@inject IAccountService AccountService
|
||||
@inject AccountManagementService AccountManagement
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
@using TelegramPanel.Data.Entities
|
||||
|
||||
<PageTitle>账号列表 - Telegram Panel</PageTitle>
|
||||
|
||||
@@ -8,18 +10,22 @@
|
||||
|
||||
<MudCard>
|
||||
<MudCardContent>
|
||||
<MudTable Items="@accounts" Hover="true" Breakpoint="Breakpoint.Sm" Loading="@loading">
|
||||
<MudTable Items="@filteredAccounts" Hover="true" Breakpoint="Breakpoint.Sm" Loading="@loading">
|
||||
<ToolBarContent>
|
||||
<MudText Typo="Typo.h6">已导入账号</MudText>
|
||||
<MudText Typo="Typo.h6">已导入账号 (@accounts.Count)</MudText>
|
||||
<MudSpacer />
|
||||
<MudTextField @bind-Value="searchString" Placeholder="搜索账号..." Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" />
|
||||
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"
|
||||
Immediate="true" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
|
||||
OnClick="LoadAccounts" Class="ml-2">刷新</MudButton>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>手机号</MudTh>
|
||||
<MudTh>用户名</MudTh>
|
||||
<MudTh>用户ID</MudTh>
|
||||
<MudTh>分类</MudTh>
|
||||
<MudTh>频道/群组</MudTh>
|
||||
<MudTh>状态</MudTh>
|
||||
<MudTh>最后同步</MudTh>
|
||||
<MudTh>操作</MudTh>
|
||||
@@ -28,22 +34,40 @@
|
||||
<MudTd DataLabel="手机号">@context.Phone</MudTd>
|
||||
<MudTd DataLabel="用户名">@(context.Username ?? "-")</MudTd>
|
||||
<MudTd DataLabel="用户ID">@context.UserId</MudTd>
|
||||
<MudTd DataLabel="分类">@(context.Category ?? "未分类")</MudTd>
|
||||
<MudTd DataLabel="分类">
|
||||
@if (context.Category != null)
|
||||
{
|
||||
<MudChip Size="Size.Small" Style="@($"background-color: {context.Category.Color ?? "#9E9E9E"}")">
|
||||
@context.Category.Name
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2" Color="Color.Default">未分类</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd DataLabel="频道/群组">
|
||||
<MudText Typo="Typo.body2">@context.Channels.Count / @context.Groups.Count</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="状态">
|
||||
<MudChip Size="Size.Small" Color="@(context.IsActive ? Color.Success : Color.Default)">
|
||||
@(context.IsActive ? "在线" : "离线")
|
||||
@(context.IsActive ? "活跃" : "停用")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="最后同步">@context.LastSyncAt.ToString("yyyy-MM-dd HH:mm")</MudTd>
|
||||
<MudTd DataLabel="操作">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Info" Size="Size.Small" Color="Color.Info"
|
||||
OnClick="@(() => ShowDetails(context))" />
|
||||
OnClick="@(() => ShowDetails(context))" Title="查看详情" />
|
||||
<MudIconButton Icon="@(context.IsActive ? Icons.Material.Filled.Stop : Icons.Material.Filled.PlayArrow)"
|
||||
Size="Size.Small" Color="@(context.IsActive ? Color.Warning : Color.Success)"
|
||||
OnClick="@(() => ToggleActive(context))"
|
||||
Title="@(context.IsActive ? "停用" : "启用")" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Small" Color="Color.Error"
|
||||
OnClick="@(() => DeleteAccount(context.Id))" />
|
||||
OnClick="@(() => DeleteAccount(context))" Title="删除账号" />
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>暂无账号数据,请先导入账号</MudText>
|
||||
<MudText>暂无账号数据,请先<MudLink Href="/accounts/import">导入账号</MudLink></MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
<MudProgressCircular Indeterminate="true" />
|
||||
@@ -53,10 +77,18 @@
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
private List<AccountInfo> accounts = new();
|
||||
private List<Account> accounts = new();
|
||||
private bool loading = true;
|
||||
private string searchString = "";
|
||||
|
||||
private IEnumerable<Account> filteredAccounts =>
|
||||
string.IsNullOrWhiteSpace(searchString)
|
||||
? accounts
|
||||
: accounts.Where(a =>
|
||||
a.Phone.Contains(searchString, StringComparison.OrdinalIgnoreCase) ||
|
||||
(a.Username?.Contains(searchString, StringComparison.OrdinalIgnoreCase) ?? false) ||
|
||||
a.UserId.ToString().Contains(searchString));
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAccounts();
|
||||
@@ -67,9 +99,8 @@
|
||||
loading = true;
|
||||
try
|
||||
{
|
||||
// TODO: 从数据库加载账号列表
|
||||
accounts = new List<AccountInfo>();
|
||||
Snackbar.Add("账号列表加载成功", Severity.Success);
|
||||
var accountList = await AccountManagement.GetAllAccountsAsync();
|
||||
accounts = accountList.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -81,15 +112,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowDetails(AccountInfo account)
|
||||
private async Task ShowDetails(Account account)
|
||||
{
|
||||
Snackbar.Add($"查看账号详情:{account.Phone}", Severity.Info);
|
||||
// TODO: 显示账号详情对话框
|
||||
var (channels, groups) = await AccountManagement.GetAccountStatisticsAsync(account.Id);
|
||||
var message = $@"
|
||||
账号详情:
|
||||
手机号:{account.Phone}
|
||||
用户名:{account.Username ?? "无"}
|
||||
用户ID:{account.UserId}
|
||||
分类:{account.Category?.Name ?? "未分类"}
|
||||
创建的频道:{channels} 个
|
||||
创建的群组:{groups} 个
|
||||
创建时间:{account.CreatedAt:yyyy-MM-dd HH:mm}
|
||||
最后同步:{account.LastSyncAt:yyyy-MM-dd HH:mm}
|
||||
Session路径:{account.SessionPath}
|
||||
";
|
||||
await DialogService.ShowMessageBox("账号详情", message, "关闭");
|
||||
}
|
||||
|
||||
private async Task DeleteAccount(int accountId)
|
||||
private async Task ToggleActive(Account account)
|
||||
{
|
||||
// TODO: 确认对话框 + 删除账号
|
||||
Snackbar.Add("删除功能开发中", Severity.Warning);
|
||||
try
|
||||
{
|
||||
await AccountManagement.SetAccountActiveStatusAsync(account.Id, !account.IsActive);
|
||||
account.IsActive = !account.IsActive;
|
||||
Snackbar.Add($"账号已{(account.IsActive ? "启用" : "停用")}", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"操作失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteAccount(Account account)
|
||||
{
|
||||
bool? result = await DialogService.ShowMessageBox(
|
||||
"确认删除",
|
||||
$"确定要删除账号 {account.Phone} 吗?此操作不可撤销!",
|
||||
yesText: "删除", cancelText: "取消");
|
||||
|
||||
if (result == true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await AccountManagement.DeleteAccountAsync(account.Id);
|
||||
accounts.Remove(account);
|
||||
Snackbar.Add("账号已删除", Severity.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"删除失败:{ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user