refactor: 移除开发日志并优化代码格式

fix(api): 添加头像前缀参数支持
This commit is contained in:
anghunk
2026-01-20 14:25:29 +08:00
parent 78e263faa4
commit 039411b142
5 changed files with 708 additions and 740 deletions

File diff suppressed because one or more lines are too long

View File

@@ -13,417 +13,405 @@ import styles from '@/styles/main.css?inline';
* CWDComments 评论组件主类 * CWDComments 评论组件主类
*/ */
export class CWDComments { export class CWDComments {
/** /**
* @param {Object} config - 配置对象 * @param {Object} config - 配置对象
* @param {string|HTMLElement} config.el - 挂载元素选择器或 DOM 元素 * @param {string|HTMLElement} config.el - 挂载元素选择器或 DOM 元素
* @param {string} config.apiBaseUrl - API 基础地址 * @param {string} config.apiBaseUrl - API 基础地址
* @param {'light'|'dark'} [config.theme] - 主题(可选) * @param {'light'|'dark'} [config.theme] - 主题(可选)
* @param {number} [config.pageSize] - 每页评论数(可选,默认 20 * @param {number} [config.pageSize] - 每页评论数(可选,默认 20
* *
* 以下字段由组件自动推导或从后端读取,无需通过 config 传入: * 以下字段由组件自动推导或从后端读取,无需通过 config 传入:
* - postSlugwindow.location.origin + window.location.pathname * - postSlugwindow.location.origin + window.location.pathname
* - postTitledocument.title 或 postSlug * - postTitledocument.title 或 postSlug
* - postUrlwindow.location.href * - postUrlwindow.location.href
* - avatarPrefix/adminEmail/adminBadge通过 /api/config/comments 接口获取 * - avatarPrefix/adminEmail/adminBadge通过 /api/config/comments 接口获取
*/ */
constructor(config) { constructor(config) {
this.config = { ...config }; this.config = { ...config };
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
this.config.postSlug = window.location.origin + window.location.pathname; this.config.postSlug = window.location.origin + window.location.pathname;
} }
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
this.config.postTitle = document.title || this.config.postSlug; this.config.postTitle = document.title || this.config.postSlug;
} }
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
this.config.postUrl = window.location.href; this.config.postUrl = window.location.href;
} }
this.hostElement = this._resolveElement(config.el); this.hostElement = this._resolveElement(config.el);
this.shadowRoot = null; this.shadowRoot = null;
this.mountPoint = null; this.mountPoint = null;
this.commentForm = null; this.commentForm = null;
this.commentList = null; this.commentList = null;
this.store = null; this.store = null;
this.unsubscribe = null; this.unsubscribe = null;
// 初始加载标志 // 初始加载标志
this._mounted = false; this._mounted = false;
} }
/** /**
* 解析挂载元素 * 解析挂载元素
* @private * @private
*/ */
_resolveElement(el) { _resolveElement(el) {
if (typeof el === 'string') { if (typeof el === 'string') {
const element = document.querySelector(el); const element = document.querySelector(el);
if (!element) { if (!element) {
throw new Error(`元素未找到: ${el}`); throw new Error(`元素未找到: ${el}`);
} }
if (!(element instanceof HTMLElement)) { if (!(element instanceof HTMLElement)) {
throw new Error(`目标不是 HTMLElement: ${el}`); throw new Error(`目标不是 HTMLElement: ${el}`);
} }
return element; return element;
} }
return el; return el;
} }
async _loadServerConfig() { async _loadServerConfig() {
try { try {
const base = this.config.apiBaseUrl; const base = this.config.apiBaseUrl;
if (!base) { if (!base) {
return {}; return {};
} }
const apiBaseUrl = base.replace(/\/$/, ''); const apiBaseUrl = base.replace(/\/$/, '');
const res = await fetch(`${apiBaseUrl}/api/config/comments`); const res = await fetch(`${apiBaseUrl}/api/config/comments`);
if (!res.ok) { if (!res.ok) {
return {}; return {};
} }
const data = await res.json(); const data = await res.json();
return { return {
adminEmail: data.adminEmail || '', adminEmail: data.adminEmail || '',
adminBadge: data.adminBadge || '', adminBadge: data.adminBadge || '',
adminEnabled: !!data.adminEnabled, adminEnabled: !!data.adminEnabled,
avatarPrefix: data.avatarPrefix || '' avatarPrefix: data.avatarPrefix || '',
}; };
} catch (e) { } catch (e) {
console.warn('[CWDComments] 加载服务端评论配置失败:', e); return {};
return {}; }
} }
}
/** /**
* 挂载组件 * 挂载组件
*/ */
mount() { mount() {
if (this._mounted) { if (this._mounted) {
return; return;
} }
// 创建 Shadow DOM // 创建 Shadow DOM
this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' }); this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' });
// 注入样式 // 注入样式
const styleElement = document.createElement('style'); const styleElement = document.createElement('style');
if (typeof styles === 'string') { if (typeof styles === 'string') {
styleElement.textContent = styles; styleElement.textContent = styles;
} else if (styles && typeof styles === 'object' && 'default' in styles) { } else if (styles && typeof styles === 'object' && 'default' in styles) {
styleElement.textContent = styles.default; styleElement.textContent = styles.default;
} }
this.shadowRoot.appendChild(styleElement); this.shadowRoot.appendChild(styleElement);
// 创建容器 // 创建容器
this.mountPoint = document.createElement('div'); this.mountPoint = document.createElement('div');
this.mountPoint.className = 'cwd-comments-container'; this.mountPoint.className = 'cwd-comments-container';
this.shadowRoot.appendChild(this.mountPoint); this.shadowRoot.appendChild(this.mountPoint);
// 设置主题 // 设置主题
if (this.config.theme) { if (this.config.theme) {
this.mountPoint.setAttribute('data-theme', this.config.theme); this.mountPoint.setAttribute('data-theme', this.config.theme);
} }
(async () => { (async () => {
const serverConfig = await this._loadServerConfig(); const serverConfig = await this._loadServerConfig();
if (!this._mounted) { if (!this._mounted) {
return; return;
} }
if (serverConfig.avatarPrefix) { if (serverConfig.avatarPrefix) {
this.config.avatarPrefix = serverConfig.avatarPrefix; this.config.avatarPrefix = serverConfig.avatarPrefix;
} }
if (serverConfig.adminEnabled && serverConfig.adminEmail) { if (serverConfig.adminEnabled && serverConfig.adminEmail) {
this.config.adminEmail = serverConfig.adminEmail; this.config.adminEmail = serverConfig.adminEmail;
} }
if (serverConfig.adminEnabled && serverConfig.adminBadge) { if (serverConfig.adminEnabled && serverConfig.adminBadge) {
this.config.adminBadge = serverConfig.adminBadge; this.config.adminBadge = serverConfig.adminBadge;
} }
const api = createApiClient(this.config); const api = createApiClient(this.config);
this.store = createCommentStore( this.store = createCommentStore(this.config, api.fetchComments.bind(api), api.submitComment.bind(api));
this.config,
api.fetchComments.bind(api),
api.submitComment.bind(api)
);
this.unsubscribe = this.store.store.subscribe((state) => { this.unsubscribe = this.store.store.subscribe((state) => {
this._onStateChange(state); this._onStateChange(state);
}); });
this._render(); this._render();
this.store.loadComments(); this.store.loadComments();
})(); })();
this._mounted = true; this._mounted = true;
} }
/** /**
* 卸载组件 * 卸载组件
*/ */
unmount() { unmount() {
if (!this._mounted) { if (!this._mounted) {
return; return;
} }
// 销毁组件 // 销毁组件
if (this.commentForm) { if (this.commentForm) {
this.commentForm.destroy(); this.commentForm.destroy();
this.commentForm = null; this.commentForm = null;
} }
if (this.commentList) { if (this.commentList) {
this.commentList.destroy(); this.commentList.destroy();
this.commentList = null; this.commentList = null;
} }
// 取消订阅 // 取消订阅
if (this.unsubscribe) { if (this.unsubscribe) {
this.unsubscribe(); this.unsubscribe();
this.unsubscribe = null; this.unsubscribe = null;
} }
// 移除 Shadow DOM - 通过替换所有子节点 // 移除 Shadow DOM - 通过替换所有子节点
if (this.hostElement) { if (this.hostElement) {
// Shadow DOM 会在清空子节点时自动移除 // Shadow DOM 会在清空子节点时自动移除
while (this.hostElement.firstChild) { while (this.hostElement.firstChild) {
this.hostElement.removeChild(this.hostElement.firstChild); this.hostElement.removeChild(this.hostElement.firstChild);
} }
} }
this.shadowRoot = null; this.shadowRoot = null;
this.mountPoint = null; this.mountPoint = null;
this.store = null; this.store = null;
this._mounted = false; this._mounted = false;
}
console.log('[CWDComments] 组件已卸载'); /**
} * 渲染组件
* @private
*/
_render() {
if (!this.mountPoint) {
return;
}
/** const state = this.store.store.getState();
* 渲染组件
* @private
*/
_render() {
if (!this.mountPoint) {
return;
}
const state = this.store.store.getState(); // 创建评论表单
if (!this.commentForm) {
this.commentForm = new CommentForm(this.mountPoint, {
form: state.form,
formErrors: state.formErrors,
submitting: state.submitting,
onSubmit: () => this._handleSubmit(),
onFieldChange: (field, value) => this.store.updateFormField(field, value),
});
this.commentForm.render();
}
// 创建评论表单 // 创建错误提示
if (!this.commentForm) { const existingError = this.mountPoint.querySelector('.cwd-error-inline');
this.commentForm = new CommentForm(this.mountPoint, { if (state.error) {
form: state.form, if (!existingError) {
formErrors: state.formErrors, const errorEl = document.createElement('div');
submitting: state.submitting, errorEl.className = 'cwd-error-inline';
onSubmit: () => this._handleSubmit(), errorEl.innerHTML = `
onFieldChange: (field, value) => this.store.updateFormField(field, value)
});
this.commentForm.render();
}
// 创建错误提示
const existingError = this.mountPoint.querySelector('.cwd-error-inline');
if (state.error) {
if (!existingError) {
const errorEl = document.createElement('div');
errorEl.className = 'cwd-error-inline';
errorEl.innerHTML = `
<span>${state.error}</span> <span>${state.error}</span>
<button type="button" class="cwd-error-close" data-action="clear-error">✕</button> <button type="button" class="cwd-error-close" data-action="clear-error">✕</button>
`; `;
errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => {
this.store.clearError(); this.store.clearError();
}); });
this.mountPoint.insertBefore(errorEl, this.mountPoint.firstChild); this.mountPoint.insertBefore(errorEl, this.mountPoint.firstChild);
} }
} else if (existingError) { } else if (existingError) {
existingError.remove(); existingError.remove();
} }
// 创建头部统计 // 创建头部统计
let header = this.mountPoint.querySelector('.cwd-comments-header'); let header = this.mountPoint.querySelector('.cwd-comments-header');
if (!header) { if (!header) {
header = document.createElement('div'); header = document.createElement('div');
header.className = 'cwd-comments-header'; header.className = 'cwd-comments-header';
header.innerHTML = ` header.innerHTML = `
<h3 class="cwd-comments-count"> <h3 class="cwd-comments-count">
共 <span class="cwd-comments-count-number">0</span> 条评论 共 <span class="cwd-comments-count-number">0</span> 条评论
</h3> </h3>
`; `;
this.mountPoint.appendChild(header); this.mountPoint.appendChild(header);
} }
const countEl = header.querySelector('.cwd-comments-count-number'); const countEl = header.querySelector('.cwd-comments-count-number');
if (countEl) { if (countEl) {
countEl.textContent = state.pagination.totalCount; countEl.textContent = state.pagination.totalCount;
} }
// 创建评论列表 // 创建评论列表
if (!this.commentList) { if (!this.commentList) {
const listContainer = document.createElement('div'); const listContainer = document.createElement('div');
this.mountPoint.appendChild(listContainer); this.mountPoint.appendChild(listContainer);
this.commentList = new CommentList(listContainer, { this.commentList = new CommentList(listContainer, {
comments: state.comments, comments: state.comments,
loading: state.loading, loading: state.loading,
error: null, error: null,
currentPage: state.pagination.page, currentPage: state.pagination.page,
totalPages: this.store.getTotalPages(), totalPages: this.store.getTotalPages(),
replyingTo: state.replyingTo, replyingTo: state.replyingTo,
replyContent: state.replyContent, replyContent: state.replyContent,
replyError: state.replyError, replyError: state.replyError,
submitting: state.submitting, submitting: state.submitting,
adminEmail: this.config.adminEmail, adminEmail: this.config.adminEmail,
adminBadge: this.config.adminBadge || '博主', adminBadge: this.config.adminBadge || '博主',
onRetry: () => this.store.loadComments(), onRetry: () => this.store.loadComments(),
onReply: (commentId) => this.store.startReply(commentId), onReply: (commentId) => this.store.startReply(commentId),
onSubmitReply: (commentId) => this.store.submitReply(commentId), onSubmitReply: (commentId) => this.store.submitReply(commentId),
onCancelReply: () => this.store.cancelReply(), onCancelReply: () => this.store.cancelReply(),
onUpdateReplyContent: (content) => this.store.updateReplyContent(content), onUpdateReplyContent: (content) => this.store.updateReplyContent(content),
onClearReplyError: () => this.store.clearReplyError(), onClearReplyError: () => this.store.clearReplyError(),
onPrevPage: () => this.store.goToPage(state.pagination.page - 1), onPrevPage: () => this.store.goToPage(state.pagination.page - 1),
onNextPage: () => this.store.goToPage(state.pagination.page + 1), onNextPage: () => this.store.goToPage(state.pagination.page + 1),
onGoToPage: (page) => this.store.goToPage(page) onGoToPage: (page) => this.store.goToPage(page),
}); });
this.commentList.render(); this.commentList.render();
} }
} }
/** /**
* 状态变化处理 * 状态变化处理
* @private * @private
*/ */
_onStateChange(state, prevState) { _onStateChange(state, prevState) {
if (!this._mounted) { if (!this._mounted) {
return; return;
} }
// 根据回复状态显示/隐藏主评论表单 // 根据回复状态显示/隐藏主评论表单
if (this.commentForm?.elements?.root) { if (this.commentForm?.elements?.root) {
const formRoot = this.commentForm.elements.root; const formRoot = this.commentForm.elements.root;
if (state.replyingTo !== null) { if (state.replyingTo !== null) {
formRoot.style.display = 'none'; formRoot.style.display = 'none';
} else { } else {
formRoot.style.display = ''; formRoot.style.display = '';
} }
} }
// 更新评论表单 // 更新评论表单
if (this.commentForm) { if (this.commentForm) {
this.commentForm.setProps({ this.commentForm.setProps({
form: state.form, form: state.form,
formErrors: state.formErrors, formErrors: state.formErrors,
submitting: state.submitting submitting: state.submitting,
}); });
} }
// 更新错误提示 // 更新错误提示
const existingError = this.mountPoint?.querySelector('.cwd-error-inline'); const existingError = this.mountPoint?.querySelector('.cwd-error-inline');
if (state.error) { if (state.error) {
if (!existingError) { if (!existingError) {
const errorEl = document.createElement('div'); const errorEl = document.createElement('div');
errorEl.className = 'cwd-error-inline'; errorEl.className = 'cwd-error-inline';
errorEl.innerHTML = ` errorEl.innerHTML = `
<span>${state.error}</span> <span>${state.error}</span>
<button type="button" class="cwd-error-close" data-action="clear-error">✕</button> <button type="button" class="cwd-error-close" data-action="clear-error">✕</button>
`; `;
errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => {
this.store.clearError(); this.store.clearError();
}); });
this.mountPoint?.insertBefore(errorEl, this.mountPoint.firstChild); this.mountPoint?.insertBefore(errorEl, this.mountPoint.firstChild);
} }
} else if (existingError) { } else if (existingError) {
existingError.remove(); existingError.remove();
} }
// 更新头部统计 // 更新头部统计
const header = this.mountPoint?.querySelector('.cwd-comments-header'); const header = this.mountPoint?.querySelector('.cwd-comments-header');
const countEl = header?.querySelector('.cwd-comments-count-number'); const countEl = header?.querySelector('.cwd-comments-count-number');
if (countEl) { if (countEl) {
countEl.textContent = state.pagination.totalCount; countEl.textContent = state.pagination.totalCount;
} }
// 更新评论列表 // 更新评论列表
if (this.commentList) { if (this.commentList) {
this.commentList.setProps({ this.commentList.setProps({
comments: state.comments, comments: state.comments,
loading: state.loading, loading: state.loading,
currentPage: state.pagination.page, currentPage: state.pagination.page,
totalPages: this.store.getTotalPages(), totalPages: this.store.getTotalPages(),
replyingTo: state.replyingTo, replyingTo: state.replyingTo,
replyContent: state.replyContent, replyContent: state.replyContent,
replyError: state.replyError, replyError: state.replyError,
submitting: state.submitting submitting: state.submitting,
}); });
} }
} }
/** /**
* 处理评论提交 * 处理评论提交
* @private * @private
*/ */
async _handleSubmit() { async _handleSubmit() {
const success = await this.store.submitNewComment(); const success = await this.store.submitNewComment();
if (success) { if (success) {
// 表单内容已在 store 中清空 // 表单内容已在 store 中清空
// 更新表单组件 // 更新表单组件
if (this.commentForm) { if (this.commentForm) {
this.commentForm.state.localForm = { ...this.store.store.getState().form }; this.commentForm.state.localForm = { ...this.store.store.getState().form };
} }
} }
} }
/** /**
* 更新配置 * 更新配置
* @param {Object} newConfig - 新配置 * @param {Object} newConfig - 新配置
*/ */
updateConfig(newConfig) { updateConfig(newConfig) {
const prevConfig = { ...this.config }; const prevConfig = { ...this.config };
Object.assign(this.config, newConfig); Object.assign(this.config, newConfig);
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
this.config.postSlug = window.location.origin + window.location.pathname; this.config.postSlug = window.location.origin + window.location.pathname;
} }
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
this.config.postTitle = document.title || this.config.postSlug; this.config.postTitle = document.title || this.config.postSlug;
} }
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
this.config.postUrl = window.location.href; this.config.postUrl = window.location.href;
} }
// 更新主题 // 更新主题
if (newConfig.theme && this.mountPoint) { if (newConfig.theme && this.mountPoint) {
this.mountPoint.setAttribute('data-theme', newConfig.theme); this.mountPoint.setAttribute('data-theme', newConfig.theme);
} }
const shouldReload = const shouldReload =
this.config.apiBaseUrl !== prevConfig.apiBaseUrl || this.config.apiBaseUrl !== prevConfig.apiBaseUrl ||
this.config.pageSize !== prevConfig.pageSize || this.config.pageSize !== prevConfig.pageSize ||
this.config.postSlug !== prevConfig.postSlug; this.config.postSlug !== prevConfig.postSlug;
if (shouldReload) { if (shouldReload) {
const api = createApiClient(this.config); const api = createApiClient(this.config);
if (this.unsubscribe) { if (this.unsubscribe) {
this.unsubscribe(); this.unsubscribe();
} }
this.store = createCommentStore( this.store = createCommentStore(this.config, api.fetchComments.bind(api), api.submitComment.bind(api));
this.config,
api.fetchComments.bind(api),
api.submitComment.bind(api)
);
this.unsubscribe = this.store.store.subscribe((state) => { this.unsubscribe = this.store.store.subscribe((state) => {
this._onStateChange(state); this._onStateChange(state);
}); });
this.store.loadComments(); this.store.loadComments();
} }
} }
/**
* 获取当前配置
* @returns {Object}
*/
getConfig() {
return { ...this.config };
}
/**
* 获取当前配置
* @returns {Object}
*/
getConfig() {
return { ...this.config };
}
} }

View File

@@ -11,67 +11,70 @@
* @param {string} config.postUrl - 文章 URL可选 * @param {string} config.postUrl - 文章 URL可选
* @returns {Object} * @returns {Object}
*/ */
export function createApiClient(config) { export function createApiClient(config) {
const baseUrl = config.apiBaseUrl.replace(/\/$/, ''); const baseUrl = config.apiBaseUrl.replace(/\/$/, '');
/** /**
* 获取评论列表 * 获取评论列表
* @param {number} page - 页码 * @param {number} page - 页码
* @param {number} limit - 每页数量 * @param {number} limit - 每页数量
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
async function fetchComments(page = 1, limit = 20) { async function fetchComments(page = 1, limit = 20) {
const params = new URLSearchParams({ const params = new URLSearchParams({
post_slug: config.postSlug, post_slug: config.postSlug,
page: page.toString(), page: page.toString(),
limit: limit.toString(), limit: limit.toString(),
nested: 'true' nested: 'true',
}); });
const response = await fetch(`${baseUrl}/api/comments?${params}`); if (config.avatarPrefix) {
if (!response.ok) { params.set('avatar_prefix', config.avatarPrefix);
throw new Error(`获取评论失败: ${response.status} ${response.statusText}`); }
}
console.log('[API] fetchComments response:', response);
return response.json();
}
/** const response = await fetch(`${baseUrl}/api/comments?${params}`);
* 提交评论 if (!response.ok) {
* @param {Object} data - 评论数据 throw new Error(`获取评论失败: ${response.status} ${response.statusText}`);
* @param {string} data.name - 昵称 }
* @param {string} data.email - 邮箱 return response.json();
* @param {string} data.url - 网址(可选) }
* @param {string} data.content - 评论内容
* @param {number} data.parentId - 父评论 ID可选用于回复
* @returns {Promise<Object>}
*/
async function submitComment(data) {
const response = await fetch(`${baseUrl}/api/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
post_slug: config.postSlug,
post_title: config.postTitle,
post_url: config.postUrl,
name: data.name,
email: data.email,
url: data.url || undefined,
content: data.content,
parent_id: data.parentId
})
});
if (!response.ok) { /**
throw new Error(`提交评论失败: ${response.status} ${response.statusText}`); * 提交评论
} * @param {Object} data - 评论数据
return response.json(); * @param {string} data.name - 昵称
} * @param {string} data.email - 邮箱
* @param {string} data.url - 网址(可选)
* @param {string} data.content - 评论内容
* @param {number} data.parentId - 父评论 ID可选用于回复
* @returns {Promise<Object>}
*/
async function submitComment(data) {
const response = await fetch(`${baseUrl}/api/comments`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
post_slug: config.postSlug,
post_title: config.postTitle,
post_url: config.postUrl,
name: data.name,
email: data.email,
url: data.url || undefined,
content: data.content,
parent_id: data.parentId,
}),
});
return { if (!response.ok) {
fetchComments, throw new Error(`提交评论失败: ${response.status} ${response.statusText}`);
submitComment }
}; return response.json();
}
return {
fetchComments,
submitComment,
};
} }

View File

@@ -10,20 +10,18 @@ const STORAGE_KEY = 'cwd_user_info';
* @returns {Object} * @returns {Object}
*/ */
function loadUserInfo() { function loadUserInfo() {
try { try {
const data = localStorage.getItem(STORAGE_KEY); const data = localStorage.getItem(STORAGE_KEY);
if (data) { if (data) {
const parsed = JSON.parse(data); const parsed = JSON.parse(data);
return { return {
name: parsed.name || '', name: parsed.name || '',
email: parsed.email || '', email: parsed.email || '',
url: parsed.url || '' url: parsed.url || '',
}; };
} }
} catch (e) { } catch (e) {}
console.error('读取用户信息失败:', e); return { name: '', email: '', url: '' };
}
return { name: '', email: '', url: '' };
} }
/** /**
@@ -33,58 +31,56 @@ function loadUserInfo() {
* @param {string} url - 网址 * @param {string} url - 网址
*/ */
function saveUserInfo(name, email, url) { function saveUserInfo(name, email, url) {
try { try {
const data = { name, 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);
}
} }
/** /**
* 简单的 Store 类 * 简单的 Store 类
*/ */
class Store { class Store {
constructor(initialState) { constructor(initialState) {
this.state = { ...initialState }; this.state = { ...initialState };
this.listeners = []; this.listeners = [];
} }
/** /**
* 获取当前状态 * 获取当前状态
* @returns {Object} * @returns {Object}
*/ */
getState() { getState() {
return { ...this.state }; return { ...this.state };
} }
/** /**
* 更新状态 * 更新状态
* @param {Object} updates - 要更新的属性 * @param {Object} updates - 要更新的属性
*/ */
setState(updates) { setState(updates) {
const prevState = { ...this.state }; const prevState = { ...this.state };
this.state = { ...this.state, ...updates }; this.state = { ...this.state, ...updates };
// 通知所有监听器 // 通知所有监听器
this.listeners.forEach(listener => { this.listeners.forEach((listener) => {
listener(this.state, prevState); listener(this.state, prevState);
}); });
} }
/** /**
* 订阅状态变化 * 订阅状态变化
* @param {Function} listener - 监听器函数 * @param {Function} listener - 监听器函数
* @returns {Function} - 取消订阅的函数 * @returns {Function} - 取消订阅的函数
*/ */
subscribe(listener) { subscribe(listener) {
this.listeners.push(listener); this.listeners.push(listener);
// 返回取消订阅的函数 // 返回取消订阅的函数
return () => { return () => {
this.listeners = this.listeners.filter(l => l !== listener); this.listeners = this.listeners.filter((l) => l !== listener);
}; };
} }
} }
/** /**
@@ -95,281 +91,277 @@ class Store {
* @returns {Object} * @returns {Object}
*/ */
export function createCommentStore(config, fetchComments, submitComment) { export function createCommentStore(config, fetchComments, submitComment) {
// 从 localStorage 加载用户信息 // 从 localStorage 加载用户信息
const savedInfo = loadUserInfo(); const savedInfo = loadUserInfo();
// 创建 store 实例 // 创建 store 实例
const store = new Store({ const store = new Store({
// 评论数据 // 评论数据
comments: [], comments: [],
loading: true, loading: true,
error: null, error: null,
// 分页 // 分页
pagination: { pagination: {
page: 1, page: 1,
limit: config.pageSize || 20, limit: config.pageSize || 20,
total: 0, total: 0,
totalCount: 0 totalCount: 0,
}, },
// 表单数据 // 表单数据
form: { form: {
name: savedInfo.name || '', name: savedInfo.name || '',
email: savedInfo.email || '', email: savedInfo.email || '',
url: savedInfo.url || '', url: savedInfo.url || '',
content: '' content: '',
}, },
formErrors: {}, formErrors: {},
submitting: false, submitting: false,
// 回复状态 // 回复状态
replyingTo: null, replyingTo: null,
replyContent: '', replyContent: '',
replyError: null replyError: null,
}); });
// 监听用户信息变化,自动保存到 localStorage // 监听用户信息变化,自动保存到 localStorage
store.subscribe((state) => { store.subscribe((state) => {
if (state.form.name || state.form.email || state.form.url) { if (state.form.name || state.form.email || state.form.url) {
saveUserInfo(state.form.name, state.form.email, state.form.url); saveUserInfo(state.form.name, state.form.email, state.form.url);
} }
}); });
/** /**
* 加载评论列表 * 加载评论列表
* @param {number} page - 页码 * @param {number} page - 页码
*/ */
async function loadComments(page = 1) { async function loadComments(page = 1) {
store.setState({ store.setState({
loading: true, loading: true,
error: null error: null,
}); });
try { try {
const response = await fetchComments(page, store.getState().pagination.limit); const response = await fetchComments(page, store.getState().pagination.limit);
console.log('[Store] loadComments response:', response); store.setState({
console.log('[Store] comments data:', response.data); comments: response.data,
store.setState({ pagination: {
comments: response.data, page: response.pagination.page,
pagination: { limit: response.pagination.limit,
page: response.pagination.page, total: response.pagination.total,
limit: response.pagination.limit, totalCount: response.pagination.totalCount,
total: response.pagination.total, },
totalCount: response.pagination.totalCount loading: false,
}, });
loading: false } catch (e) {
}); store.setState({
} catch (e) { error: e instanceof Error ? e.message : '加载评论失败',
store.setState({ loading: false,
error: e instanceof Error ? e.message : '加载评论失败', });
loading: false }
}); }
}
}
/** /**
* 提交评论 * 提交评论
*/ */
async function submitNewComment() { async function submitNewComment() {
const state = store.getState(); const state = store.getState();
const form = state.form; const form = state.form;
// 验证表单 // 验证表单
const { validateCommentForm } = await import('@/utils/validator.js'); const { validateCommentForm } = await import('@/utils/validator.js');
const validation = validateCommentForm(form); const validation = validateCommentForm(form);
if (!validation.valid) { if (!validation.valid) {
store.setState({ store.setState({
formErrors: validation.errors formErrors: validation.errors,
}); });
return false; return false;
} }
// 清空错误 // 清空错误
store.setState({ store.setState({
formErrors: {}, formErrors: {},
submitting: true, submitting: true,
error: null error: null,
}); });
try { try {
await submitComment({ await submitComment({
name: form.name, name: form.name,
email: form.email, email: form.email,
url: form.url, url: form.url,
content: form.content content: form.content,
}); });
// 清空评论内容 // 清空评论内容
store.setState({ store.setState({
form: { ...form, content: '' }, form: { ...form, content: '' },
submitting: false submitting: false,
}); });
// 重新加载评论 // 重新加载评论
await loadComments(state.pagination.page); await loadComments(state.pagination.page);
return true; return true;
} catch (e) { } catch (e) {
store.setState({ store.setState({
error: e instanceof Error ? e.message : '提交评论失败', error: e instanceof Error ? e.message : '提交评论失败',
submitting: false submitting: false,
}); });
return false; return false;
} }
} }
/** /**
* 提交回复 * 提交回复
* @param {number} parentId - 父评论 ID * @param {number} parentId - 父评论 ID
*/ */
async function submitReply(parentId) { async function submitReply(parentId) {
const state = store.getState(); const state = store.getState();
// 验证回复内容 // 验证回复内容
if (!state.replyContent.trim()) { if (!state.replyContent.trim()) {
return false; return false;
} }
// 验证用户信息 // 验证用户信息
const { validateReplyUserInfo } = await import('@/utils/validator.js'); const { validateReplyUserInfo } = await import('@/utils/validator.js');
const validation = validateReplyUserInfo(state.form); const validation = validateReplyUserInfo(state.form);
if (!validation.valid) { if (!validation.valid) {
const errorMessages = Object.values(validation.errors).join(''); const errorMessages = Object.values(validation.errors).join('');
store.setState({ store.setState({
replyError: errorMessages replyError: errorMessages,
}); });
return false; return false;
} }
store.setState({ store.setState({
formErrors: {}, formErrors: {},
submitting: true, submitting: true,
replyError: null replyError: null,
}); });
try { try {
await submitComment({ await submitComment({
name: state.form.name, 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,
parentId parentId,
}); });
// 清空回复内容并关闭回复框 // 清空回复内容并关闭回复框
store.setState({ store.setState({
replyContent: '', replyContent: '',
replyingTo: null, replyingTo: null,
submitting: false submitting: false,
}); });
// 重新加载评论 // 重新加载评论
await loadComments(state.pagination.page); await loadComments(state.pagination.page);
return true; return true;
} catch (e) { } catch (e) {
store.setState({ store.setState({
error: e instanceof Error ? e.message : '提交回复失败', error: e instanceof Error ? e.message : '提交回复失败',
submitting: false submitting: false,
}); });
return false; return false;
} }
} }
/** /**
* 开始回复 * 开始回复
* @param {number} commentId - 评论 ID * @param {number} commentId - 评论 ID
*/ */
function startReply(commentId) { function startReply(commentId) {
console.log('[Store] startReply called with commentId:', commentId); store.setState({
store.setState({ replyingTo: commentId,
replyingTo: commentId, replyContent: '',
replyContent: '', replyError: null,
replyError: null });
}); }
console.log('[Store] New state:', store.getState());
}
/** /**
* 取消回复 * 取消回复
*/ */
function cancelReply() { function cancelReply() {
store.setState({ store.setState({
replyingTo: null, replyingTo: null,
replyContent: '', replyContent: '',
replyError: null replyError: null,
}); });
} }
/** /**
* 更新表单字段 * 更新表单字段
* @param {string} field - 字段名 * @param {string} field - 字段名
* @param {string} value - 值 * @param {string} value - 值
*/ */
function updateFormField(field, value) { function updateFormField(field, value) {
const form = { ...store.getState().form }; const form = { ...store.getState().form };
form[field] = value; form[field] = value;
store.setState({ form }); store.setState({ form });
} }
/** /**
* 更新回复内容 * 更新回复内容
* @param {string} content - 回复内容 * @param {string} content - 回复内容
*/ */
function updateReplyContent(content) { function updateReplyContent(content) {
store.setState({ store.setState({
replyContent: content replyContent: content,
}); });
} }
/** /**
* 清除回复错误 * 清除回复错误
*/ */
function clearReplyError() { function clearReplyError() {
store.setState({ store.setState({
replyError: null replyError: null,
}); });
} }
/** /**
* 清除错误 * 清除错误
*/ */
function clearError() { function clearError() {
store.setState({ store.setState({
error: null error: null,
}); });
} }
/** /**
* 切换页码 * 切换页码
* @param {number} page - 页码 * @param {number} page - 页码
*/ */
function goToPage(page) { function goToPage(page) {
const totalPages = store.getState().pagination.total; const totalPages = store.getState().pagination.total;
if (page >= 1 && page <= totalPages) { if (page >= 1 && page <= totalPages) {
loadComments(page); loadComments(page);
} }
} }
return { return {
// Store 实例 // Store 实例
store, store,
// 计算属性方法 // 计算属性方法
getTotalPages: () => { getTotalPages: () => {
const state = store.getState(); const state = store.getState();
return state.pagination.total; return state.pagination.total;
}, },
// 操作方法 // 操作方法
loadComments, loadComments,
submitNewComment, submitNewComment,
submitReply, submitReply,
startReply, startReply,
cancelReply, cancelReply,
updateFormField, updateFormField,
updateReplyContent, updateReplyContent,
clearReplyError, clearReplyError,
clearError, clearError,
goToPage goToPage,
}; };
} }

View File

@@ -25,9 +25,7 @@ function loadConfigFromStorage() {
if (saved) { if (saved) {
return { ...DEFAULT_CONFIG, ...JSON.parse(saved) }; return { ...DEFAULT_CONFIG, ...JSON.parse(saved) };
} }
} catch (e) { } catch (e) {}
console.warn('[CWDComments] 读取本地存储失败:', e);
}
return DEFAULT_CONFIG; return DEFAULT_CONFIG;
} }
@@ -37,9 +35,7 @@ function loadConfigFromStorage() {
function saveConfigToStorage(config) { function saveConfigToStorage(config) {
try { try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
} catch (e) { } catch (e) {}
console.warn('[CWDComments] 保存到本地存储失败:', e);
}
} }
/** /**
@@ -94,10 +90,7 @@ async function initWidget() {
pageSize: 20, pageSize: 20,
}); });
widgetInstance.mount(); widgetInstance.mount();
console.log('[CWDComments] Widget 初始化成功', config); } catch (error) {}
} catch (error) {
console.error('[CWDComments] Widget 初始化失败:', error);
}
} }
/** /**
@@ -105,7 +98,6 @@ async function initWidget() {
*/ */
function toggleTheme() { function toggleTheme() {
if (!widgetInstance) { if (!widgetInstance) {
console.warn('[CWDComments] 请先初始化 widget');
return; return;
} }
@@ -123,8 +115,6 @@ function toggleTheme() {
const config = getConfigFromInputs(); const config = getConfigFromInputs();
config.theme = newTheme; config.theme = newTheme;
saveConfigToStorage(config); saveConfigToStorage(config);
console.log('[CWDComments] 主题已切换为:', newTheme);
} }
/** /**
@@ -134,10 +124,7 @@ function clearConfig() {
try { try {
localStorage.removeItem(STORAGE_KEY); localStorage.removeItem(STORAGE_KEY);
populateInputs(DEFAULT_CONFIG); populateInputs(DEFAULT_CONFIG);
console.log('[CWDComments] 配置已重置为默认值'); } catch (e) {}
} catch (e) {
console.error('[CWDComments] 重置配置失败:', e);
}
} }
// 将函数挂载到 window 对象,使其在 HTML 中可访问 // 将函数挂载到 window 对象,使其在 HTML 中可访问
@@ -147,8 +134,6 @@ window.clearConfig = clearConfig;
// 页面加载完成后自动初始化 // 页面加载完成后自动初始化
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
console.log('[CWDComments] 开发模式 - 页面加载完成,正在初始化...');
// 从本地存储加载配置并填充到输入框 // 从本地存储加载配置并填充到输入框
const savedConfig = loadConfigFromStorage(); const savedConfig = loadConfigFromStorage();
populateInputs(savedConfig); populateInputs(savedConfig);