From 5df49e6baea005c46099d41d7221abd2f5fa16bc Mon Sep 17 00:00:00 2001
From: xiaoxia
Date: Tue, 28 Jul 2026 09:14:05 +0800
Subject: [PATCH 1/3] =?UTF-8?q?refactor(upload-voice-modal):=20=E4=B8=89?=
=?UTF-8?q?=E9=98=B6=E6=AE=B5=E9=87=8D=E6=9E=84=20UploadVoiceModal?=
=?UTF-8?q?=EF=BC=88329=E2=86=9287=E8=A1=8C,=20-74%=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 1: 抽 types/constants
- types.ts — UploadVoiceModalProps, GENDER_OPTIONS, UPLOAD_CONFIG
Phase 2: 拆 5 个 UI 组件
- FileUploadZone — 拖拽上传区
- FileInfoCard — 已选文件信息卡
- UploadProgress — 上传进度条
- FormFields — 名称/性别/描述表单
- ActionButtons — 取消/上传操作按钮
Phase 3: 主组件瘦身
- 仅保留 Modal 框架 + 子组件组装
- 内联样式变量化,labelStyle/inputStyle 复用
---
.../voices/components/UploadVoiceModal.tsx | 316 ++----------------
.../upload-voice-modal/ActionButtons.tsx | 68 ++++
.../upload-voice-modal/FileInfoCard.tsx | 42 +++
.../upload-voice-modal/FileUploadZone.tsx | 56 ++++
.../upload-voice-modal/FormFields.tsx | 106 ++++++
.../upload-voice-modal/UploadProgress.tsx | 45 +++
.../components/upload-voice-modal/index.ts | 6 +
.../components/upload-voice-modal/types.ts | 30 ++
8 files changed, 390 insertions(+), 279 deletions(-)
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/ActionButtons.tsx
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/FileInfoCard.tsx
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/FormFields.tsx
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/UploadProgress.tsx
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/index.ts
create mode 100755 apps/web/src/pages/voices/components/upload-voice-modal/types.ts
diff --git a/apps/web/src/pages/voices/components/UploadVoiceModal.tsx b/apps/web/src/pages/voices/components/UploadVoiceModal.tsx
index 75aaff541..56928dbb7 100755
--- a/apps/web/src/pages/voices/components/UploadVoiceModal.tsx
+++ b/apps/web/src/pages/voices/components/UploadVoiceModal.tsx
@@ -1,24 +1,13 @@
import React from "react"
-import { UploadOutlined, SoundOutlined } from "@ant-design/icons"
-import { Modal, Upload, message } from "antd"
-import { type VoiceGender } from "@/pages/voices/types"
-import { formatFileSize } from "@/pages/voices/utils/format"
-
-export interface UploadVoiceModalProps {
- open: boolean
- uploadFile: File | null
- uploadName: string
- uploadGender: VoiceGender
- uploadDesc: string
- uploadProgress: number | null
- onClose: () => void
- onFileSelect: (file: File) => void
- onFileRemove: () => void
- onNameChange: (name: string) => void
- onGenderChange: (gender: VoiceGender) => void
- onDescChange: (desc: string) => void
- onUpload: () => void
-}
+import { Modal } from "antd"
+import {
+ FileUploadZone,
+ FileInfoCard,
+ UploadProgress,
+ FormFields,
+ ActionButtons,
+} from "./upload-voice-modal"
+import type { UploadVoiceModalProps } from "./upload-voice-modal"
/** 上传音频弹窗 */
const UploadVoiceModal: React.FC = ({
@@ -36,17 +25,20 @@ const UploadVoiceModal: React.FC = ({
onDescChange,
onUpload,
}) => {
+ const uploading = uploadProgress !== null
+ const canUpload = !!uploadFile && !!uploadName.trim()
+
return (
{
- if (uploadProgress !== null) return // 上传中不可关闭
+ if (uploading) return // 上传中不可关闭
onClose()
}}
footer={null}
width={520}
- maskClosable={uploadProgress === null}
+ maskClosable={!uploading}
>
= ({
}}
>
{/* 拖拽上传区 */}
-
{
- onFileSelect(file)
- return false
- }}
- onRemove={() => {
- onFileRemove()
- }}
- showUploadList={false}
- disabled={uploadProgress !== null}
- >
-
-
-
-
- 点击或拖拽音频文件到此处
-
-
- 支持 MP3、WAV、AAC、FLAC 等格式,最大 200MB
-
-
+
{/* 已选文件信息 */}
- {uploadFile && (
-
-
-
-
- {uploadFile.name}
-
-
- {formatFileSize(uploadFile.size)}
-
-
-
- )}
+ {uploadFile &&
}
{/* 上传进度 */}
- {uploadProgress !== null && (
-
-
- {uploadProgress}%
-
-
- {uploadProgress < 100 ? "上传中..." : "处理中..."}
-
-
-
- )}
+ {uploadProgress !== null &&
}
- {/* 名称 */}
-
-
- 素材名称
-
-
onNameChange(e.target.value)}
- placeholder="输入素材名称"
- maxLength={100}
- disabled={uploadProgress !== null}
- style={{
- width: "100%",
- padding: "8px 12px",
- border: "1px solid var(--border-color)",
- borderRadius: 8,
- fontSize: 13,
- background: "var(--bg-primary)",
- color: "var(--text-primary)",
- outline: "none",
- }}
- />
-
-
- {/* 性别选择 */}
-
-
- 音色性别
-
-
- {(["female", "male", "child"] as VoiceGender[]).map((g) => (
-
- ))}
-
-
-
- {/* 描述 */}
-
+ {/* 表单字段 */}
+
{/* 操作按钮 */}
-
-
-
-
+
)
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/ActionButtons.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/ActionButtons.tsx
new file mode 100755
index 000000000..99020e4a0
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/ActionButtons.tsx
@@ -0,0 +1,68 @@
+import React from "react"
+
+interface ActionButtonsProps {
+ uploading: boolean
+ canUpload: boolean
+ onCancel: () => void
+ onUpload: () => void
+}
+
+const ActionButtons: React.FC = ({
+ uploading,
+ canUpload,
+ onCancel,
+ onUpload,
+}) => {
+ const disabled = uploading || !canUpload
+
+ const handleUpload = () => {
+ onUpload()
+ }
+
+ return (
+
+
+
+
+ )
+}
+
+export default ActionButtons
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/FileInfoCard.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/FileInfoCard.tsx
new file mode 100755
index 000000000..a48123dda
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/FileInfoCard.tsx
@@ -0,0 +1,42 @@
+import React from "react"
+import { SoundOutlined } from "@ant-design/icons"
+import { formatFileSize } from "@/pages/voices/utils/format"
+
+interface FileInfoCardProps {
+ file: File
+}
+
+const FileInfoCard: React.FC = ({ file }) => {
+ return (
+
+
+
+
+ {file.name}
+
+
+ {formatFileSize(file.size)}
+
+
+
+ )
+}
+
+export default FileInfoCard
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
new file mode 100755
index 000000000..984672eab
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
@@ -0,0 +1,56 @@
+import React from "react"
+import { UploadOutlined } from "@ant-design/icons"
+import { Upload } from "antd"
+import { UPLOAD_CONFIG } from "./types"
+
+interface FileUploadZoneProps {
+ disabled: boolean
+ onFileSelect: (file: File) => void
+ onFileRemove: () => void
+}
+
+const FileUploadZone: React.FC = ({
+ disabled,
+ onFileSelect,
+ onFileRemove,
+}) => {
+ return (
+ {
+ onFileSelect(file)
+ return false
+ }}
+ onRemove={() => {
+ onFileRemove()
+ }}
+ showUploadList={false}
+ disabled={disabled}
+ >
+
+
+
+
+ 点击或拖拽音频文件到此处
+
+
+ 支持 MP3、WAV、AAC、FLAC 等格式,最大 {UPLOAD_CONFIG.maxSizeMB}MB
+
+
+ )
+}
+
+export default FileUploadZone
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/FormFields.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/FormFields.tsx
new file mode 100755
index 000000000..48d4d9e59
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/FormFields.tsx
@@ -0,0 +1,106 @@
+import React from "react"
+import type { VoiceGender } from "@/pages/voices/types"
+import { GENDER_OPTIONS, UPLOAD_CONFIG } from "./types"
+
+interface FormFieldsProps {
+ name: string
+ gender: VoiceGender
+ desc: string
+ disabled: boolean
+ onNameChange: (name: string) => void
+ onGenderChange: (gender: VoiceGender) => void
+ onDescChange: (desc: string) => void
+}
+
+const labelStyle: React.CSSProperties = {
+ fontSize: 13,
+ color: "var(--text-secondary)",
+ marginBottom: 6,
+}
+
+const inputStyle: React.CSSProperties = {
+ width: "100%",
+ padding: "8px 12px",
+ border: "1px solid var(--border-color)",
+ borderRadius: 8,
+ fontSize: 13,
+ background: "var(--bg-primary)",
+ color: "var(--text-primary)",
+ outline: "none",
+}
+
+const FormFields: React.FC = ({
+ name,
+ gender,
+ desc,
+ disabled,
+ onNameChange,
+ onGenderChange,
+ onDescChange,
+}) => {
+ return (
+ <>
+ {/* 名称 */}
+
+
素材名称
+
onNameChange(e.target.value)}
+ placeholder="输入素材名称"
+ maxLength={UPLOAD_CONFIG.maxNameLength}
+ disabled={disabled}
+ style={inputStyle}
+ />
+
+
+ {/* 性别选择 */}
+
+
音色性别
+
+ {GENDER_OPTIONS.map((opt) => (
+
+ ))}
+
+
+
+ {/* 描述 */}
+
+ >
+ )
+}
+
+export default FormFields
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/UploadProgress.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/UploadProgress.tsx
new file mode 100755
index 000000000..9f47c3aaa
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/UploadProgress.tsx
@@ -0,0 +1,45 @@
+import React from "react"
+
+interface UploadProgressProps {
+ progress: number
+}
+
+const UploadProgress: React.FC = ({ progress }) => {
+ return (
+
+
+ {progress}%
+
+
+ {progress < 100 ? "上传中..." : "处理中..."}
+
+
+
+ )
+}
+
+export default UploadProgress
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/index.ts b/apps/web/src/pages/voices/components/upload-voice-modal/index.ts
new file mode 100755
index 000000000..0cec1b114
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/index.ts
@@ -0,0 +1,6 @@
+export { default as FileUploadZone } from "./FileUploadZone"
+export { default as FileInfoCard } from "./FileInfoCard"
+export { default as UploadProgress } from "./UploadProgress"
+export { default as FormFields } from "./FormFields"
+export { default as ActionButtons } from "./ActionButtons"
+export * from "./types"
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/types.ts b/apps/web/src/pages/voices/components/upload-voice-modal/types.ts
new file mode 100755
index 000000000..a40683bc3
--- /dev/null
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/types.ts
@@ -0,0 +1,30 @@
+import type { VoiceGender } from "@/pages/voices/types"
+
+export interface UploadVoiceModalProps {
+ open: boolean
+ uploadFile: File | null
+ uploadName: string
+ uploadGender: VoiceGender
+ uploadDesc: string
+ uploadProgress: number | null
+ onClose: () => void
+ onFileSelect: (file: File) => void
+ onFileRemove: () => void
+ onNameChange: (name: string) => void
+ onGenderChange: (gender: VoiceGender) => void
+ onDescChange: (desc: string) => void
+ onUpload: () => void
+}
+
+export const GENDER_OPTIONS: { value: VoiceGender; label: string }[] = [
+ { value: "female", label: "女声" },
+ { value: "male", label: "男声" },
+ { value: "child", label: "童声" },
+]
+
+export const UPLOAD_CONFIG = {
+ maxSizeMB: 200,
+ maxNameLength: 100,
+ maxDescLength: 500,
+ accept: "audio/*",
+} as const
--
2.54.0
From 9e4ed48debce1dcc98096d9cef57f06d8cdded7b Mon Sep 17 00:00:00 2001
From: xiaoxia
Date: Tue, 28 Jul 2026 09:19:03 +0800
Subject: [PATCH 2/3] fix: prettier format FileUploadZone
---
.../voices/components/upload-voice-modal/FileUploadZone.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx b/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
index 984672eab..201be9b1b 100755
--- a/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
+++ b/apps/web/src/pages/voices/components/upload-voice-modal/FileUploadZone.tsx
@@ -37,9 +37,7 @@ const FileUploadZone: React.FC = ({
>
-
- 点击或拖拽音频文件到此处
-
+ 点击或拖拽音频文件到此处
Date: Tue, 28 Jul 2026 09:27:03 +0800
Subject: [PATCH 3/3] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85voices=20smoke?=
=?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9A=84upload-voice-modal=E4=BE=9D=E8=B5=96?=
=?UTF-8?q?=E9=93=BE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apps/web/src/test/pages/voices/smoke.test.tsx | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/apps/web/src/test/pages/voices/smoke.test.tsx b/apps/web/src/test/pages/voices/smoke.test.tsx
index 63b6a7625..bd450bdb5 100755
--- a/apps/web/src/test/pages/voices/smoke.test.tsx
+++ b/apps/web/src/test/pages/voices/smoke.test.tsx
@@ -14,6 +14,12 @@ import "@/pages/voices/components/CloneVoiceCard"
import "@/pages/voices/components/CloneDetailModal"
import "@/pages/voices/components/CloneCardSkeleton"
import "@/pages/voices/components/UploadVoiceModal"
+import "@/pages/voices/components/upload-voice-modal/FileUploadZone"
+import "@/pages/voices/components/upload-voice-modal/FileInfoCard"
+import "@/pages/voices/components/upload-voice-modal/UploadProgress"
+import "@/pages/voices/components/upload-voice-modal/FormFields"
+import "@/pages/voices/components/upload-voice-modal/ActionButtons"
+import "@/pages/voices/components/upload-voice-modal/types"
import "@/pages/voices/components/TtsModal"
import "@/pages/voices/components/VoiceFilterBar"
import "@/pages/voices/components/MaterialVoiceCard"
--
2.54.0