Files
video-gen/video-gen-admin/src/pages/homeMaterials/HomeMaterialUploadModal.tsx
T
2026-07-11 13:49:36 +08:00

220 lines
8.1 KiB
TypeScript

import React, { useEffect, useMemo, useState } from 'react';
import { Form, Input, InputNumber, Modal, Select, Switch, Upload, Button, message } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { uploadHomeMaterialAsset } from '../../api';
import type { HomeMaterialCategory, HomeMaterialMediaReference, HomeMaterialMediaType, HomeMaterialWatermark, HomeMaterialWatermarkConfig } from '../../types';
import WatermarkEditor from './WatermarkEditor';
import MediaReferencesEditor from './MediaReferencesEditor';
interface Props {
open: boolean;
onClose: () => void;
categories: HomeMaterialCategory[];
watermarks: HomeMaterialWatermark[];
onSuccess: () => void;
}
const defaultConfig: HomeMaterialWatermarkConfig = {
watermarkType: 'image',
watermarkId: null,
opacityLevel: 6,
position: 'bottom_right',
customXRatio: null,
customYRatio: null,
sizeMode: 'ratio',
widthRatio: 0.18,
widthPx: null,
marginX: 24,
marginY: 24,
textWatermark: {
text: '民众智创',
opacityLevel: 2,
fontSizePx: 28,
color: '#ffffff',
rotateDeg: -30,
gapX: 220,
gapY: 140,
staggered: true,
},
};
function isValidConfig(config: HomeMaterialWatermarkConfig): boolean {
if (config.watermarkType === 'repeated_text') return !!config.textWatermark?.text?.trim();
return !!config.watermarkId;
}
const HomeMaterialUploadModal: React.FC<Props> = ({ open, onClose, categories, watermarks, onSuccess }) => {
const [form] = Form.useForm();
const [file, setFile] = useState<File | null>(null);
const [fileList, setFileList] = useState<any[]>([]);
const [mediaType, setMediaType] = useState<HomeMaterialMediaType>('image');
const [config, setConfig] = useState<HomeMaterialWatermarkConfig>(defaultConfig);
const [submitting, setSubmitting] = useState(false);
const [mediaObjectUrl, setMediaObjectUrl] = useState<string>('');
const [mediaReferences, setMediaReferences] = useState<HomeMaterialMediaReference[]>([]);
const resetState = () => {
setFile(null);
setFileList([]);
setMediaType('image');
setMediaObjectUrl('');
setSubmitting(false);
setMediaReferences([]);
};
useEffect(() => {
if (open) {
resetState();
const defaultWatermark = watermarks.find(w => w.isDefault) || watermarks[0];
setConfig({ ...defaultConfig, watermarkId: defaultWatermark?.id || null });
form.setFieldsValue({ media_type: 'image', is_active: true, sort_order: 0, title: undefined, generation_prompt: undefined });
}
}, [open, watermarks, form]);
useEffect(() => {
return () => {
if (mediaObjectUrl.startsWith('blob:')) URL.revokeObjectURL(mediaObjectUrl);
};
}, [mediaObjectUrl]);
const mediaUrl = useMemo(() => mediaObjectUrl || undefined, [mediaObjectUrl]);
const handleBeforeUpload = (selectedFile: File) => {
const isImage = selectedFile.type.startsWith('image/');
const isVideo = selectedFile.type.startsWith('video/');
if (mediaType === 'image' && !isImage) {
message.warning('当前素材类型为图片,请选择图片文件');
return Upload.LIST_IGNORE;
}
if (mediaType === 'video' && !isVideo) {
message.warning('当前素材类型为视频,请选择视频文件');
return Upload.LIST_IGNORE;
}
if (mediaObjectUrl.startsWith('blob:')) URL.revokeObjectURL(mediaObjectUrl);
const nextUrl = URL.createObjectURL(selectedFile);
setFile(selectedFile);
setMediaObjectUrl(nextUrl);
setFileList([{ uid: selectedFile.name, name: selectedFile.name, status: 'done', originFileObj: selectedFile }]);
return false;
};
const handleRemoveFile = () => {
setFile(null);
setFileList([]);
if (mediaObjectUrl.startsWith('blob:')) URL.revokeObjectURL(mediaObjectUrl);
setMediaObjectUrl('');
};
const changeMediaType = (value: HomeMaterialMediaType) => {
setMediaType(value);
handleRemoveFile();
};
const closeModal = () => {
if (submitting) return;
onClose();
};
const submit = async () => {
if (submitting) return;
const values = await form.validateFields();
if (!file) {
message.warning('请选择素材文件');
return;
}
if (config.watermarkType === 'image' && !config.watermarkId) {
message.warning('请选择水印图片');
return;
}
if (config.watermarkType === 'repeated_text' && !config.textWatermark?.text?.trim()) {
message.warning('请输入重复文字水印内容');
return;
}
setSubmitting(true);
try {
const res = await uploadHomeMaterialAsset({
categoryId: values.category_id,
file,
mediaType,
title: values.title?.trim() || null,
generationPrompt: values.generation_prompt?.trim() || null,
mediaReferences,
watermarkType: config.watermarkType,
watermarkId: config.watermarkType === 'image' ? config.watermarkId : null,
opacityLevel: config.opacityLevel,
position: config.position,
customXRatio: config.customXRatio,
customYRatio: config.customYRatio,
sizeMode: config.sizeMode,
widthRatio: config.widthRatio,
widthPx: config.widthPx,
marginX: config.marginX,
marginY: config.marginY,
textWatermark: config.watermarkType === 'repeated_text' ? config.textWatermark : null,
isActive: values.is_active,
sortOrder: values.sort_order,
wait: false,
});
message.success(res.message || '已提交水印处理');
onSuccess();
onClose();
} finally {
setSubmitting(false);
}
};
return (
<Modal
title="上传首页素材"
open={open}
onCancel={closeModal}
onOk={submit}
confirmLoading={submitting}
okButtonProps={{ disabled: submitting || !file || !isValidConfig(config) }}
cancelButtonProps={{ disabled: submitting }}
width={980}
destroyOnHidden
>
<Form form={form} layout="vertical">
<Form.Item name="category_id" label="行业" rules={[{ required: true, message: '请选择行业' }]}>
<Select options={categories.map(c => ({ label: c.name, value: c.id }))} disabled={submitting} />
</Form.Item>
<Form.Item name="media_type" label="素材类型" rules={[{ required: true }]}>
<Select
value={mediaType}
onChange={changeMediaType}
disabled={submitting}
options={[{ label: '图片', value: 'image' }, { label: '视频', value: 'video' }]}
/>
</Form.Item>
<Form.Item label="素材文件" required>
<Upload
beforeUpload={(f) => handleBeforeUpload(f as File)}
onRemove={handleRemoveFile}
fileList={fileList}
maxCount={1}
accept={mediaType === 'image' ? 'image/*' : 'video/*'}
disabled={submitting}
>
<Button icon={<UploadOutlined />} loading={submitting} disabled={submitting}>选择{mediaType === 'image' ? '图片' : '视频'}</Button>
</Upload>
</Form.Item>
<Form.Item name="title" label="素材标题" extra="选填;不填时后台显示“未命名素材”,不会再自动使用文件名作为标题。"><Input disabled={submitting} /></Form.Item>
<Form.Item name="generation_prompt" label="生成提词" extra="选填;会随前台素材接口返回。">
<Input.TextArea rows={5} maxLength={8000} showCount disabled={submitting} placeholder="请输入用于生成该素材的提示词" />
</Form.Item>
<Form.Item label="附件 / 参考素材">
<MediaReferencesEditor value={mediaReferences} onChange={setMediaReferences} disabled={submitting} />
</Form.Item>
<Form.Item name="sort_order" label="排序"><InputNumber min={0} style={{ width: '100%' }} disabled={submitting} /></Form.Item>
<Form.Item name="is_active" label="前台展示" valuePropName="checked"><Switch disabled={submitting} /></Form.Item>
</Form>
<WatermarkEditor value={config} onChange={setConfig} watermarks={watermarks} mediaUrl={mediaUrl} mediaType={mediaType} />
</Modal>
);
};
export default HomeMaterialUploadModal;