完成Work组件的界面和一些小修改
> 1. 修改优化路由守卫
> 2. 去除拦截器中的调试信息
> 3. 修改头部导航条下拉菜单的样式增加图标。
> 4. work组件现在使用mock数据
Change-Id: Ic602a35bb02e645a0d5253c5cbd12a68d70bfb33
diff --git a/src/feature/work/workSlice.ts b/src/feature/work/workSlice.ts
new file mode 100644
index 0000000..6b2c12c
--- /dev/null
+++ b/src/feature/work/workSlice.ts
@@ -0,0 +1,469 @@
+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;
\ No newline at end of file