fix(voices): 配音库「配音素材」tab 补删除按钮 (#1538)
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3m24s
CI/CD Pipeline / Check push changed paths (push) Successful in 3m26s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 5m31s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 5m41s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 3m37s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 3m22s
AI Code Review / AI Code Review (pull_request) Successful in 4m22s
CI/CD Pipeline / Build Staging API Image (push) Successful in 39s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m56s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m58s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 9m32s
CI/CD Pipeline / Validate - Code Quality (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 10m6s
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 432h5m5s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 432h9m8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 432h9m21s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 432h9m10s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 432h11m10s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 432h11m12s
CI/CD Pipeline / PR Build Web Image (push) Failing after 432h11m14s
CI/CD Pipeline / Frontend Lint (push) Failing after 432h14m39s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 432h14m41s
CI/CD Pipeline / PR Build API Image (push) Failing after 432h45m19s

Co-authored-by: xiaoxia <dev@xiaoxiajianji.com>
Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #1538.
This commit is contained in:
2026-08-29 15:11:00 +08:00
committed by auto-approve-bot
parent 5672757e23
commit ece9ac48a8
4 changed files with 128 additions and 1 deletions
@@ -32,6 +32,7 @@ import { useAudioPlayer } from "./hooks/useAudioPlayer"
import { useCloneOperations } from "./hooks/useCloneOperations"
import { useTtsSynthesize } from "./hooks/useTtsSynthesize"
import { useVoiceUpload } from "./hooks/useVoiceUpload"
import { useMaterialDelete } from "./hooks/useMaterialDelete"
import "./voices.css"
let toastIdSeq = 0
@@ -70,6 +71,12 @@ const VoiceLibrary: React.FC = () => {
materialCount,
} = useVoicesData()
// ── 配音素材删除 ──────────────────────────────────────
const { handleMaterialDelete } = useMaterialDelete({
materials: materials as AssetItem[],
showToast,
})
// ── 播放控制 ──────────────────────────────────────────
const {
playingId,
@@ -234,6 +241,7 @@ const VoiceLibrary: React.FC = () => {
loading={materialLoading}
materials={materials as AssetItem[]}
onOpenUpload={() => setUploadOpen(true)}
onDelete={handleMaterialDelete}
/>
)}
@@ -2,7 +2,7 @@
* VoiceLibrary 配音素材 Tab 内容
*/
import React from "react"
import { SoundOutlined, UploadOutlined, AudioOutlined } from "@ant-design/icons"
import { SoundOutlined, UploadOutlined, AudioOutlined, DeleteOutlined } from "@ant-design/icons"
import { Button } from "@/components/ui"
import type { AssetItem } from "@/api/assets"
@@ -10,12 +10,14 @@ export interface MaterialVoiceTabProps {
loading: boolean
materials: AssetItem[]
onOpenUpload: () => void
onDelete: (asset: AssetItem) => void
}
export const MaterialVoiceTab: React.FC<MaterialVoiceTabProps> = ({
loading,
materials,
onOpenUpload,
onDelete,
}) => {
return (
<div className="xx-voices-tab-content">
@@ -43,6 +45,21 @@ export const MaterialVoiceTab: React.FC<MaterialVoiceTabProps> = ({
const seconds = Math.floor(duration % 60)
return (
<div key={asset.id} className="vmat-card">
{/* hover 操作区:删除 */}
<div className="vmat-card-actions">
<button
type="button"
className="vmat-card-action-btn vmat-card-action-btn--danger"
onClick={(e) => {
e.stopPropagation()
onDelete(asset)
}}
title="删除"
aria-label="删除配音素材"
>
<DeleteOutlined />
</button>
</div>
<div className="vmat-thumb">
<AudioOutlined className="vmat-thumb-icon" />
<span className="vmat-duration">
@@ -0,0 +1,48 @@
/**
* 配音库「配音素材」Tab 删除逻辑
*/
import { useCallback } from "react"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { Modal } from "@/components/ui"
import { deleteAsset, type AssetItem } from "@/api/assets"
import type { Toast } from "../components/VoiceToasts"
interface UseMaterialDeleteOptions {
materials: AssetItem[]
showToast: (message: string, type: Toast["type"]) => void
}
export function useMaterialDelete({ materials, showToast }: UseMaterialDeleteOptions) {
const queryClient = useQueryClient()
const deleteMutation = useMutation({
mutationFn: (assetId: string) => deleteAsset(assetId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["voice-materials"] })
queryClient.invalidateQueries({ queryKey: ["assets", "voice"] })
showToast("素材已删除", "success")
},
onError: (err: unknown) => {
const msg = err instanceof Error ? err.message : "未知错误"
showToast(`删除失败:${msg}`, "error")
},
})
const handleMaterialDelete = useCallback(
(asset: AssetItem) => {
const material = materials.find((m) => m.id === asset.id)
if (!material) return
Modal.confirm({
title: "确认删除",
content: `确定删除配音素材「${material.name}」吗?删除后不可恢复。`,
okText: "删除",
okButtonProps: { danger: true },
cancelText: "取消",
onOk: () => deleteMutation.mutate(material.id),
})
},
[materials, deleteMutation],
)
return { handleMaterialDelete, isDeleting: deleteMutation.isPending }
}
+54
View File
@@ -976,6 +976,7 @@
================================================================ */
.vmat-card {
position: relative;
background: var(--bg-primary);
border-radius: var(--radius-lg);
overflow: hidden;
@@ -984,6 +985,59 @@
cursor: pointer;
}
/* 卡片 hover 操作按钮 */
.vmat-card-actions {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
display: flex;
gap: 4px;
opacity: 0;
transition: opacity var(--transition-fast);
}
.vmat-card:hover .vmat-card-actions {
opacity: 1;
}
/* 触摸屏设备无 hover:操作按钮常驻显示 */
@media (hover: none) {
.vmat-card-actions {
opacity: 1;
}
.vmat-card-action-btn {
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
}
.vmat-card-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
border-radius: var(--radius-sm);
background: var(--bg-hover);
color: var(--text-secondary);
cursor: pointer;
font-size: 13px;
transition: all var(--transition-fast);
}
.vmat-card-action-btn:hover {
background: var(--primary-50);
color: var(--primary-600);
}
.vmat-card-action-btn--danger:hover {
background: var(--error-50);
color: var(--error-600);
}
.vmat-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);