完成Work组件的界面和一些小修改
> 1. 修改优化路由守卫
> 2. 去除拦截器中的调试信息
> 3. 修改头部导航条下拉菜单的样式增加图标。
> 4. work组件现在使用mock数据
Change-Id: Ic602a35bb02e645a0d5253c5cbd12a68d70bfb33
diff --git a/src/feature/work/CreatWorkComponents.tsx b/src/feature/work/CreatWorkComponents.tsx
new file mode 100644
index 0000000..12766ee
--- /dev/null
+++ b/src/feature/work/CreatWorkComponents.tsx
@@ -0,0 +1,545 @@
+import React, { useState, useCallback } from 'react';
+import {
+ Form, Input, Select, Upload, Button, Card, message, Space, Modal,
+ Divider, Row, Col, Typography, Alert, List, Tag,
+ type FormInstance
+} from 'antd';
+import {
+ PlusOutlined, InboxOutlined, DeleteOutlined, EditOutlined,
+ CheckCircleOutlined, SendOutlined, FileTextOutlined
+} from '@ant-design/icons';
+import ReactMarkdown from 'react-markdown';
+import type {
+ UploadFile, UploadProps, UploadChangeParam, RcFile,
+} from 'antd/es/upload';
+import { type StepFormProps, type BasicInfo, ARTWORK_CATEGORIES, type VersionFormData } from './types';
+import { getBase64 } from './utils';
+
+const { TextArea } = Input;
+const { Dragger } = Upload;
+const { Title, Text, Paragraph } = Typography;
+
+// ==================== 描述组件 ====================
+interface DescriptionsProps {
+ bordered?: boolean;
+ column?: number;
+ children: React.ReactNode;
+}
+
+interface DescriptionsItemProps {
+ label: string;
+ children: React.ReactNode;
+}
+
+export const Descriptions: React.FC<DescriptionsProps> & {
+ Item: React.FC<DescriptionsItemProps>;
+} = ({ children }) => {
+ return <div style={{ marginBottom: 16 }}>{children}</div>;
+};
+
+Descriptions.Item = ({ label, children }: DescriptionsItemProps) => {
+ return (
+ <div style={{ marginBottom: 12 }}>
+ <Text strong style={{ marginRight: 16 }}>{label}:</Text>
+ <span>{children}</span>
+ </div>
+ );
+};
+
+// ==================== 步骤1:基础信息 ====================
+export const BasicInfoStep: React.FC<StepFormProps> = ({ data, onUpdate, onNext }) => {
+ const [form]: [FormInstance<BasicInfo>] = Form.useForm<BasicInfo>();
+ const [previewMode, setPreviewMode] = useState<boolean>(false);
+
+ const handleFinish = useCallback((values: BasicInfo) => {
+ onUpdate('basicInfo', values);
+ onNext?.();
+ }, [onUpdate, onNext]);
+
+ const handlePreview = useCallback(() => {
+ form.validateFields().then((values) => {
+ onUpdate('basicInfo', values);
+ setPreviewMode(true);
+ }).catch(() => {
+ message.error('请先填写完整信息');
+ });
+ }, [form, onUpdate]);
+
+ return (
+ <>
+ <Card>
+ <Form form={form} layout="vertical" initialValues={data.basicInfo} onFinish={handleFinish} autoComplete="off">
+ <Form.Item
+ label="作品名称"
+ name="artworkName"
+ rules={[
+ { required: true, message: '请输入作品名称' },
+ { min: 2, message: '作品名称至少2个字符' },
+ { max: 50, message: '作品名称最多50个字符' },
+ ]}
+ >
+ <Input placeholder="请输入作品名称,例如:未来城市概念设计" size="large" showCount maxLength={50} />
+ </Form.Item>
+
+ <Form.Item label="作品分类" name="artworkCategory" rules={[{ required: true, message: '请选择作品分类' }]}>
+ <Select placeholder="请选择作品分类" size="large" options={ARTWORK_CATEGORIES} />
+ </Form.Item>
+
+ <Form.Item
+ label="作品描述"
+ name="artworkDescription"
+ rules={[
+ { required: true, message: '请输入作品描述' },
+ { min: 20, message: '作品描述至少20个字符' },
+ { max: 2000, message: '作品描述最多2000个字符' },
+ ]}
+ extra="支持 Markdown 格式,可以使用 # 标题、**粗体**、*斜体* 等格式"
+ >
+ <TextArea
+ placeholder="请详细描述你的作品,包括创作理念、技术特点、使用说明等"
+ rows={10}
+ showCount
+ maxLength={2000}
+ />
+ </Form.Item>
+
+ <Form.Item>
+ <Space size="middle">
+ <Button type="primary" htmlType="submit" size="large">下一步</Button>
+ <Button onClick={handlePreview} size="large">预览描述</Button>
+ </Space>
+ </Form.Item>
+ </Form>
+ </Card>
+
+ <Modal title="作品描述预览" open={previewMode} onCancel={() => setPreviewMode(false)} footer={null} width={800}>
+ <div style={{ maxHeight: '60vh', overflow: 'auto' }}>
+ <ReactMarkdown>{form.getFieldValue('artworkDescription') || ''}</ReactMarkdown>
+ </div>
+ </Modal>
+ </>
+ );
+};
+
+// ==================== 步骤2:封面上传 ====================
+export const CoverUploadStep: React.FC<StepFormProps> = ({ data, onUpdate, onNext, onPrev }) => {
+ const [fileList, setFileList] = useState<UploadFile[]>(data.coverInfo.coverFile ? [data.coverInfo.coverFile] : []);
+ const [previewImage, setPreviewImage] = useState<string>('');
+ const [previewOpen, setPreviewOpen] = useState<boolean>(false);
+
+ const handleChange: UploadProps['onChange'] = useCallback((info: UploadChangeParam<UploadFile>) => {
+ const { fileList: newFileList } = info;
+ setFileList(newFileList);
+ onUpdate('coverInfo', { coverFile: newFileList[0] });
+ }, [onUpdate]);
+
+ const handlePreview = useCallback(async (file: UploadFile): Promise<void> => {
+ if (!file.url && !file.preview) {
+ file.preview = await getBase64(file.originFileObj as RcFile);
+ }
+ setPreviewImage(file.url || (file.preview as string));
+ setPreviewOpen(true);
+ }, []);
+
+ const beforeUpload = useCallback((file: RcFile): boolean => {
+ const isImage = file.type.startsWith('image/');
+ if (!isImage) {
+ message.error('只能上传图片文件!');
+ return false;
+ }
+ const isLt5M = file.size / 1024 / 1024 < 5;
+ if (!isLt5M) {
+ message.error('图片大小不能超过 5MB!');
+ return false;
+ }
+ return false;
+ }, []);
+
+ const handleNext = useCallback((): void => {
+ if (fileList.length === 0) {
+ message.error('请上传作品封面');
+ return;
+ }
+ onNext?.();
+ }, [fileList, onNext]);
+
+ return (
+ <Card>
+ <Alert
+ message="封面图片要求"
+ description={
+ <ul style={{ marginBottom: 0, paddingLeft: 20 }}>
+ <li>图片格式:JPG、PNG、GIF</li>
+ <li>图片大小:不超过 5MB</li>
+ <li>建议尺寸:宽高比 3:4,最小分辨率 600x800</li>
+ <li>内容要求:清晰展示作品特色,避免模糊或像素化</li>
+ </ul>
+ }
+ type="info"
+ showIcon
+ style={{ marginBottom: 24 }}
+ />
+
+ <Upload
+ listType="picture-card"
+ fileList={fileList}
+ onChange={handleChange}
+ onPreview={handlePreview}
+ beforeUpload={beforeUpload}
+ maxCount={1}
+ accept="image/*"
+ >
+ {fileList.length === 0 && (
+ <div>
+ <PlusOutlined />
+ <div style={{ marginTop: 8 }}>上传封面</div>
+ </div>
+ )}
+ </Upload>
+
+ <Modal open={previewOpen} title="封面预览" footer={null} onCancel={() => setPreviewOpen(false)}>
+ <img alt="封面预览" style={{ width: '100%' }} src={previewImage} />
+ </Modal>
+
+ <Divider />
+ <Space size="middle">
+ <Button onClick={onPrev} size="large">上一步</Button>
+ <Button type="primary" onClick={handleNext} size="large">下一步</Button>
+ </Space>
+ </Card>
+ );
+};
+
+// ==================== 版本管理相关组件 ====================
+const VersionItem: React.FC<{
+ version: VersionFormData;
+ index: number;
+ onEdit: (index: number) => void;
+ onDelete: (index: number) => void;
+}> = ({ version, index, onEdit, onDelete }) => {
+ return (
+ <Row align="middle">
+ <Col span={20}>
+ <Space direction="vertical" style={{ width: '100%' }}>
+ <Space>
+ <Tag color="blue">v{version.version}</Tag>
+ <Text strong>{version.versionDescription}</Text>
+ </Space>
+ {version.seedFile && (
+ <Space>
+ <FileTextOutlined />
+ <Text type="secondary">{version.seedFile.name}</Text>
+ </Space>
+ )}
+ </Space>
+ </Col>
+ <Col span={4} style={{ textAlign: 'right' }}>
+ <Space>
+ <Button type="text" icon={<EditOutlined />} onClick={() => onEdit(index)} />
+ <Button type="text" danger icon={<DeleteOutlined />} onClick={() => onDelete(index)} />
+ </Space>
+ </Col>
+ </Row>
+ );
+};
+
+const VersionEditForm: React.FC<{
+ form: FormInstance<VersionFormData>;
+ version: VersionFormData;
+ onSave: () => void;
+ onCancel: () => void;
+ onFileChange: (file: UploadFile | undefined) => void;
+}> = ({ form, version, onSave, onCancel, onFileChange }) => {
+ const beforeUpload = useCallback((file: RcFile): boolean => {
+ const isLt100M = file.size / 1024 / 1024 < 100;
+ if (!isLt100M) {
+ message.error('种子文件大小不能超过 100MB!');
+ return false;
+ }
+ return false;
+ }, []);
+
+ return (
+ <Form form={form} layout="vertical" initialValues={version}>
+ <Row gutter={16}>
+ <Col span={6}>
+ <Form.Item label="版本号" name="version" rules={[{ required: true, message: '请输入版本号' }]}>
+ <Input placeholder="例如:1.0" />
+ </Form.Item>
+ </Col>
+ <Col span={18}>
+ <Form.Item
+ label="版本描述"
+ name="versionDescription"
+ rules={[
+ { required: true, message: '请输入版本描述' },
+ { min: 10, message: '版本描述至少10个字符' },
+ ]}
+ >
+ <TextArea placeholder="描述此版本的更新内容、新增功能等" rows={3} showCount maxLength={500} />
+ </Form.Item>
+ </Col>
+ </Row>
+
+ <Form.Item label="种子文件">
+ <Dragger
+ maxCount={1}
+ beforeUpload={beforeUpload}
+ fileList={version.seedFile ? [version.seedFile] : []}
+ onChange={({ fileList }) => onFileChange(fileList[0])}
+ onRemove={() => onFileChange(undefined)}
+ >
+ <p className="ant-upload-drag-icon"><InboxOutlined /></p>
+ <p className="ant-upload-text">点击或拖拽文件到此区域上传</p>
+ <p className="ant-upload-hint">支持单个文件上传,文件大小不超过 100MB</p>
+ </Dragger>
+ </Form.Item>
+
+ <Space>
+ <Button type="primary" onClick={onSave}>保存</Button>
+ <Button onClick={onCancel}>取消</Button>
+ </Space>
+ </Form>
+ );
+};
+
+// ==================== 步骤3:版本管理 ====================
+export const VersionManagementStep: React.FC<StepFormProps> = ({ data, onUpdate, onNext, onPrev }) => {
+ const [versions, setVersions] = useState<VersionFormData[]>(
+ data.versions.length > 0 ? data.versions : [{ version: '1.0', versionDescription: '', seedFile: undefined }]
+ );
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
+ const [form]: [FormInstance<VersionFormData>] = Form.useForm<VersionFormData>();
+
+ const handleAddVersion = useCallback(() => {
+ const newVersion: VersionFormData = {
+ version: `${versions.length + 1}.0`,
+ versionDescription: '',
+ seedFile: undefined,
+ };
+ setVersions([...versions, newVersion]);
+ setEditingIndex(versions.length);
+ }, [versions]);
+
+ const handleSaveVersion = useCallback((index: number) => {
+ form.validateFields().then((values) => {
+ const newVersions = [...versions];
+ newVersions[index] = { ...newVersions[index], ...values };
+ setVersions(newVersions);
+ setEditingIndex(null);
+ form.resetFields();
+ message.success('版本信息已保存');
+ }).catch(() => {
+ message.error('请完整填写版本信息');
+ });
+ }, [form, versions]);
+
+ const handleDeleteVersion = useCallback((index: number) => {
+ Modal.confirm({
+ title: '确认删除',
+ content: '确定要删除这个版本吗?',
+ onOk: () => {
+ const newVersions = versions.filter((_, i) => i !== index);
+ setVersions(newVersions);
+ if (editingIndex === index) {
+ setEditingIndex(null);
+ }
+ },
+ });
+ }, [versions, editingIndex]);
+
+ const handleFileChange = useCallback((index: number, file: UploadFile | undefined) => {
+ const newVersions = [...versions];
+ newVersions[index].seedFile = file;
+ setVersions(newVersions);
+ }, [versions]);
+
+ const handleNext = useCallback(() => {
+ if (versions.length === 0) {
+ message.error('至少需要添加一个版本');
+ return;
+ }
+
+ const incompleteVersion = versions.find((v, index) =>
+ !v.version || !v.versionDescription || !v.seedFile || index === editingIndex
+ );
+
+ if (incompleteVersion) {
+ message.error('请完成所有版本的信息填写');
+ return;
+ }
+
+ onUpdate('versions', versions);
+ onNext?.();
+ }, [versions, editingIndex, onUpdate, onNext]);
+
+ return (
+ <Card>
+ <div style={{ marginBottom: 16 }}>
+ <Title level={4}>版本列表</Title>
+ <Paragraph type="secondary">每个版本需要包含版本号、版本描述和种子文件</Paragraph>
+ </div>
+
+ <List
+ dataSource={versions}
+ renderItem={(version, index) => (
+ <List.Item
+ key={index}
+ style={{
+ background: editingIndex === index ? '#fafafa' : 'transparent',
+ padding: 16,
+ marginBottom: 16,
+ border: '1px solid #f0f0f0',
+ borderRadius: 8,
+ }}
+ >
+ {editingIndex === index ? (
+ <VersionEditForm
+ form={form}
+ version={version}
+ onSave={() => handleSaveVersion(index)}
+ onCancel={() => setEditingIndex(null)}
+ onFileChange={(file) => handleFileChange(index, file)}
+ />
+ ) : (
+ <VersionItem
+ version={version}
+ index={index}
+ onEdit={(idx) => {
+ setEditingIndex(idx);
+ form.setFieldsValue(version);
+ }}
+ onDelete={handleDeleteVersion}
+ />
+ )}
+ </List.Item>
+ )}
+ />
+
+ {editingIndex === null && (
+ <Button
+ type="dashed"
+ onClick={handleAddVersion}
+ style={{ width: '100%', marginBottom: 24 }}
+ icon={<PlusOutlined />}
+ >
+ 添加版本
+ </Button>
+ )}
+
+ <Divider />
+ <Space size="middle">
+ <Button onClick={onPrev} size="large">上一步</Button>
+ <Button type="primary" onClick={handleNext} size="large">下一步</Button>
+ </Space>
+ </Card>
+ );
+};
+
+// ==================== 步骤4:确认发布 ====================
+export const ConfirmPublishStep: React.FC<StepFormProps & { onPublish: () => void }> = ({ data, onPrev, onPublish }) => {
+ const [publishing, setPublishing] = useState<boolean>(false);
+
+ const handlePublish = useCallback(async () => {
+ setPublishing(true);
+ try {
+ await new Promise(resolve => setTimeout(resolve, 2000));
+ message.success('作品发布成功!');
+ onPublish();
+ } catch {
+ message.error('发布失败,请重试');
+ } finally {
+ setPublishing(false);
+ }
+ }, [onPublish]);
+
+ return (
+ <Card>
+ <Title level={3} style={{ marginBottom: 24 }}>
+ <CheckCircleOutlined style={{ color: '#52c41a', marginRight: 8 }} />
+ 确认发布信息
+ </Title>
+
+ <div style={{ marginBottom: 32 }}>
+ <Title level={4}>基础信息</Title>
+ <Descriptions bordered column={1}>
+ <Descriptions.Item label="作品名称">{data.basicInfo.artworkName}</Descriptions.Item>
+ <Descriptions.Item label="作品分类">
+ <Tag color="blue">{data.basicInfo.artworkCategory}</Tag>
+ </Descriptions.Item>
+ <Descriptions.Item label="作品描述">
+ <div style={{ maxHeight: 200, overflow: 'auto' }}>
+ <ReactMarkdown>{data.basicInfo.artworkDescription}</ReactMarkdown>
+ </div>
+ </Descriptions.Item>
+ </Descriptions>
+ </div>
+
+ <div style={{ marginBottom: 32 }}>
+ <Title level={4}>作品封面</Title>
+ {data.coverInfo.coverFile && (
+ <img
+ src={data.coverInfo.coverFile.thumbUrl || data.coverInfo.coverFile.url}
+ alt="作品封面"
+ style={{ maxWidth: 300, maxHeight: 400, objectFit: 'cover' }}
+ />
+ )}
+ </div>
+
+ <div style={{ marginBottom: 32 }}>
+ <Title level={4}>版本信息</Title>
+ <List
+ dataSource={data.versions}
+ renderItem={(version) => (
+ <List.Item>
+ <List.Item.Meta
+ title={
+ <Space>
+ <Tag color="green">v{version.version}</Tag>
+ <Text>{version.versionDescription}</Text>
+ </Space>
+ }
+ description={
+ version.seedFile && (
+ <Space>
+ <FileTextOutlined />
+ <Text type="secondary">{version.seedFile.name}</Text>
+ </Space>
+ )
+ }
+ />
+ </List.Item>
+ )}
+ />
+ </div>
+
+ <Alert
+ message="发布须知"
+ description={
+ <ul style={{ marginBottom: 0, paddingLeft: 20 }}>
+ <li>发布后的作品将公开展示,所有用户都可以查看和下载</li>
+ <li>请确保作品内容符合社区规范,不包含违法违规内容</li>
+ <li>发布后您仍可以编辑作品信息和添加新版本</li>
+ <li>请尊重他人知识产权,确保作品为原创或已获得授权</li>
+ </ul>
+ }
+ type="warning"
+ showIcon
+ style={{ marginBottom: 24 }}
+ />
+
+ <Space size="middle">
+ <Button onClick={onPrev} size="large">上一步</Button>
+ <Button
+ type="primary"
+ onClick={handlePublish}
+ loading={publishing}
+ icon={<SendOutlined />}
+ size="large"
+ >
+ 确认发布
+ </Button>
+ </Space>
+ </Card>
+ );
+};
\ No newline at end of file