Commit 50dc23f5 authored by 健杭 徐's avatar 健杭 徐
Browse files

finish

parent 646b90d2
......@@ -29,74 +29,46 @@ type Response struct {
// 获取评论
func GetCommentsHandler(c *gin.Context) {
// 参数验证
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil || page < 1 {
pageStr := c.DefaultQuery("page", "1")
sizeStr := c.DefaultQuery("size", "10")
page, _ := strconv.Atoi(pageStr)
size, _ := strconv.Atoi(sizeStr)
if page < 1 {
page = 1
}
size, err := strconv.Atoi(c.DefaultQuery("size", "10"))
if err != nil || size < -1 {
size = 10
}
comments := make([]Comment, 0)
var total int64
// 查询总数
var total int
countQuery := "SELECT COUNT(*) FROM comments"
err = db.QueryRow(countQuery).Scan(&total)
// 统计总数
err := db.QueryRow("SELECT COUNT(*) FROM comments").Scan(&total)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "查询总数失败: " + err.Error(),
})
c.JSON(http.StatusOK, Response{Code: 500, Msg: err.Error()})
return
}
// 构建查询
query := "SELECT id, name, content, TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') AS created_at FROM comments"
args := []interface{}{}
// 始终排序
query += " ORDER BY created_at DESC"
// 分页处理
var rows *sql.Rows
if size != -1 {
offset := (page - 1) * size
query += " LIMIT $1 OFFSET $2"
args = append(args, size, offset)
rows, err = db.Query("SELECT id, name, content, created_at FROM comments ORDER BY created_at ASC LIMIT $1 OFFSET $2", size, offset)
} else {
rows, err = db.Query("SELECT id, name, content, created_at FROM comments ORDER BY created_at ASC")
}
// 执行查询
rows, err := db.Query(query, args...)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "查询评论失败: " + err.Error(),
})
c.JSON(http.StatusOK, Response{Code: 500, Msg: err.Error()})
return
}
defer rows.Close()
// 处理结果
var comments []Comment
for rows.Next() {
var comment Comment
if err := rows.Scan(
&comment.ID,
&comment.Name,
&comment.Content,
&comment.CreatedAt,
); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"msg": "解析评论失败: " + err.Error(),
})
if err := rows.Scan(&comment.ID, &comment.Name, &comment.Content, &comment.CreatedAt); err != nil {
c.JSON(http.StatusOK, Response{Code: 500, Msg: err.Error()})
return
}
comments = append(comments, comment)
}
// 返回标准响应结构
c.JSON(http.StatusOK, gin.H{
"code": 0,
"msg": "success",
......
......@@ -81,7 +81,6 @@ func main() {
log.Fatal(router.Run(":8080"))
}
// 新增表创建函数
func createTable() {
query := `
CREATE TABLE IF NOT EXISTS comments (
......
......@@ -9,6 +9,5 @@
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="/src/load.js"></script>
</body>
</html>
</html>
\ No newline at end of file
import { format } from "date-fns";
const currentTime = new Date();
const formattedTime = format(currentTime, 'yyyy-MM-dd HH:mm');
async function AddComment({name, profilePhoto}:{name: string, profilePhoto:string}) {
async function AddComment({ name, onSuccess }: { name: string; onSuccess: () => void; }) {
const textInput = document.getElementById('textInput') as HTMLInputElement;
const content = textInput.value;
if (content === '')
{
alert('Text input cannot be empty')
return
if (content === '') {
alert('Text input cannot be empty');
return;
}
if (name === '')
{
name = '小黑子'
if (name === '') {
name = '小黑子';
}
try {
const response = await fetch('http://localhost:8080/comment/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, content, created_at: formattedTime})
body: JSON.stringify({ name, content, created_at: new Date().toISOString() })
});
const result = await response.json();
if (result.code !== 0)
{
if (result.code !== 0) {
alert(`操作失败: ${result.msg}`);
throw new Error(result.msg);
}
const newComment = result.data;
const NewCommentPart = document.createElement('div')
const UserPhoto = document.createElement('img')
UserPhoto.className = 'comment-avatar'
UserPhoto.src = profilePhoto
UserPhoto.alt = name
UserPhoto.style.width = '30px'
UserPhoto.style.height = '30px'
UserPhoto.style.borderRadius = '50%'
NewCommentPart.appendChild(UserPhoto)
const UserCommentContent = document.createElement('div')
UserCommentContent.className = 'comment-content'
const UserName = document.createElement('span')
UserName.textContent = name
UserName.className = 'comment-meta'
UserCommentContent.appendChild(UserName)
const UserDate = document.createElement('span')
UserDate.textContent = formattedTime
UserDate.className = 'comment-date'
UserCommentContent.appendChild(UserDate)
const UserComment = document.createElement('p')
UserComment.textContent = textInput.value
UserComment.style.marginLeft = '10px'
textInput.value = '';
(document.getElementById('nameInput') as HTMLInputElement).value = '';
UserCommentContent.appendChild(UserComment)
const DeleteButtonArea = document.createElement('div')
DeleteButtonArea.className = 'comment-delete'
const DeleteButton = document.createElement('button')
DeleteButton.textContent = 'Delete'
DeleteButton.id = 'deleteButton'
DeleteButton.onclick = async () => {
const commentItem = DeleteButton.closest('.comment-item')
if (commentItem) {
commentItem.remove()
try {
await fetch(`http://localhost:8080/comment/delete?id=${newComment.id}`, {
method: 'POST'
});
commentItem.remove();
} catch {
alert('删除失败:');
}
}
}
DeleteButtonArea.appendChild(DeleteButton)
UserCommentContent.appendChild(DeleteButtonArea)
NewCommentPart.appendChild(UserCommentContent)
const NewComment = document.createElement('li')
NewComment.className = 'comment-item'
NewComment.style.listStyle = 'none'
NewComment.appendChild(NewCommentPart)
const CommentList = document.getElementById('commentlist') as HTMLUListElement
CommentList.appendChild(NewComment)
}
catch (error) {
textInput.value = '';
(document.getElementById('nameInput') as HTMLInputElement).value = '';
onSuccess();
} catch (error) {
console.error('添加评论失败:', error);
};
}
}
export default AddComment
\ No newline at end of file
export default AddComment;
\ No newline at end of file
import { useState, useEffect, useRef } from 'react';
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef<() => void>(undefined);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
if (savedCallback.current) {
savedCallback.current();
}
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
interface Comment {
id: number;
name: string;
content: string;
created_at: string;
}
const pageSize = 5;
const POLLING_INTERVAL = 1000;
export function CommentSection({ refreshTrigger }: { refreshTrigger: number }) {
const [comments, setComments] = useState<Comment[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [totalComments, setTotalComments] = useState(0);
const loadComments = async (page: number) => {
try {
const response = await fetch(`http://localhost:8080/comment/get?page=${page}&size=${pageSize}`);
const result = await response.json();
if (result.code !== 0) throw new Error(result.msg);
setComments(result.data.comments);
setTotalComments(result.data.total);
} catch (error) {
console.error('加载评论错误:', error);
}
};
const deleteComment = async (commentId: number) => {
if (!confirm('确定要删除这条评论吗?'))
return;
try {
const response = await fetch(`http://localhost:8080/comment/delete?id=${commentId}`, {
method: 'POST'
});
const result = await response.json();
if (result.code !== 0) throw new Error(result.msg);
alert('评论已删除');
loadComments(currentPage);
} catch (error) {
console.error('删除评论错误:', error);
alert('删除评论失败: ' + (error as Error).message);
}
};
useEffect(() => {
loadComments(currentPage);
}, [currentPage, refreshTrigger]);
useInterval(() => {
loadComments(currentPage);
}, POLLING_INTERVAL);
const totalPages = Math.ceil(totalComments / pageSize);
return (
<div className="comment">
<div>
<h3 style={{ textAlign: 'center' }}>All Comment</h3>
</div>
<ul id="commentlist">
{comments.length > 0 ? (
comments.map(comment => (
<li key={comment.id} className="comment-item">
<div>
<img src='https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg'
className='comment-avatar' alt={comment.name} />
<div className='comment-content'>
<div>
<span className='comment-meta'>{comment.name}</span>
<span className='comment-date'>{new Date(comment.created_at).toLocaleString()}</span>
</div>
<p>{comment.content}</p>
<div className='comment-delete'>
<button id='deleteButton' onClick={() => deleteComment(comment.id)}>Delete</button>
</div>
</div>
</div>
</li>
))
) : (
<div className="no-comments">No Comment Yet</div>
)}
</ul>
<div id="pagination-controls" className="pagination-controls">
<button onClick={() => setCurrentPage(prev => prev - 1)} disabled={currentPage <= 1}>
&laquo; Last
</button>
<span>
{totalComments > 0 ? `Page ${currentPage} / ${totalPages}` : 'No Comment Yet'}
</span>
<button onClick={() => setCurrentPage(prev => prev + 1)} disabled={currentPage >= totalPages}>
Next &raquo;
</button>
</div>
</div>
);
}
\ No newline at end of file
......@@ -539,4 +539,43 @@ h3::after {
::-webkit-scrollbar-thumb:hover {
background: var(--primary-color);
}
.pagination-controls {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
gap: 15px;
}
.pagination-controls button {
padding: 8px 16px;
border: 1px solid #ddd;
background-color: #f8f8f8;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.pagination-controls button:hover:not(:disabled) {
background-color: #e2e2e2;
}
.pagination-controls button:disabled {
cursor: not-allowed;
color: #ccc;
background-color: #fdfdfd;
}
#page-info {
font-size: 14px;
color: #555;
}
.no-comments {
text-align: center;
color: #888;
padding: 20px;
font-style: italic;
}
\ No newline at end of file
async function loadComments() {
let currentPage = 1;
const pageSize = 5;
let totalComments = 0;
async function loadComments(page) {
currentPage = page;
try {
const response = await fetch('http://localhost:8080/comment/get');
const response = await fetch(`http://localhost:8080/comment/get?page=${page}&size=${pageSize}`);
const result = await response.json();
// 检查业务状态码
if (result.code !== 0) {
throw new Error(result.msg || '未知错误');
}
// 正确的数据结构访问
totalComments = result.data.total;
displayComments(result.data.comments);
updatePaginationControls();
} catch (error) {
console.error('加载评论错误:', error);
alert('加载评论失败: ' + error.message);
......@@ -19,7 +25,7 @@ async function loadComments() {
function displayComments(comments) {
const commentsContainer = document.getElementById('commentlist');
commentsContainer.innerHTML = '';
if (!comments || comments.length === 0) {
commentsContainer.innerHTML = '<div class="no-comments">No Comments Yet</div>';
return;
......@@ -29,6 +35,8 @@ function displayComments(comments) {
const commentElement = document.createElement('li');
commentElement.className = 'comment-item';
const displayDate = new Date(comment.created_at).toLocaleString();
commentElement.innerHTML = `
<div>
<img src='https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg'
......@@ -36,7 +44,7 @@ function displayComments(comments) {
<div class='comment-content'>
<div>
<span class='comment-meta'>${comment.name}</span>
<span class='comment-date'>${comment.created_at}</span>
<span class='comment-date'>${displayDate}</span>
</div>
<p>${comment.content}</p>
<div class='comment-delete'>
......@@ -63,7 +71,7 @@ function deleteComment(commentId) {
throw new Error(result.msg || '删除失败');
}
alert('评论已删除');
loadComments(); // 重新加载评论列表
loadComments(currentPage);
})
.catch(error => {
console.error('删除评论错误:', error);
......@@ -71,9 +79,50 @@ function deleteComment(commentId) {
});
}
function setupPagination() {
const paginationContainer = document.getElementById('pagination-controls');
if (!paginationContainer) return;
paginationContainer.innerHTML = `
<button id="prev-button">&laquo; 上一页</button>
<span id="page-info"></span>
<button id="next-button">下一页 &raquo;</button>
`;
document.getElementById('prev-button').addEventListener('click', () => {
if (currentPage > 1) {
loadComments(currentPage - 1);
}
});
document.getElementById('next-button').addEventListener('click', () => {
const totalPages = Math.ceil(totalComments / pageSize);
if (currentPage < totalPages) {
loadComments(currentPage + 1);
}
});
}
function updatePaginationControls() {
const prevButton = document.getElementById('prev-button');
const nextButton = document.getElementById('next-button');
const pageInfo = document.getElementById('page-info');
if (!prevButton || !nextButton || !pageInfo) return;
const totalPages = Math.ceil(totalComments / pageSize);
if (totalPages <= 0) {
pageInfo.textContent = '暂无评论';
} else {
pageInfo.textContent = `第 ${currentPage} / ${totalPages} 页 (共 ${totalComments} 条)`;
}
prevButton.disabled = currentPage <= 1;
nextButton.disabled = currentPage >= totalPages;
}
// 页面加载时调用
document.addEventListener('DOMContentLoaded', () => {
loadComments();
setupPagination();
loadComments(1);
});
\ No newline at end of file
import { StrictMode } from 'react'
import { StrictMode, useState } from 'react'
import { createRoot } from 'react-dom/client'
import AddComment from './App.tsx'
import DateTimeDisplay from './date.tsx'
import { CommentSection } from './CommentSection.tsx'
import './index.css'
const profilePhoto:string = 'https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg'
const profilePhoto: string = 'https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<div>
<h1 style={{textAlign: 'center'}}>Welcome To Xanadu`s Comment</h1>
</div>
<Input_Area />
<div className='comment'>
function App() {
const [refreshTrigger, setRefreshTrigger] = useState(0);
const handleCommentAdded = () => {
setRefreshTrigger(prev => prev + 1);
};
return (
<StrictMode>
<div>
<h3 style={{textAlign: 'center'}}>
All Comment
</h3>
<h1 style={{ textAlign: 'center' }}>Welcome To Xanadu`s Comment</h1>
</div>
<ul id='commentlist'></ul>
</div>
</StrictMode>
)
<Input_Area onCommentAdded={handleCommentAdded} />
<CommentSection refreshTrigger={refreshTrigger} />
</StrictMode>
);
}
function Input_Area() {
function Input_Area({ onCommentAdded }: { onCommentAdded: () => void }) {
return (
<div className='inputarea'>
<img src={profilePhoto} alt={'小黑子'}
style={{width: '40px', height: '40px', borderRadius: '50%'}} />
<img src={profilePhoto} alt={'小黑子'}
style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
<DateTimeDisplay />
<input type="text" placeholder="Please enter your name" id="nameInput" />
<input type="text" placeholder="Please follow the Community Guidelines" id="textInput" onKeyUpCapture={
(e) => {
const name:string = (document.getElementById('nameInput') as HTMLInputElement).value;
if (e.key === 'Enter') {
AddComment({name, profilePhoto});
const name: string = (document.getElementById('nameInput') as HTMLInputElement).value;
AddComment({ name, onSuccess: onCommentAdded });
}
}
}/>
} />
<button
id="submitButton"
onClick={() => AddComment({
name: (document.getElementById('nameInput') as HTMLInputElement).value,
profilePhoto: profilePhoto
})}
onClick={() => {
const name: string = (document.getElementById('nameInput') as HTMLInputElement).value;
AddComment({ name, onSuccess: onCommentAdded });
}}
>
Submit
</button>
......@@ -54,4 +56,4 @@ function Input_Area() {
)
}
// Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
\ No newline at end of file
createRoot(document.getElementById('root')!).render(<App />);
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment