parent
2f7e0439c9
commit
b86dda189d
@ -0,0 +1,97 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
const Comments = () => {
|
||||||
|
const [comments, setComments] = useState([]);
|
||||||
|
const [newComment, setNewComment] = useState('');
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
// 从 localStorage 加载评论
|
||||||
|
useEffect(() => {
|
||||||
|
const savedComments = localStorage.getItem('docComments');
|
||||||
|
if (savedComments) {
|
||||||
|
setComments(JSON.parse(savedComments));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 保存评论到 localStorage
|
||||||
|
const saveComments = (newComments) => {
|
||||||
|
localStorage.setItem('docComments', JSON.stringify(newComments));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!newComment.trim() || !name.trim()) return;
|
||||||
|
|
||||||
|
const comment = {
|
||||||
|
id: Date.now(),
|
||||||
|
name,
|
||||||
|
content: newComment,
|
||||||
|
date: new Date().toLocaleString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedComments = [...comments, comment];
|
||||||
|
setComments(updatedComments);
|
||||||
|
saveComments(updatedComments);
|
||||||
|
setNewComment('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (commentId) => {
|
||||||
|
const updatedComments = comments.filter(comment => comment.id !== commentId);
|
||||||
|
setComments(updatedComments);
|
||||||
|
saveComments(updatedComments);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.commentsContainer}>
|
||||||
|
<h2 className={styles.title}>评论</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
|
<div className={styles.inputGroup}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="您的名字"
|
||||||
|
className={styles.input}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputGroup}>
|
||||||
|
<textarea
|
||||||
|
value={newComment}
|
||||||
|
onChange={(e) => setNewComment(e.target.value)}
|
||||||
|
placeholder="写下您的评论..."
|
||||||
|
className={styles.textarea}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className={styles.submitButton}>
|
||||||
|
发表评论
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className={styles.commentsList}>
|
||||||
|
{comments.map((comment) => (
|
||||||
|
<div key={comment.id} className={styles.comment}>
|
||||||
|
<div className={styles.commentHeader}>
|
||||||
|
<span className={styles.commentName}>{comment.name}</span>
|
||||||
|
<div className={styles.commentActions}>
|
||||||
|
<span className={styles.commentDate}>{comment.date}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDelete(comment.id)}
|
||||||
|
className={styles.deleteButton}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className={styles.commentContent}>{comment.content}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Comments;
|
@ -0,0 +1,114 @@
|
|||||||
|
.commentsContainer {
|
||||||
|
margin: 2rem 0;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputGroup {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
color: var(--ifm-color-emphasis-900);
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus,
|
||||||
|
.textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
min-height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background-color: var(--ifm-color-primary);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submitButton:hover {
|
||||||
|
background-color: var(--ifm-color-primary-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentsList {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid var(--ifm-color-emphasis-300);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--ifm-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentHeader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentName {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentActions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentDate {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--ifm-color-emphasis-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteButton {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background-color: var(--ifm-color-danger);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deleteButton:hover {
|
||||||
|
background-color: var(--ifm-color-danger-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentContent {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--ifm-color-emphasis-900);
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import {useDoc} from '@docusaurus/theme-common/internal';
|
||||||
|
import DocItemContent from '@theme-original/DocItem/Content';
|
||||||
|
import DocItemFooter from '@theme-original/DocItem/Footer';
|
||||||
|
import DocItemPaginator from '@theme-original/DocItem/Paginator';
|
||||||
|
import DocItemTOCMobile from '@theme-original/DocItem/TOC/Mobile';
|
||||||
|
import DocItemTOCDesktop from '@theme-original/DocItem/TOC/Desktop';
|
||||||
|
import DocVersionBadge from '@theme-original/DocVersionBadge';
|
||||||
|
import DocVersionBanner from '@theme-original/DocVersionBanner';
|
||||||
|
import DocItemLocalTOC from '@theme-original/DocItem/LocalTOC';
|
||||||
|
import DocBreadcrumbs from '@theme-original/DocBreadcrumbs';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
import Comments from '@site/src/components/Comments';
|
||||||
|
|
||||||
|
export default function DocItem(props) {
|
||||||
|
const {metadata, frontMatter, assets} = useDoc();
|
||||||
|
const {
|
||||||
|
hide_table_of_contents: hideTableOfContents,
|
||||||
|
toc_min_heading_level: tocMinHeadingLevel,
|
||||||
|
toc_max_heading_level: tocMaxHeadingLevel,
|
||||||
|
} = frontMatter;
|
||||||
|
const {hide_comment_section: hideCommentSection} = frontMatter;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={clsx(styles.docItemContainer, 'container')}>
|
||||||
|
<div className={styles.docItemCol}>
|
||||||
|
<DocVersionBanner />
|
||||||
|
<div className={styles.docItemMain}>
|
||||||
|
<DocBreadcrumbs />
|
||||||
|
<DocVersionBadge />
|
||||||
|
<DocItemContent>
|
||||||
|
<DocItemTOCMobile
|
||||||
|
toc={props.toc}
|
||||||
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
|
/>
|
||||||
|
<DocItemLocalTOC
|
||||||
|
toc={props.toc}
|
||||||
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
|
/>
|
||||||
|
<DocItemContent {...props} />
|
||||||
|
{!hideCommentSection && <Comments />}
|
||||||
|
</DocItemContent>
|
||||||
|
<DocItemFooter {...props} />
|
||||||
|
</div>
|
||||||
|
<DocItemPaginator />
|
||||||
|
</div>
|
||||||
|
{!hideTableOfContents && props.toc && (
|
||||||
|
<div className="col col--3">
|
||||||
|
<DocItemTOCDesktop
|
||||||
|
toc={props.toc}
|
||||||
|
minHeadingLevel={tocMinHeadingLevel}
|
||||||
|
maxHeadingLevel={tocMaxHeadingLevel}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
.docItemContainer {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docItemCol {
|
||||||
|
flex: 1 0 75%;
|
||||||
|
max-width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.docItemMain {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 996px) {
|
||||||
|
.docItemCol {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue