diff --git a/src/TelegramPanel.Core/ServiceCollectionExtensions.cs b/src/TelegramPanel.Core/ServiceCollectionExtensions.cs index f2ca234..0239835 100644 --- a/src/TelegramPanel.Core/ServiceCollectionExtensions.cs +++ b/src/TelegramPanel.Core/ServiceCollectionExtensions.cs @@ -26,7 +26,9 @@ public static class ServiceCollectionExtensions // 注册数据管理服务 services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/TelegramPanel.Core/Services/AccountCategoryManagementService.cs b/src/TelegramPanel.Core/Services/AccountCategoryManagementService.cs new file mode 100644 index 0000000..e9771de --- /dev/null +++ b/src/TelegramPanel.Core/Services/AccountCategoryManagementService.cs @@ -0,0 +1,51 @@ +using TelegramPanel.Data.Entities; +using TelegramPanel.Data.Repositories; + +namespace TelegramPanel.Core.Services; + +/// +/// 账号分类管理服务 +/// +public class AccountCategoryManagementService +{ + private readonly IAccountCategoryRepository _categoryRepository; + + public AccountCategoryManagementService(IAccountCategoryRepository categoryRepository) + { + _categoryRepository = categoryRepository; + } + + public async Task> GetAllCategoriesAsync() + { + return await _categoryRepository.GetAllAsync(); + } + + public async Task GetCategoryAsync(int id) + { + return await _categoryRepository.GetByIdAsync(id); + } + + public async Task GetCategoryByNameAsync(string name) + { + return await _categoryRepository.GetByNameAsync(name); + } + + public async Task 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); + } + } +} diff --git a/src/TelegramPanel.Core/Services/ChannelGroupManagementService.cs b/src/TelegramPanel.Core/Services/ChannelGroupManagementService.cs new file mode 100644 index 0000000..ae1e31b --- /dev/null +++ b/src/TelegramPanel.Core/Services/ChannelGroupManagementService.cs @@ -0,0 +1,51 @@ +using TelegramPanel.Data.Entities; +using TelegramPanel.Data.Repositories; + +namespace TelegramPanel.Core.Services; + +/// +/// 频道分组管理服务 +/// +public class ChannelGroupManagementService +{ + private readonly IChannelGroupRepository _groupRepository; + + public ChannelGroupManagementService(IChannelGroupRepository groupRepository) + { + _groupRepository = groupRepository; + } + + public async Task> GetAllGroupsAsync() + { + return await _groupRepository.GetAllAsync(); + } + + public async Task GetGroupAsync(int id) + { + return await _groupRepository.GetByIdAsync(id); + } + + public async Task GetGroupByNameAsync(string name) + { + return await _groupRepository.GetByNameAsync(name); + } + + public async Task 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); + } + } +} diff --git a/src/TelegramPanel.Data/Repositories/AccountCategoryRepository.cs b/src/TelegramPanel.Data/Repositories/AccountCategoryRepository.cs new file mode 100644 index 0000000..e0761cb --- /dev/null +++ b/src/TelegramPanel.Data/Repositories/AccountCategoryRepository.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; +using TelegramPanel.Data.Entities; + +namespace TelegramPanel.Data.Repositories; + +/// +/// 账号分类仓储实现 +/// +public class AccountCategoryRepository : Repository, IAccountCategoryRepository +{ + public AccountCategoryRepository(AppDbContext context) : base(context) + { + } + + public override async Task> GetAllAsync() + { + return await _dbSet + .Include(c => c.Accounts) + .OrderBy(c => c.Name) + .ToListAsync(); + } + + public async Task GetByNameAsync(string name) + { + return await _dbSet + .Include(c => c.Accounts) + .FirstOrDefaultAsync(c => c.Name == name); + } +} diff --git a/src/TelegramPanel.Data/Repositories/ChannelGroupRepository.cs b/src/TelegramPanel.Data/Repositories/ChannelGroupRepository.cs new file mode 100644 index 0000000..ab95741 --- /dev/null +++ b/src/TelegramPanel.Data/Repositories/ChannelGroupRepository.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; +using TelegramPanel.Data.Entities; + +namespace TelegramPanel.Data.Repositories; + +/// +/// 频道分组仓储实现 +/// +public class ChannelGroupRepository : Repository, IChannelGroupRepository +{ + public ChannelGroupRepository(AppDbContext context) : base(context) + { + } + + public override async Task> GetAllAsync() + { + return await _dbSet + .Include(g => g.Channels) + .OrderBy(g => g.Name) + .ToListAsync(); + } + + public async Task GetByNameAsync(string name) + { + return await _dbSet + .Include(g => g.Channels) + .FirstOrDefaultAsync(g => g.Name == name); + } +} diff --git a/src/TelegramPanel.Data/Repositories/IAccountCategoryRepository.cs b/src/TelegramPanel.Data/Repositories/IAccountCategoryRepository.cs new file mode 100644 index 0000000..58f7dc0 --- /dev/null +++ b/src/TelegramPanel.Data/Repositories/IAccountCategoryRepository.cs @@ -0,0 +1,11 @@ +using TelegramPanel.Data.Entities; + +namespace TelegramPanel.Data.Repositories; + +/// +/// 账号分类仓储接口 +/// +public interface IAccountCategoryRepository : IRepository +{ + Task GetByNameAsync(string name); +} diff --git a/src/TelegramPanel.Data/Repositories/IChannelGroupRepository.cs b/src/TelegramPanel.Data/Repositories/IChannelGroupRepository.cs new file mode 100644 index 0000000..c0e214c --- /dev/null +++ b/src/TelegramPanel.Data/Repositories/IChannelGroupRepository.cs @@ -0,0 +1,11 @@ +using TelegramPanel.Data.Entities; + +namespace TelegramPanel.Data.Repositories; + +/// +/// 频道分组仓储接口 +/// +public interface IChannelGroupRepository : IRepository +{ + Task GetByNameAsync(string name); +} diff --git a/src/TelegramPanel.Data/ServiceCollectionExtensions.cs b/src/TelegramPanel.Data/ServiceCollectionExtensions.cs index cf81aad..0fb39fb 100644 --- a/src/TelegramPanel.Data/ServiceCollectionExtensions.cs +++ b/src/TelegramPanel.Data/ServiceCollectionExtensions.cs @@ -17,7 +17,9 @@ public static class ServiceCollectionExtensions // 注册所有Repository services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); diff --git a/src/TelegramPanel.Web/Components/Pages/AccountLogin.razor b/src/TelegramPanel.Web/Components/Pages/AccountLogin.razor index c7eac82..3881e90 100644 --- a/src/TelegramPanel.Web/Components/Pages/AccountLogin.razor +++ b/src/TelegramPanel.Web/Components/Pages/AccountLogin.razor @@ -1,6 +1,9 @@ @page "/accounts/login" @inject IAccountService AccountService +@inject AccountManagementService AccountManagement @inject ISnackbar Snackbar +@inject NavigationManager Navigation +@using TelegramPanel.Data.Entities 手机号登录 - Telegram Panel @@ -16,6 +19,9 @@ + + * 手机号需包含国家代码,例如:+8613800138000 + @@ -24,6 +30,9 @@ + + 请在Telegram客户端查收验证码 + @@ -36,10 +45,14 @@ - 登录成功! + 登录成功并保存到数据库! - 用户名:@username - 用户ID:@userId + 手机号:@phoneNumber + 用户名:@(username ?? "无") + 用户ID:@userId + + 查看账号列表 + @@ -48,7 +61,7 @@ 重置 - @if (stepper.GetActiveIndex() > 0) + @if (stepper.GetActiveIndex() > 0 && !stepper.IsAllStepsCompleted()) { 上一步 @@ -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); + } } } diff --git a/src/TelegramPanel.Web/Components/Pages/Accounts.razor b/src/TelegramPanel.Web/Components/Pages/Accounts.razor index 18676df..b30bdef 100644 --- a/src/TelegramPanel.Web/Components/Pages/Accounts.razor +++ b/src/TelegramPanel.Web/Components/Pages/Accounts.razor @@ -1,6 +1,8 @@ @page "/accounts" -@inject IAccountService AccountService +@inject AccountManagementService AccountManagement @inject ISnackbar Snackbar +@inject IDialogService DialogService +@using TelegramPanel.Data.Entities 账号列表 - Telegram Panel @@ -8,18 +10,22 @@ - + - 已导入账号 + 已导入账号 (@accounts.Count) + AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0" + Immediate="true" /> + 刷新 手机号 用户名 用户ID 分类 + 频道/群组 状态 最后同步 操作 @@ -28,22 +34,40 @@ @context.Phone @(context.Username ?? "-") @context.UserId - @(context.Category ?? "未分类") + + @if (context.Category != null) + { + + @context.Category.Name + + } + else + { + 未分类 + } + + + @context.Channels.Count / @context.Groups.Count + - @(context.IsActive ? "在线" : "离线") + @(context.IsActive ? "活跃" : "停用") @context.LastSyncAt.ToString("yyyy-MM-dd HH:mm") + OnClick="@(() => ShowDetails(context))" Title="查看详情" /> + + OnClick="@(() => DeleteAccount(context))" Title="删除账号" /> - 暂无账号数据,请先导入账号 + 暂无账号数据,请先导入账号 @@ -53,10 +77,18 @@ @code { - private List accounts = new(); + private List accounts = new(); private bool loading = true; private string searchString = ""; + private IEnumerable 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(); - 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); + } + } } }