refactor(comment): 统一字段命名并改用时间戳存储创建时间

将 author 字段重命名为 name,pub_date 改为 created 并使用时间戳存储
更新相关 API、数据库 schema 和前端组件以适配新字段
同时将 user_agent 简化为 ua 并改进日期处理逻辑
This commit is contained in:
anghunk
2026-01-20 13:50:08 +08:00
parent 520cfcc447
commit eb0a7a32aa
15 changed files with 112 additions and 99 deletions

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@ export type AdminLoginResponse = {
export type CommentItem = { export type CommentItem = {
id: number; id: number;
pubDate: string; created: number;
author: string; name: string;
email: string; email: string;
avatar: string; avatar: string;
postSlug: string; postSlug: string;
@@ -18,6 +18,7 @@ export type CommentItem = {
contentText: string; contentText: string;
contentHtml: string; contentHtml: string;
status: string; status: string;
ua?: string | null;
}; };
export type CommentListResponse = { export type CommentListResponse = {

View File

@@ -32,12 +32,12 @@
v-if="item.avatar" v-if="item.avatar"
:src="item.avatar" :src="item.avatar"
class="cell-avatar" class="cell-avatar"
:alt="item.author" :alt="item.name"
/> />
<div class="cell-author-main"> <div class="cell-author-main">
<div class="cell-author-name">{{ item.author }}</div> <div class="cell-author-name">{{ item.name }}</div>
<div class="cell-author-email">{{ item.email }}</div> <div class="cell-author-email">{{ item.email }}</div>
<span class="cell-time">{{ formatDate(item.pubDate) }}</span> <span class="cell-time">{{ formatDate(item.created) }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -138,10 +138,10 @@ const filteredComments = computed(() => {
return comments.value.filter((item) => item.status === statusFilter.value); return comments.value.filter((item) => item.status === statusFilter.value);
}); });
function formatDate(value: string) { function formatDate(value: number) {
const d = new Date(value); const d = new Date(value);
if (Number.isNaN(d.getTime())) { if (Number.isNaN(d.getTime())) {
return value; return String(value);
} }
return d.toLocaleString(); return d.toLocaleString();
} }

View File

@@ -1,16 +1,16 @@
-- create comment table -- create comment table
CREATE TABLE IF NOT EXISTS Comment ( CREATE TABLE IF NOT EXISTS Comment (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
pub_date DATETIME DEFAULT CURRENT_TIMESTAMP, created INTEGER NOT NULL,
post_slug TEXT NOT NULL, post_slug TEXT NOT NULL,
author TEXT NOT NULL, name TEXT NOT NULL,
email TEXT NOT NULL, email TEXT NOT NULL,
url TEXT, url TEXT,
ip_address TEXT, ip_address TEXT,
device TEXT, device TEXT,
os TEXT, os TEXT,
browser TEXT, browser TEXT,
user_agent TEXT, ua TEXT,
content_text TEXT NOT NULL, content_text TEXT NOT NULL,
content_html TEXT NOT NULL, content_html TEXT NOT NULL,
parent_id INTEGER, parent_id INTEGER,

View File

@@ -4,7 +4,7 @@ import { Bindings } from '../../bindings';
export const exportComments = async (c: Context<{ Bindings: Bindings }>) => { export const exportComments = async (c: Context<{ Bindings: Bindings }>) => {
try { try {
const { results } = await c.env.CWD_DB.prepare( const { results } = await c.env.CWD_DB.prepare(
'SELECT * FROM Comment ORDER BY pub_date DESC' 'SELECT * FROM Comment ORDER BY created DESC'
).all(); ).all();
return c.json(results); return c.json(results);

View File

@@ -25,16 +25,16 @@ export const importComments = async (c: Context<{ Bindings: Bindings }>) => {
const stmts = comments.map((comment: any) => { const stmts = comments.map((comment: any) => {
const { const {
id, id,
pub_date, created,
post_slug, post_slug,
author, name,
email, email,
url, url,
ip_address, ip_address,
device, device,
os, os,
browser, browser,
user_agent, ua,
content_text, content_text,
content_html, content_html,
parent_id, parent_id,
@@ -48,15 +48,26 @@ export const importComments = async (c: Context<{ Bindings: Bindings }>) => {
// 构建 SQL。 // 构建 SQL。
// 能够处理 id 存在或不存在的情况 // 能够处理 id 存在或不存在的情况
const fields = [ const fields = [
'pub_date', 'post_slug', 'author', 'email', 'url', 'created', 'post_slug', 'name', 'email', 'url',
'ip_address', 'device', 'os', 'browser', 'user_agent', 'ip_address', 'device', 'os', 'browser', 'ua',
'content_text', 'content_html', 'parent_id', 'status' 'content_text', 'content_html', 'parent_id', 'status'
]; ];
const values = [ const values = [
pub_date, post_slug, author, email, url, created || Date.now(),
ip_address, device, os, browser, user_agent, post_slug || "",
content_text, content_html, parent_id, status name || "Anonymous",
].map(v => v === undefined ? null : v); email || "",
url || null,
ip_address || null,
device || null,
os || null,
browser || null,
ua || null,
content_text || "",
content_html || "",
parent_id || null,
status || "approved"
];
if (id) { if (id) {
fields.unshift('id'); fields.unshift('id');

View File

@@ -14,7 +14,7 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
).first<{ count: number }>(); ).first<{ count: number }>();
const { results } = await c.env.CWD_DB.prepare( const { results } = await c.env.CWD_DB.prepare(
'SELECT * FROM Comment ORDER BY pub_date DESC LIMIT ? OFFSET ?' 'SELECT * FROM Comment ORDER BY created DESC LIMIT ? OFFSET ?'
) )
.bind(limit, offset) .bind(limit, offset)
.all(); .all();
@@ -30,8 +30,8 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
const data = await Promise.all( const data = await Promise.all(
results.map(async (row: any) => ({ results.map(async (row: any) => ({
id: row.id, id: row.id,
pubDate: row.pub_date, created: row.created,
author: row.author, name: row.name,
email: row.email, email: row.email,
postSlug: row.post_slug, postSlug: row.post_slug,
url: row.url, url: row.url,
@@ -39,6 +39,7 @@ export const listComments = async (c: Context<{ Bindings: Bindings }>) => {
contentText: row.content_text, contentText: row.content_text,
contentHtml: row.content_html, contentHtml: row.content_html,
status: row.status, status: row.status,
ua: row.ua,
avatar: await getCravatar(row.email, avatarPrefix || undefined) avatar: await getCravatar(row.email, avatarPrefix || undefined)
})) }))
); );

View File

@@ -15,12 +15,12 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
try { try {
// 1. 查询审核通过的评论 // 1. 查询审核通过的评论
const query = ` const query = `
SELECT id, author, email, url, content_text as contentText, SELECT id, name, email, url, content_text as contentText,
content_html as contentHtml, pub_date as pubDate, parent_id as parentId, content_html as contentHtml, created, parent_id as parentId,
post_slug as postSlug post_slug as postSlug
FROM Comment FROM Comment
WHERE post_slug = ? AND status = "approved" WHERE post_slug = ? AND status = "approved"
ORDER BY pub_date DESC ORDER BY created DESC
` `
const { results } = await c.env.CWD_DB.prepare(query).bind(post_slug).all() const { results } = await c.env.CWD_DB.prepare(query).bind(post_slug).all()
@@ -52,7 +52,7 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
// 获取直接父评论的作者名 // 获取直接父评论的作者名
const parentComment = commentMap.get(comment.parentId) const parentComment = commentMap.get(comment.parentId)
if (parentComment) { if (parentComment) {
comment.replyToAuthor = parentComment.author comment.replyToAuthor = parentComment.name
} }
// 向上查找根评论 // 向上查找根评论
@@ -74,7 +74,7 @@ export const getComments = async (c: Context<{ Bindings: Bindings }>) => {
// 对每个根评论的 replies 按时间正序排列 // 对每个根评论的 replies 按时间正序排列
rootComments.forEach(root => { rootComments.forEach(root => {
root.replies.sort((a: any, b: any) => root.replies.sort((a: any, b: any) =>
new Date(a.pubDate).getTime() - new Date(b.pubDate).getTime() a.created - b.created
) )
}) })

View File

@@ -22,7 +22,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!data || typeof data !== 'object') { if (!data || typeof data !== 'object') {
return c.json({ message: '无效的请求体' }, 400); return c.json({ message: '无效的请求体' }, 400);
} }
const { post_slug, content: rawContent, author: rawAuthor, email, url, post_title, post_url } = data; const { post_slug, content: rawContent, name: rawName, email, url, post_title, post_url } = data;
const parentId = (data as any).parent_id ?? (data as any).parentId ?? null; const parentId = (data as any).parent_id ?? (data as any).parentId ?? null;
if (!post_slug || typeof post_slug !== 'string') { if (!post_slug || typeof post_slug !== 'string') {
return c.json({ message: 'post_slug 必填' }, 400); return c.json({ message: 'post_slug 必填' }, 400);
@@ -30,7 +30,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!rawContent || typeof rawContent !== 'string') { if (!rawContent || typeof rawContent !== 'string') {
return c.json({ message: '评论内容不能为空' }, 400); return c.json({ message: '评论内容不能为空' }, 400);
} }
if (!rawAuthor || typeof rawAuthor !== 'string') { if (!rawName || typeof rawName !== 'string') {
return c.json({ message: '昵称不能为空' }, 400); return c.json({ message: '昵称不能为空' }, 400);
} }
if (!email || typeof email !== 'string') { if (!email || typeof email !== 'string') {
@@ -39,7 +39,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (!isValidEmail(email)) { if (!isValidEmail(email)) {
return c.json({ message: '邮箱格式不正确' }, 400); return c.json({ message: '邮箱格式不正确' }, 400);
} }
const userAgent = c.req.header('user-agent') || ""; const ua = c.req.header('user-agent') || "";
// 1. 获取 IP (Worker 获取 IP 的标准方式) // 1. 获取 IP (Worker 获取 IP 的标准方式)
const ip = c.req.header('cf-connecting-ip') || "127.0.0.1"; const ip = c.req.header('cf-connecting-ip') || "127.0.0.1";
@@ -47,11 +47,11 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
// 2. 检查评论频率控制 (对应 canPostComment) // 2. 检查评论频率控制 (对应 canPostComment)
// 这里建议使用 D1 查最近一条评论的时间,或者直接放行(如果使用了 Cloudflare WAF // 这里建议使用 D1 查最近一条评论的时间,或者直接放行(如果使用了 Cloudflare WAF
const lastComment = await c.env.CWD_DB.prepare( const lastComment = await c.env.CWD_DB.prepare(
'SELECT pub_date FROM Comment WHERE ip_address = ? ORDER BY pub_date DESC LIMIT 1' 'SELECT created FROM Comment WHERE ip_address = ? ORDER BY created DESC LIMIT 1'
).bind(ip).first<{ pub_date: string }>(); ).bind(ip).first<{ created: number }>();
if (lastComment) { if (lastComment) {
const lastTime = new Date(lastComment.pub_date).getTime(); const lastTime = lastComment.created;
if (Date.now() - lastTime < 10 * 1000) { if (Date.now() - lastTime < 10 * 1000) {
return c.json({ message: "评论频繁等10s后再试" }, 429); return c.json({ message: "评论频繁等10s后再试" }, 429);
} }
@@ -71,7 +71,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
// 3. 准备数据 // 3. 准备数据
const cleanedContent = checkContent(rawContent); const cleanedContent = checkContent(rawContent);
const contentText = cleanedContent; const contentText = cleanedContent;
const author = checkContent(rawAuthor); const name = checkContent(rawName);
// Markdown 渲染与 XSS 过滤 // Markdown 渲染与 XSS 过滤
const html = await marked.parse(cleanedContent, { async: true }); const html = await marked.parse(cleanedContent, { async: true });
@@ -89,32 +89,32 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
console.log('PostComment:request', { console.log('PostComment:request', {
postSlug: post_slug, postSlug: post_slug,
hasParent: parentId !== null && parentId !== undefined, hasParent: parentId !== null && parentId !== undefined,
author, name,
email, email,
ip ip
}); });
const uaParser = new UAParser(userAgent); const uaParser = new UAParser(ua);
const uaResult = uaParser.getResult(); const uaResult = uaParser.getResult();
// 4. 写入 D1 数据库 // 4. 写入 D1 数据库
try { try {
const { success } = await c.env.CWD_DB.prepare(` const { success } = await c.env.CWD_DB.prepare(`
INSERT INTO Comment ( INSERT INTO Comment (
pub_date, post_slug, author, email, url, ip_address, created, post_slug, name, email, url, ip_address,
os, browser, device, user_agent, content_text, content_html, os, browser, device, ua, content_text, content_html,
parent_id, status parent_id, status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).bind( `).bind(
new Date().toISOString(), Date.now(),
post_slug, post_slug,
author, name,
email, email,
url || null, url || null,
ip, ip,
`${uaResult.os.name || ""} ${uaResult.os.version || ""}`.trim(), `${uaResult.os.name || ""} ${uaResult.os.version || ""}`.trim(),
`${uaResult.browser.name || ""} ${uaResult.browser.version || ""}`.trim(), `${uaResult.browser.name || ""} ${uaResult.browser.version || ""}`.trim(),
uaResult.device.model || uaResult.device.type || "Desktop", uaResult.device.model || uaResult.device.type || "Desktop",
userAgent, ua,
contentText, contentText,
contentHtml, contentHtml,
parentId || null, parentId || null,
@@ -156,8 +156,8 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
const isAdminReply = !!adminEmail && email === adminEmail; const isAdminReply = !!adminEmail && email === adminEmail;
const parentComment = await c.env.CWD_DB.prepare( const parentComment = await c.env.CWD_DB.prepare(
"SELECT author, email, content_html FROM Comment WHERE id = ?" "SELECT name, email, content_html FROM Comment WHERE id = ?"
).bind(parentId).first<{ author: string, email: string, content_html: string }>(); ).bind(parentId).first<{ name: string, email: string, content_html: string }>();
if (parentComment && parentComment.email && parentComment.email !== email) { if (parentComment && parentComment.email && parentComment.email !== email) {
let canSendUserMail = true; let canSendUserMail = true;
@@ -173,14 +173,14 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
if (canSendUserMail && isValidEmail(parentComment.email)) { if (canSendUserMail && isValidEmail(parentComment.email)) {
console.log('PostComment:mailDispatch:userReply:send', { console.log('PostComment:mailDispatch:userReply:send', {
toEmail: parentComment.email, toEmail: parentComment.email,
toName: parentComment.author toName: parentComment.name
}); });
await sendCommentReplyNotification(c.env, { await sendCommentReplyNotification(c.env, {
toEmail: parentComment.email, toEmail: parentComment.email,
toName: parentComment.author, toName: parentComment.name,
postTitle: data.post_title, postTitle: data.post_title,
parentComment: parentComment.content_html, parentComment: parentComment.content_html,
replyAuthor: author, replyAuthor: name,
replyContent: contentHtml, replyContent: contentHtml,
postUrl: data.post_url, postUrl: data.post_url,
}, notifySettings.smtp); }, notifySettings.smtp);
@@ -202,7 +202,7 @@ export const postComment = async (c: Context<{ Bindings: Bindings }>) => {
await sendCommentNotification(c.env, { await sendCommentNotification(c.env, {
postTitle: data.post_title, postTitle: data.post_title,
postUrl: data.post_url, postUrl: data.post_url,
commentAuthor: author, commentAuthor: name,
commentContent: contentHtml commentContent: contentHtml
}, notifySettings.smtp); }, notifySettings.smtp);
await c.env.CWD_DB.prepare( await c.env.CWD_DB.prepare(

View File

@@ -25,7 +25,7 @@ export class CommentForm extends Component {
const { formErrors, submitting } = this.props; const { formErrors, submitting } = this.props;
const { localForm } = this.state; const { localForm } = this.state;
const canSubmit = localForm.author.trim() && localForm.email.trim() && localForm.content.trim(); const canSubmit = localForm.name.trim() && localForm.email.trim() && localForm.content.trim();
const root = this.createElement('form', { const root = this.createElement('form', {
className: 'cwd-comment-form', className: 'cwd-comment-form',
@@ -46,7 +46,7 @@ export class CommentForm extends Component {
className: 'cwd-form-row', className: 'cwd-form-row',
children: [ children: [
// 昵称 // 昵称
this.createFormField('昵称 *', 'text', 'author', localForm.author, formErrors.author), this.createFormField('昵称 *', 'text', 'name', localForm.name, formErrors.name),
// 邮箱 // 邮箱
this.createFormField('邮箱 *', 'email', 'email', localForm.email, formErrors.email), this.createFormField('邮箱 *', 'email', 'email', localForm.email, formErrors.email),
// 网址 // 网址
@@ -103,13 +103,13 @@ export class CommentForm extends Component {
// 只在非提交状态时同步表单数据(避免覆盖用户正在输入的内容) // 只在非提交状态时同步表单数据(避免覆盖用户正在输入的内容)
if (!this.props.submitting && this.props.form !== prevProps.form) { if (!this.props.submitting && this.props.form !== prevProps.form) {
// 保留当前正在输入的内容 // 保留当前正在输入的内容
const currentAuthor = this.state.localForm.author || ''; const currentName = this.state.localForm.name || '';
const currentEmail = this.state.localForm.email || ''; const currentEmail = this.state.localForm.email || '';
const currentUrl = this.state.localForm.url || ''; const currentUrl = this.state.localForm.url || '';
const currentContent = this.state.localForm.content || ''; const currentContent = this.state.localForm.content || '';
this.state.localForm = { this.state.localForm = {
author: this.props.form.author || currentAuthor, name: this.props.form.name || currentName,
email: this.props.form.email || currentEmail, email: this.props.form.email || currentEmail,
url: this.props.form.url || currentUrl, url: this.props.form.url || currentUrl,
content: this.props.form.content !== undefined ? this.props.form.content : currentContent, content: this.props.form.content !== undefined ? this.props.form.content : currentContent,
@@ -134,7 +134,7 @@ export class CommentForm extends Component {
const { formErrors, submitting } = this.props; const { formErrors, submitting } = this.props;
const { localForm } = this.state; const { localForm } = this.state;
const canSubmit = localForm.author.trim() && localForm.email.trim() && localForm.content.trim(); const canSubmit = localForm.name.trim() && localForm.email.trim() && localForm.content.trim();
// 更新提交按钮状态 // 更新提交按钮状态
const submitBtn = this.elements.root.querySelector('button[type="submit"]'); const submitBtn = this.elements.root.querySelector('button[type="submit"]');
@@ -159,8 +159,8 @@ export class CommentForm extends Component {
updateErrors(formErrors) { updateErrors(formErrors) {
if (!this.elements.root) return; if (!this.elements.root) return;
const authorInput = this.elements.root.querySelector('input[name="author"]'); const nameInput = this.elements.root.querySelector('input[name="name"]');
this.updateFieldError(authorInput, formErrors?.author); this.updateFieldError(nameInput, formErrors?.name);
const emailInput = this.elements.root.querySelector('input[name="email"]'); const emailInput = this.elements.root.querySelector('input[name="email"]');
this.updateFieldError(emailInput, formErrors?.email); this.updateFieldError(emailInput, formErrors?.email);
@@ -227,12 +227,12 @@ export class CommentForm extends Component {
* 设置输入框的值 * 设置输入框的值
*/ */
setInputValues(root, form) { setInputValues(root, form) {
const authorInput = root.querySelector('input[name="author"]'); const nameInput = root.querySelector('input[name="name"]');
const emailInput = root.querySelector('input[name="email"]'); const emailInput = root.querySelector('input[name="email"]');
const urlInput = root.querySelector('input[name="url"]'); const urlInput = root.querySelector('input[name="url"]');
const contentTextarea = root.querySelector('textarea'); const contentTextarea = root.querySelector('textarea');
if (authorInput) authorInput.value = form.author || ''; if (nameInput) nameInput.value = form.name || '';
if (emailInput) emailInput.value = form.email || ''; if (emailInput) emailInput.value = form.email || '';
if (urlInput) urlInput.value = form.url || ''; if (urlInput) urlInput.value = form.url || '';
if (contentTextarea) contentTextarea.value = form.content || ''; if (contentTextarea) contentTextarea.value = form.content || '';

View File

@@ -45,7 +45,7 @@ export class CommentItem extends Component {
this.createElement('img', { this.createElement('img', {
attributes: { attributes: {
src: comment.avatar, src: comment.avatar,
alt: comment.author, alt: comment.name,
loading: 'lazy' loading: 'lazy'
} }
}) })
@@ -74,11 +74,11 @@ export class CommentItem extends Component {
target: '_blank', target: '_blank',
rel: 'noopener noreferrer' rel: 'noopener noreferrer'
}, },
text: comment.author text: comment.name
}) })
] ]
}) })
: this.createTextElement('span', comment.author, 'cwd-author-name'), : this.createTextElement('span', comment.name, 'cwd-author-name'),
// 博主标识 // 博主标识
...(isAdmin ? [ ...(isAdmin ? [
this.createTextElement('span', `${adminBadge}`, 'cwd-admin-badge') this.createTextElement('span', `${adminBadge}`, 'cwd-admin-badge')
@@ -102,7 +102,7 @@ export class CommentItem extends Component {
}, },
text: '回复' text: '回复'
}), }),
this.createTextElement('span', formatRelativeTime(comment.pubDate), 'cwd-comment-time') this.createTextElement('span', formatRelativeTime(comment.created), 'cwd-comment-time')
] ]
}) })
] ]
@@ -140,7 +140,7 @@ export class CommentItem extends Component {
const replyContainer = root.querySelector('.cwd-reply-editor-container'); const replyContainer = root.querySelector('.cwd-reply-editor-container');
if (replyContainer) { if (replyContainer) {
this.replyEditor = new ReplyEditor(replyContainer, { this.replyEditor = new ReplyEditor(replyContainer, {
replyToAuthor: comment.author, replyToAuthor: comment.name,
content: this.props.replyContent, content: this.props.replyContent,
error: this.props.replyError, error: this.props.replyError,
submitting: this.props.submitting, submitting: this.props.submitting,
@@ -212,7 +212,7 @@ export class CommentItem extends Component {
if (isReplying && replyContainer) { if (isReplying && replyContainer) {
// 显示回复编辑器 // 显示回复编辑器
this.replyEditor = new ReplyEditor(replyContainer, { this.replyEditor = new ReplyEditor(replyContainer, {
replyToAuthor: comment.author, replyToAuthor: comment.name,
content: this.props.replyContent, content: this.props.replyContent,
error: this.props.replyError, error: this.props.replyError,
submitting: this.props.submitting, submitting: this.props.submitting,

View File

@@ -39,7 +39,7 @@ export function createApiClient(config) {
/** /**
* 提交评论 * 提交评论
* @param {Object} data - 评论数据 * @param {Object} data - 评论数据
* @param {string} data.author - 昵称 * @param {string} data.name - 昵称
* @param {string} data.email - 邮箱 * @param {string} data.email - 邮箱
* @param {string} data.url - 网址(可选) * @param {string} data.url - 网址(可选)
* @param {string} data.content - 评论内容 * @param {string} data.content - 评论内容
@@ -56,7 +56,7 @@ export function createApiClient(config) {
post_slug: config.postSlug, post_slug: config.postSlug,
post_title: config.postTitle, post_title: config.postTitle,
post_url: config.postUrl, post_url: config.postUrl,
author: data.author, name: data.name,
email: data.email, email: data.email,
url: data.url || undefined, url: data.url || undefined,
content: data.content, content: data.content,

View File

@@ -15,7 +15,7 @@ function loadUserInfo() {
if (data) { if (data) {
const parsed = JSON.parse(data); const parsed = JSON.parse(data);
return { return {
author: parsed.author || '', name: parsed.name || '',
email: parsed.email || '', email: parsed.email || '',
url: parsed.url || '' url: parsed.url || ''
}; };
@@ -23,18 +23,18 @@ function loadUserInfo() {
} catch (e) { } catch (e) {
console.error('读取用户信息失败:', e); console.error('读取用户信息失败:', e);
} }
return { author: '', email: '', url: '' }; return { name: '', email: '', url: '' };
} }
/** /**
* 保存用户信息到 localStorage * 保存用户信息到 localStorage
* @param {string} author - 昵称 * @param {string} name - 昵称
* @param {string} email - 邮箱 * @param {string} email - 邮箱
* @param {string} url - 网址 * @param {string} url - 网址
*/ */
function saveUserInfo(author, email, url) { function saveUserInfo(name, email, url) {
try { try {
const data = { author, email, url }; const data = { name, email, url };
localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) { } catch (e) {
console.error('保存用户信息失败:', e); console.error('保存用户信息失败:', e);
@@ -115,7 +115,7 @@ export function createCommentStore(config, fetchComments, submitComment) {
// 表单数据 // 表单数据
form: { form: {
author: savedInfo.author || '', name: savedInfo.name || '',
email: savedInfo.email || '', email: savedInfo.email || '',
url: savedInfo.url || '', url: savedInfo.url || '',
content: '' content: ''
@@ -131,8 +131,8 @@ export function createCommentStore(config, fetchComments, submitComment) {
// 监听用户信息变化,自动保存到 localStorage // 监听用户信息变化,自动保存到 localStorage
store.subscribe((state) => { store.subscribe((state) => {
if (state.form.author || state.form.email || state.form.url) { if (state.form.name || state.form.email || state.form.url) {
saveUserInfo(state.form.author, state.form.email, state.form.url); saveUserInfo(state.form.name, state.form.email, state.form.url);
} }
}); });
@@ -194,7 +194,7 @@ export function createCommentStore(config, fetchComments, submitComment) {
try { try {
await submitComment({ await submitComment({
author: form.author, name: form.name,
email: form.email, email: form.email,
url: form.url, url: form.url,
content: form.content content: form.content
@@ -249,7 +249,7 @@ export function createCommentStore(config, fetchComments, submitComment) {
try { try {
await submitComment({ await submitComment({
author: state.form.author, name: state.form.name,
email: state.form.email, email: state.form.email,
url: state.form.url, url: state.form.url,
content: state.replyContent, content: state.replyContent,

View File

@@ -4,11 +4,11 @@
/** /**
* 格式化时间3天内显示相对时间超过3天显示完整日期 * 格式化时间3天内显示相对时间超过3天显示完整日期
* @param {string} dateStr - 日期字符串 * @param {string|number} dateValue - 日期字符串或时间戳
* @returns {string} * @returns {string}
*/ */
export function formatRelativeTime(dateStr) { export function formatRelativeTime(dateValue) {
const date = new Date(dateStr); const date = new Date(dateValue);
const now = new Date(); const now = new Date();
const diff = now.getTime() - date.getTime(); const diff = now.getTime() - date.getTime();
@@ -32,16 +32,16 @@ export function formatRelativeTime(dateStr) {
} }
// 超过3天显示完整日期 // 超过3天显示完整日期
return formatDateTime(dateStr); return formatDateTime(dateValue);
} }
/** /**
* 格式化日期时间 * 格式化日期时间
* @param {string} dateStr - 日期字符串 * @param {string|number} dateValue - 日期字符串或时间戳
* @returns {string} * @returns {string}
*/ */
export function formatDateTime(dateStr) { export function formatDateTime(dateValue) {
const date = new Date(dateStr); const date = new Date(dateValue);
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
@@ -53,11 +53,11 @@ export function formatDateTime(dateStr) {
/** /**
* 格式化日期 * 格式化日期
* @param {string} dateStr - 日期字符串 * @param {string|number} dateValue - 日期字符串或时间戳
* @returns {string} * @returns {string}
*/ */
export function formatDate(dateStr) { export function formatDate(dateValue) {
const date = new Date(dateStr); const date = new Date(dateValue);
const year = date.getFullYear(); const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0');
@@ -66,11 +66,11 @@ export function formatDate(dateStr) {
/** /**
* 格式化时间 * 格式化时间
* @param {string} dateStr - 日期字符串 * @param {string|number} dateValue - 日期字符串或时间戳
* @returns {string} * @returns {string}
*/ */
export function formatTime(dateStr) { export function formatTime(dateValue) {
const date = new Date(dateStr); const date = new Date(dateValue);
const hours = String(date.getHours()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0');

View File

@@ -45,7 +45,7 @@ export function validateCommentContent(content) {
/** /**
* 验证评论表单 * 验证评论表单
* @param {Object} data - 表单数据 * @param {Object} data - 表单数据
* @param {string} data.author - 昵称 * @param {string} data.name - 昵称
* @param {string} data.email - 邮箱 * @param {string} data.email - 邮箱
* @param {string} data.url - 网址 * @param {string} data.url - 网址
* @param {string} data.content - 评论内容 * @param {string} data.content - 评论内容
@@ -55,10 +55,10 @@ export function validateCommentForm(data) {
const errors = {}; const errors = {};
// 验证昵称 // 验证昵称
if (!data.author || data.author.trim().length === 0) { if (!data.name || data.name.trim().length === 0) {
errors.author = '请输入昵称'; errors.name = '请输入昵称';
} else if (data.author.length > 50) { } else if (data.name.length > 50) {
errors.author = '昵称不能超过 50 字'; errors.name = '昵称不能超过 50 字';
} }
// 验证邮箱 // 验证邮箱
@@ -88,7 +88,7 @@ export function validateCommentForm(data) {
/** /**
* 验证回复所需的用户信息 * 验证回复所需的用户信息
* @param {Object} data - 用户信息 * @param {Object} data - 用户信息
* @param {string} data.author - 昵称 * @param {string} data.name - 昵称
* @param {string} data.email - 邮箱 * @param {string} data.email - 邮箱
* @param {string} data.url - 网址 * @param {string} data.url - 网址
* @returns {{valid: boolean, errors: Object<string, string>}} * @returns {{valid: boolean, errors: Object<string, string>}}
@@ -97,10 +97,10 @@ export function validateReplyUserInfo(data) {
const errors = {}; const errors = {};
// 验证昵称 // 验证昵称
if (!data.author || data.author.trim().length === 0) { if (!data.name || data.name.trim().length === 0) {
errors.author = '请输入昵称'; errors.name = '请输入昵称';
} else if (data.author.length > 50) { } else if (data.name.length > 50) {
errors.author = '昵称不能超过 50 字'; errors.name = '昵称不能超过 50 字';
} }
// 验证邮箱 // 验证邮箱