This commit is contained in:
jeremygan2021
2026-02-12 15:51:18 +08:00
parent e69a24b555
commit 4ac8767659
14 changed files with 2851 additions and 141 deletions

View File

@@ -1,27 +1,59 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Button, message, Upload, Select, Divider, Radio, Tabs, Alert } from 'antd';
import { InboxOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons';
import { createTopic, uploadMedia, getMyPaidItems } from '../api';
import { Modal, Form, Input, Button, message, Upload, Select } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import { createTopic, updateTopic, uploadMedia, getMyPaidItems } from '../api';
import MDEditor from '@uiw/react-md-editor';
import rehypeKatex from 'rehype-katex';
import remarkMath from 'remark-math';
import 'katex/dist/katex.css';
const { TextArea } = Input;
const { Option } = Select;
const { Dragger } = Upload;
const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
const CreateTopicModal = ({ visible, onClose, onSuccess, initialValues, isEditMode, topicId }) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [paidItems, setPaidItems] = useState({ configs: [], courses: [], services: [] });
const [uploading, setUploading] = useState(false);
const [mediaIds, setMediaIds] = useState([]);
// eslint-disable-next-line no-unused-vars
const [mediaList, setMediaList] = useState([]); // Store uploaded media details for preview
const [content, setContent] = useState("");
useEffect(() => {
if (visible) {
fetchPaidItems();
setMediaIds([]); // Reset media IDs
setMediaList([]); // Reset media list
if (isEditMode && initialValues) {
// Edit Mode: Populate form with initial values
form.setFieldsValue({
title: initialValues.title,
category: initialValues.category,
});
setContent(initialValues.content);
form.setFieldValue('content', initialValues.content);
// Handle related item
let relatedVal = null;
if (initialValues.related_product) relatedVal = `config_${initialValues.related_product.id || initialValues.related_product}`;
else if (initialValues.related_course) relatedVal = `course_${initialValues.related_course.id || initialValues.related_course}`;
else if (initialValues.related_service) relatedVal = `service_${initialValues.related_service.id || initialValues.related_service}`;
if (relatedVal) form.setFieldValue('related_item', relatedVal);
// Note: We start with empty *new* media IDs.
// Existing media is embedded in content or stored in DB, we don't need to re-upload or track them here unless we want to delete them (which is complex).
// For now, we just allow adding NEW media.
setMediaIds([]);
setMediaList([]);
} else {
// Create Mode: Reset form
setMediaIds([]);
setMediaList([]);
setContent("");
form.resetFields();
form.setFieldsValue({ content: "", category: 'discussion' });
}
}
}, [visible]);
}, [visible, isEditMode, initialValues, form]);
const fetchPaidItems = async () => {
try {
@@ -77,14 +109,14 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
}]);
// 插入到编辑器
const currentContent = form.getFieldValue('content') || '';
const insertText = file.type.startsWith('video')
? `\n<video src="${url}" controls width="100%"></video>\n`
: `\n![${file.name}](${url})\n`;
form.setFieldsValue({
content: currentContent + insertText
});
const newContent = content + insertText;
setContent(newContent);
form.setFieldsValue({ content: newContent });
message.success('上传成功');
} catch (error) {
console.error(error);
@@ -100,7 +132,8 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
try {
// 处理关联项目 ID (select value format: "type_id")
const relatedValue = values.related_item;
const payload = { ...values, media_ids: mediaIds };
// Use content state instead of form value to ensure consistency
const payload = { ...values, content: content, media_ids: mediaIds };
delete payload.related_item;
if (relatedValue) {
@@ -108,16 +141,27 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
if (type === 'config') payload.related_product = id;
if (type === 'course') payload.related_course = id;
if (type === 'service') payload.related_service = id;
} else {
// If cleared, set to null
payload.related_product = null;
payload.related_course = null;
payload.related_service = null;
}
await createTopic(payload);
message.success('发布成功');
if (isEditMode && topicId) {
await updateTopic(topicId, payload);
message.success('修改成功');
} else {
await createTopic(payload);
message.success('发布成功');
}
form.resetFields();
if (onSuccess) onSuccess();
onClose();
} catch (error) {
console.error(error);
message.error('发布失败: ' + (error.response?.data?.detail || '网络错误'));
message.error((isEditMode ? '修改' : '发布') + '失败: ' + (error.response?.data?.detail || '网络错误'));
} finally {
setLoading(false);
}
@@ -125,12 +169,12 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
return (
<Modal
title="发布新帖"
title={isEditMode ? "编辑帖子" : "发布新帖"}
open={visible}
onCancel={onClose}
footer={null}
destroyOnClose
width={800}
width={1000}
style={{ top: 20 }}
>
<Form
@@ -189,47 +233,33 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
<Form.Item
name="content"
label="内容 (支持 Markdown)"
label="内容 (支持 Markdown 与 LaTeX 公式)"
rules={[{ required: true, message: '请输入内容' }]}
>
<div>
<Upload
beforeUpload={handleUpload}
showUploadList={false}
accept="image/*,video/*"
>
<Button icon={<UploadOutlined />} loading={uploading} size="small" style={{ marginBottom: 8 }}>
插入图片/视频
</Button>
</Upload>
{/* Media Preview Area */}
{mediaList.length > 0 && (
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 10 }}>
{mediaList.map((item, index) => (
<div key={index} style={{ position: 'relative', width: 80, height: 80, border: '1px solid #ddd', borderRadius: 4, overflow: 'hidden' }}>
{item.type === 'video' ? (
<video src={item.url} style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
) : (
<img src={item.url} alt="preview" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
)}
</div>
))}
</div>
)}
<div data-color-mode="light">
<div style={{ marginBottom: 10 }}>
<Upload
beforeUpload={handleUpload}
showUploadList={false}
accept="image/*,video/*"
>
<Button icon={<UploadOutlined />} loading={uploading} size="small">
插入图片/视频
</Button>
</Upload>
</div>
<TextArea
rows={12}
placeholder="请详细描述您的问题...
支持 Markdown 语法:
**加粗**
# 标题
- 列表
[链接](url)
"
showCount
maxLength={10000}
style={{ fontFamily: 'monospace' }}
<MDEditor
value={content}
onChange={(val) => {
setContent(val);
form.setFieldsValue({ content: val });
}}
height={400}
previewOptions={{
rehypePlugins: [[rehypeKatex]],
remarkPlugins: [[remarkMath]],
}}
/>
</div>
</Form.Item>
@@ -238,7 +268,7 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" htmlType="submit" loading={loading} size="large">
立即发布
{isEditMode ? "保存修改" : "立即发布"}
</Button>
</div>
</Form.Item>
@@ -247,4 +277,4 @@ const CreateTopicModal = ({ visible, onClose, onSuccess }) => {
);
};
export default CreateTopicModal;
export default CreateTopicModal;