Skip to content

Commit a08f45a

Browse files
committed
feat: 重构题目贡献页面,将组件拆分得更细致,增加了基本信息、难度选择器、标签选择器等组件
1 parent f7d4bc4 commit a08f45a

21 files changed

+1405
-202
lines changed

docs/TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
作者这个字段,是要能够自动补全的,单击输入的就要把平台上所有的作者都下拉展开(按照出现次数倒序排列),然后根据用户的输入自动筛选补全,当然用户也可以不选择已有的作者,选择输入新的作者都是可以的,下拉仅仅只是为了补全,减少用户输入的工作量

package-lock.json

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
},
3131
"devDependencies": {
3232
"@faker-js/faker": "^9.6.0",
33+
"@types/js-yaml": "^4.0.9",
3334
"@types/node": "^22.14.0",
3435
"@types/react": "^18.2.67",
3536
"@types/react-dom": "^18.2.22",

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import NavBar from './components/NavBar'
44
import HomePage from './components/HomePage'
55
import ChallengeListPage from './components/ChallengeListPage'
66
import ChallengeDetailPage from './components/ChallengeDetailPage'
7+
import ChallengeContributePage from './components/ChallengeContributePage'
78
import AboutPage from './components/AboutPage'
89
import GitHubRibbon from './components/GitHubRibbon'
910
import './gh-fork-ribbon.css';
@@ -20,6 +21,7 @@ const App = () => {
2021
<Route path="/" element={<HomePage/>}/>
2122
<Route path="/challenges" element={<ChallengeListPage/>}/>
2223
<Route path="/challenge/:id" element={<ChallengeDetailPage/>}/>
24+
<Route path="/challenge/contribute" element={<ChallengeContributePage />}/>
2325
<Route path="/about" element={<AboutPage/>}/>
2426
<Route path="*" element={<HomePage/>}/>
2527
</Routes>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from 'react';
2+
import { Form, Input, InputNumber, Radio, FormInstance } from 'antd';
3+
import { SectionProps } from '../types';
4+
5+
interface BasicInfoProps {
6+
form: FormInstance;
7+
}
8+
9+
/**
10+
* 挑战基本信息表单部分
11+
*/
12+
const BasicInfo: React.FC<BasicInfoProps> = ({ form }) => {
13+
return (
14+
<>
15+
<Form.Item
16+
name="id"
17+
label="ID"
18+
rules={[{ required: true, message: '请输入ID' }]}
19+
tooltip="每个挑战的唯一数字ID,已自动填入可用的下一个ID"
20+
>
21+
<InputNumber style={{ width: '100%' }} min={1} />
22+
</Form.Item>
23+
24+
<Form.Item
25+
name="idAlias"
26+
label="ID别名"
27+
tooltip="用于在URL和界面上显示的友好名称"
28+
>
29+
<Input placeholder="如: ruishu-5、leetcode-42、jsvmp-advanced 等" />
30+
</Form.Item>
31+
32+
<Form.Item
33+
name="platform"
34+
label="平台"
35+
rules={[{ required: true, message: '请选择平台' }]}
36+
>
37+
<Radio.Group>
38+
<Radio.Button value="Web">Web</Radio.Button>
39+
<Radio.Button value="Android">Android</Radio.Button>
40+
<Radio.Button value="iOS">iOS</Radio.Button>
41+
</Radio.Group>
42+
</Form.Item>
43+
44+
<Form.Item
45+
name="name"
46+
label="中文名称"
47+
rules={[{ required: true, message: '请输入中文名称' }]}
48+
>
49+
<Input placeholder="如: 5代瑞数反爬、字节TikTok登录加密等" />
50+
</Form.Item>
51+
52+
<Form.Item
53+
name="nameEn"
54+
label="英文名称"
55+
>
56+
<Input placeholder="如: Riskined v5 Anti-Crawler, ByteDance TikTok Login Encryption" />
57+
</Form.Item>
58+
</>
59+
);
60+
};
61+
62+
export default BasicInfo;

src/components/ChallengeContributePage/components/DescriptionFields.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,51 @@ const DescriptionFields: React.FC<SectionProps> = ({ form }) => {
154154
};
155155
}, [form]);
156156

157+
// 监听description-updated事件
158+
useEffect(() => {
159+
const handleDescriptionUpdate = (event: any) => {
160+
if (event.detail) {
161+
try {
162+
const { description, descriptionEn } = event.detail;
163+
164+
// 安全转换为字符串
165+
const chineseText = safeToString(description);
166+
const englishText = safeToString(descriptionEn);
167+
168+
console.log('接收到description更新事件:', { chineseText, englishText });
169+
170+
// 使用setTimeout确保在DOM更新后再更新编辑器内容
171+
setTimeout(() => {
172+
// 更新编辑器内容
173+
if (editorChineseRef.current?.getMdElement) {
174+
editorChineseRef.current.setText(chineseText);
175+
// 确保表单中也保存了正确的字符串
176+
form.setFieldsValue({
177+
description: chineseText,
178+
descriptionMarkdown: chineseText
179+
});
180+
}
181+
182+
if (editorEnglishRef.current?.getMdElement) {
183+
editorEnglishRef.current.setText(englishText);
184+
form.setFieldsValue({
185+
descriptionEn: englishText,
186+
descriptionMarkdownEn: englishText
187+
});
188+
}
189+
}, 100);
190+
} catch (err) {
191+
console.error('处理description更新事件时出错:', err);
192+
}
193+
}
194+
};
195+
196+
window.addEventListener('description-updated', handleDescriptionUpdate);
197+
return () => {
198+
window.removeEventListener('description-updated', handleDescriptionUpdate);
199+
};
200+
}, [form]);
201+
157202
// 初始化编辑器内容
158203
useEffect(() => {
159204
if (hasInitializedRef.current) return;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as React from 'react';
2+
import { useState, useEffect } from 'react';
3+
import { Form, Select, FormInstance } from 'antd';
4+
import StarRating from '../../StarRating';
5+
6+
const { Option } = Select;
7+
8+
interface DifficultySelectorProps {
9+
form: FormInstance;
10+
value?: number;
11+
onChange?: (value: number) => void;
12+
}
13+
14+
/**
15+
* 难度选择组件
16+
*/
17+
const DifficultySelector: React.FC<DifficultySelectorProps> = ({ form, value, onChange }) => {
18+
const [currentDifficulty, setCurrentDifficulty] = useState<number>(value || 1);
19+
20+
// 监听表单中难度级别的变化
21+
useEffect(() => {
22+
const formDifficulty = form.getFieldValue('difficultyLevel');
23+
if (formDifficulty && formDifficulty !== currentDifficulty) {
24+
setCurrentDifficulty(formDifficulty);
25+
}
26+
}, [form, currentDifficulty]);
27+
28+
// 难度级别变化处理
29+
const handleDifficultyChange = (value: number) => {
30+
setCurrentDifficulty(value);
31+
form.setFieldsValue({ difficultyLevel: value });
32+
33+
if (onChange) {
34+
onChange(value);
35+
}
36+
};
37+
38+
return (
39+
<Form.Item
40+
name="difficultyLevel"
41+
label="难度级别"
42+
rules={[{ required: true, message: '请选择难度级别' }]}
43+
>
44+
<Select
45+
style={{ width: 'auto', minWidth: '180px' }}
46+
onChange={handleDifficultyChange}
47+
value={currentDifficulty}
48+
dropdownStyle={{ minWidth: '250px' }}
49+
placeholder="请选择难度级别"
50+
popupMatchSelectWidth={false}
51+
>
52+
<Option value={1}>
53+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
54+
<span>1 (初级)</span>
55+
<StarRating difficulty={1} />
56+
</div>
57+
</Option>
58+
<Option value={2}>
59+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
60+
<span>2 (简单)</span>
61+
<StarRating difficulty={2} />
62+
</div>
63+
</Option>
64+
<Option value={3}>
65+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
66+
<span>3 (中等)</span>
67+
<StarRating difficulty={3} />
68+
</div>
69+
</Option>
70+
<Option value={4}>
71+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
72+
<span>4 (困难)</span>
73+
<StarRating difficulty={4} />
74+
</div>
75+
</Option>
76+
<Option value={5}>
77+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
78+
<span>5 (专家)</span>
79+
<StarRating difficulty={5} />
80+
</div>
81+
</Option>
82+
</Select>
83+
</Form.Item>
84+
);
85+
};
86+
87+
export default DifficultySelector;

src/components/ChallengeContributePage/components/FormSubmitSection.tsx

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)