208 lines
7.5 KiB
TypeScript
208 lines
7.5 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
||
import { Button, Form, Image, Input, Modal, Popconfirm, Space, Switch, Table, Upload, message } from 'antd';
|
||
import { DeleteOutlined, EditOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
|
||
import { deleteHomeMaterialWatermark, getHomeMaterialWatermarks, updateHomeMaterialWatermark, uploadHomeMaterialWatermark } from '../../api';
|
||
import type { HomeMaterialWatermark } from '../../types';
|
||
import { apiUrl } from '../../utils/resourceUrl';
|
||
|
||
interface Props {
|
||
open?: boolean;
|
||
onClose?: () => void;
|
||
embedded?: boolean;
|
||
onChanged?: () => void;
|
||
}
|
||
|
||
const WatermarkLibraryModal: React.FC<Props> = ({ open = true, onClose, embedded = false, onChanged }) => {
|
||
const [items, setItems] = useState<HomeMaterialWatermark[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [uploadOpen, setUploadOpen] = useState(false);
|
||
const [editing, setEditing] = useState<HomeMaterialWatermark | null>(null);
|
||
const [file, setFile] = useState<File | null>(null);
|
||
const [fileList, setFileList] = useState<any[]>([]);
|
||
const [previewUrl, setPreviewUrl] = useState<string>('');
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [form] = Form.useForm();
|
||
|
||
const load = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await getHomeMaterialWatermarks({ page: 1, pageSize: 200 });
|
||
setItems(res.items);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => { if (open) load(); }, [open]);
|
||
|
||
useEffect(() => {
|
||
return () => {
|
||
if (previewUrl.startsWith('blob:')) URL.revokeObjectURL(previewUrl);
|
||
};
|
||
}, [previewUrl]);
|
||
|
||
const resetUploadState = () => {
|
||
setFile(null);
|
||
setFileList([]);
|
||
setPreviewUrl('');
|
||
setSubmitting(false);
|
||
};
|
||
|
||
const openUpload = () => {
|
||
setEditing(null);
|
||
resetUploadState();
|
||
form.setFieldsValue({ name: '', is_default: false, is_active: true });
|
||
setUploadOpen(true);
|
||
};
|
||
|
||
const openEdit = (row: HomeMaterialWatermark) => {
|
||
setEditing(row);
|
||
resetUploadState();
|
||
setPreviewUrl(apiUrl(row.fileUrl));
|
||
form.setFieldsValue({ name: row.name, is_default: row.isDefault, is_active: row.isActive });
|
||
setUploadOpen(true);
|
||
};
|
||
|
||
const closeUploadModal = () => {
|
||
if (submitting) return;
|
||
setUploadOpen(false);
|
||
resetUploadState();
|
||
};
|
||
|
||
const handleBeforeUpload = (selectedFile: File) => {
|
||
if (!selectedFile.type.startsWith('image/')) {
|
||
message.warning('请选择图片文件');
|
||
return Upload.LIST_IGNORE;
|
||
}
|
||
|
||
if (previewUrl.startsWith('blob:')) URL.revokeObjectURL(previewUrl);
|
||
const nextPreviewUrl = URL.createObjectURL(selectedFile);
|
||
setFile(selectedFile);
|
||
setPreviewUrl(nextPreviewUrl);
|
||
setFileList([{ uid: selectedFile.name, name: selectedFile.name, status: 'done', originFileObj: selectedFile }]);
|
||
return false;
|
||
};
|
||
|
||
const handleRemoveFile = () => {
|
||
setFile(null);
|
||
setFileList([]);
|
||
if (previewUrl.startsWith('blob:')) URL.revokeObjectURL(previewUrl);
|
||
setPreviewUrl('');
|
||
};
|
||
|
||
const submit = async () => {
|
||
if (submitting) return;
|
||
const values = await form.validateFields();
|
||
|
||
if (!editing && !file) {
|
||
message.warning('请选择水印图片');
|
||
return;
|
||
}
|
||
|
||
setSubmitting(true);
|
||
try {
|
||
if (editing) {
|
||
await updateHomeMaterialWatermark(editing.id, { name: values.name, is_default: values.is_default, is_active: values.is_active });
|
||
} else if (file) {
|
||
await uploadHomeMaterialWatermark(file, values.name, values.is_default);
|
||
}
|
||
|
||
message.success('保存成功');
|
||
setUploadOpen(false);
|
||
resetUploadState();
|
||
await load();
|
||
onChanged?.();
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
};
|
||
|
||
const table = (
|
||
<>
|
||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between' }}>
|
||
<div />
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={openUpload}>上传水印</Button>
|
||
</div>
|
||
<Table
|
||
rowKey="id"
|
||
loading={loading}
|
||
dataSource={items}
|
||
pagination={{ pageSize: 20 }}
|
||
columns={[
|
||
{ title: '预览', dataIndex: 'fileUrl', render: (url: string) => <Image width={80} src={apiUrl(url)} /> },
|
||
{ title: '名称', dataIndex: 'name' },
|
||
{ title: '尺寸', render: (_: unknown, r: HomeMaterialWatermark) => r.width && r.height ? `${r.width}×${r.height}` : '-' },
|
||
{ title: '默认', dataIndex: 'isDefault', render: (v: boolean) => v ? '是' : '否' },
|
||
{ title: '启用', dataIndex: 'isActive', render: (v: boolean) => v ? '启用' : '禁用' },
|
||
{
|
||
title: '操作',
|
||
render: (_: unknown, row: HomeMaterialWatermark) => (
|
||
<Space>
|
||
<Button size="small" icon={<EditOutlined />} onClick={() => openEdit(row)}>编辑</Button>
|
||
<Popconfirm title="确认删除该水印?" onConfirm={async () => { await deleteHomeMaterialWatermark(row.id); message.success('删除成功'); await load(); onChanged?.(); }}>
|
||
<Button size="small" danger icon={<DeleteOutlined />}>删除</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
]}
|
||
/>
|
||
<Modal
|
||
title={editing ? '编辑水印' : '上传水印'}
|
||
open={uploadOpen}
|
||
onOk={submit}
|
||
onCancel={closeUploadModal}
|
||
confirmLoading={submitting}
|
||
okButtonProps={{ disabled: submitting || (!editing && !file) }}
|
||
cancelButtonProps={{ disabled: submitting }}
|
||
destroyOnHidden
|
||
>
|
||
<Form form={form} layout="vertical">
|
||
{!editing && (
|
||
<Form.Item label="水印图片" required>
|
||
<Upload
|
||
beforeUpload={(f) => handleBeforeUpload(f as File)}
|
||
onRemove={handleRemoveFile}
|
||
fileList={fileList}
|
||
maxCount={1}
|
||
accept="image/png,image/jpeg,image/webp"
|
||
disabled={submitting}
|
||
>
|
||
<Button icon={<UploadOutlined />} loading={submitting} disabled={submitting}>选择图片</Button>
|
||
</Upload>
|
||
{previewUrl && (
|
||
<div style={{ marginTop: 12 }}>
|
||
<Image
|
||
src={previewUrl}
|
||
alt="水印预览"
|
||
width={180}
|
||
style={{ maxHeight: 120, objectFit: 'contain', background: '#0f172a', borderRadius: 8, padding: 8 }}
|
||
/>
|
||
</div>
|
||
)}
|
||
</Form.Item>
|
||
)}
|
||
{editing && previewUrl && (
|
||
<Form.Item label="当前水印">
|
||
<Image
|
||
src={previewUrl}
|
||
alt="当前水印"
|
||
width={180}
|
||
style={{ maxHeight: 120, objectFit: 'contain', background: '#0f172a', borderRadius: 8, padding: 8 }}
|
||
/>
|
||
</Form.Item>
|
||
)}
|
||
<Form.Item name="name" label="水印名称" rules={[{ required: true, message: '请输入水印名称' }]}><Input disabled={submitting} /></Form.Item>
|
||
<Form.Item name="is_default" label="默认水印" valuePropName="checked"><Switch disabled={submitting} /></Form.Item>
|
||
<Form.Item name="is_active" label="启用" valuePropName="checked"><Switch disabled={submitting} /></Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</>
|
||
);
|
||
|
||
if (embedded) return table;
|
||
return <Modal title="水印库" open={open} onCancel={onClose} footer={null} width={900} destroyOnHidden>{table}</Modal>;
|
||
};
|
||
|
||
export default WatermarkLibraryModal;
|