fix: 论坛样式优化

This commit is contained in:
Shu Guang 2025-04-19 02:09:57 +08:00
parent b7f33da8b5
commit fe7b58cb6d
5 changed files with 345 additions and 82 deletions

View File

@ -1,4 +1,4 @@
import { GithubOutlined, HeartFilled, WechatOutlined, MailOutlined } from '@ant-design/icons'; import { HeartFilled } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-components'; import { DefaultFooter } from '@ant-design/pro-components';
import React from 'react'; import React from 'react';
@ -16,7 +16,7 @@ const Footer: React.FC = () => {
copyright={ copyright={
<div> <div>
<div > <div >
Copyright © 2023-{currentYear} 西 Copyright © {currentYear} 西
</div> </div>
<div > <div >
Powered by <HeartFilled /> SunHe Powered by <HeartFilled /> SunHe
@ -31,12 +31,7 @@ const Footer: React.FC = () => {
href: 'https://www.sust.edu.cn/', href: 'https://www.sust.edu.cn/',
blankTarget: true, blankTarget: true,
}, },
{
key: 'github',
title: <GithubOutlined />,
href: 'https://github.com/shuguangnet',
blankTarget: true,
},
]} ]}
/> />

View File

@ -0,0 +1,62 @@
.forumContainer {
.searchBar {
background: #fff;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin-bottom: 16px;
}
.postItem {
transition: all 0.3s;
&:hover {
background-color: #fafafa;
}
.postTitle {
font-size: 18px;
font-weight: 500;
color: #1a1a1a;
&:hover {
color: #1890ff;
}
}
.postMeta {
color: #8c8c8c;
font-size: 14px;
}
.postStats {
color: #595959;
font-size: 14px;
.statItem {
display: inline-flex;
align-items: center;
gap: 4px;
&:hover {
color: #1890ff;
}
}
}
.coverImage {
border-radius: 8px;
object-fit: cover;
transition: transform 0.3s;
&:hover {
transform: scale(1.02);
}
}
}
.categoryTag {
border-radius: 12px;
padding: 2px 12px;
}
}

View File

@ -1,7 +1,14 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Card, List, Tag, Space, Input, Button, Select } from 'antd'; import { Card, List, Tag, Space, Input, Button, Select, Badge } from 'antd';
import { history } from '@umijs/max'; import { history } from '@umijs/max';
import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import {
SearchOutlined,
PlusOutlined,
EyeOutlined,
MessageOutlined,
LikeOutlined,
} from '@ant-design/icons';
import styles from './index.less';
const { Search } = Input; const { Search } = Input;
@ -9,6 +16,74 @@ const ForumList: React.FC = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [filter, setFilter] = useState('all'); const [filter, setFilter] = useState('all');
const mockPosts = [
{
id: 1,
title: '使用 GPT-4 进行高级数据分析的实践经验',
category: '技术讨论',
author: '数据专家',
createTime: '2024-01-15 14:30',
description: '分享在大规模数据集上使用 GPT-4 进行智能分析的经验,包括提示词工程、数据预处理技巧,以及如何提高分析准确率...',
views: 2150,
comments: 156,
likes: 342,
isTop: true,
cover: 'https://picsum.photos/272/153',
},
{
id: 2,
title: 'AI 辅助数据可视化最佳实践',
category: '经验分享',
author: '可视化工程师',
createTime: '2024-01-14 16:20',
description: '探讨如何利用 AI 技术自动生成数据可视化方案,包括图表类型选择、配色方案优化、以及交互设计的智能推荐...',
views: 1856,
comments: 89,
likes: 267,
isTop: false,
cover: 'https://picsum.photos/272/153?random=2',
},
{
id: 3,
title: '智能预测模型准确率问题求助',
category: '问题求助',
author: '数据新手',
createTime: '2024-01-13 09:45',
description: '在使用系统进行销售预测时发现准确率不够理想,数据预处理已经做了基础清洗,请问还有哪些方面需要优化?...',
views: 632,
comments: 42,
likes: 28,
isTop: false,
cover: null,
},
{
id: 4,
title: 'AIGC 在金融数据分析中的应用实践',
category: '技术讨论',
author: '金融分析师',
createTime: '2024-01-12 11:30',
description: '分享我们团队使用 AIGC 技术进行金融市场分析的经验,包括风险评估、趋势预测和投资建议生成的完整流程...',
views: 1967,
comments: 156,
likes: 420,
isTop: true,
cover: 'https://picsum.photos/272/153?random=4',
},
{
id: 5,
title: '大规模数据集的智能分析方法论',
category: '经验分享',
author: '资深数据科学家',
createTime: '2024-01-11 15:15',
description: '详细介绍如何处理和分析大规模数据集,包括数据清洗策略、特征工程技巧、模型选择以及结果验证方法...',
views: 2543,
comments: 189,
likes: 534,
isTop: false,
cover: 'https://picsum.photos/272/153?random=5',
},
];
const categories = [ const categories = [
{ value: 'all', label: '全部' }, { value: 'all', label: '全部' },
{ value: 'tech', label: '技术讨论' }, { value: 'tech', label: '技术讨论' },
@ -21,51 +96,67 @@ const ForumList: React.FC = () => {
}; };
return ( return (
<Card> <div className={styles.forumContainer}>
<div style={{ marginBottom: 16 }}> <Card className={styles.searchBar} bordered={false}>
<Space size="large"> <Space size="large" wrap>
<Search <Search
placeholder="搜索帖子" placeholder="搜索帖子标题或内容"
onSearch={handleSearch} onSearch={handleSearch}
style={{ width: 300 }} style={{ width: 300 }}
allowClear
/> />
<Select <Select
value={filter} value={filter}
onChange={setFilter} onChange={setFilter}
options={categories} options={categories}
style={{ width: 120 }} style={{ width: 120 }}
placeholder="选择分类"
/> />
<Button <Button
type="primary" type="primary"
icon={<PlusOutlined />} icon={<PlusOutlined />}
onClick={() => history.push('/forum/publish')} onClick={() => history.push('/forum/publish')}
size="large"
> >
</Button> </Button>
</Space> </Space>
</div> </Card>
<List <List
loading={loading}
itemLayout="vertical" itemLayout="vertical"
size="large" size="large"
pagination={{ pagination={{
pageSize: 10, pageSize: 10,
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => `${total} 条帖子`,
}} }}
dataSource={[]} // 这里需要接入实际数据 dataSource={mockPosts} // 使用模拟数据
renderItem={(item: any) => ( renderItem={(item) => (
<List.Item <List.Item
key={item.id} key={item.id}
className={styles.postItem}
actions={[ actions={[
<Space> <Space className={styles.postStats}>
<span> {item.views}</span> <span className={styles.statItem}>
<span> {item.comments}</span> <EyeOutlined /> {item.views}
<span> {item.likes}</span> </span>
<span className={styles.statItem}>
<MessageOutlined /> {item.comments}
</span>
<span className={styles.statItem}>
<LikeOutlined /> {item.likes}
</span>
</Space>, </Space>,
]} ]}
extra={ extra={
item.cover && ( item.cover && (
<img <img
className={styles.coverImage}
width={272} width={272}
height={153}
alt="cover" alt="cover"
src={item.cover} src={item.cover}
/> />
@ -74,23 +165,35 @@ const ForumList: React.FC = () => {
> >
<List.Item.Meta <List.Item.Meta
title={ title={
<Space> <Space size="middle" align="center">
<a onClick={() => history.push(`/forum/detail/${item.id}`)}>{item.title}</a> <a
<Tag color="blue">{item.category}</Tag> className={styles.postTitle}
onClick={() => history.push(`/forum/detail/${item.id}`)}
>
{item.title}
</a>
<Tag className={styles.categoryTag} color="blue">
{item.category}
</Tag>
{item.isTop && (
<Badge color="red" text="置顶" />
)}
</Space> </Space>
} }
description={ description={
<Space> <Space className={styles.postMeta} size="middle">
<span>{item.author}</span> <span>{item.author}</span>
<span>{item.createTime}</span> <span> {item.createTime}</span>
</Space> </Space>
} }
/> />
{item.description} <div className={styles.postContent}>
{item.description}
</div>
</List.Item> </List.Item>
)} )}
/> />
</Card> </div>
); );
}; };

View File

@ -0,0 +1,44 @@
.publishContainer {
max-width: 800px;
margin: 0 auto;
padding: 24px;
.publishCard {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
:global {
.ant-card-head {
border-bottom: 1px solid #f0f0f0;
padding: 16px 24px;
.ant-card-head-title {
font-size: 18px;
font-weight: 500;
}
}
.ant-card-body {
padding: 24px;
}
}
}
.formLabel {
font-weight: 500;
font-size: 15px;
}
.uploadHint {
color: #666;
font-size: 13px;
margin-top: 8px;
}
.buttonGroup {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
}

View File

@ -1,8 +1,9 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Card, Form, Input, Button, Upload, Select, message } from 'antd'; import { Card, Form, Input, Button, Upload, Select, message, Space, Alert } from 'antd';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined, InfoCircleOutlined } from '@ant-design/icons';
import type { UploadFile } from 'antd/es/upload/interface'; import type { UploadFile } from 'antd/es/upload/interface';
import { history } from '@umijs/max'; import { history } from '@umijs/max';
import styles from './index.less';
const { TextArea } = Input; const { TextArea } = Input;
@ -11,6 +12,12 @@ const ForumPublish: React.FC = () => {
const [fileList, setFileList] = useState<UploadFile[]>([]); const [fileList, setFileList] = useState<UploadFile[]>([]);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const categories = [
{ value: 'tech', label: '技术讨论', description: '分享技术经验和最佳实践' },
{ value: 'share', label: '经验分享', description: '分享数据分析案例和心得' },
{ value: 'question', label: '问题求助', description: '寻求技术支持和解决方案' },
];
const handleSubmit = async (values: any) => { const handleSubmit = async (values: any) => {
setSubmitting(true); setSubmitting(true);
try { try {
@ -25,61 +32,113 @@ const ForumPublish: React.FC = () => {
}; };
return ( return (
<Card title="发布帖子"> <div className={styles.publishContainer}>
<Form <Card title="发布帖子" className={styles.publishCard}>
form={form} <Alert
layout="vertical" message="发帖提示"
onFinish={handleSubmit} description="请确保发布的内容与 AIGC 数据分析相关,并遵守社区规范。"
> type="info"
<Form.Item showIcon
name="title" style={{ marginBottom: 24 }}
label="标题" />
rules={[{ required: true, message: '请输入标题' }]}
>
<Input placeholder="请输入帖子标题" />
</Form.Item>
<Form.Item <Form
name="category" form={form}
label="分类" layout="vertical"
rules={[{ required: true, message: '请选择分类' }]} onFinish={handleSubmit}
requiredMark="optional"
> >
<Select> <Form.Item
<Select.Option value="tech"></Select.Option> name="title"
<Select.Option value="share"></Select.Option> label={<span className={styles.formLabel}></span>}
<Select.Option value="question"></Select.Option> rules={[
</Select> { required: true, message: '请输入标题' },
</Form.Item> { max: 100, message: '标题最多100个字符' }
]}
<Form.Item
name="content"
label="内容"
rules={[{ required: true, message: '请输入内容' }]}
>
<TextArea rows={8} placeholder="请输入帖子内容..." />
</Form.Item>
<Form.Item label="封面图">
<Upload
listType="picture-card"
fileList={fileList}
onChange={({ fileList }) => setFileList(fileList)}
beforeUpload={() => false}
> >
{fileList.length < 1 && <PlusOutlined />} <Input
</Upload> placeholder="请输入一个简洁明了的标题"
</Form.Item> showCount
maxLength={100}
/>
</Form.Item>
<Form.Item> <Form.Item
<Button type="primary" htmlType="submit" loading={submitting}> name="category"
label={<span className={styles.formLabel}></span>}
</Button> rules={[{ required: true, message: '请选择分类' }]}
<Button style={{ marginLeft: 8 }} onClick={() => history.back()}> tooltip={{
title: '选择合适的分类有助于其他用户更好地找到您的帖子',
</Button> icon: <InfoCircleOutlined />
</Form.Item> }}
</Form> >
</Card> <Select placeholder="请选择帖子分类">
{categories.map(cat => (
<Select.Option key={cat.value} value={cat.value}>
<Space>
{cat.label}
<span style={{ color: '#999', fontSize: '12px' }}>
({cat.description})
</span>
</Space>
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="content"
label={<span className={styles.formLabel}></span>}
rules={[
{ required: true, message: '请输入内容' },
{ min: 20, message: '内容至少20个字符' }
]}
>
<TextArea
rows={12}
placeholder="请详细描述您要分享的内容..."
showCount
maxLength={5000}
/>
</Form.Item>
<Form.Item
label={<span className={styles.formLabel}></span>}
extra={<div className={styles.uploadHint}> jpgpng 800x450px</div>}
>
<Upload
listType="picture-card"
fileList={fileList}
onChange={({ fileList }) => setFileList(fileList)}
beforeUpload={(file) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('只能上传图片文件!');
}
return false;
}}
maxCount={1}
>
{fileList.length < 1 && (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}></div>
</div>
)}
</Upload>
</Form.Item>
<div className={styles.buttonGroup}>
<Button onClick={() => history.back()}>
</Button>
<Button type="primary" htmlType="submit" loading={submitting}>
</Button>
</div>
</Form>
</Card>
</div>
); );
}; };