完成Work组件的界面和一些小修改
> 1. 修改优化路由守卫
> 2. 去除拦截器中的调试信息
> 3. 修改头部导航条下拉菜单的样式增加图标。
> 4. work组件现在使用mock数据
Change-Id: Ic602a35bb02e645a0d5253c5cbd12a68d70bfb33
diff --git a/src/feature/work/WorkComponents.tsx b/src/feature/work/WorkComponents.tsx
new file mode 100644
index 0000000..64ef37a
--- /dev/null
+++ b/src/feature/work/WorkComponents.tsx
@@ -0,0 +1,368 @@
+import React, { useState } from 'react';
+import { Card, Typography, Tag, Flex, Table, Collapse, List, Spin, Alert, Button, Input, Form, message } from 'antd';
+import { EditOutlined, SendOutlined } from '@ant-design/icons';
+import ReactMarkdown from 'react-markdown';
+import type { ColumnsType } from 'antd/es/table';
+import type { PaginationConfig } from 'antd/es/pagination';
+import type { FormInstance } from 'antd/es/form';
+import type { Comment, Version, User, HistoryUser } from './types';
+import { parseUploadSize } from './types';
+
+const { Title, Paragraph } = Typography;
+const { Panel } = Collapse;
+const { TextArea } = Input;
+
+// 作品封面组件
+export const ArtworkCover: React.FC<{ coverUrl: string }> = ({ coverUrl }) => (
+ <Card cover={<img alt="作品封面" src={coverUrl} style={{ height: 250, objectFit: 'cover' }} />} />
+);
+
+// 当前做种用户组件
+export const CurrentSeedingUsers: React.FC<{ users: User[] }> = ({ users }) => (
+ <Card>
+ <Title level={4} style={{ marginBottom: 12 }}>当前做种用户</Title>
+ <Flex wrap="wrap" gap={8}>
+ {users.map((user) => (
+ <Tag key={user.userId} color="green">{user.username}</Tag>
+ ))}
+ </Flex>
+ </Card>
+);
+
+// 历史做种用户组件
+export const HistorySeedingUsers: React.FC<{ users: HistoryUser[] }> = ({ users }) => {
+ const sortedUsers = [...users].sort((a, b) => parseUploadSize(b.uploadTotal) - parseUploadSize(a.uploadTotal));
+
+ const columns: ColumnsType<HistoryUser> = [
+ { title: '用户名', dataIndex: 'username', key: 'username' },
+ {
+ title: '上传总量',
+ dataIndex: 'uploadTotal',
+ key: 'uploadTotal',
+ sorter: (a: HistoryUser, b: HistoryUser) => parseUploadSize(a.uploadTotal) - parseUploadSize(b.uploadTotal),
+ },
+ ];
+
+ return (
+ <Card>
+ <Title level={4} style={{ marginBottom: 12 }}>历史做种用户</Title>
+ <Table columns={columns} dataSource={sortedUsers} rowKey="username" pagination={false} size="small" />
+ </Card>
+ );
+};
+
+// 作品描述组件
+export const ArtworkDescription: React.FC<{
+ name: string;
+ author: string;
+ category: string;
+ description: string;
+ isAuthor?: boolean;
+ onEdit?: () => void;
+}> = ({ name, author, category, description, isAuthor = false, onEdit }) => (
+ <Card style={{ marginBottom: 20 }}>
+ <Flex justify="space-between" align="flex-start">
+ <div style={{ flex: 1 }}>
+ <Title level={2} style={{ marginBottom: 8 }}>{name}</Title>
+ <Paragraph style={{ marginBottom: 8, fontSize: 16 }}>
+ <strong>作者:</strong>{author}
+ </Paragraph>
+ <div style={{ marginBottom: 16 }}>
+ <Tag color="blue">{category}</Tag>
+ </div>
+ <div style={{ lineHeight: 1.6 }}>
+ <ReactMarkdown>{description}</ReactMarkdown>
+ </div>
+ </div>
+ {isAuthor && (
+ <Button type="primary" icon={<EditOutlined />} onClick={onEdit} style={{ marginLeft: 16 }}>
+ 编辑作品
+ </Button>
+ )}
+ </Flex>
+ </Card>
+);
+
+// 版本历史组件
+export const VersionHistory: React.FC<{ versions: Version[] }> = ({ versions }) => (
+ <Card title="版本历史" style={{ marginBottom: 20 }}>
+ <Collapse>
+ {versions.map((version, index) => (
+ <Panel
+ header={`版本 ${version.version}`}
+ key={`version-${index}`}
+ extra={<Tag color="blue">v{version.version}</Tag>}
+ >
+ <div style={{ marginBottom: 16 }}>
+ <strong>版本描述:</strong>
+ <div style={{ marginTop: 8, lineHeight: 1.6 }}>
+ <ReactMarkdown>{version.versionDescription}</ReactMarkdown>
+ </div>
+ </div>
+ <div>
+ <strong>种子文件:</strong>
+ <a href={version.seedFile} target="_blank" rel="noopener noreferrer" style={{ marginLeft: 8 }}>
+ 下载链接
+ </a>
+ </div>
+ </Panel>
+ ))}
+ </Collapse>
+ </Card>
+);
+
+// 评论项组件(递归)
+export const CommentItem: React.FC<{
+ comment: Comment;
+ level?: number;
+ onReply?: (parentId: string, parentAuthor: string) => void;
+}> = ({ comment, level = 0, onReply }) => (
+ <div style={{ marginLeft: level * 20 }}>
+ <div style={{ marginBottom: 8 }}>
+ <Paragraph style={{ marginBottom: 4 }}>
+ <strong>{comment.author}:</strong>{comment.content}
+ </Paragraph>
+ <div style={{ fontSize: 12, color: '#999', marginBottom: 4 }}>
+ {comment.createdAt && <span style={{ marginRight: 16 }}>{comment.createdAt}</span>}
+ {onReply && (
+ <Button
+ type="link"
+ size="small"
+ style={{ padding: 0, height: 'auto', fontSize: 12 }}
+ onClick={() => onReply(comment.id || comment.author, comment.author)}
+ >
+ 回复
+ </Button>
+ )}
+ </div>
+ </div>
+ {comment.child && comment.child.length > 0 && (
+ <div style={{
+ borderLeft: level === 0 ? '2px solid #f0f0f0' : 'none',
+ paddingLeft: level === 0 ? 12 : 0
+ }}>
+ {comment.child.map((child, index) => (
+ <CommentItem
+ key={child.id || `child-${level}-${index}`}
+ comment={child}
+ level={level + 1}
+ onReply={onReply}
+ />
+ ))}
+ </div>
+ )}
+ </div>
+);
+
+// 发表评论组件
+export const CommentForm: React.FC<{
+ loading?: boolean;
+ onSubmit: (content: string, parentId?: string) => void;
+ replyTo?: { id: string; author: string } | null;
+ onCancelReply?: () => void;
+}> = ({ loading = false, onSubmit, replyTo, onCancelReply }) => {
+ const [form]: [FormInstance] = Form.useForm();
+ const [content, setContent] = useState('');
+
+ const handleSubmit = (): void => {
+ if (!content.trim()) {
+ message.warning('请输入评论内容');
+ return;
+ }
+ onSubmit(content.trim(), replyTo?.id);
+ setContent('');
+ form.resetFields();
+ };
+
+ const placeholder = replyTo ? `回复 @${replyTo.author}:` : "发表你的看法...";
+
+ return (
+ <Card
+ size="small"
+ style={{ marginBottom: 16 }}
+ title={replyTo ? (
+ <div style={{ fontSize: 14 }}>
+ <span>回复 @{replyTo.author}</span>
+ <Button type="link" size="small" onClick={onCancelReply} style={{ padding: '0 0 0 8px', fontSize: 12 }}>
+ 取消
+ </Button>
+ </div>
+ ) : undefined}
+ >
+ <Form form={form} layout="vertical">
+ <Form.Item>
+ <TextArea
+ value={content}
+ onChange={(e) => setContent(e.target.value)}
+ placeholder={placeholder}
+ rows={3}
+ maxLength={500}
+ showCount
+ />
+ </Form.Item>
+ <Form.Item style={{ marginBottom: 0 }}>
+ <Flex justify="flex-end" gap={8}>
+ {replyTo && <Button onClick={onCancelReply}>取消</Button>}
+ <Button
+ type="primary"
+ icon={<SendOutlined />}
+ loading={loading}
+ onClick={handleSubmit}
+ disabled={!content.trim()}
+ >
+ {replyTo ? '发表回复' : '发表评论'}
+ </Button>
+ </Flex>
+ </Form.Item>
+ </Form>
+ </Card>
+ );
+};
+
+// 评论区组件
+export const CommentSection: React.FC<{
+ comments: Comment[];
+ total: number;
+ loading?: boolean;
+ error?: string | null;
+ addCommentLoading?: boolean;
+ onPageChange: (page: number, pageSize: number) => void;
+ onAddComment: (content: string, parentId?: string) => void;
+ currentPage: number;
+ pageSize: number;
+}> = ({ comments, total, loading, error, addCommentLoading, onPageChange, onAddComment, currentPage, pageSize }) => {
+ const [replyTo, setReplyTo] = useState<{ id: string; author: string } | null>(null);
+
+ const handleReply = (parentId: string, parentAuthor: string): void => {
+ setReplyTo({ id: parentId, author: parentAuthor });
+ };
+
+ const handleCancelReply = (): void => {
+ setReplyTo(null);
+ };
+
+ const handleSubmitComment = (content: string, parentId?: string): void => {
+ onAddComment(content, parentId);
+ setReplyTo(null);
+ };
+
+ const paginationConfig: PaginationConfig = {
+ current: currentPage,
+ pageSize,
+ total,
+ showSizeChanger: true,
+ showQuickJumper: true,
+ showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条评论`,
+ pageSizeOptions: ['5', '10', '20'],
+ onChange: onPageChange,
+ onShowSizeChange: onPageChange,
+ };
+
+ return (
+ <Card title={`评论 (${total})`} style={{ marginBottom: 20 }}>
+ <CommentForm
+ loading={addCommentLoading}
+ onSubmit={handleSubmitComment}
+ replyTo={replyTo}
+ onCancelReply={handleCancelReply}
+ />
+
+ {error ? (
+ <Alert message="加载评论失败" description={error} type="error" showIcon />
+ ) : loading ? (
+ <Flex justify="center" align="center" style={{ minHeight: 200 }}>
+ <Spin size="large" />
+ </Flex>
+ ) : comments.length > 0 ? (
+ <List
+ dataSource={comments}
+ pagination={paginationConfig}
+ renderItem={(comment, index) => (
+ <List.Item
+ key={comment.id || `comment-${index}`}
+ style={{ border: 'none', padding: '16px 0', borderBottom: '1px solid #f0f0f0' }}
+ >
+ <CommentItem comment={comment} onReply={handleReply} />
+ </List.Item>
+ )}
+ />
+ ) : (
+ <Paragraph style={{ textAlign: 'center', color: '#999', margin: '20px 0' }}>
+ 暂无评论,来发表第一条评论吧!
+ </Paragraph>
+ )}
+ </Card>
+ );
+};
+
+// 侧边栏组合组件
+export const Sidebar: React.FC<{
+ coverUrl: string;
+ currentUsers: User[];
+ historyUsers: HistoryUser[];
+ loading?: boolean;
+ error?: string | null;
+}> = ({ coverUrl, currentUsers, historyUsers, loading, error }) => (
+ <Flex flex={1} vertical gap={20}>
+ <ArtworkCover coverUrl={coverUrl} />
+ {loading ? (
+ <Flex justify="center" align="center" style={{ minHeight: 200 }}>
+ <Spin size="large" />
+ </Flex>
+ ) : error ? (
+ <Alert message="加载用户信息失败" description={error} type="error" showIcon />
+ ) : (
+ <>
+ <CurrentSeedingUsers users={currentUsers} />
+ <HistorySeedingUsers users={historyUsers} />
+ </>
+ )}
+ </Flex>
+);
+
+// 主内容区组合组件
+export const MainContent: React.FC<{
+ artworkName: string;
+ author: string;
+ category: string;
+ description: string;
+ versions: Version[];
+ comments: Comment[];
+ commentsTotal: number;
+ commentsLoading?: boolean;
+ commentsError?: string | null;
+ addCommentLoading?: boolean;
+ onCommentsPageChange: (page: number, pageSize: number) => void;
+ onAddComment: (content: string, parentId?: string) => void;
+ currentPage: number;
+ pageSize: number;
+ isAuthor?: boolean;
+ onEditArtwork?: () => void;
+}> = ({
+ artworkName, author, category, description, versions, comments, commentsTotal,
+ commentsLoading, commentsError, addCommentLoading, onCommentsPageChange, onAddComment,
+ currentPage, pageSize, isAuthor, onEditArtwork
+}) => (
+ <Flex flex={4} vertical>
+ <ArtworkDescription
+ name={artworkName}
+ author={author}
+ category={category}
+ description={description}
+ isAuthor={isAuthor}
+ onEdit={onEditArtwork}
+ />
+ <VersionHistory versions={versions} />
+ <CommentSection
+ comments={comments}
+ total={commentsTotal}
+ loading={commentsLoading}
+ error={commentsError}
+ addCommentLoading={addCommentLoading}
+ onPageChange={onCommentsPageChange}
+ onAddComment={onAddComment}
+ currentPage={currentPage}
+ pageSize={pageSize}
+ />
+ </Flex>
+ );
\ No newline at end of file