feat: 添加删除租户和管理员功能
- 在渠道管理页面添加删除租户按钮和确认对话框 - 在渠道设置页面添加删除管理员按钮和确认对话框 - 在渠道编辑对话框中添加删除管理员功能 - 添加完整的删除确认流程,包含警告提示 - 删除成功后自动刷新列表 - 添加API调用调试日志,便于排查问题 - 支持中英文双语显示
This commit is contained in:
@@ -101,6 +101,9 @@ export function ChannelsTab({
|
||||
const [isChannelAccessDialogOpen, setIsChannelAccessDialogOpen] = useState(false)
|
||||
const [isApprovalDialogOpen, setIsApprovalDialogOpen] = useState(false)
|
||||
const [isAgentApprovalDialogOpen, setIsAgentApprovalDialogOpen] = useState(false)
|
||||
const [showDeleteChannelAdminDialog, setShowDeleteChannelAdminDialog] = useState(false)
|
||||
const [selectedAdminForDelete, setSelectedAdminForDelete] = useState<any>(null)
|
||||
const [isDeletingChannelAdmin, setIsDeletingChannelAdmin] = useState(false)
|
||||
|
||||
// Data states
|
||||
const [selectedChannel, setSelectedChannel] = useState<Channel | null>(null)
|
||||
@@ -480,12 +483,35 @@ export function ChannelsTab({
|
||||
console.log("Change password for tenant:", tenantId)
|
||||
}
|
||||
|
||||
const handleRemoveAdmin = async (adminId: string) => {
|
||||
if (!selectedChannel) return
|
||||
const handleRemoveAdmin = (admin: any) => {
|
||||
setSelectedAdminForDelete(admin)
|
||||
setShowDeleteChannelAdminDialog(true)
|
||||
}
|
||||
|
||||
const handleConfirmDeleteChannelAdmin = async () => {
|
||||
if (!selectedAdminForDelete || !selectedChannel) return
|
||||
|
||||
try {
|
||||
console.log(`Removing admin from channel:`, selectedChannel.id)
|
||||
setIsDeletingChannelAdmin(true)
|
||||
const response = await TaijiAPIClient.deleteAdmin(selectedAdminForDelete.id)
|
||||
if (response.success) {
|
||||
// Refresh channel admins list
|
||||
const adminsResponse = await TaijiAPIClient.getChannelAdmins()
|
||||
if (adminsResponse.success && adminsResponse.data?.admins) {
|
||||
// Filter admins for current channel
|
||||
const channelAdminsList = adminsResponse.data.admins.filter(
|
||||
(admin: any) => admin.channelId === selectedChannel.id
|
||||
)
|
||||
setChannelAdmins(channelAdminsList)
|
||||
}
|
||||
console.log(`Successfully removed admin from channel:`, selectedChannel.id)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to remove admin:", error)
|
||||
} finally {
|
||||
setIsDeletingChannelAdmin(false)
|
||||
setShowDeleteChannelAdminDialog(false)
|
||||
setSelectedAdminForDelete(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1353,7 +1379,7 @@ export function ChannelsTab({
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => handleRemoveAdmin(admin.id)}
|
||||
onClick={() => handleRemoveAdmin(admin)}
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -1977,6 +2003,76 @@ export function ChannelsTab({
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Channel Admin Confirmation Dialog */}
|
||||
<Dialog open={showDeleteChannelAdminDialog} onOpenChange={setShowDeleteChannelAdminDialog}>
|
||||
<DialogContent className="bg-card border-border">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-destructive">
|
||||
{language === "zh" ? "删除渠道管理员" : "Delete Channel Administrator"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh"
|
||||
? "此操作将永久删除该管理员账号。此操作无法撤销。"
|
||||
: "This action will permanently delete this administrator account. This action cannot be undone."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedAdminForDelete && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="rounded-lg bg-destructive/10 border border-destructive/20 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Trash2 className="h-5 w-5 text-destructive mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm text-foreground">
|
||||
{language === "zh" ? "将要删除的管理员:" : "Administrator to be deleted:"}
|
||||
</p>
|
||||
<p className="text-lg font-bold mt-1 text-foreground">{selectedAdminForDelete.name}</p>
|
||||
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
||||
<p>• {language === "zh" ? "邮箱" : "Email"}: {selectedAdminForDelete.email}</p>
|
||||
<p>• {language === "zh" ? "角色" : "Role"}: {
|
||||
selectedAdminForDelete.role === "channel_admin"
|
||||
? (language === "zh" ? "渠道管理员" : "Channel Admin")
|
||||
: selectedAdminForDelete.role === "billing" || selectedAdminForDelete.role === "billing_admin"
|
||||
? (language === "zh" ? "计费管理员" : "Billing Admin")
|
||||
: (language === "zh" ? "运营管理员" : "Operations Admin")
|
||||
}</p>
|
||||
{selectedChannel && (
|
||||
<p>• {language === "zh" ? "所属渠道" : "Channel"}: {selectedChannel.name}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-amber-500/10 border border-amber-500/20 p-3">
|
||||
<p className="text-sm font-medium text-amber-700 dark:text-amber-500">
|
||||
⚠️ {language === "zh"
|
||||
? "警告:删除后将无法恢复管理员账号及其权限,请谨慎操作。"
|
||||
: "Warning: Administrator account and permissions cannot be recovered after deletion. Please proceed with caution."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteChannelAdminDialog(false)}
|
||||
disabled={isDeletingChannelAdmin}
|
||||
className="border-border text-foreground"
|
||||
>
|
||||
{language === "zh" ? "取消" : "Cancel"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleConfirmDeleteChannelAdmin}
|
||||
disabled={isDeletingChannelAdmin}
|
||||
>
|
||||
{isDeletingChannelAdmin
|
||||
? (language === "zh" ? "删除中..." : "Deleting...")
|
||||
: (language === "zh" ? "确认删除" : "Confirm Delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,6 +99,9 @@ export default function ChannelDashboard() {
|
||||
const [isAddAdminOpen, setIsAddAdminOpen] = useState(false)
|
||||
const [channelAdmins, setChannelAdmins] = useState<any[]>([])
|
||||
const [isLoadingAdmins, setIsLoadingAdmins] = useState(false)
|
||||
const [showDeleteAdminDialog, setShowDeleteAdminDialog] = useState(false)
|
||||
const [selectedAdminForDelete, setSelectedAdminForDelete] = useState<any>(null)
|
||||
const [isDeletingAdmin, setIsDeletingAdmin] = useState(false)
|
||||
const [createAdminForm, setCreateAdminForm] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
@@ -130,6 +133,8 @@ export default function ChannelDashboard() {
|
||||
const [showTenantDetailsDialog, setShowTenantDetailsDialog] = useState(false)
|
||||
const [selectedTenantForDetails, setSelectedTenantForDetails] = useState<any>(null)
|
||||
const [isDeletingTenant, setIsDeletingTenant] = useState(false)
|
||||
const [showDeleteTenantDialog, setShowDeleteTenantDialog] = useState(false)
|
||||
const [selectedTenantForDelete, setSelectedTenantForDelete] = useState<any>(null)
|
||||
|
||||
// API Data states
|
||||
const [tenantsData, setTenantsData] = useState<any[]>([])
|
||||
@@ -341,16 +346,100 @@ export default function ChannelDashboard() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle delete tenant
|
||||
const handleDeleteTenant = async () => {
|
||||
if (!selectedTenantForDelete) return
|
||||
|
||||
try {
|
||||
setIsDeletingTenant(true)
|
||||
const response = await TaijiAPIClient.deleteTenant(selectedTenantForDelete.id)
|
||||
if (response.success) {
|
||||
toast({
|
||||
title: language === "zh" ? "删除成功" : "Deleted Successfully",
|
||||
description: language === "zh"
|
||||
? `租户 "${selectedTenantForDelete.name}" 已被删除`
|
||||
: `Tenant "${selectedTenantForDelete.name}" has been deleted`,
|
||||
})
|
||||
// Refresh the tenants list
|
||||
await loadChannelData()
|
||||
} else {
|
||||
toast({
|
||||
title: language === "zh" ? "删除失败" : "Delete Failed",
|
||||
description: response.error?.message || (language === "zh" ? "无法删除租户" : "Failed to delete tenant"),
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: language === "zh" ? "删除失败" : "Delete Failed",
|
||||
description: error.message || (language === "zh" ? "删除租户时发生错误" : "Error occurred while deleting tenant"),
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsDeletingTenant(false)
|
||||
setShowDeleteTenantDialog(false)
|
||||
setSelectedTenantForDelete(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle delete admin
|
||||
const handleDeleteAdmin = async () => {
|
||||
if (!selectedAdminForDelete) return
|
||||
|
||||
try {
|
||||
setIsDeletingAdmin(true)
|
||||
const response = await TaijiAPIClient.deleteAdmin(selectedAdminForDelete.id)
|
||||
if (response.success) {
|
||||
toast({
|
||||
title: language === "zh" ? "删除成功" : "Deleted Successfully",
|
||||
description: language === "zh"
|
||||
? `管理员 "${selectedAdminForDelete.name}" 已被删除`
|
||||
: `Administrator "${selectedAdminForDelete.name}" has been deleted`,
|
||||
})
|
||||
// Refresh the admins list
|
||||
await loadChannelAdmins()
|
||||
} else {
|
||||
toast({
|
||||
title: language === "zh" ? "删除失败" : "Delete Failed",
|
||||
description: response.error?.message || (language === "zh" ? "无法删除管理员" : "Failed to delete administrator"),
|
||||
variant: "destructive",
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: language === "zh" ? "删除失败" : "Delete Failed",
|
||||
description: error.message || (language === "zh" ? "删除管理员时发生错误" : "Error occurred while deleting administrator"),
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsDeletingAdmin(false)
|
||||
setShowDeleteAdminDialog(false)
|
||||
setSelectedAdminForDelete(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Load channel administrators
|
||||
const loadChannelAdmins = async () => {
|
||||
try {
|
||||
setIsLoadingAdmins(true)
|
||||
console.log("🔍 正在加载渠道管理员列表...")
|
||||
const response = await TaijiAPIClient.getChannelAdmins()
|
||||
console.log("📥 获取管理员列表响应:", response)
|
||||
if (response.success && response.data?.admins) {
|
||||
console.log("✅ 成功获取管理员列表:", response.data.admins)
|
||||
setChannelAdmins(response.data.admins)
|
||||
} else {
|
||||
console.warn("⚠️ 响应格式异常或无管理员数据:", response)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load channel admins:", error)
|
||||
console.error("❌ 加载渠道管理员失败:", error)
|
||||
toast({
|
||||
title: language === "zh" ? "加载失败" : "Failed to Load",
|
||||
description: language === "zh"
|
||||
? "无法加载管理员列表,请检查网络连接和登录状态"
|
||||
: "Failed to load administrators. Please check network connection and login status.",
|
||||
variant: "destructive",
|
||||
})
|
||||
} finally {
|
||||
setIsLoadingAdmins(false)
|
||||
}
|
||||
@@ -957,6 +1046,16 @@ export default function ChannelDashboard() {
|
||||
<DollarSign className="mr-2 h-4 w-4" />
|
||||
{language === "zh" ? "管理计费" : "Manage Billing"}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setSelectedTenantForDelete(tenant)
|
||||
setShowDeleteTenantDialog(true)
|
||||
}}
|
||||
className="text-destructive focus:text-destructive"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{language === "zh" ? "删除租户" : "Delete Tenant"}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
@@ -1716,6 +1815,17 @@ export default function ChannelDashboard() {
|
||||
: (language === "zh" ? "已停用" : "Inactive")}
|
||||
</Badge>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedAdminForDelete(admin)
|
||||
setShowDeleteAdminDialog(true)
|
||||
}}
|
||||
className="text-destructive hover:text-destructive hover:bg-destructive/10"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -2628,6 +2738,140 @@ export default function ChannelDashboard() {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Tenant Confirmation Dialog */}
|
||||
<Dialog open={showDeleteTenantDialog} onOpenChange={setShowDeleteTenantDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-destructive">
|
||||
{language === "zh" ? "删除租户" : "Delete Tenant"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh"
|
||||
? "此操作将永久删除该租户及其所有数据。此操作无法撤销。"
|
||||
: "This action will permanently delete this tenant and all its data. This action cannot be undone."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedTenantForDelete && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="rounded-lg bg-destructive/10 border border-destructive/20 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Trash2 className="h-5 w-5 text-destructive mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm">
|
||||
{language === "zh" ? "将要删除的租户:" : "Tenant to be deleted:"}
|
||||
</p>
|
||||
<p className="text-lg font-bold mt-1">{selectedTenantForDelete.name}</p>
|
||||
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
||||
<p>• {language === "zh" ? "用户数" : "Users"}: {selectedTenantForDelete.users}</p>
|
||||
<p>• {language === "zh" ? "当前方案" : "Current Plan"}: {selectedTenantForDelete.plan}</p>
|
||||
<p>• {language === "zh" ? "状态" : "Status"}: {selectedTenantForDelete.status}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-amber-500/10 border border-amber-500/20 p-3">
|
||||
<p className="text-sm font-medium text-amber-700 dark:text-amber-500">
|
||||
⚠️ {language === "zh"
|
||||
? "警告:删除后将无法恢复租户数据,请谨慎操作。"
|
||||
: "Warning: Tenant data cannot be recovered after deletion. Please proceed with caution."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteTenantDialog(false)}
|
||||
disabled={isDeletingTenant}
|
||||
>
|
||||
{language === "zh" ? "取消" : "Cancel"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteTenant}
|
||||
disabled={isDeletingTenant}
|
||||
>
|
||||
{isDeletingTenant
|
||||
? (language === "zh" ? "删除中..." : "Deleting...")
|
||||
: (language === "zh" ? "确认删除" : "Confirm Delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Admin Confirmation Dialog */}
|
||||
<Dialog open={showDeleteAdminDialog} onOpenChange={setShowDeleteAdminDialog}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-destructive">
|
||||
{language === "zh" ? "删除管理员" : "Delete Administrator"}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{language === "zh"
|
||||
? "此操作将永久删除该管理员账号。此操作无法撤销。"
|
||||
: "This action will permanently delete this administrator account. This action cannot be undone."}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{selectedAdminForDelete && (
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="rounded-lg bg-destructive/10 border border-destructive/20 p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<Trash2 className="h-5 w-5 text-destructive mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium text-sm">
|
||||
{language === "zh" ? "将要删除的管理员:" : "Administrator to be deleted:"}
|
||||
</p>
|
||||
<p className="text-lg font-bold mt-1">{selectedAdminForDelete.name}</p>
|
||||
<div className="mt-3 space-y-1 text-sm text-muted-foreground">
|
||||
<p>• {language === "zh" ? "邮箱" : "Email"}: {selectedAdminForDelete.email}</p>
|
||||
<p>• {language === "zh" ? "角色" : "Role"}: {
|
||||
selectedAdminForDelete.role === "channel_admin"
|
||||
? (language === "zh" ? "渠道管理员" : "Channel Admin")
|
||||
: selectedAdminForDelete.role === "billing_admin"
|
||||
? (language === "zh" ? "计费管理员" : "Billing Admin")
|
||||
: (language === "zh" ? "运营管理员" : "Operations Admin")
|
||||
}</p>
|
||||
{selectedAdminForDelete.status && (
|
||||
<p>• {language === "zh" ? "状态" : "Status"}: {
|
||||
selectedAdminForDelete.status === "active"
|
||||
? (language === "zh" ? "活跃" : "Active")
|
||||
: (language === "zh" ? "已停用" : "Inactive")
|
||||
}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-amber-500/10 border border-amber-500/20 p-3">
|
||||
<p className="text-sm font-medium text-amber-700 dark:text-amber-500">
|
||||
⚠️ {language === "zh"
|
||||
? "警告:删除后将无法恢复管理员账号及其权限,请谨慎操作。"
|
||||
: "Warning: Administrator account and permissions cannot be recovered after deletion. Please proceed with caution."}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteAdminDialog(false)}
|
||||
disabled={isDeletingAdmin}
|
||||
>
|
||||
{language === "zh" ? "取消" : "Cancel"}
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteAdmin}
|
||||
disabled={isDeletingAdmin}
|
||||
>
|
||||
{isDeletingAdmin
|
||||
? (language === "zh" ? "删除中..." : "Deleting...")
|
||||
: (language === "zh" ? "确认删除" : "Confirm Delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
||||
+6
-3
@@ -699,9 +699,12 @@ export class TaijiAPIClient {
|
||||
* GET /api/channel/admins
|
||||
*/
|
||||
static async getChannelAdmins() {
|
||||
const response = await fetch(`${API_BASE_URLS.mcpServer}/api/channel/admins`, {
|
||||
headers: buildHeaders(),
|
||||
})
|
||||
const url = `${API_BASE_URLS.mcpServer}/api/channel/admins`
|
||||
console.log("🌐 API请求: GET", url)
|
||||
const headers = buildHeaders()
|
||||
console.log("📋 请求头:", headers)
|
||||
const response = await fetch(url, { headers })
|
||||
console.log("📨 响应状态:", response.status, response.statusText)
|
||||
return handleResponse(response)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user