Skip to content

Commit 199677d

Browse files
committed
feat: 移除Solution中不需要的language和content字段,优化解决方案组件
1 parent 8c459a0 commit 199677d

File tree

2 files changed

+285
-63
lines changed

2 files changed

+285
-63
lines changed
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
import * as React from 'react';
2+
import { useState, useEffect } from 'react';
3+
import { Form, Input, Button, Divider, Space, AutoComplete, message } from 'antd';
4+
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
5+
import { Solution, SectionProps } from '../types';
6+
7+
// URL校验正则表达式
8+
const URL_REGEX = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
9+
10+
// 定义常见来源的域名映射
11+
const SOURCE_MAPPING: Record<string, string> = {
12+
'github.com': 'GitHub',
13+
'mp.weixin.qq.com': '微信公众号',
14+
'juejin.cn': '掘金',
15+
'csdn.net': 'CSDN',
16+
'zhihu.com': '知乎',
17+
'segmentfault.com': 'SegmentFault',
18+
'jianshu.com': '简书',
19+
'leetcode.cn': 'LeetCode',
20+
'leetcode.com': 'LeetCode',
21+
'bilibili.com': 'B站',
22+
'youtube.com': 'YouTube',
23+
'blog.csdn.net': 'CSDN',
24+
'medium.com': 'Medium',
25+
'dev.to': 'Dev.to',
26+
'stackoverflow.com': 'Stack Overflow'
27+
};
28+
29+
/**
30+
* 验证URL是否合法
31+
*/
32+
const isValidUrl = (url: string): boolean => {
33+
try {
34+
new URL(url);
35+
return true;
36+
} catch {
37+
return false;
38+
}
39+
};
40+
41+
/**
42+
* 参考资料部分组件
43+
*/
44+
const SolutionsSection: React.FC<SectionProps> = ({ form }) => {
45+
const [solutions, setSolutions] = useState<Solution[]>([]);
46+
const [editingSolutionIndex, setEditingSolutionIndex] = useState<number | null>(null);
47+
const [authors, setAuthors] = useState<string[]>([]);
48+
const [urlError, setUrlError] = useState<string>('');
49+
const [newSolution, setNewSolution] = useState<Solution>({
50+
title: '',
51+
url: '',
52+
source: '',
53+
author: ''
54+
});
55+
56+
// 从所有参考资料中提取作者列表
57+
useEffect(() => {
58+
const uniqueAuthors = Array.from(new Set(
59+
solutions
60+
.map(s => s.author)
61+
.filter((author): author is string => author !== undefined && author.trim() !== '')
62+
));
63+
setAuthors(uniqueAuthors);
64+
}, [solutions]);
65+
66+
// 根据URL自动判断来源
67+
const getSourceFromUrl = (url: string): string => {
68+
if (!isValidUrl(url)) return '';
69+
70+
try {
71+
const urlObj = new URL(url);
72+
const domain = urlObj.hostname.toLowerCase();
73+
74+
// 遍历SOURCE_MAPPING查找匹配的来源
75+
for (const [key, value] of Object.entries(SOURCE_MAPPING)) {
76+
if (domain.includes(key)) {
77+
return value;
78+
}
79+
}
80+
81+
// 如果没有匹配的预定义来源,返回域名的首字母大写形式
82+
return domain.split('.')[0].charAt(0).toUpperCase() + domain.split('.')[0].slice(1);
83+
} catch {
84+
return '';
85+
}
86+
};
87+
88+
const handleSolutionChange = (field: keyof Solution, value: string) => {
89+
setNewSolution(prev => {
90+
const updated = { ...prev, [field]: value };
91+
92+
// 如果更新的是URL字段,验证URL并自动填充source
93+
if (field === 'url') {
94+
if (value && !isValidUrl(value)) {
95+
setUrlError('请输入有效的URL地址');
96+
} else {
97+
setUrlError('');
98+
const source = getSourceFromUrl(value);
99+
if (source) {
100+
updated.source = source;
101+
}
102+
}
103+
}
104+
105+
return updated;
106+
});
107+
};
108+
109+
const handleAddSolution = () => {
110+
// 验证必填字段
111+
if (!newSolution.title) {
112+
message.error('请输入参考资料标题');
113+
return;
114+
}
115+
116+
if (!newSolution.url) {
117+
message.error('请输入参考资料链接');
118+
return;
119+
}
120+
121+
// 验证URL合法性
122+
if (!isValidUrl(newSolution.url)) {
123+
message.error('请输入有效的URL地址');
124+
return;
125+
}
126+
127+
if (editingSolutionIndex !== null) {
128+
// 编辑现有参考资料
129+
const updatedSolutions = [...solutions];
130+
updatedSolutions[editingSolutionIndex] = newSolution;
131+
setSolutions(updatedSolutions);
132+
setEditingSolutionIndex(null);
133+
form.setFieldsValue({ solutions: updatedSolutions });
134+
} else {
135+
// 添加新参考资料
136+
const updatedSolutions = [...solutions, newSolution];
137+
setSolutions(updatedSolutions);
138+
form.setFieldsValue({ solutions: updatedSolutions });
139+
}
140+
141+
// 重置表单
142+
setNewSolution({
143+
title: '',
144+
url: '',
145+
source: '',
146+
author: ''
147+
});
148+
setUrlError('');
149+
};
150+
151+
const handleEditSolution = (index: number) => {
152+
setNewSolution(solutions[index]);
153+
setEditingSolutionIndex(index);
154+
setUrlError('');
155+
};
156+
157+
const handleRemoveSolution = (index: number) => {
158+
const updatedSolutions = [...solutions];
159+
updatedSolutions.splice(index, 1);
160+
setSolutions(updatedSolutions);
161+
form.setFieldsValue({ solutions: updatedSolutions });
162+
};
163+
164+
const solutionContainerStyle = {
165+
marginBottom: '16px',
166+
padding: '8px',
167+
border: '1px solid #f0f0f0',
168+
borderRadius: '4px',
169+
};
170+
171+
return (
172+
<>
173+
<Divider orientation="left">参考资料</Divider>
174+
{solutions.map((solution, index) => (
175+
<div key={index} style={solutionContainerStyle}>
176+
<Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between' }}>
177+
<div>
178+
<strong>标题:</strong> {solution.title}
179+
{solution.source && <> | <strong>来源:</strong> {solution.source}</>}
180+
{solution.author && <> | <strong>作者:</strong> {solution.author}</>}
181+
</div>
182+
<Space>
183+
<Button icon={<EditOutlined />} size="small" onClick={() => handleEditSolution(index)}>
184+
编辑
185+
</Button>
186+
<Button icon={<DeleteOutlined />} size="small" danger onClick={() => handleRemoveSolution(index)}>
187+
删除
188+
</Button>
189+
</Space>
190+
</Space>
191+
<div>
192+
<a href={solution.url} target="_blank" rel="noopener noreferrer">{solution.url}</a>
193+
</div>
194+
</div>
195+
))}
196+
197+
<Form layout="vertical">
198+
<Form.Item
199+
label="标题"
200+
required
201+
help="请输入参考资料的标题"
202+
>
203+
<Input
204+
value={newSolution.title}
205+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleSolutionChange('title', e.target.value)}
206+
placeholder="如: 使用正则表达式解析URL"
207+
/>
208+
</Form.Item>
209+
210+
<Form.Item
211+
label="URL"
212+
required
213+
help={urlError || "请输入参考资料的链接地址"}
214+
validateStatus={urlError ? "error" : undefined}
215+
>
216+
<Input
217+
value={newSolution.url}
218+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleSolutionChange('url', e.target.value)}
219+
placeholder="如: https://github.com/your-repo"
220+
status={urlError ? "error" : undefined}
221+
/>
222+
</Form.Item>
223+
224+
<Form.Item
225+
label="来源"
226+
help="可选,会根据URL自动填充"
227+
>
228+
<Input
229+
value={newSolution.source}
230+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleSolutionChange('source', e.target.value)}
231+
placeholder="如: GitHub"
232+
/>
233+
</Form.Item>
234+
235+
<Form.Item
236+
label="作者"
237+
help="可选,支持自动补全已有作者"
238+
>
239+
<AutoComplete
240+
value={newSolution.author}
241+
onChange={(value) => handleSolutionChange('author', value)}
242+
options={authors.map(author => ({ value: author }))}
243+
placeholder="请输入作者名称"
244+
filterOption={(inputValue, option) =>
245+
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
246+
}
247+
/>
248+
</Form.Item>
249+
250+
<Button
251+
type="dashed"
252+
onClick={handleAddSolution}
253+
icon={<PlusOutlined />}
254+
disabled={!newSolution.title || !newSolution.url || !!urlError}
255+
>
256+
{editingSolutionIndex !== null ? '更新参考资料' : '添加参考资料'}
257+
</Button>
258+
{editingSolutionIndex !== null && (
259+
<Button
260+
style={{ marginLeft: 8 }}
261+
onClick={() => {
262+
setEditingSolutionIndex(null);
263+
setNewSolution({
264+
title: '',
265+
url: '',
266+
source: '',
267+
author: ''
268+
});
269+
setUrlError('');
270+
}}
271+
>
272+
取消编辑
273+
</Button>
274+
)}
275+
</Form>
276+
277+
{/* 隐藏的表单字段,用于提交参考资料数据 */}
278+
<Form.Item name="solutions" hidden>
279+
<input type="hidden" />
280+
</Form.Item>
281+
</>
282+
);
283+
};
284+
285+
export default SolutionsSection;

src/components/ChallengeContributePage/index.tsx

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import TagsSelector from './components/TagsSelector';
1919
import SolutionsSection from './components/SolutionsSection';
2020
import UrlInput from './components/UrlInput';
2121
import YamlPreviewSection from './components/YamlPreviewSection';
22-
import FormSubmitSection from './components/FormSubmitSection';
2322

2423
// 导入样式
2524
import { styles } from './styles';
@@ -37,9 +36,6 @@ const ChallengeContributePage: React.FC = () => {
3736
// 状态
3837
const [yamlOutput, setYamlOutput] = useState<string>('');
3938
const [isFormDirty, setIsFormDirty] = useState<boolean>(false);
40-
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
41-
const [isSubmitSuccess, setIsSubmitSuccess] = useState<boolean>(false);
42-
const [submitErrorMessage, setSubmitErrorMessage] = useState<string | null>(null);
4339
const [initialFormValues, setInitialFormValues] = useState<ChallengeFormData>({
4440
id: null,
4541
platform: 'Web',
@@ -148,55 +144,6 @@ const ChallengeContributePage: React.FC = () => {
148144
}
149145
};
150146

151-
// 表单提交
152-
const handleFinish = async () => {
153-
try {
154-
// 验证表单
155-
await form.validateFields();
156-
157-
// 获取表单数据
158-
const values = form.getFieldsValue() as ChallengeFormData;
159-
160-
// 确保URL是Base64编码
161-
if (values.base64Url) {
162-
values.base64Url = ensureBase64Encoded(values.base64Url);
163-
}
164-
165-
// 生成YAML
166-
const yamlString = generateYamlFromFormData(values);
167-
168-
// 标记提交中
169-
setIsSubmitting(true);
170-
setSubmitErrorMessage(null);
171-
172-
// 模拟提交操作
173-
setTimeout(() => {
174-
console.log('提交的表单数据:', values);
175-
console.log('生成的YAML:', yamlString);
176-
177-
// 提交成功
178-
setIsSubmitting(false);
179-
setIsSubmitSuccess(true);
180-
setIsFormDirty(false);
181-
182-
// 显示成功消息
183-
message.success('挑战已成功提交!');
184-
185-
// 清除本地存储的表单数据
186-
localStorage.removeItem(STORAGE_KEY);
187-
188-
// 3秒后重置成功状态
189-
setTimeout(() => {
190-
setIsSubmitSuccess(false);
191-
}, 3000);
192-
}, 1000);
193-
} catch (error) {
194-
console.error('表单验证失败:', error);
195-
setSubmitErrorMessage('表单验证失败,请检查填写的内容。');
196-
setIsSubmitting(false);
197-
}
198-
};
199-
200147
return (
201148
<div style={styles.container}>
202149
<Alert
@@ -257,16 +204,6 @@ const ChallengeContributePage: React.FC = () => {
257204
onGenerateYaml={generateYaml}
258205
onCopyYaml={handleCopyYaml}
259206
/>
260-
261-
{/* 表单提交部分 */}
262-
<FormSubmitSection
263-
onFinish={handleFinish}
264-
onGenerateYaml={generateYaml}
265-
isFormDirty={isFormDirty}
266-
isSubmitting={isSubmitting}
267-
isSubmitSuccess={isSubmitSuccess}
268-
submitErrorMessage={submitErrorMessage}
269-
/>
270207
</Form>
271208
</div>
272209
);

0 commit comments

Comments
 (0)