add forum
This commit is contained in:
419
src/views/ValueForum/components/CreatePostModal.js
Normal file
419
src/views/ValueForum/components/CreatePostModal.js
Normal file
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* 发帖模态框组件
|
||||
* 用于创建新帖子
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ModalCloseButton,
|
||||
Button,
|
||||
Input,
|
||||
Textarea,
|
||||
VStack,
|
||||
HStack,
|
||||
Text,
|
||||
Box,
|
||||
Image,
|
||||
IconButton,
|
||||
Tag,
|
||||
TagLabel,
|
||||
TagCloseButton,
|
||||
useToast,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
FormErrorMessage,
|
||||
} from '@chakra-ui/react';
|
||||
import { ImagePlus, X, Hash } from 'lucide-react';
|
||||
import { forumColors } from '@theme/forumTheme';
|
||||
import { createPost } from '@services/elasticsearchService';
|
||||
import { useAuth } from '@contexts/AuthContext';
|
||||
|
||||
const CreatePostModal = ({ isOpen, onClose, onPostCreated }) => {
|
||||
const toast = useToast();
|
||||
const { user } = useAuth();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
title: '',
|
||||
content: '',
|
||||
images: [],
|
||||
tags: [],
|
||||
category: '',
|
||||
});
|
||||
|
||||
const [currentTag, setCurrentTag] = useState('');
|
||||
const [errors, setErrors] = useState({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// 表单验证
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
|
||||
if (!formData.title.trim()) {
|
||||
newErrors.title = '请输入标题';
|
||||
} else if (formData.title.length > 100) {
|
||||
newErrors.title = '标题不能超过100个字符';
|
||||
}
|
||||
|
||||
if (!formData.content.trim()) {
|
||||
newErrors.content = '请输入内容';
|
||||
} else if (formData.content.length > 5000) {
|
||||
newErrors.content = '内容不能超过5000个字符';
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
// 处理图片上传
|
||||
const handleImageUpload = (e) => {
|
||||
const files = Array.from(e.target.files);
|
||||
|
||||
files.forEach((file) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
images: [...prev.images, reader.result],
|
||||
}));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
// 移除图片
|
||||
const removeImage = (index) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
images: prev.images.filter((_, i) => i !== index),
|
||||
}));
|
||||
};
|
||||
|
||||
// 添加标签
|
||||
const addTag = () => {
|
||||
if (currentTag.trim() && !formData.tags.includes(currentTag.trim())) {
|
||||
if (formData.tags.length >= 5) {
|
||||
toast({
|
||||
title: '标签数量已达上限',
|
||||
description: '最多只能添加5个标签',
|
||||
status: 'warning',
|
||||
duration: 2000,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
tags: [...prev.tags, currentTag.trim()],
|
||||
}));
|
||||
setCurrentTag('');
|
||||
}
|
||||
};
|
||||
|
||||
// 移除标签
|
||||
const removeTag = (tag) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
tags: prev.tags.filter((t) => t !== tag),
|
||||
}));
|
||||
};
|
||||
|
||||
// 提交帖子
|
||||
const handleSubmit = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
const postData = {
|
||||
...formData,
|
||||
author_id: user?.id || 'anonymous',
|
||||
author_name: user?.name || '匿名用户',
|
||||
author_avatar: user?.avatar || '',
|
||||
};
|
||||
|
||||
const newPost = await createPost(postData);
|
||||
|
||||
toast({
|
||||
title: '发布成功',
|
||||
description: '帖子已成功发布到论坛',
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
});
|
||||
|
||||
// 重置表单
|
||||
setFormData({
|
||||
title: '',
|
||||
content: '',
|
||||
images: [],
|
||||
tags: [],
|
||||
category: '',
|
||||
});
|
||||
|
||||
onClose();
|
||||
|
||||
// 通知父组件刷新
|
||||
if (onPostCreated) {
|
||||
onPostCreated(newPost);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('发布帖子失败:', error);
|
||||
toast({
|
||||
title: '发布失败',
|
||||
description: error.message || '发布帖子时出错,请稍后重试',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl">
|
||||
<ModalOverlay bg="blackAlpha.800" />
|
||||
<ModalContent
|
||||
bg={forumColors.background.elevated}
|
||||
borderColor={forumColors.border.gold}
|
||||
borderWidth="1px"
|
||||
maxH="90vh"
|
||||
>
|
||||
<ModalHeader
|
||||
color={forumColors.text.primary}
|
||||
borderBottomWidth="1px"
|
||||
borderBottomColor={forumColors.border.default}
|
||||
>
|
||||
<Text
|
||||
bgGradient={forumColors.text.goldGradient}
|
||||
bgClip="text"
|
||||
fontWeight="bold"
|
||||
fontSize="xl"
|
||||
>
|
||||
发布新帖
|
||||
</Text>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton color={forumColors.text.secondary} />
|
||||
|
||||
<ModalBody py="6" overflowY="auto">
|
||||
<VStack spacing="5" align="stretch">
|
||||
{/* 标题输入 */}
|
||||
<FormControl isInvalid={errors.title}>
|
||||
<FormLabel color={forumColors.text.secondary} fontSize="sm">
|
||||
标题
|
||||
</FormLabel>
|
||||
<Input
|
||||
placeholder="给你的帖子起个吸引人的标题..."
|
||||
value={formData.title}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({ ...prev, title: e.target.value }))
|
||||
}
|
||||
bg={forumColors.background.secondary}
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.default}
|
||||
color={forumColors.text.primary}
|
||||
_placeholder={{ color: forumColors.text.tertiary }}
|
||||
_hover={{ borderColor: forumColors.border.light }}
|
||||
_focus={{
|
||||
borderColor: forumColors.border.gold,
|
||||
boxShadow: `0 0 0 1px ${forumColors.border.goldGlow}`,
|
||||
}}
|
||||
/>
|
||||
<FormErrorMessage>{errors.title}</FormErrorMessage>
|
||||
</FormControl>
|
||||
|
||||
{/* 内容输入 */}
|
||||
<FormControl isInvalid={errors.content}>
|
||||
<FormLabel color={forumColors.text.secondary} fontSize="sm">
|
||||
内容
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
placeholder="分享你的投资见解、市场观点或交易心得..."
|
||||
value={formData.content}
|
||||
onChange={(e) =>
|
||||
setFormData((prev) => ({ ...prev, content: e.target.value }))
|
||||
}
|
||||
minH="200px"
|
||||
bg={forumColors.background.secondary}
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.default}
|
||||
color={forumColors.text.primary}
|
||||
_placeholder={{ color: forumColors.text.tertiary }}
|
||||
_hover={{ borderColor: forumColors.border.light }}
|
||||
_focus={{
|
||||
borderColor: forumColors.border.gold,
|
||||
boxShadow: `0 0 0 1px ${forumColors.border.goldGlow}`,
|
||||
}}
|
||||
resize="vertical"
|
||||
/>
|
||||
<FormErrorMessage>{errors.content}</FormErrorMessage>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={forumColors.text.muted}
|
||||
mt="2"
|
||||
textAlign="right"
|
||||
>
|
||||
{formData.content.length} / 5000
|
||||
</Text>
|
||||
</FormControl>
|
||||
|
||||
{/* 图片上传 */}
|
||||
<Box>
|
||||
<FormLabel color={forumColors.text.secondary} fontSize="sm">
|
||||
图片(最多9张)
|
||||
</FormLabel>
|
||||
|
||||
<HStack spacing="3" flexWrap="wrap">
|
||||
{formData.images.map((img, index) => (
|
||||
<Box key={index} position="relative" w="100px" h="100px">
|
||||
<Image
|
||||
src={img}
|
||||
alt={`预览 ${index + 1}`}
|
||||
w="100%"
|
||||
h="100%"
|
||||
objectFit="cover"
|
||||
borderRadius="md"
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.default}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<X size={14} />}
|
||||
size="xs"
|
||||
position="absolute"
|
||||
top="-2"
|
||||
right="-2"
|
||||
borderRadius="full"
|
||||
bg={forumColors.background.main}
|
||||
color={forumColors.text.primary}
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.gold}
|
||||
onClick={() => removeImage(index)}
|
||||
_hover={{ bg: forumColors.background.hover }}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{formData.images.length < 9 && (
|
||||
<Box
|
||||
as="label"
|
||||
w="100px"
|
||||
h="100px"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
bg={forumColors.background.secondary}
|
||||
border="2px dashed"
|
||||
borderColor={forumColors.border.default}
|
||||
borderRadius="md"
|
||||
cursor="pointer"
|
||||
_hover={{ borderColor: forumColors.border.gold }}
|
||||
>
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
multiple
|
||||
display="none"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
<ImagePlus size={24} color={forumColors.text.tertiary} />
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
</Box>
|
||||
|
||||
{/* 标签输入 */}
|
||||
<Box>
|
||||
<FormLabel color={forumColors.text.secondary} fontSize="sm">
|
||||
标签(最多5个)
|
||||
</FormLabel>
|
||||
|
||||
<HStack mb="3">
|
||||
<Input
|
||||
placeholder="输入标签后按回车"
|
||||
value={currentTag}
|
||||
onChange={(e) => setCurrentTag(e.target.value)}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addTag();
|
||||
}
|
||||
}}
|
||||
bg={forumColors.background.secondary}
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.default}
|
||||
color={forumColors.text.primary}
|
||||
_placeholder={{ color: forumColors.text.tertiary }}
|
||||
_focus={{
|
||||
borderColor: forumColors.border.gold,
|
||||
boxShadow: `0 0 0 1px ${forumColors.border.goldGlow}`,
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<Hash size={18} />}
|
||||
onClick={addTag}
|
||||
bg={forumColors.gradients.goldPrimary}
|
||||
color={forumColors.background.main}
|
||||
_hover={{ opacity: 0.9 }}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
<HStack spacing="2" flexWrap="wrap">
|
||||
{formData.tags.map((tag) => (
|
||||
<Tag
|
||||
key={tag}
|
||||
size="md"
|
||||
bg={forumColors.gradients.goldSubtle}
|
||||
color={forumColors.primary[500]}
|
||||
border="1px solid"
|
||||
borderColor={forumColors.border.gold}
|
||||
borderRadius="full"
|
||||
>
|
||||
<TagLabel>#{tag}</TagLabel>
|
||||
<TagCloseButton onClick={() => removeTag(tag)} />
|
||||
</Tag>
|
||||
))}
|
||||
</HStack>
|
||||
</Box>
|
||||
</VStack>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter
|
||||
borderTopWidth="1px"
|
||||
borderTopColor={forumColors.border.default}
|
||||
>
|
||||
<HStack spacing="3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onClose}
|
||||
color={forumColors.text.secondary}
|
||||
_hover={{ bg: forumColors.background.hover }}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
bg={forumColors.gradients.goldPrimary}
|
||||
color={forumColors.background.main}
|
||||
fontWeight="bold"
|
||||
onClick={handleSubmit}
|
||||
isLoading={isSubmitting}
|
||||
loadingText="发布中..."
|
||||
_hover={{
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: forumColors.shadows.goldHover,
|
||||
}}
|
||||
_active={{ transform: 'translateY(0)' }}
|
||||
>
|
||||
发布帖子
|
||||
</Button>
|
||||
</HStack>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreatePostModal;
|
||||
Reference in New Issue
Block a user