频道列表完善:详情显示创建者/时间/管理员,支持编辑与踢人

This commit is contained in:
meoacgx committed 2025-12-19 15:00:04 +08:00
1 parent 3159c7e5a7
commit 63d48bec59
5 files changed
+392 -8

No files matched your search

@@ -0,0 +1,230 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject ChannelManagementService ChannelManagement
@inject AccountManagementService AccountManagement
@inject IChannelService ChannelService
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@using TelegramPanel.Data.Entities
<MudDialog>
<DialogContent>
@if (loading)
{
<MudProgressLinear Indeterminate="true" />
}
else if (channel == null)
{
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">未找到频道数据</MudAlert>
}
else
{
<MudStack Spacing="2">
<MudText Typo="Typo.h6">@channel.Title</MudText>
<MudGrid>
<MudItem xs="12" md="6">
<MudText Typo="Typo.body2"><strong>TelegramId:</strong>@channel.TelegramId</MudText>
<MudText Typo="Typo.body2"><strong>用户名:</strong>@(string.IsNullOrWhiteSpace(channel.Username) ? "(私密)" : "@" + channel.Username)</MudText>
<MudText Typo="Typo.body2"><strong>成员数:</strong>@channel.MemberCount</MudText>
</MudItem>
<MudItem xs="12" md="6">
<MudText Typo="Typo.body2"><strong>创建者:</strong>@creatorDisplay</MudText>
<MudText Typo="Typo.body2"><strong>创建时间:</strong>@(channel.CreatedAt?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? "(未知)")</MudText>
<MudText Typo="Typo.body2"><strong>最后同步:</strong>@channel.SyncedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")</MudText>
</MudItem>
</MudGrid>
@if (!string.IsNullOrWhiteSpace(channel.About))
{
<MudText Typo="Typo.body2"><strong>简介:</strong></MudText>
<MudPaper Class="pa-3" Elevation="0" Style="background: rgba(0,0,0,0.03);">
<MudText Typo="Typo.body2" Style="white-space: pre-wrap;">@channel.About</MudText>
</MudPaper>
}
<MudDivider />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudText Typo="Typo.h6">管理员</MudText>
<MudSpacer />
<MudButton Variant="Variant.Outlined" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Refresh"
Disabled="@adminsLoading" OnClick="LoadAdmins">
刷新
</MudButton>
</MudStack>
@if (adminsLoading)
{
<MudProgressLinear Indeterminate="true" />
}
else if (admins.Count == 0)
{
<MudText Typo="Typo.body2" Color="Color.Secondary">暂无管理员数据(或无权限获取)</MudText>
}
else
{
<MudTable Items="@admins" Dense="true" Hover="true" Breakpoint="Breakpoint.Sm">
<HeaderContent>
<MudTh>身份</MudTh>
<MudTh>昵称</MudTh>
<MudTh>用户名</MudTh>
<MudTh>头衔</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="身份">
<MudChip T="string" Size="Size.Small" Color="@(context.IsCreator ? Color.Success : Color.Default)">
@(context.IsCreator ? "创建者" : "管理员")
</MudChip>
</MudTd>
<MudTd DataLabel="昵称">@context.DisplayName</MudTd>
<MudTd DataLabel="用户名">@(!string.IsNullOrWhiteSpace(context.Username) ? "@" + context.Username : "-")</MudTd>
<MudTd DataLabel="头衔">@(string.IsNullOrWhiteSpace(context.Rank) ? "-" : context.Rank)</MudTd>
</RowTemplate>
</MudTable>
}
<MudDivider />
<MudText Typo="Typo.h6">踢出成员</MudText>
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined" Dense="true">
输入 @@用户名 执行踢出;“非永久”会使用短时间封禁达到踢出效果,随后自动解除,方便对方重新加入。
</MudAlert>
<MudStack Row="true" Spacing="2" AlignItems="AlignItems.End">
<MudTextField @bind-Value="kickUsername" Label="用户名" Variant="Variant.Outlined" Placeholder="例如:someuser"
Immediate="true" Style="min-width: 260px;" />
<MudSwitch @bind-Value="kickPermanent" Color="Color.Error" Label="永久封禁" />
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.PersonRemove"
Disabled="@kickLoading" OnClick="KickUser">
@if (kickLoading)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
<span>处理中...</span>
}
else
{
<span>踢出</span>
}
</MudButton>
</MudStack>
</MudStack>
}
</DialogContent>
<DialogActions>
@if (channel != null)
{
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="OpenEdit">编辑频道</MudButton>
}
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="Close">关闭</MudButton>
</DialogActions>
</MudDialog>
@code
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[Parameter] public int ChannelDbId { get; set; }
private bool loading = true;
private Channel? channel;
private string creatorDisplay = "-";
private bool adminsLoading;
private List<ChannelAdminInfo> admins = new();
private string kickUsername = "";
private bool kickPermanent;
private bool kickLoading;
protected override async Task OnInitializedAsync()
{
await LoadChannel();
await LoadAdmins();
}
private async Task LoadChannel()
{
loading = true;
try
{
channel = await ChannelManagement.GetChannelAsync(ChannelDbId);
if (channel == null)
return;
var creator = await AccountManagement.GetAccountAsync(channel.CreatorAccountId);
creatorDisplay = creator == null
? $"账号 {channel.CreatorAccountId}"
: $"{creator.Phone}({creator.Nickname ?? creator.Username ?? creator.UserId.ToString()})";
}
catch (Exception ex)
{
Snackbar.Add($"加载频道失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task LoadAdmins()
{
if (channel == null)
return;
adminsLoading = true;
try
{
admins = await ChannelService.GetAdminsAsync(channel.CreatorAccountId, channel.TelegramId);
}
catch (Exception ex)
{
admins.Clear();
Snackbar.Add($"加载管理员失败:{ex.Message}", Severity.Error);
}
finally
{
adminsLoading = false;
}
}
private async Task KickUser()
{
if (channel == null)
return;
kickLoading = true;
try
{
await ChannelService.KickUserAsync(channel.CreatorAccountId, channel.TelegramId, kickUsername, kickPermanent);
Snackbar.Add("已执行踢出操作", Severity.Success);
kickUsername = "";
}
catch (Exception ex)
{
Snackbar.Add($"踢出失败:{ex.Message}", Severity.Error);
}
finally
{
kickLoading = false;
}
}
private async Task OpenEdit()
{
if (channel == null)
return;
var parameters = new DialogParameters { ["ChannelDbId"] = ChannelDbId };
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
var dialog = DialogService.Show<ChannelEditDialog>("编辑频道", parameters, options);
var result = await dialog.Result;
if (!result.Canceled)
{
await LoadChannel();
await LoadAdmins();
}
}
private void Close()
{
MudDialog.Close();
}
}
@@ -0,0 +1,136 @@
@namespace TelegramPanel.Web.Components.Dialogs
@inject ChannelManagementService ChannelManagement
@inject IChannelService ChannelService
@inject ISnackbar Snackbar
@using TelegramPanel.Data.Entities
<MudDialog>
<DialogContent>
@if (loading)
{
<MudProgressLinear Indeterminate="true" />
}
else if (channel == null)
{
<MudAlert Severity="Severity.Error" Variant="Variant.Outlined">未找到频道数据</MudAlert>
}
else
{
<MudStack Spacing="2">
<MudTextField @bind-Value="title" Label="频道标题" Variant="Variant.Outlined" Required="true" />
<MudTextField @bind-Value="about" Label="频道简介" Variant="Variant.Outlined" Lines="4" />
<MudSwitch @bind-Value="isPublic" Label="公开频道" Color="Color.Primary" />
@if (isPublic)
{
<MudTextField @bind-Value="username" Label="用户名(t.me/xxx)" Variant="Variant.Outlined"
HelperText="只能包含字母/数字/下划线,且必须唯一" />
}
<MudSwitch @bind-Value="forwardingAllowed" Label="允许转发/保存内容" Color="Color.Primary"
HelperText="关闭后为“保护内容”,禁止转发/保存(Telegram 官方功能)" />
</MudStack>
}
</DialogContent>
<DialogActions>
<MudButton Variant="Variant.Text" Color="Color.Default" Disabled="@saving" OnClick="Cancel">取消</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!CanSave || saving)" OnClick="Save">
@if (saving)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
<span>保存中...</span>
}
else
{
<span>保存</span>
}
</MudButton>
</DialogActions>
</MudDialog>
@code
{
[CascadingParameter] private MudDialogInstance MudDialog { get; set; } = default!;
[Parameter] public int ChannelDbId { get; set; }
private bool loading = true;
private bool saving;
private Channel? channel;
private string title = "";
private string? about;
private bool isPublic;
private string username = "";
private bool forwardingAllowed = true;
private bool CanSave => channel != null && !string.IsNullOrWhiteSpace(title) && (!isPublic || !string.IsNullOrWhiteSpace(username));
protected override async Task OnInitializedAsync()
{
await LoadChannel();
}
private async Task LoadChannel()
{
loading = true;
try
{
channel = await ChannelManagement.GetChannelAsync(ChannelDbId);
if (channel == null)
return;
title = channel.Title;
about = channel.About;
isPublic = !string.IsNullOrWhiteSpace(channel.Username);
username = channel.Username ?? "";
}
catch (Exception ex)
{
Snackbar.Add($"加载频道失败:{ex.Message}", Severity.Error);
}
finally
{
loading = false;
}
}
private async Task Save()
{
if (channel == null)
return;
saving = true;
try
{
var newTitle = title.Trim();
var newAbout = string.IsNullOrWhiteSpace(about) ? null : about.Trim();
var newUsername = string.IsNullOrWhiteSpace(username) ? null : username.Trim().TrimStart('@');
await ChannelService.UpdateChannelInfoAsync(channel.CreatorAccountId, channel.TelegramId, newTitle, newAbout);
await ChannelService.SetChannelVisibilityAsync(channel.CreatorAccountId, channel.TelegramId, isPublic, newUsername);
await ChannelService.SetForwardingAllowedAsync(channel.CreatorAccountId, channel.TelegramId, forwardingAllowed);
channel.Title = newTitle;
channel.About = newAbout;
channel.Username = isPublic ? newUsername : null;
await ChannelManagement.UpdateChannelAsync(channel);
Snackbar.Add("保存成功", Severity.Success);
MudDialog.Close(DialogResult.Ok(true));
}
catch (Exception ex)
{
Snackbar.Add($"保存失败:{ex.Message}", Severity.Error);
}
finally
{
saving = false;
}
}
private void Cancel()
{
MudDialog.Cancel();
}
}
@@ -4,6 +4,7 @@
@inject ISnackbar Snackbar
@inject IDialogService DialogService
@inject NavigationManager Navigation
@using TelegramPanel.Web.Components.Dialogs
@using TelegramPanel.Data.Entities
<PageTitle>频道列表 - Telegram Panel</PageTitle>
@@ -82,8 +83,9 @@
<MudTd DataLabel="最后同步">@context.SyncedAt.ToString("yyyy-MM-dd HH:mm")</MudTd>
<MudTd DataLabel="操作">
<MudMenu Icon="@Icons.Material.Filled.MoreVert" Size="Size.Small">
<MudMenuItem Icon="@Icons.Material.Filled.Info" OnClick="@(() => ShowDetails(context))">查看详情</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Edit" OnClick="@(() => EditChannel(context))">编辑频道</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Info" OnClick="@(async () => await ShowDetails(context))">查看详情</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Edit" OnClick="@(async () => await EditChannel(context))">编辑频道</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.PersonRemove" OnClick="@(async () => await KickUser(context))">踢人</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.PersonAdd" OnClick="@(() => InviteUsers(context))">邀请用户</MudMenuItem>
<MudMenuItem Icon="@Icons.Material.Filled.Link" OnClick="@(() => CopyLink(context))">复制链接</MudMenuItem>
<MudDivider />
@@ -169,14 +171,28 @@
return false;
}
private void ShowDetails(Channel channel)
private async Task ShowDetails(Channel channel)
{
Snackbar.Add($"查看频道详情:{channel.Title}", Severity.Info);
var parameters = new DialogParameters { ["ChannelDbId"] = channel.Id };
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Large, FullWidth = true };
DialogService.Show<ChannelDetailsDialog>("频道详情", parameters, options);
await Task.CompletedTask;
}
private void EditChannel(Channel channel)
private async Task EditChannel(Channel channel)
{
Snackbar.Add($"编辑频道:{channel.Title}", Severity.Info);
var parameters = new DialogParameters { ["ChannelDbId"] = channel.Id };
var options = new DialogOptions { CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
var dialog = DialogService.Show<ChannelEditDialog>("编辑频道", parameters, options);
var result = await dialog.Result;
if (!result.Canceled)
await LoadChannels();
}
private async Task KickUser(Channel channel)
{
// 复用详情弹窗里的“踢人”能力,减少重复 UI
await ShowDetails(channel);
}
private void InviteUsers(Channel channel)
@@ -208,7 +208,8 @@
IsBroadcast = channelInfo.IsBroadcast,
MemberCount = channelInfo.MemberCount,
About = channelInfo.About,
CreatorAccountId = account.Id
CreatorAccountId = account.Id,
CreatedAt = channelInfo.CreatedAt
};
await ChannelManagement.CreateOrUpdateChannelAsync(channel);
totalChannelsSynced++;
@@ -245,7 +245,8 @@
IsBroadcast = channelInfo.IsBroadcast,
MemberCount = channelInfo.MemberCount,
About = channelInfo.About,
CreatorAccountId = account.Id
CreatorAccountId = account.Id,
CreatedAt = channelInfo.CreatedAt
};
await ChannelManagement.CreateOrUpdateChannelAsync(channel);
totalChannelsSynced++;