From 86be1c230e4f5c5b354894ea2d6437e142b1ebc1 Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Sun, 31 May 2026 03:47:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=A1=E5=9B=AD=E8=AE=BA=E5=9D=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bootstrap/providers.php | 2 + database/seeders/SysUserSeeder.php | 83 +++++++ .../Controllers/ForumCategoryController.php | 73 +++++++ .../Controllers/ForumCommentController.php | 74 +++++++ .../Http/Controllers/ForumPostController.php | 76 +++++++ .../ForumPostFavoriteController.php | 46 ++++ .../Controllers/ForumPostLikeController.php | 46 ++++ .../ForumSensitiveWordController.php | 74 +++++++ .../Requests/ForumCategoryFormRequest.php | 50 +++++ .../Http/Requests/ForumCommentFormRequest.php | 31 +++ .../Http/Requests/ForumPostFormRequest.php | 33 +++ .../ForumSensitiveWordFormRequest.php | 43 ++++ modules/Forum/Models/ForumCategoryModel.php | 26 +++ modules/Forum/Models/ForumCommentModel.php | 29 +++ .../Forum/Models/ForumPostFavoriteModel.php | 23 ++ modules/Forum/Models/ForumPostLikeModel.php | 23 ++ modules/Forum/Models/ForumPostModel.php | 41 ++++ .../Forum/Models/ForumSensitiveWordModel.php | 22 ++ .../Forum/Providers/ForumServiceProvider.php | 14 ++ web/domain/iForumCategory.ts | 12 ++ web/domain/iForumComment.ts | 12 ++ web/domain/iForumPost.ts | 18 ++ web/domain/iForumPostFavorite.ts | 6 + web/domain/iForumPostLike.ts | 6 + web/domain/iForumSensitiveWord.ts | 9 + web/locales/en_US/forum/category.ts | 18 ++ web/locales/en_US/forum/comment.ts | 17 ++ web/locales/en_US/forum/post-favorite.ts | 8 + web/locales/en_US/forum/post-like.ts | 8 + web/locales/en_US/forum/post.ts | 28 +++ web/locales/en_US/forum/sensitive-word.ts | 16 ++ web/locales/en_US/index.ts | 13 ++ web/locales/en_US/menu.ts | 7 + web/locales/zh_CN/forum/category.ts | 18 ++ web/locales/zh_CN/forum/comment.ts | 17 ++ web/locales/zh_CN/forum/post-favorite.ts | 8 + web/locales/zh_CN/forum/post-like.ts | 8 + web/locales/zh_CN/forum/post.ts | 28 +++ web/locales/zh_CN/forum/sensitive-word.ts | 16 ++ web/locales/zh_CN/index.ts | 13 ++ web/locales/zh_CN/menu.ts | 7 + web/pages/forum/category/index.tsx | 142 ++++++++++++ web/pages/forum/comment/index.tsx | 144 +++++++++++++ web/pages/forum/post-favorite/index.tsx | 84 ++++++++ web/pages/forum/post-like/index.tsx | 84 ++++++++ web/pages/forum/post/index.tsx | 203 ++++++++++++++++++ web/pages/forum/sensitive-word/index.tsx | 133 ++++++++++++ 47 files changed, 1892 insertions(+) create mode 100644 modules/Forum/Http/Controllers/ForumCategoryController.php create mode 100644 modules/Forum/Http/Controllers/ForumCommentController.php create mode 100644 modules/Forum/Http/Controllers/ForumPostController.php create mode 100644 modules/Forum/Http/Controllers/ForumPostFavoriteController.php create mode 100644 modules/Forum/Http/Controllers/ForumPostLikeController.php create mode 100644 modules/Forum/Http/Controllers/ForumSensitiveWordController.php create mode 100644 modules/Forum/Http/Requests/ForumCategoryFormRequest.php create mode 100644 modules/Forum/Http/Requests/ForumCommentFormRequest.php create mode 100644 modules/Forum/Http/Requests/ForumPostFormRequest.php create mode 100644 modules/Forum/Http/Requests/ForumSensitiveWordFormRequest.php create mode 100644 modules/Forum/Models/ForumCategoryModel.php create mode 100644 modules/Forum/Models/ForumCommentModel.php create mode 100644 modules/Forum/Models/ForumPostFavoriteModel.php create mode 100644 modules/Forum/Models/ForumPostLikeModel.php create mode 100644 modules/Forum/Models/ForumPostModel.php create mode 100644 modules/Forum/Models/ForumSensitiveWordModel.php create mode 100644 modules/Forum/Providers/ForumServiceProvider.php create mode 100644 web/domain/iForumCategory.ts create mode 100644 web/domain/iForumComment.ts create mode 100644 web/domain/iForumPost.ts create mode 100644 web/domain/iForumPostFavorite.ts create mode 100644 web/domain/iForumPostLike.ts create mode 100644 web/domain/iForumSensitiveWord.ts create mode 100644 web/locales/en_US/forum/category.ts create mode 100644 web/locales/en_US/forum/comment.ts create mode 100644 web/locales/en_US/forum/post-favorite.ts create mode 100644 web/locales/en_US/forum/post-like.ts create mode 100644 web/locales/en_US/forum/post.ts create mode 100644 web/locales/en_US/forum/sensitive-word.ts create mode 100644 web/locales/zh_CN/forum/category.ts create mode 100644 web/locales/zh_CN/forum/comment.ts create mode 100644 web/locales/zh_CN/forum/post-favorite.ts create mode 100644 web/locales/zh_CN/forum/post-like.ts create mode 100644 web/locales/zh_CN/forum/post.ts create mode 100644 web/locales/zh_CN/forum/sensitive-word.ts create mode 100644 web/pages/forum/category/index.tsx create mode 100644 web/pages/forum/comment/index.tsx create mode 100644 web/pages/forum/post-favorite/index.tsx create mode 100644 web/pages/forum/post-like/index.tsx create mode 100644 web/pages/forum/post/index.tsx create mode 100644 web/pages/forum/sensitive-word/index.tsx diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 2086dba..fa02973 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -7,6 +7,7 @@ use Modules\SystemAgent\Providers\SystemAgentServiceProvider; use Modules\SystemTool\Providers\SystemToolServiceProvider; use Modules\SystemUser\Providers\SystemUserServiceProvider; use Modules\Member\Providers\MemberServiceProvider; +use Modules\Forum\Providers\ForumServiceProvider; return [ AppServiceProvider::class, @@ -16,4 +17,5 @@ return [ SystemUserServiceProvider::class, SystemToolServiceProvider::class, MemberServiceProvider::class, + ForumServiceProvider::class, ]; diff --git a/database/seeders/SysUserSeeder.php b/database/seeders/SysUserSeeder.php index 2e3912e..2991830 100644 --- a/database/seeders/SysUserSeeder.php +++ b/database/seeders/SysUserSeeder.php @@ -394,6 +394,89 @@ class SysUserSeeder extends Seeder ], ], ], + [ + 'type' => 'menu', + 'name' => '校园论坛', + 'local' => 'menu.forum', + 'icon' => 'MessageOutlined', + 'key' => 'forum', + 'children' => [ + [ + 'type' => 'route', + 'name' => '话题管理', + 'key' => 'forum.category', + 'path' => '/forum/category', + 'local' => 'menu.forum.category', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.category.query'], + ['type' => 'rule', 'name' => '新增分类', 'key' => 'forum.category.create'], + ['type' => 'rule', 'name' => '修改分类', 'key' => 'forum.category.update'], + ['type' => 'rule', 'name' => '删除分类', 'key' => 'forum.category.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '帖子管理', + 'key' => 'forum.post', + 'path' => '/forum/post', + 'local' => 'menu.forum.post', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.post.query'], + ['type' => 'rule', 'name' => '新增帖子', 'key' => 'forum.post.create'], + ['type' => 'rule', 'name' => '修改帖子', 'key' => 'forum.post.update'], + ['type' => 'rule', 'name' => '删除帖子', 'key' => 'forum.post.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '评论管理', + 'key' => 'forum.comment', + 'path' => '/forum/comment', + 'local' => 'menu.forum.comment', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.comment.query'], + ['type' => 'rule', 'name' => '新增评论', 'key' => 'forum.comment.create'], + ['type' => 'rule', 'name' => '修改评论', 'key' => 'forum.comment.update'], + ['type' => 'rule', 'name' => '删除评论', 'key' => 'forum.comment.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '敏感词管理', + 'key' => 'forum.sensitive-word', + 'path' => '/forum/sensitive-word', + 'local' => 'menu.forum.sensitive-word', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.sensitive-word.query'], + ['type' => 'rule', 'name' => '新增敏感词', 'key' => 'forum.sensitive-word.create'], + ['type' => 'rule', 'name' => '修改敏感词', 'key' => 'forum.sensitive-word.update'], + ['type' => 'rule', 'name' => '删除敏感词', 'key' => 'forum.sensitive-word.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '点赞记录', + 'key' => 'forum.post-like', + 'path' => '/forum/post-like', + 'local' => 'menu.forum.post-like', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.post-like.query'], + ['type' => 'rule', 'name' => '删除记录', 'key' => 'forum.post-like.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '收藏记录', + 'key' => 'forum.post-favorite', + 'path' => '/forum/post-favorite', + 'local' => 'menu.forum.post-favorite', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'forum.post-favorite.query'], + ['type' => 'rule', 'name' => '删除记录', 'key' => 'forum.post-favorite.delete'], + ], + ], + ], + ], [ 'type' => 'route', 'name' => 'XinAdmin', diff --git a/modules/Forum/Http/Controllers/ForumCategoryController.php b/modules/Forum/Http/Controllers/ForumCategoryController.php new file mode 100644 index 0000000..67ae4d4 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumCategoryController.php @@ -0,0 +1,73 @@ + '=', + ]; + + protected array $quickSearchField = ['name', 'slug']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumCategoryModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(ForumCategoryFormRequest $request): JsonResponse + { + ForumCategoryModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, ForumCategoryFormRequest $request): JsonResponse + { + $model = ForumCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Controllers/ForumCommentController.php b/modules/Forum/Http/Controllers/ForumCommentController.php new file mode 100644 index 0000000..dc51112 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumCommentController.php @@ -0,0 +1,74 @@ + '=', + 'post_id' => '=', + ]; + + protected array $quickSearchField = ['content']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumCommentModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(ForumCommentFormRequest $request): JsonResponse + { + ForumCommentModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, ForumCommentFormRequest $request): JsonResponse + { + $model = ForumCommentModel::find($id); + if (empty($model)) { + return $this->error('评论不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumCommentModel::find($id); + if (empty($model)) { + return $this->error('评论不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Controllers/ForumPostController.php b/modules/Forum/Http/Controllers/ForumPostController.php new file mode 100644 index 0000000..1b6af38 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumPostController.php @@ -0,0 +1,76 @@ + '=', + 'is_hot' => '=', + 'is_top' => '=', + 'category_id' => '=', + ]; + + protected array $quickSearchField = ['title']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumPostModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(ForumPostFormRequest $request): JsonResponse + { + ForumPostModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, ForumPostFormRequest $request): JsonResponse + { + $model = ForumPostModel::find($id); + if (empty($model)) { + return $this->error('帖子不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumPostModel::find($id); + if (empty($model)) { + return $this->error('帖子不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Controllers/ForumPostFavoriteController.php b/modules/Forum/Http/Controllers/ForumPostFavoriteController.php new file mode 100644 index 0000000..7ed09f1 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumPostFavoriteController.php @@ -0,0 +1,46 @@ + '=', + ]; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumPostFavoriteModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumPostFavoriteModel::find($id); + if (empty($model)) { + return $this->error('收藏记录不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Controllers/ForumPostLikeController.php b/modules/Forum/Http/Controllers/ForumPostLikeController.php new file mode 100644 index 0000000..80f87f2 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumPostLikeController.php @@ -0,0 +1,46 @@ + '=', + ]; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumPostLikeModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumPostLikeModel::find($id); + if (empty($model)) { + return $this->error('点赞记录不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Controllers/ForumSensitiveWordController.php b/modules/Forum/Http/Controllers/ForumSensitiveWordController.php new file mode 100644 index 0000000..cca3163 --- /dev/null +++ b/modules/Forum/Http/Controllers/ForumSensitiveWordController.php @@ -0,0 +1,74 @@ + '=', + 'type' => '=', + ]; + + protected array $quickSearchField = ['word']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ForumSensitiveWordModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(ForumSensitiveWordFormRequest $request): JsonResponse + { + ForumSensitiveWordModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, ForumSensitiveWordFormRequest $request): JsonResponse + { + $model = ForumSensitiveWordModel::find($id); + if (empty($model)) { + return $this->error('敏感词不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ForumSensitiveWordModel::find($id); + if (empty($model)) { + return $this->error('敏感词不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Forum/Http/Requests/ForumCategoryFormRequest.php b/modules/Forum/Http/Requests/ForumCategoryFormRequest.php new file mode 100644 index 0000000..51c5475 --- /dev/null +++ b/modules/Forum/Http/Requests/ForumCategoryFormRequest.php @@ -0,0 +1,50 @@ +isUpdate()) { + return [ + 'name' => 'required|max:50', + 'slug' => 'required|max:50|unique:forum_categories,slug', + 'parent_id' => 'nullable|integer', + 'description' => 'nullable|max:255', + 'icon' => 'nullable|max:255', + 'sort' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + $id = $this->route('id'); + return [ + 'name' => 'required|max:50', + 'slug' => [ + 'required', + 'max:50', + Rule::unique('forum_categories', 'slug')->ignore($id), + ], + 'parent_id' => 'nullable|integer', + 'description' => 'nullable|max:255', + 'icon' => 'nullable|max:255', + 'sort' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '分类名称不能为空', + 'slug.required' => '分类标识不能为空', + 'slug.unique' => '分类标识已存在', + ]; + } +} diff --git a/modules/Forum/Http/Requests/ForumCommentFormRequest.php b/modules/Forum/Http/Requests/ForumCommentFormRequest.php new file mode 100644 index 0000000..f95e37d --- /dev/null +++ b/modules/Forum/Http/Requests/ForumCommentFormRequest.php @@ -0,0 +1,31 @@ + 'required|integer', + 'user_id' => 'required|integer', + 'content' => 'required', + 'parent_id' => 'nullable|integer', + 'reply_to' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + public function messages(): array + { + return [ + 'post_id.required' => '帖子ID不能为空', + 'user_id.required' => '评论用户不能为空', + 'content.required' => '评论内容不能为空', + ]; + } +} diff --git a/modules/Forum/Http/Requests/ForumPostFormRequest.php b/modules/Forum/Http/Requests/ForumPostFormRequest.php new file mode 100644 index 0000000..0218692 --- /dev/null +++ b/modules/Forum/Http/Requests/ForumPostFormRequest.php @@ -0,0 +1,33 @@ + 'required|max:100', + 'content' => 'required', + 'category_id' => 'nullable|integer', + 'user_id' => 'required|integer', + 'images' => 'nullable|array', + 'is_hot' => 'nullable|integer|in:0,1', + 'is_top' => 'nullable|integer|in:0,1', + 'status' => 'nullable|integer|in:0,1,2', + ]; + } + + public function messages(): array + { + return [ + 'title.required' => '帖子标题不能为空', + 'content.required' => '帖子内容不能为空', + 'user_id.required' => '发布用户不能为空', + ]; + } +} diff --git a/modules/Forum/Http/Requests/ForumSensitiveWordFormRequest.php b/modules/Forum/Http/Requests/ForumSensitiveWordFormRequest.php new file mode 100644 index 0000000..0159a5a --- /dev/null +++ b/modules/Forum/Http/Requests/ForumSensitiveWordFormRequest.php @@ -0,0 +1,43 @@ +isUpdate()) { + return [ + 'word' => 'required|max:100|unique:forum_sensitive_words,word', + 'replacement' => 'nullable|max:100', + 'type' => 'nullable|integer|in:1,2', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + $id = $this->route('id'); + return [ + 'word' => [ + 'required', + 'max:100', + Rule::unique('forum_sensitive_words', 'word')->ignore($id), + ], + 'replacement' => 'nullable|max:100', + 'type' => 'nullable|integer|in:1,2', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + public function messages(): array + { + return [ + 'word.required' => '敏感词不能为空', + 'word.unique' => '敏感词已存在', + ]; + } +} diff --git a/modules/Forum/Models/ForumCategoryModel.php b/modules/Forum/Models/ForumCategoryModel.php new file mode 100644 index 0000000..13224ad --- /dev/null +++ b/modules/Forum/Models/ForumCategoryModel.php @@ -0,0 +1,26 @@ + 'integer', + 'sort' => 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Forum/Models/ForumCommentModel.php b/modules/Forum/Models/ForumCommentModel.php new file mode 100644 index 0000000..69c51b4 --- /dev/null +++ b/modules/Forum/Models/ForumCommentModel.php @@ -0,0 +1,29 @@ + 'integer', + 'user_id' => 'integer', + 'parent_id' => 'integer', + 'reply_to' => 'integer', + 'like_count' => 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Forum/Models/ForumPostFavoriteModel.php b/modules/Forum/Models/ForumPostFavoriteModel.php new file mode 100644 index 0000000..626da08 --- /dev/null +++ b/modules/Forum/Models/ForumPostFavoriteModel.php @@ -0,0 +1,23 @@ + 'integer', + 'user_id' => 'integer', + ]; +} diff --git a/modules/Forum/Models/ForumPostLikeModel.php b/modules/Forum/Models/ForumPostLikeModel.php new file mode 100644 index 0000000..2a11eee --- /dev/null +++ b/modules/Forum/Models/ForumPostLikeModel.php @@ -0,0 +1,23 @@ + 'integer', + 'user_id' => 'integer', + ]; +} diff --git a/modules/Forum/Models/ForumPostModel.php b/modules/Forum/Models/ForumPostModel.php new file mode 100644 index 0000000..b90b074 --- /dev/null +++ b/modules/Forum/Models/ForumPostModel.php @@ -0,0 +1,41 @@ + 'integer', + 'category_id' => 'integer', + 'images' => 'array', + 'view_count' => 'integer', + 'comment_count' => 'integer', + 'like_count' => 'integer', + 'favorite_count' => 'integer', + 'is_hot' => 'integer', + 'is_top' => 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Forum/Models/ForumSensitiveWordModel.php b/modules/Forum/Models/ForumSensitiveWordModel.php new file mode 100644 index 0000000..d2e7dcb --- /dev/null +++ b/modules/Forum/Models/ForumSensitiveWordModel.php @@ -0,0 +1,22 @@ + 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Forum/Providers/ForumServiceProvider.php b/modules/Forum/Providers/ForumServiceProvider.php new file mode 100644 index 0000000..24b1629 --- /dev/null +++ b/modules/Forum/Providers/ForumServiceProvider.php @@ -0,0 +1,14 @@ +register(base_path('modules/Forum/Http/Controllers')); + } +} diff --git a/web/domain/iForumCategory.ts b/web/domain/iForumCategory.ts new file mode 100644 index 0000000..466e7cd --- /dev/null +++ b/web/domain/iForumCategory.ts @@ -0,0 +1,12 @@ +export default interface IForumCategory { + id?: number; + parent_id?: number; + name?: string; + slug?: string; + description?: string; + icon?: string; + sort?: number; + status?: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iForumComment.ts b/web/domain/iForumComment.ts new file mode 100644 index 0000000..698cca0 --- /dev/null +++ b/web/domain/iForumComment.ts @@ -0,0 +1,12 @@ +export default interface IForumComment { + id?: number; + post_id?: number; + user_id?: number; + parent_id?: number; + reply_to?: number; + content?: string; + like_count?: number; + status?: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iForumPost.ts b/web/domain/iForumPost.ts new file mode 100644 index 0000000..9f9d2d4 --- /dev/null +++ b/web/domain/iForumPost.ts @@ -0,0 +1,18 @@ +export default interface IForumPost { + id?: number; + user_id?: number; + category_id?: number; + title?: string; + content?: string; + images?: string[]; + view_count?: number; + comment_count?: number; + like_count?: number; + favorite_count?: number; + is_hot?: number; + is_top?: number; + status?: number; + created_at?: string; + updated_at?: string; + deleted_at?: string; +} diff --git a/web/domain/iForumPostFavorite.ts b/web/domain/iForumPostFavorite.ts new file mode 100644 index 0000000..50fb404 --- /dev/null +++ b/web/domain/iForumPostFavorite.ts @@ -0,0 +1,6 @@ +export default interface IForumPostFavorite { + id?: number; + post_id?: number; + user_id?: number; + created_at?: string; +} diff --git a/web/domain/iForumPostLike.ts b/web/domain/iForumPostLike.ts new file mode 100644 index 0000000..e23a48a --- /dev/null +++ b/web/domain/iForumPostLike.ts @@ -0,0 +1,6 @@ +export default interface IForumPostLike { + id?: number; + post_id?: number; + user_id?: number; + created_at?: string; +} diff --git a/web/domain/iForumSensitiveWord.ts b/web/domain/iForumSensitiveWord.ts new file mode 100644 index 0000000..f845186 --- /dev/null +++ b/web/domain/iForumSensitiveWord.ts @@ -0,0 +1,9 @@ +export default interface IForumSensitiveWord { + id?: number; + word?: string; + replacement?: string; + type?: number; + status?: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/locales/en_US/forum/category.ts b/web/locales/en_US/forum/category.ts new file mode 100644 index 0000000..40ef448 --- /dev/null +++ b/web/locales/en_US/forum/category.ts @@ -0,0 +1,18 @@ +export default { + "forum.category.page.title": "Topic Categories", + "forum.category.page.description": "Manage campus forum topic categories with parent-child structure", + "forum.category.id": "ID", + "forum.category.parent_id": "Parent Category", + "forum.category.name": "Name", + "forum.category.name.required": "Category name is required", + "forum.category.slug": "Slug", + "forum.category.slug.required": "Category slug is required", + "forum.category.description": "Description", + "forum.category.icon": "Icon", + "forum.category.sort": "Sort", + "forum.category.status": "Status", + "forum.category.status.0": "Disabled", + "forum.category.status.1": "Active", + "forum.category.created_at": "Created At", + "forum.category.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/forum/comment.ts b/web/locales/en_US/forum/comment.ts new file mode 100644 index 0000000..74ca8a0 --- /dev/null +++ b/web/locales/en_US/forum/comment.ts @@ -0,0 +1,17 @@ +export default { + "forum.comment.page.title": "Comment Management", + "forum.comment.page.description": "Manage campus forum post comments, support view and review", + "forum.comment.id": "ID", + "forum.comment.post_id": "Post", + "forum.comment.user_id": "User", + "forum.comment.parent_id": "Parent Comment", + "forum.comment.reply_to": "Reply To", + "forum.comment.content": "Content", + "forum.comment.content.required": "Comment content is required", + "forum.comment.like_count": "Likes", + "forum.comment.status": "Status", + "forum.comment.status.0": "Hidden", + "forum.comment.status.1": "Active", + "forum.comment.created_at": "Commented At", + "forum.comment.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/forum/post-favorite.ts b/web/locales/en_US/forum/post-favorite.ts new file mode 100644 index 0000000..3cacefd --- /dev/null +++ b/web/locales/en_US/forum/post-favorite.ts @@ -0,0 +1,8 @@ +export default { + "forum.post-favorite.page.title": "Favorite Records", + "forum.post-favorite.page.description": "View campus forum post favorite records", + "forum.post-favorite.id": "ID", + "forum.post-favorite.post_id": "Post ID", + "forum.post-favorite.user_id": "User ID", + "forum.post-favorite.created_at": "Favorited At", +}; diff --git a/web/locales/en_US/forum/post-like.ts b/web/locales/en_US/forum/post-like.ts new file mode 100644 index 0000000..96fe54c --- /dev/null +++ b/web/locales/en_US/forum/post-like.ts @@ -0,0 +1,8 @@ +export default { + "forum.post-like.page.title": "Like Records", + "forum.post-like.page.description": "View campus forum post like records", + "forum.post-like.id": "ID", + "forum.post-like.post_id": "Post ID", + "forum.post-like.user_id": "User ID", + "forum.post-like.created_at": "Liked At", +}; diff --git a/web/locales/en_US/forum/post.ts b/web/locales/en_US/forum/post.ts new file mode 100644 index 0000000..6fd9857 --- /dev/null +++ b/web/locales/en_US/forum/post.ts @@ -0,0 +1,28 @@ +export default { + "forum.post.page.title": "Post Management", + "forum.post.page.description": "Manage campus forum posts, support pin, hot, review and more", + "forum.post.id": "ID", + "forum.post.user_id": "Author", + "forum.post.category_id": "Category", + "forum.post.title": "Title", + "forum.post.title.required": "Post title is required", + "forum.post.content": "Content", + "forum.post.content.required": "Post content is required", + "forum.post.images": "Images", + "forum.post.view_count": "Views", + "forum.post.comment_count": "Comments", + "forum.post.like_count": "Likes", + "forum.post.favorite_count": "Favorites", + "forum.post.is_hot": "Hot", + "forum.post.is_hot.0": "No", + "forum.post.is_hot.1": "Yes", + "forum.post.is_top": "Pinned", + "forum.post.is_top.0": "No", + "forum.post.is_top.1": "Yes", + "forum.post.status": "Status", + "forum.post.status.0": "Draft", + "forum.post.status.1": "Published", + "forum.post.status.2": "Hidden", + "forum.post.created_at": "Posted At", + "forum.post.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/forum/sensitive-word.ts b/web/locales/en_US/forum/sensitive-word.ts new file mode 100644 index 0000000..c6f0e21 --- /dev/null +++ b/web/locales/en_US/forum/sensitive-word.ts @@ -0,0 +1,16 @@ +export default { + "forum.sensitive-word.page.title": "Sensitive Words", + "forum.sensitive-word.page.description": "Manage campus forum sensitive word filter rules, support replace and block modes", + "forum.sensitive-word.id": "ID", + "forum.sensitive-word.word": "Word", + "forum.sensitive-word.word.required": "Sensitive word is required", + "forum.sensitive-word.replacement": "Replacement", + "forum.sensitive-word.type": "Type", + "forum.sensitive-word.type.1": "Replace", + "forum.sensitive-word.type.2": "Block", + "forum.sensitive-word.status": "Status", + "forum.sensitive-word.status.0": "Disabled", + "forum.sensitive-word.status.1": "Enabled", + "forum.sensitive-word.created_at": "Created At", + "forum.sensitive-word.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/index.ts b/web/locales/en_US/index.ts index 9131b5d..5e134f6 100644 --- a/web/locales/en_US/index.ts +++ b/web/locales/en_US/index.ts @@ -24,6 +24,13 @@ import aiAgent from "./ai/agent"; import memberUser from "./member/user"; import memberAddress from "./member/address"; +import forumCategory from "./forum/category"; +import forumPost from "./forum/post"; +import forumComment from "./forum/comment"; +import forumSensitiveWord from "./forum/sensitive-word"; +import forumPostLike from "./forum/post-like"; +import forumPostFavorite from "./forum/post-favorite"; + import userProfile from "./user/profile"; import xinForm from "./components/xin-form"; @@ -54,6 +61,12 @@ export default { ...aiAgent, ...memberUser, ...memberAddress, + ...forumCategory, + ...forumPost, + ...forumComment, + ...forumSensitiveWord, + ...forumPostLike, + ...forumPostFavorite, ...userProfile, ...xinForm, ...xinTable, diff --git a/web/locales/en_US/menu.ts b/web/locales/en_US/menu.ts index d5baaaf..8a60733 100644 --- a/web/locales/en_US/menu.ts +++ b/web/locales/en_US/menu.ts @@ -47,5 +47,12 @@ export default { "menu.member": "Mini Program Users", "menu.member.user": "User List", "menu.member.address": "Address Management", + "menu.forum": "Campus Forum", + "menu.forum.category": "Categories", + "menu.forum.post": "Posts", + "menu.forum.comment": "Comments", + "menu.forum.sensitive-word": "Sensitive Words", + "menu.forum.post-like": "Like Records", + "menu.forum.post-favorite": "Favorite Records", "menu.xin-admin": "XinAdmin", } diff --git a/web/locales/zh_CN/forum/category.ts b/web/locales/zh_CN/forum/category.ts new file mode 100644 index 0000000..d7160de --- /dev/null +++ b/web/locales/zh_CN/forum/category.ts @@ -0,0 +1,18 @@ +export default { + "forum.category.page.title": "话题分类", + "forum.category.page.description": "管理校园论坛话题分类,支持父子级分类结构", + "forum.category.id": "ID", + "forum.category.parent_id": "父级分类", + "forum.category.name": "分类名称", + "forum.category.name.required": "分类名称不能为空", + "forum.category.slug": "标识", + "forum.category.slug.required": "分类标识不能为空", + "forum.category.description": "描述", + "forum.category.icon": "图标", + "forum.category.sort": "排序", + "forum.category.status": "状态", + "forum.category.status.0": "禁用", + "forum.category.status.1": "正常", + "forum.category.created_at": "创建时间", + "forum.category.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/forum/comment.ts b/web/locales/zh_CN/forum/comment.ts new file mode 100644 index 0000000..f35fb28 --- /dev/null +++ b/web/locales/zh_CN/forum/comment.ts @@ -0,0 +1,17 @@ +export default { + "forum.comment.page.title": "评论管理", + "forum.comment.page.description": "管理校园论坛帖子评论,支持查看和审核评论", + "forum.comment.id": "ID", + "forum.comment.post_id": "所属帖子", + "forum.comment.user_id": "评论用户", + "forum.comment.parent_id": "父评论", + "forum.comment.reply_to": "回复用户", + "forum.comment.content": "评论内容", + "forum.comment.content.required": "评论内容不能为空", + "forum.comment.like_count": "点赞数", + "forum.comment.status": "状态", + "forum.comment.status.0": "隐藏", + "forum.comment.status.1": "正常", + "forum.comment.created_at": "评论时间", + "forum.comment.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/forum/post-favorite.ts b/web/locales/zh_CN/forum/post-favorite.ts new file mode 100644 index 0000000..41a15d9 --- /dev/null +++ b/web/locales/zh_CN/forum/post-favorite.ts @@ -0,0 +1,8 @@ +export default { + "forum.post-favorite.page.title": "收藏记录", + "forum.post-favorite.page.description": "查看校园论坛帖子收藏记录", + "forum.post-favorite.id": "ID", + "forum.post-favorite.post_id": "帖子ID", + "forum.post-favorite.user_id": "用户ID", + "forum.post-favorite.created_at": "收藏时间", +}; diff --git a/web/locales/zh_CN/forum/post-like.ts b/web/locales/zh_CN/forum/post-like.ts new file mode 100644 index 0000000..00dd191 --- /dev/null +++ b/web/locales/zh_CN/forum/post-like.ts @@ -0,0 +1,8 @@ +export default { + "forum.post-like.page.title": "点赞记录", + "forum.post-like.page.description": "查看校园论坛帖子点赞记录", + "forum.post-like.id": "ID", + "forum.post-like.post_id": "帖子ID", + "forum.post-like.user_id": "用户ID", + "forum.post-like.created_at": "点赞时间", +}; diff --git a/web/locales/zh_CN/forum/post.ts b/web/locales/zh_CN/forum/post.ts new file mode 100644 index 0000000..415d1f3 --- /dev/null +++ b/web/locales/zh_CN/forum/post.ts @@ -0,0 +1,28 @@ +export default { + "forum.post.page.title": "帖子管理", + "forum.post.page.description": "管理校园论坛帖子,支持置顶、热门、审核等操作", + "forum.post.id": "ID", + "forum.post.user_id": "发布用户", + "forum.post.category_id": "所属分类", + "forum.post.title": "帖子标题", + "forum.post.title.required": "帖子标题不能为空", + "forum.post.content": "帖子内容", + "forum.post.content.required": "帖子内容不能为空", + "forum.post.images": "图片", + "forum.post.view_count": "浏览数", + "forum.post.comment_count": "评论数", + "forum.post.like_count": "点赞数", + "forum.post.favorite_count": "收藏数", + "forum.post.is_hot": "热门", + "forum.post.is_hot.0": "否", + "forum.post.is_hot.1": "是", + "forum.post.is_top": "置顶", + "forum.post.is_top.0": "否", + "forum.post.is_top.1": "是", + "forum.post.status": "状态", + "forum.post.status.0": "草稿", + "forum.post.status.1": "发布", + "forum.post.status.2": "隐藏", + "forum.post.created_at": "发布时间", + "forum.post.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/forum/sensitive-word.ts b/web/locales/zh_CN/forum/sensitive-word.ts new file mode 100644 index 0000000..ca7cb0c --- /dev/null +++ b/web/locales/zh_CN/forum/sensitive-word.ts @@ -0,0 +1,16 @@ +export default { + "forum.sensitive-word.page.title": "敏感词管理", + "forum.sensitive-word.page.description": "管理校园论坛敏感词过滤规则,支持替换和禁止发布两种模式", + "forum.sensitive-word.id": "ID", + "forum.sensitive-word.word": "敏感词", + "forum.sensitive-word.word.required": "敏感词不能为空", + "forum.sensitive-word.replacement": "替换词", + "forum.sensitive-word.type": "类型", + "forum.sensitive-word.type.1": "替换", + "forum.sensitive-word.type.2": "禁止发布", + "forum.sensitive-word.status": "状态", + "forum.sensitive-word.status.0": "禁用", + "forum.sensitive-word.status.1": "启用", + "forum.sensitive-word.created_at": "创建时间", + "forum.sensitive-word.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/index.ts b/web/locales/zh_CN/index.ts index 9131b5d..5e134f6 100644 --- a/web/locales/zh_CN/index.ts +++ b/web/locales/zh_CN/index.ts @@ -24,6 +24,13 @@ import aiAgent from "./ai/agent"; import memberUser from "./member/user"; import memberAddress from "./member/address"; +import forumCategory from "./forum/category"; +import forumPost from "./forum/post"; +import forumComment from "./forum/comment"; +import forumSensitiveWord from "./forum/sensitive-word"; +import forumPostLike from "./forum/post-like"; +import forumPostFavorite from "./forum/post-favorite"; + import userProfile from "./user/profile"; import xinForm from "./components/xin-form"; @@ -54,6 +61,12 @@ export default { ...aiAgent, ...memberUser, ...memberAddress, + ...forumCategory, + ...forumPost, + ...forumComment, + ...forumSensitiveWord, + ...forumPostLike, + ...forumPostFavorite, ...userProfile, ...xinForm, ...xinTable, diff --git a/web/locales/zh_CN/menu.ts b/web/locales/zh_CN/menu.ts index cf73f49..4b4e9bd 100644 --- a/web/locales/zh_CN/menu.ts +++ b/web/locales/zh_CN/menu.ts @@ -47,5 +47,12 @@ export default { "menu.member": "小程序用户", "menu.member.user": "用户列表", "menu.member.address": "地址管理", + "menu.forum": "校园论坛", + "menu.forum.category": "话题管理", + "menu.forum.post": "帖子管理", + "menu.forum.comment": "评论管理", + "menu.forum.sensitive-word": "敏感词管理", + "menu.forum.post-like": "点赞记录", + "menu.forum.post-favorite": "收藏记录", "menu.xin-admin": "XinAdmin", }; diff --git a/web/pages/forum/category/index.tsx b/web/pages/forum/category/index.tsx new file mode 100644 index 0000000..e287d5d --- /dev/null +++ b/web/pages/forum/category/index.tsx @@ -0,0 +1,142 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumCategory from '@/domain/iForumCategory'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.category.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.category.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('forum.category.name.required') }], + }, + { + title: t('forum.category.slug'), + dataIndex: 'slug', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('forum.category.slug.required') }], + }, + { + title: t('forum.category.parent_id'), + dataIndex: 'parent_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.category.description'), + dataIndex: 'description', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.category.icon'), + dataIndex: 'icon', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.category.sort'), + dataIndex: 'sort', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.category.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.category.status.0') }, + { value: 1, label: t('forum.category.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('forum.category.status.1')} + : {t('forum.category.status.0')}; + }, + filters: [ + { text: t('forum.category.status.0'), value: 0 }, + { text: t('forum.category.status.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('forum.category.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('forum.category.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/category', + columns, + rowKey: 'id', + accessName: 'forum.category', + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 600, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.category.page.title')} + {t('forum.category.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/forum/comment/index.tsx b/web/pages/forum/comment/index.tsx new file mode 100644 index 0000000..b26ea5a --- /dev/null +++ b/web/pages/forum/comment/index.tsx @@ -0,0 +1,144 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumComment from '@/domain/iForumComment'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.comment.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.comment.post_id'), + dataIndex: 'post_id', + valueType: 'text', + align: 'center', + required: true, + }, + { + title: t('forum.comment.user_id'), + dataIndex: 'user_id', + valueType: 'text', + align: 'center', + required: true, + hideInSearch: true, + }, + { + title: t('forum.comment.parent_id'), + dataIndex: 'parent_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.comment.reply_to'), + dataIndex: 'reply_to', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.comment.content'), + dataIndex: 'content', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + required: true, + rules: [{ required: true, message: t('forum.comment.content.required') }], + }, + { + title: t('forum.comment.like_count'), + dataIndex: 'like_count', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('forum.comment.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.comment.status.0') }, + { value: 1, label: t('forum.comment.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('forum.comment.status.1')} + : {t('forum.comment.status.0')}; + }, + filters: [ + { text: t('forum.comment.status.0'), value: 0 }, + { text: t('forum.comment.status.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('forum.comment.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('forum.comment.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/comment', + columns, + rowKey: 'id', + accessName: 'forum.comment', + scroll: { x: 1200 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 700, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.comment.page.title')} + {t('forum.comment.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/forum/post-favorite/index.tsx b/web/pages/forum/post-favorite/index.tsx new file mode 100644 index 0000000..1699ba8 --- /dev/null +++ b/web/pages/forum/post-favorite/index.tsx @@ -0,0 +1,84 @@ +import { Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumPostFavorite from '@/domain/iForumPostFavorite'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.post-favorite.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.post-favorite.post_id'), + dataIndex: 'post_id', + valueType: 'text', + align: 'center', + }, + { + title: t('forum.post-favorite.user_id'), + dataIndex: 'user_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.post-favorite.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/post-favorite', + columns, + rowKey: 'id', + accessName: 'forum.post-favorite', + createShow: false, + editShow: false, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 500, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.post-favorite.page.title')} + {t('forum.post-favorite.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/forum/post-like/index.tsx b/web/pages/forum/post-like/index.tsx new file mode 100644 index 0000000..4ce9c99 --- /dev/null +++ b/web/pages/forum/post-like/index.tsx @@ -0,0 +1,84 @@ +import { Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumPostLike from '@/domain/iForumPostLike'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.post-like.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.post-like.post_id'), + dataIndex: 'post_id', + valueType: 'text', + align: 'center', + }, + { + title: t('forum.post-like.user_id'), + dataIndex: 'user_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.post-like.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/post-like', + columns, + rowKey: 'id', + accessName: 'forum.post-like', + createShow: false, + editShow: false, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 500, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.post-like.page.title')} + {t('forum.post-like.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/forum/post/index.tsx b/web/pages/forum/post/index.tsx new file mode 100644 index 0000000..c161f9f --- /dev/null +++ b/web/pages/forum/post/index.tsx @@ -0,0 +1,203 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumPost from '@/domain/iForumPost'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.post.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.post.title'), + dataIndex: 'title', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('forum.post.title.required') }], + }, + { + title: t('forum.post.user_id'), + dataIndex: 'user_id', + valueType: 'text', + align: 'center', + required: true, + hideInSearch: true, + }, + { + title: t('forum.post.category_id'), + dataIndex: 'category_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('forum.post.content'), + dataIndex: 'content', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + required: true, + rules: [{ required: true, message: t('forum.post.content.required') }], + }, + { + title: t('forum.post.view_count'), + dataIndex: 'view_count', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('forum.post.comment_count'), + dataIndex: 'comment_count', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('forum.post.like_count'), + dataIndex: 'like_count', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('forum.post.favorite_count'), + dataIndex: 'favorite_count', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('forum.post.is_hot'), + dataIndex: 'is_hot', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.post.is_hot.0') }, + { value: 1, label: t('forum.post.is_hot.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('forum.post.is_hot.1')} + : {t('forum.post.is_hot.0')}; + }, + filters: [ + { text: t('forum.post.is_hot.0'), value: 0 }, + { text: t('forum.post.is_hot.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('forum.post.is_top'), + dataIndex: 'is_top', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.post.is_top.0') }, + { value: 1, label: t('forum.post.is_top.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('forum.post.is_top.1')} + : {t('forum.post.is_top.0')}; + }, + filters: [ + { text: t('forum.post.is_top.0'), value: 0 }, + { text: t('forum.post.is_top.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('forum.post.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.post.status.0') }, + { value: 1, label: t('forum.post.status.1') }, + { value: 2, label: t('forum.post.status.2') }, + ], + }, + render: (value: number) => { + const colorMap: Record = { 0: 'default', 1: 'success', 2: 'warning' }; + const labelMap: Record = { 0: t('forum.post.status.0'), 1: t('forum.post.status.1'), 2: t('forum.post.status.2') }; + return {labelMap[value] || '-'}; + }, + filters: [ + { text: t('forum.post.status.0'), value: 0 }, + { text: t('forum.post.status.1'), value: 1 }, + { text: t('forum.post.status.2'), value: 2 }, + ], + align: 'center', + }, + { + title: t('forum.post.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('forum.post.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/post', + columns, + rowKey: 'id', + accessName: 'forum.post', + scroll: { x: 1600 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 800, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.post.page.title')} + {t('forum.post.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/forum/sensitive-word/index.tsx b/web/pages/forum/sensitive-word/index.tsx new file mode 100644 index 0000000..b031d35 --- /dev/null +++ b/web/pages/forum/sensitive-word/index.tsx @@ -0,0 +1,133 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IForumSensitiveWord from '@/domain/iForumSensitiveWord'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('forum.sensitive-word.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('forum.sensitive-word.word'), + dataIndex: 'word', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('forum.sensitive-word.word.required') }], + }, + { + title: t('forum.sensitive-word.replacement'), + dataIndex: 'replacement', + valueType: 'text', + align: 'center', + }, + { + title: t('forum.sensitive-word.type'), + dataIndex: 'type', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 1, label: t('forum.sensitive-word.type.1') }, + { value: 2, label: t('forum.sensitive-word.type.2') }, + ], + }, + render: (value: number) => { + return value === 2 + ? {t('forum.sensitive-word.type.2')} + : {t('forum.sensitive-word.type.1')}; + }, + filters: [ + { text: t('forum.sensitive-word.type.1'), value: 1 }, + { text: t('forum.sensitive-word.type.2'), value: 2 }, + ], + align: 'center', + }, + { + title: t('forum.sensitive-word.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('forum.sensitive-word.status.0') }, + { value: 1, label: t('forum.sensitive-word.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('forum.sensitive-word.status.1')} + : {t('forum.sensitive-word.status.0')}; + }, + filters: [ + { text: t('forum.sensitive-word.status.0'), value: 0 }, + { text: t('forum.sensitive-word.status.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('forum.sensitive-word.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('forum.sensitive-word.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/forum/sensitive-word', + columns, + rowKey: 'id', + accessName: 'forum.sensitive-word', + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { + width: 600, + }, + cardProps: { + variant: 'borderless', + }, + pagination: { + size: 'small', + style: { marginBottom: 0 }, + }, + }; + + return ( + <> +
+ {t('forum.sensitive-word.page.title')} + {t('forum.sensitive-word.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table;