blob: 6b2c12cfc262a8ae6d759fbd6ba3cf38f5a145b7 [file] [log] [blame]
import { createSlice, createAsyncThunk, type PayloadAction } from '@reduxjs/toolkit';
import type { ArtworkData, Comment } from './types';
import type { RootState } from '../../store/store';
import { getArtworkById, getCommentsForArtwork } from './mockData';
// ==================== 类型定义 ====================
interface WorkState {
currentArtwork: ArtworkData | null;
loading: {
artwork: boolean;
comments: boolean;
addComment: boolean;
updateArtwork: boolean;
deleteComment: boolean;
};
error: {
artwork: string | null;
comments: string | null;
addComment: string | null;
updateArtwork: string | null;
deleteComment: string | null;
};
comments: {
list: Comment[];
total: number;
current: number;
pageSize: number;
};
}
interface FetchCommentsParams {
workId: string;
page: number;
pageSize: number;
}
interface AddCommentParams {
workId: string;
content: string;
parentId?: string;
}
interface UpdateArtworkParams {
workId: string;
updates: Partial<ArtworkData>;
}
interface DeleteCommentParams {
workId: string;
commentId: string;
}
interface SetCommentsPageParams {
current: number;
pageSize: number;
}
// ==================== 初始状态 ====================
const initialState: WorkState = {
currentArtwork: null,
loading: {
artwork: false,
comments: false,
addComment: false,
updateArtwork: false,
deleteComment: false,
},
error: {
artwork: null,
comments: null,
addComment: null,
updateArtwork: null,
deleteComment: null,
},
comments: {
list: [],
total: 0,
current: 1,
pageSize: 5,
},
};
// ==================== Mock 工具函数 ====================
// 模拟网络延迟
const mockDelay = (ms: number = 800): Promise<void> =>
new Promise(resolve => setTimeout(resolve, ms));
// 生成新评论ID
const generateCommentId = (): string => {
return `comment_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
};
// 生成新评论数据
const createNewComment = (content: string): Comment => {
return {
id: generateCommentId(),
content,
author: '当前用户', // 实际应用中从用户状态获取
authorId: 'current_user_id',
createdAt: new Date().toLocaleString('zh-CN'),
child: [],
};
};
// 递归删除评论
const removeCommentById = (comments: Comment[], targetId: string): Comment[] => {
return comments.filter(comment => {
if (comment.id === targetId) {
return false;
}
if (comment.child.length > 0) {
comment.child = removeCommentById(comment.child, targetId);
}
return true;
});
};
// 递归添加回复评论
const addReplyToComment = (comments: Comment[], parentId: string, newComment: Comment): Comment[] => {
return comments.map(comment => {
if (comment.id === parentId) {
return {
...comment,
child: [...comment.child, newComment]
};
}
if (comment.child.length > 0) {
return {
...comment,
child: addReplyToComment(comment.child, parentId, newComment)
};
}
return comment;
});
};
// 分页处理评论
const paginateComments = (comments: Comment[], page: number, pageSize: number): Comment[] => {
const startIndex = (page - 1) * pageSize;
const endIndex = startIndex + pageSize;
return comments.slice(startIndex, endIndex);
};
// ==================== 异步 Actions ====================
// 获取作品详情
export const fetchArtworkDetail = createAsyncThunk<
ArtworkData,
string,
{ rejectValue: string }
>(
'work/fetchArtworkDetail',
async (workId: string, { rejectWithValue }) => {
try {
await mockDelay(600); // 模拟网络延迟
const artwork = getArtworkById(workId);
if (!artwork) {
throw new Error(`作品 ${workId} 不存在`);
}
return artwork;
} catch (error) {
const message = error instanceof Error ? error.message : '获取作品详情失败';
return rejectWithValue(message);
}
}
);
// 获取评论列表
export const fetchComments = createAsyncThunk<
{ comments: Comment[]; total: number },
FetchCommentsParams,
{ rejectValue: string }
>(
'work/fetchComments',
async ({ workId, page, pageSize }, { rejectWithValue }) => {
try {
await mockDelay(400); // 模拟网络延迟
const allComments = getCommentsForArtwork(workId);
const paginatedComments = paginateComments(allComments, page, pageSize);
return {
comments: paginatedComments,
total: allComments.length
};
} catch (error) {
const message = error instanceof Error ? error.message : '获取评论失败';
return rejectWithValue(message);
}
}
);
// 添加评论
export const addComment = createAsyncThunk<
Comment,
AddCommentParams,
{ rejectValue: string }
>(
'work/addComment',
async ({ workId, content }, { rejectWithValue }) => {
try {
await mockDelay(500); // 模拟网络延迟
// 验证作品是否存在
const artwork = getArtworkById(workId);
if (!artwork) {
throw new Error('作品不存在');
}
// 创建新评论
const newComment = createNewComment(content);
// 模拟服务器返回完整的评论数据
return newComment;
} catch (error) {
const message = error instanceof Error ? error.message : '添加评论失败';
return rejectWithValue(message);
}
}
);
// 更新作品信息
export const updateArtwork = createAsyncThunk<
ArtworkData,
UpdateArtworkParams,
{ rejectValue: string }
>(
'work/updateArtwork',
async ({ workId, updates }, { rejectWithValue }) => {
try {
await mockDelay(1000); // 模拟网络延迟
const currentArtwork = getArtworkById(workId);
if (!currentArtwork) {
throw new Error('作品不存在');
}
// 模拟文件上传处理
const processedUpdates = { ...updates };
// 如果包含 blob URL,模拟转换为正式URL
if (updates.artworkCover && updates.artworkCover.startsWith('blob:')) {
// 模拟上传成功,生成新的图片URL
processedUpdates.artworkCover = `https://picsum.photos/300/400?random=${Date.now()}`;
}
// 处理版本文件上传
if (updates.versionList) {
processedUpdates.versionList = updates.versionList.map(version => ({
...version,
seedFile: version.seedFile.startsWith?.('blob:')
? `magnet:?xt=urn:btih:updated_${Date.now()}&dn=${version.version}.zip`
: version.seedFile
}));
}
// 合并更新后的数据
const updatedArtwork: ArtworkData = {
...currentArtwork,
...processedUpdates,
updatedAt: new Date().toISOString(),
};
return updatedArtwork;
} catch (error) {
const message = error instanceof Error ? error.message : '更新作品失败';
return rejectWithValue(message);
}
}
);
// 删除评论
export const deleteComment = createAsyncThunk<
string,
DeleteCommentParams,
{ rejectValue: string }
>(
'work/deleteComment',
async ({ workId, commentId }, { rejectWithValue }) => {
try {
await mockDelay(300); // 模拟网络延迟
// 验证作品是否存在
const artwork = getArtworkById(workId);
if (!artwork) {
throw new Error('作品不存在');
}
// 模拟删除成功
return commentId;
} catch (error) {
const message = error instanceof Error ? error.message : '删除评论失败';
return rejectWithValue(message);
}
}
);
// ==================== Slice 定义 ====================
const workSlice = createSlice({
name: 'work',
initialState,
reducers: {
// 清除当前作品
clearCurrentArtwork: (state) => {
state.currentArtwork = null;
state.comments.list = [];
state.comments.total = 0;
state.comments.current = 1;
// 清除所有错误状态
Object.keys(state.error).forEach(key => {
state.error[key as keyof typeof state.error] = null;
});
},
// 设置评论分页
setCommentsPage: (state, action: PayloadAction<SetCommentsPageParams>) => {
state.comments.current = action.payload.current;
state.comments.pageSize = action.payload.pageSize;
},
// 清除特定错误
clearError: (state, action: PayloadAction<keyof WorkState['error']>) => {
state.error[action.payload] = null;
},
// 清除所有错误
clearAllErrors: (state) => {
Object.keys(state.error).forEach(key => {
state.error[key as keyof typeof state.error] = null;
});
},
},
extraReducers: (builder) => {
// 获取作品详情
builder
.addCase(fetchArtworkDetail.pending, (state) => {
state.loading.artwork = true;
state.error.artwork = null;
})
.addCase(fetchArtworkDetail.fulfilled, (state, action) => {
state.loading.artwork = false;
state.currentArtwork = action.payload;
state.error.artwork = null;
})
.addCase(fetchArtworkDetail.rejected, (state, action) => {
state.loading.artwork = false;
state.error.artwork = action.payload || '获取作品详情失败';
});
// 获取评论列表
builder
.addCase(fetchComments.pending, (state) => {
state.loading.comments = true;
state.error.comments = null;
})
.addCase(fetchComments.fulfilled, (state, action) => {
state.loading.comments = false;
state.comments.list = action.payload.comments;
state.comments.total = action.payload.total;
state.error.comments = null;
})
.addCase(fetchComments.rejected, (state, action) => {
state.loading.comments = false;
state.error.comments = action.payload || '获取评论失败';
});
// 添加评论
builder
.addCase(addComment.pending, (state) => {
state.loading.addComment = true;
state.error.addComment = null;
})
.addCase(addComment.fulfilled, (state, action) => {
state.loading.addComment = false;
const newComment = action.payload;
const { parentId } = action.meta.arg;
if (parentId) {
// 添加回复评论
state.comments.list = addReplyToComment(state.comments.list, parentId, newComment);
} else {
// 添加顶级评论
state.comments.list.unshift(newComment);
state.comments.total += 1;
}
state.error.addComment = null;
})
.addCase(addComment.rejected, (state, action) => {
state.loading.addComment = false;
state.error.addComment = action.payload || '添加评论失败';
});
// 更新作品信息
builder
.addCase(updateArtwork.pending, (state) => {
state.loading.updateArtwork = true;
state.error.updateArtwork = null;
})
.addCase(updateArtwork.fulfilled, (state, action) => {
state.loading.updateArtwork = false;
state.currentArtwork = action.payload;
state.error.updateArtwork = null;
})
.addCase(updateArtwork.rejected, (state, action) => {
state.loading.updateArtwork = false;
state.error.updateArtwork = action.payload || '更新作品失败';
});
// 删除评论
builder
.addCase(deleteComment.pending, (state) => {
state.loading.deleteComment = true;
state.error.deleteComment = null;
})
.addCase(deleteComment.fulfilled, (state, action) => {
state.loading.deleteComment = false;
// 从评论列表中移除已删除的评论
state.comments.list = removeCommentById(state.comments.list, action.payload);
state.comments.total = Math.max(0, state.comments.total - 1);
state.error.deleteComment = null;
})
.addCase(deleteComment.rejected, (state, action) => {
state.loading.deleteComment = false;
state.error.deleteComment = action.payload || '删除评论失败';
});
},
});
// ==================== Actions 导出 ====================
export const {
clearCurrentArtwork,
setCommentsPage,
clearError,
clearAllErrors
} = workSlice.actions;
// ==================== Selectors 导出 ====================
export const selectCurrentArtwork = (state: RootState): ArtworkData | null =>
state.work.currentArtwork;
export const selectWorkLoading = (state: RootState): WorkState['loading'] =>
state.work.loading;
export const selectWorkError = (state: RootState): WorkState['error'] =>
state.work.error;
export const selectComments = (state: RootState): WorkState['comments'] =>
state.work.comments;
export const selectIsAuthor = (state: RootState): boolean => {
const currentUser = state.user;
const currentArtwork = state.work.currentArtwork;
return Boolean(
currentUser?.userid &&
currentArtwork?.authorId &&
String(currentUser.userid) === String(currentArtwork.authorId)
);
};
// ==================== Reducer 导出 ====================
export default workSlice.reducer;