475 lines
9.5 KiB
JavaScript
475 lines
9.5 KiB
JavaScript
/**
|
||
* 状态管理 - 使用发布-订阅模式
|
||
*/
|
||
|
||
import { auth } from '../utils/auth.js';
|
||
|
||
// localStorage 键名
|
||
const STORAGE_KEY = 'cwd_user_info';
|
||
|
||
/**
|
||
* 从 localStorage 读取用户信息
|
||
* @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) {}
|
||
return { name: '', email: '', url: '' };
|
||
}
|
||
|
||
/**
|
||
* 保存用户信息到 localStorage
|
||
* @param {string} name - 昵称
|
||
* @param {string} email - 邮箱
|
||
* @param {string} url - 网址
|
||
*/
|
||
function saveUserInfo(name, email, url) {
|
||
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 = [];
|
||
}
|
||
|
||
/**
|
||
* 获取当前状态
|
||
* @returns {Object}
|
||
*/
|
||
getState() {
|
||
return { ...this.state };
|
||
}
|
||
|
||
/**
|
||
* 更新状态
|
||
* @param {Object} updates - 要更新的属性
|
||
*/
|
||
setState(updates) {
|
||
const prevState = { ...this.state };
|
||
this.state = { ...this.state, ...updates };
|
||
|
||
// 通知所有监听器
|
||
this.listeners.forEach((listener) => {
|
||
listener(this.state, prevState);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 订阅状态变化
|
||
* @param {Function} listener - 监听器函数
|
||
* @returns {Function} - 取消订阅的函数
|
||
*/
|
||
subscribe(listener) {
|
||
this.listeners.push(listener);
|
||
|
||
// 返回取消订阅的函数
|
||
return () => {
|
||
this.listeners = this.listeners.filter((l) => l !== listener);
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 创建评论状态管理 Store
|
||
* @param {Object} config - 配置对象
|
||
* @param {Function} fetchComments - 获取评论的函数
|
||
* @param {Function} submitComment - 提交评论的函数
|
||
* @param {Function} likeCommentFn - 点赞评论的函数
|
||
* @returns {Object}
|
||
*/
|
||
export function createCommentStore(config, fetchComments, submitComment, likeCommentFn) {
|
||
// 从 localStorage 加载用户信息
|
||
const savedInfo = loadUserInfo();
|
||
|
||
// 创建 store 实例
|
||
const store = new Store({
|
||
// 评论数据
|
||
comments: [],
|
||
loading: true,
|
||
error: null,
|
||
successMessage: '',
|
||
|
||
// 分页
|
||
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,
|
||
|
||
// 回复状态
|
||
replyingTo: null,
|
||
replyContent: '',
|
||
replyError: null,
|
||
likeCount: 0,
|
||
liked: false,
|
||
commentLikeLoadingId: 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);
|
||
}
|
||
});
|
||
|
||
/**
|
||
* 加载评论列表
|
||
* @param {number} page - 页码
|
||
*/
|
||
async function loadComments(page = 1) {
|
||
store.setState({
|
||
loading: true,
|
||
error: null,
|
||
});
|
||
|
||
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,
|
||
});
|
||
}
|
||
}
|
||
|
||
function setLikeState(likeCount, liked) {
|
||
const safeCount = typeof likeCount === 'number' && Number.isFinite(likeCount) && likeCount >= 0 ? likeCount : 0;
|
||
store.setState({
|
||
likeCount: safeCount,
|
||
liked: !!liked,
|
||
});
|
||
}
|
||
|
||
async function likeComment(commentId) {
|
||
const state = store.getState();
|
||
if (!likeCommentFn || state.commentLikeLoadingId === commentId) {
|
||
return;
|
||
}
|
||
const id =
|
||
typeof commentId === 'number'
|
||
? commentId
|
||
: typeof commentId === 'string' && commentId.trim()
|
||
? Number.parseInt(commentId.trim(), 10)
|
||
: NaN;
|
||
if (!Number.isFinite(id) || id <= 0) {
|
||
return;
|
||
}
|
||
store.setState({
|
||
commentLikeLoadingId: id,
|
||
});
|
||
try {
|
||
const safeComments = Array.isArray(state.comments) ? state.comments : [];
|
||
const nextComments = safeComments.map((item) => {
|
||
if (!item || typeof item.id !== 'number') {
|
||
return item;
|
||
}
|
||
if (item.id === id) {
|
||
const current =
|
||
typeof item.likes === 'number' && Number.isFinite(item.likes) && item.likes >= 0
|
||
? item.likes
|
||
: 0;
|
||
return {
|
||
...item,
|
||
likes: current + 1,
|
||
};
|
||
}
|
||
if (Array.isArray(item.replies) && item.replies.length > 0) {
|
||
const updatedReplies = item.replies.map((reply) => {
|
||
if (!reply || typeof reply.id !== 'number') {
|
||
return reply;
|
||
}
|
||
if (reply.id === id) {
|
||
const current =
|
||
typeof reply.likes === 'number' && Number.isFinite(reply.likes) && reply.likes >= 0
|
||
? reply.likes
|
||
: 0;
|
||
return {
|
||
...reply,
|
||
likes: current + 1,
|
||
};
|
||
}
|
||
return reply;
|
||
});
|
||
return {
|
||
...item,
|
||
replies: updatedReplies,
|
||
};
|
||
}
|
||
return item;
|
||
});
|
||
store.setState({
|
||
comments: nextComments,
|
||
});
|
||
await likeCommentFn(id);
|
||
} catch (e) {
|
||
} finally {
|
||
const latest = store.getState();
|
||
if (latest.commentLikeLoadingId === id) {
|
||
store.setState({
|
||
commentLikeLoadingId: null,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 提交评论
|
||
*/
|
||
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;
|
||
}
|
||
|
||
// 清空错误
|
||
store.setState({
|
||
formErrors: {},
|
||
submitting: true,
|
||
error: null,
|
||
successMessage: '',
|
||
});
|
||
|
||
try {
|
||
const result = await submitComment({
|
||
name: form.name,
|
||
email: form.email,
|
||
url: form.url,
|
||
content: form.content,
|
||
adminToken: auth.getToken() // Add token if exists
|
||
});
|
||
|
||
const successMessage =
|
||
result && typeof result.message === 'string'
|
||
? result.message
|
||
: config.requireReview
|
||
? '已提交评论,待管理员审核后显示'
|
||
: '评论已提交';
|
||
|
||
store.setState({
|
||
form: { ...form, content: '' },
|
||
submitting: false,
|
||
successMessage,
|
||
});
|
||
|
||
// 重新加载评论
|
||
await loadComments(state.pagination.page);
|
||
return true;
|
||
} catch (e) {
|
||
store.setState({
|
||
error: e instanceof Error ? e.message : '提交评论失败',
|
||
submitting: false,
|
||
successMessage: '',
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 提交回复
|
||
* @param {number} parentId - 父评论 ID
|
||
*/
|
||
async function submitReply(parentId) {
|
||
const state = store.getState();
|
||
|
||
// 验证回复内容
|
||
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;
|
||
}
|
||
|
||
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,
|
||
adminToken: auth.getToken()
|
||
});
|
||
|
||
// 清空回复内容并关闭回复框
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 开始回复
|
||
* @param {number} commentId - 评论 ID
|
||
*/
|
||
function startReply(commentId) {
|
||
store.setState({
|
||
replyingTo: commentId,
|
||
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} content - 回复内容
|
||
*/
|
||
function updateReplyContent(content) {
|
||
store.setState({
|
||
replyContent: content,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 清除回复错误
|
||
*/
|
||
function clearReplyError() {
|
||
store.setState({
|
||
replyError: null,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 清除错误
|
||
*/
|
||
function clearError() {
|
||
store.setState({
|
||
error: null,
|
||
});
|
||
}
|
||
|
||
function clearSuccess() {
|
||
store.setState({
|
||
successMessage: '',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 切换页码
|
||
* @param {number} page - 页码
|
||
*/
|
||
function goToPage(page) {
|
||
const totalPages = store.getState().pagination.total;
|
||
if (page >= 1 && page <= totalPages) {
|
||
loadComments(page);
|
||
}
|
||
}
|
||
|
||
return {
|
||
// Store 实例
|
||
store,
|
||
|
||
// 计算属性方法
|
||
getTotalPages: () => {
|
||
const state = store.getState();
|
||
return state.pagination.total;
|
||
},
|
||
|
||
// 操作方法
|
||
loadComments,
|
||
submitNewComment,
|
||
submitReply,
|
||
startReply,
|
||
cancelReply,
|
||
updateFormField,
|
||
updateReplyContent,
|
||
clearReplyError,
|
||
clearError,
|
||
clearSuccess,
|
||
goToPage,
|
||
setLikeState,
|
||
likeComment,
|
||
};
|
||
}
|