Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f3d7d3c1a | |||
| 7603aeca5c | |||
| 033bbf1ec3 | |||
| 35dd478f1d |
Executable → Regular
+49
-266
@@ -1,8 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频弹窗
|
||||||
|
*
|
||||||
|
* 子组件目录: upload-voice/
|
||||||
|
* - UploadDragger: 拖拽上传区
|
||||||
|
* - SelectedFileInfo: 已选文件信息
|
||||||
|
* - UploadProgress: 上传进度条
|
||||||
|
* - UploadFormFields: 表单区域(名称/性别/描述)
|
||||||
|
* - UploadActions: 底部操作按钮
|
||||||
|
*/
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { UploadOutlined, SoundOutlined } from "@ant-design/icons"
|
import { Modal } from "antd"
|
||||||
import { Modal, Upload, message } from "antd"
|
|
||||||
import { type VoiceGender } from "@/pages/voices/types"
|
import { type VoiceGender } from "@/pages/voices/types"
|
||||||
import { formatFileSize } from "@/pages/voices/utils/format"
|
import UploadDragger from "./upload-voice/UploadDragger"
|
||||||
|
import SelectedFileInfo from "./upload-voice/SelectedFileInfo"
|
||||||
|
import UploadProgress from "./upload-voice/UploadProgress"
|
||||||
|
import UploadFormFields from "./upload-voice/UploadFormFields"
|
||||||
|
import UploadActions from "./upload-voice/UploadActions"
|
||||||
|
|
||||||
export interface UploadVoiceModalProps {
|
export interface UploadVoiceModalProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -20,7 +33,6 @@ export interface UploadVoiceModalProps {
|
|||||||
onUpload: () => void
|
onUpload: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 上传音频弹窗 */
|
|
||||||
const UploadVoiceModal: React.FC<UploadVoiceModalProps> = ({
|
const UploadVoiceModal: React.FC<UploadVoiceModalProps> = ({
|
||||||
open,
|
open,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
@@ -36,17 +48,21 @@ const UploadVoiceModal: React.FC<UploadVoiceModalProps> = ({
|
|||||||
onDescChange,
|
onDescChange,
|
||||||
onUpload,
|
onUpload,
|
||||||
}) => {
|
}) => {
|
||||||
|
const isUploading = uploadProgress !== null
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (isUploading) return // 上传中不可关闭
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="上传音频"
|
title="上传音频"
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => {
|
onCancel={handleClose}
|
||||||
if (uploadProgress !== null) return // 上传中不可关闭
|
|
||||||
onClose()
|
|
||||||
}}
|
|
||||||
footer={null}
|
footer={null}
|
||||||
width={520}
|
width={520}
|
||||||
maskClosable={uploadProgress === null}
|
maskClosable={!isUploading}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@@ -57,270 +73,37 @@ const UploadVoiceModal: React.FC<UploadVoiceModalProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* 拖拽上传区 */}
|
{/* 拖拽上传区 */}
|
||||||
<Upload.Dragger
|
<UploadDragger
|
||||||
accept="audio/*"
|
disabled={isUploading}
|
||||||
maxCount={1}
|
onFileSelect={onFileSelect}
|
||||||
beforeUpload={(file) => {
|
onFileRemove={onFileRemove}
|
||||||
onFileSelect(file)
|
/>
|
||||||
return false
|
|
||||||
}}
|
|
||||||
onRemove={() => {
|
|
||||||
onFileRemove()
|
|
||||||
}}
|
|
||||||
showUploadList={false}
|
|
||||||
disabled={uploadProgress !== null}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: 32,
|
|
||||||
color: "var(--primary-color)",
|
|
||||||
marginBottom: 8,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<UploadOutlined />
|
|
||||||
</p>
|
|
||||||
<p style={{ fontSize: 14, fontWeight: 500, margin: "0 0 4px" }}>
|
|
||||||
点击或拖拽音频文件到此处
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
style={{
|
|
||||||
fontSize: 12,
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
margin: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
支持 MP3、WAV、AAC、FLAC 等格式,最大 200MB
|
|
||||||
</p>
|
|
||||||
</Upload.Dragger>
|
|
||||||
|
|
||||||
{/* 已选文件信息 */}
|
{/* 已选文件信息 */}
|
||||||
{uploadFile && (
|
{uploadFile && <SelectedFileInfo file={uploadFile} />}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "10px 12px",
|
|
||||||
background: "var(--bg-secondary)",
|
|
||||||
borderRadius: 8,
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SoundOutlined style={{ fontSize: 18, color: "var(--primary-color)" }} />
|
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: 500,
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{uploadFile.name}
|
|
||||||
</div>
|
|
||||||
<div style={{ fontSize: 11, color: "var(--text-secondary)" }}>
|
|
||||||
{formatFileSize(uploadFile.size)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 上传进度 */}
|
{/* 上传进度 */}
|
||||||
{uploadProgress !== null && (
|
{isUploading && <UploadProgress progress={uploadProgress!} />}
|
||||||
<div style={{ textAlign: "center", padding: "8px 0" }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 22,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: "var(--primary-color)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{uploadProgress}%
|
|
||||||
</div>
|
|
||||||
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>
|
|
||||||
{uploadProgress < 100 ? "上传中..." : "处理中..."}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: 4,
|
|
||||||
background: "var(--bg-tertiary)",
|
|
||||||
borderRadius: 2,
|
|
||||||
marginTop: 8,
|
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
height: "100%",
|
|
||||||
width: `${uploadProgress}%`,
|
|
||||||
background: "var(--primary-color)",
|
|
||||||
borderRadius: 2,
|
|
||||||
transition: "width 0.3s ease",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 名称 */}
|
{/* 表单字段 */}
|
||||||
<div>
|
<UploadFormFields
|
||||||
<div
|
name={uploadName}
|
||||||
style={{
|
gender={uploadGender}
|
||||||
fontSize: 13,
|
description={uploadDesc}
|
||||||
color: "var(--text-secondary)",
|
disabled={isUploading}
|
||||||
marginBottom: 6,
|
onNameChange={onNameChange}
|
||||||
}}
|
onGenderChange={onGenderChange}
|
||||||
>
|
onDescriptionChange={onDescChange}
|
||||||
素材名称
|
/>
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
value={uploadName}
|
|
||||||
onChange={(e) => 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",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 性别选择 */}
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 13,
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
音色性别
|
|
||||||
</div>
|
|
||||||
<div style={{ display: "flex", gap: 8 }}>
|
|
||||||
{(["female", "male", "child"] as VoiceGender[]).map((g) => (
|
|
||||||
<button
|
|
||||||
key={g}
|
|
||||||
type="button"
|
|
||||||
onClick={() => onGenderChange(g)}
|
|
||||||
disabled={uploadProgress !== null}
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
padding: "6px 0",
|
|
||||||
borderRadius: 8,
|
|
||||||
border: `1px solid ${uploadGender === g ? "var(--primary-color)" : "var(--border-color)"}`,
|
|
||||||
background: uploadGender === g ? "var(--primary-soft)" : "transparent",
|
|
||||||
color: uploadGender === g ? "var(--primary-color)" : "var(--text-secondary)",
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: uploadGender === g ? 600 : 400,
|
|
||||||
cursor: uploadProgress !== null ? "not-allowed" : "pointer",
|
|
||||||
transition: "all 0.2s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{g === "female" ? "女声" : g === "male" ? "男声" : "童声"}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 描述 */}
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
fontSize: 13,
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: 6,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
音色描述(可选)
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
value={uploadDesc}
|
|
||||||
onChange={(e) => onDescChange(e.target.value)}
|
|
||||||
placeholder="描述这个音色的特点..."
|
|
||||||
maxLength={500}
|
|
||||||
rows={2}
|
|
||||||
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",
|
|
||||||
resize: "vertical",
|
|
||||||
fontFamily: "inherit",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 操作按钮 */}
|
{/* 操作按钮 */}
|
||||||
<div
|
<UploadActions
|
||||||
style={{
|
hasFile={!!uploadFile}
|
||||||
display: "flex",
|
hasName={!!uploadName.trim()}
|
||||||
justifyContent: "flex-end",
|
uploading={isUploading}
|
||||||
gap: 10,
|
onCancel={onClose}
|
||||||
paddingTop: 4,
|
onUpload={onUpload}
|
||||||
}}
|
/>
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onClose}
|
|
||||||
disabled={uploadProgress !== null}
|
|
||||||
style={{
|
|
||||||
padding: "8px 20px",
|
|
||||||
borderRadius: 8,
|
|
||||||
border: "1px solid var(--border-color)",
|
|
||||||
background: "transparent",
|
|
||||||
fontSize: 13,
|
|
||||||
cursor: uploadProgress !== null ? "not-allowed" : "pointer",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
取消
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
if (!uploadFile) {
|
|
||||||
message.warning("请先选择音频文件")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!uploadName.trim()) {
|
|
||||||
message.warning("请输入素材名称")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
onUpload()
|
|
||||||
}}
|
|
||||||
disabled={!uploadFile || !uploadName.trim() || uploadProgress !== null}
|
|
||||||
style={{
|
|
||||||
padding: "8px 20px",
|
|
||||||
borderRadius: 8,
|
|
||||||
border: "none",
|
|
||||||
background:
|
|
||||||
!uploadFile || !uploadName.trim() || uploadProgress !== null
|
|
||||||
? "var(--text-tertiary)"
|
|
||||||
: "var(--primary-color)",
|
|
||||||
color: "#fff",
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: 600,
|
|
||||||
cursor:
|
|
||||||
!uploadFile || !uploadName.trim() || uploadProgress !== null
|
|
||||||
? "not-allowed"
|
|
||||||
: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{uploadProgress !== null ? "上传中..." : "开始上传"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频 - 已选文件信息
|
||||||
|
*/
|
||||||
|
import React from "react"
|
||||||
|
import { SoundOutlined } from "@ant-design/icons"
|
||||||
|
import { formatFileSize } from "@/pages/voices/utils/format"
|
||||||
|
|
||||||
|
export interface SelectedFileInfoProps {
|
||||||
|
file: File
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectedFileInfo: React.FC<SelectedFileInfoProps> = ({ file }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "10px 12px",
|
||||||
|
background: "var(--bg-secondary)",
|
||||||
|
borderRadius: 8,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SoundOutlined style={{ fontSize: 18, color: "var(--primary-color)" }} />
|
||||||
|
<div style={{ flex: 1, minWidth: 0 }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 500,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{file.name}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 11, color: "var(--text-secondary)" }}>
|
||||||
|
{formatFileSize(file.size)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectedFileInfo
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频 - 底部操作按钮
|
||||||
|
*/
|
||||||
|
import React from "react"
|
||||||
|
import { message } from "antd"
|
||||||
|
|
||||||
|
export interface UploadActionsProps {
|
||||||
|
hasFile: boolean
|
||||||
|
hasName: boolean
|
||||||
|
uploading: boolean
|
||||||
|
onCancel: () => void
|
||||||
|
onUpload: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadActions: React.FC<UploadActionsProps> = ({
|
||||||
|
hasFile,
|
||||||
|
hasName,
|
||||||
|
uploading,
|
||||||
|
onCancel,
|
||||||
|
onUpload,
|
||||||
|
}) => {
|
||||||
|
const canUpload = hasFile && hasName && !uploading
|
||||||
|
|
||||||
|
const handleUploadClick = () => {
|
||||||
|
if (!hasFile) {
|
||||||
|
message.warning("请先选择音频文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!hasName) {
|
||||||
|
message.warning("请输入素材名称")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onUpload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
gap: 10,
|
||||||
|
paddingTop: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onCancel}
|
||||||
|
disabled={uploading}
|
||||||
|
style={{
|
||||||
|
padding: "8px 20px",
|
||||||
|
borderRadius: 8,
|
||||||
|
border: "1px solid var(--border-color)",
|
||||||
|
background: "transparent",
|
||||||
|
fontSize: 13,
|
||||||
|
cursor: uploading ? "not-allowed" : "pointer",
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleUploadClick}
|
||||||
|
disabled={!canUpload}
|
||||||
|
style={{
|
||||||
|
padding: "8px 20px",
|
||||||
|
borderRadius: 8,
|
||||||
|
border: "none",
|
||||||
|
background: canUpload ? "var(--primary-color)" : "var(--text-tertiary)",
|
||||||
|
color: "#fff",
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: canUpload ? "pointer" : "not-allowed",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{uploading ? "上传中..." : "开始上传"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadActions
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频 - 拖拽上传区
|
||||||
|
*/
|
||||||
|
import React from "react"
|
||||||
|
import { UploadOutlined } from "@ant-design/icons"
|
||||||
|
import { Upload } from "antd"
|
||||||
|
|
||||||
|
export interface UploadDraggerProps {
|
||||||
|
disabled: boolean
|
||||||
|
onFileSelect: (file: File) => void
|
||||||
|
onFileRemove: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadDragger: React.FC<UploadDraggerProps> = ({
|
||||||
|
disabled,
|
||||||
|
onFileSelect,
|
||||||
|
onFileRemove,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Upload.Dragger
|
||||||
|
accept="audio/*"
|
||||||
|
maxCount={1}
|
||||||
|
beforeUpload={(file) => {
|
||||||
|
onFileSelect(file)
|
||||||
|
return false
|
||||||
|
}}
|
||||||
|
onRemove={() => {
|
||||||
|
onFileRemove()
|
||||||
|
}}
|
||||||
|
showUploadList={false}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: "var(--primary-color)",
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UploadOutlined />
|
||||||
|
</p>
|
||||||
|
<p style={{ fontSize: 14, fontWeight: 500, margin: "0 0 4px" }}>点击或拖拽音频文件到此处</p>
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontSize: 12,
|
||||||
|
color: "var(--text-secondary)",
|
||||||
|
margin: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
支持 MP3、WAV、AAC、FLAC 等格式,最大 200MB
|
||||||
|
</p>
|
||||||
|
</Upload.Dragger>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadDragger
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频 - 表单区域(名称/性别/描述)
|
||||||
|
*/
|
||||||
|
import React from "react"
|
||||||
|
import { type VoiceGender } from "@/pages/voices/types"
|
||||||
|
|
||||||
|
export interface UploadFormFieldsProps {
|
||||||
|
name: string
|
||||||
|
gender: VoiceGender
|
||||||
|
description: string
|
||||||
|
disabled: boolean
|
||||||
|
onNameChange: (name: string) => void
|
||||||
|
onGenderChange: (gender: VoiceGender) => void
|
||||||
|
onDescriptionChange: (desc: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const GENDER_LABELS: Record<VoiceGender, string> = {
|
||||||
|
female: "女声",
|
||||||
|
male: "男声",
|
||||||
|
child: "童声",
|
||||||
|
elderly: "老年音",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadFormFields: React.FC<UploadFormFieldsProps> = ({
|
||||||
|
name,
|
||||||
|
gender,
|
||||||
|
description,
|
||||||
|
disabled,
|
||||||
|
onNameChange,
|
||||||
|
onGenderChange,
|
||||||
|
onDescriptionChange,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 名称 */}
|
||||||
|
<div>
|
||||||
|
<div style={labelStyle}>素材名称</div>
|
||||||
|
<input
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => onNameChange(e.target.value)}
|
||||||
|
placeholder="输入素材名称"
|
||||||
|
maxLength={100}
|
||||||
|
disabled={disabled}
|
||||||
|
style={inputStyle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 性别选择 */}
|
||||||
|
<div>
|
||||||
|
<div style={labelStyle}>音色性别</div>
|
||||||
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
|
{(["female", "male", "child"] as VoiceGender[]).map((g) => (
|
||||||
|
<button
|
||||||
|
key={g}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onGenderChange(g)}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
padding: "6px 0",
|
||||||
|
borderRadius: 8,
|
||||||
|
border: `1px solid ${gender === g ? "var(--primary-color)" : "var(--border-color)"}`,
|
||||||
|
background: gender === g ? "var(--primary-soft)" : "transparent",
|
||||||
|
color: gender === g ? "var(--primary-color)" : "var(--text-secondary)",
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: gender === g ? 600 : 400,
|
||||||
|
cursor: disabled ? "not-allowed" : "pointer",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{GENDER_LABELS[g]}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 描述 */}
|
||||||
|
<div>
|
||||||
|
<div style={labelStyle}>音色描述(可选)</div>
|
||||||
|
<textarea
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => onDescriptionChange(e.target.value)}
|
||||||
|
placeholder="描述这个音色的特点..."
|
||||||
|
maxLength={500}
|
||||||
|
rows={2}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{
|
||||||
|
...inputStyle,
|
||||||
|
resize: "vertical",
|
||||||
|
fontFamily: "inherit",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadFormFields
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* 上传音频 - 上传进度条
|
||||||
|
*/
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export interface UploadProgressProps {
|
||||||
|
progress: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UploadProgress: React.FC<UploadProgressProps> = ({ progress }) => {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "center", padding: "8px 0" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 700,
|
||||||
|
color: "var(--primary-color)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{progress}%
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 12, color: "var(--text-secondary)" }}>
|
||||||
|
{progress < 100 ? "上传中..." : "处理中..."}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: 4,
|
||||||
|
background: "var(--bg-tertiary)",
|
||||||
|
borderRadius: 2,
|
||||||
|
marginTop: 8,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "100%",
|
||||||
|
width: `${progress}%`,
|
||||||
|
background: "var(--primary-color)",
|
||||||
|
borderRadius: 2,
|
||||||
|
transition: "width 0.3s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UploadProgress
|
||||||
Reference in New Issue
Block a user