220 lines
8.1 KiB
TypeScript
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;
|