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

View File

@@ -11,67 +11,70 @@
* @param {string} config.postUrl - 文章 URL可选
* @returns {Object}
*/
export function createApiClient(config) {
const baseUrl = config.apiBaseUrl.replace(/\/$/, '');
export function createApiClient(config) {
const baseUrl = config.apiBaseUrl.replace(/\/$/, '');
/**
* 获取评论列表
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @returns {Promise<Object>}
*/
async function fetchComments(page = 1, limit = 20) {
const params = new URLSearchParams({
post_slug: config.postSlug,
page: page.toString(),
limit: limit.toString(),
nested: 'true'
});
/**
* 获取评论列表
* @param {number} page - 页码
* @param {number} limit - 每页数量
* @returns {Promise<Object>}
*/
async function fetchComments(page = 1, limit = 20) {
const params = new URLSearchParams({
post_slug: config.postSlug,
page: page.toString(),
limit: limit.toString(),
nested: 'true',
});
const response = await fetch(`${baseUrl}/api/comments?${params}`);
if (!response.ok) {
throw new Error(`获取评论失败: ${response.status} ${response.statusText}`);
}
console.log('[API] fetchComments response:', response);
return response.json();
}
if (config.avatarPrefix) {
params.set('avatar_prefix', config.avatarPrefix);
}
/**
* 提交评论
* @param {Object} data - 评论数据
* @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
})
});
const response = await fetch(`${baseUrl}/api/comments?${params}`);
if (!response.ok) {
throw new Error(`获取评论失败: ${response.status} ${response.statusText}`);
}
return response.json();
}
if (!response.ok) {
throw new Error(`提交评论失败: ${response.status} ${response.statusText}`);
}
return response.json();
}
/**
* 提交评论
* @param {Object} data - 评论数据
* @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 {
fetchComments,
submitComment
};
if (!response.ok) {
throw new Error(`提交评论失败: ${response.status} ${response.statusText}`);
}
return response.json();
}
return {
fetchComments,
submitComment,
};
}

View File

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

View File

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