From ff968676793105b87222357f083266e83c15136a Mon Sep 17 00:00:00 2001 From: mashen Date: Thu, 9 Apr 2026 13:45:47 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=89=8D=E7=AB=AF=20?= =?UTF-8?q?MobX=20Stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RegionStore(版块管理、查询、评分、收藏) - ArticleStore(文章管理、提交、审核、评论、点赞、统计) - ServiceStore(特色服务管理、提交、审核、评论、点赞、评分、统计) - InteractionStore(交互功能:评论、评分、点赞、收藏) - 更新 UserStore(保持原有) - 更新 AuthStore(保持原有) - 更新 index.js 导入所有 Stores --- frontend/src/index.js | 12 +- frontend/src/stores/ArticleStore.js | 152 ++++++++++++++++++++++ frontend/src/stores/InteractionStore.js | 164 ++++++++++++++++++++++++ frontend/src/stores/RegionStore.js | 139 ++++++++++++++++++++ frontend/src/stores/ServiceStore.js | 164 ++++++++++++++++++++++++ 5 files changed, 629 insertions(+), 2 deletions(-) create mode 100644 frontend/src/stores/ArticleStore.js create mode 100644 frontend/src/stores/InteractionStore.js create mode 100644 frontend/src/stores/RegionStore.js create mode 100644 frontend/src/stores/ServiceStore.js diff --git a/frontend/src/index.js b/frontend/src/index.js index a1cfeff..e78e417 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -6,14 +6,22 @@ import App from './App'; import './styles/global'; // Import stores -import UserStore from './stores/UserStore'; import AuthStore from './stores/AuthStore'; +import UserStore from './stores/UserStore'; +import RegionStore from './stores/RegionStore'; +import ArticleStore from './stores/ArticleStore'; +import ServiceStore from './stores/ServiceStore'; +import InteractionStore from './stores/InteractionStore'; const root = ReactDOM.createRoot(document.getElementById('root')); const stores = { - userStore: new UserStore(), authStore: new AuthStore(), + userStore: new UserStore(), + regionStore: new RegionStore(), + articleStore: new ArticleStore(), + serviceStore: new ServiceStore(), + interactionStore: new InteractionStore(), }; root.render( diff --git a/frontend/src/stores/ArticleStore.js b/frontend/src/stores/ArticleStore.js new file mode 100644 index 0000000..f30c38f --- /dev/null +++ b/frontend/src/stores/ArticleStore.js @@ -0,0 +1,152 @@ +import { makeAutoObservable } from 'mobx'; +import api from '../services/api'; + +class ArticleStore { + articles = []; + currentArticle = null; + loading = false; + error = null; + + constructor() { + makeAutoObservable(this); + } + + async fetchArticles(params = {}) { + this.loading = true; + this.error = null; + + try { + const response = await api.get('/api/articles/', { params }); + this.articles = response.data.results || response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch articles'; + } finally { + this.loading = false; + } + } + + async fetchArticle(id) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/articles/${id}/`); + this.currentArticle = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch article'; + } finally { + this.loading = false; + } + } + + async createArticle(data) { + this.loading = true; + this.error = null; + + try { + const response = await api.post('/api/articles/', data); + return { success: true, article: response.data }; + } catch (error) { + this.error = error.response?.data || 'Failed to create article'; + return { success: false, error: this.error }; + } finally { + this.loading = false; + } + } + + async updateArticle(id, data) { + this.loading = true; + this.error = null; + + try { + const response = await api.put(`/api/articles/${id}/`, data); + return { success: true, article: response.data }; + } catch (error) { + this.error = error.response?.data || 'Failed to update article'; + return { success: false, error: this.error }; + } finally { + this.loading = false; + } + } + + async deleteArticle(id) { + try { + await api.delete(`/api/articles/${id}/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to delete article', + }; + } + } + + async submitArticle(id) { + try { + await api.post(`/api/articles/${id}/submit/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to submit article', + }; + } + } + + async approveArticle(id, reason = '') { + try { + await api.post(`/api/articles/${id}/approve/`, { action: 'approve', reason }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to approve article', + }; + } + } + + async rejectArticle(id, reason) { + try { + await api.post(`/api/articles/${id}/reject/`, { action: 'reject', reason }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to reject article', + }; + } + } + + async likeArticle(id) { + try { + const response = await api.post(`/api/articles/${id}/like/`); + return response.data; + } catch (error) { + return null; + } + } + + async fetchArticleComments(id) { + try { + const response = await api.get(`/api/articles/${id}/comments/`); + return response.data; + } catch (error) { + return []; + } + } + + async fetchArticleStats(id) { + try { + const response = await api.get(`/api/articles/${id}/stats/`); + return response.data; + } catch (error) { + return null; + } + } + + clearCurrentArticle() { + this.currentArticle = null; + } +} + +export default ArticleStore; \ No newline at end of file diff --git a/frontend/src/stores/InteractionStore.js b/frontend/src/stores/InteractionStore.js new file mode 100644 index 0000000..bc9d2a2 --- /dev/null +++ b/frontend/src/stores/InteractionStore.js @@ -0,0 +1,164 @@ +import { makeAutoObservable } from 'mobx'; +import api from '../services/api'; + +class InteractionStore { + comments = []; + ratings = []; + likes = []; + favorites = []; + loading = false; + error = null; + + constructor() { + makeAutoObservable(this); + } + + // Comments + async createComment(targetType, targetId, content) { + try { + const response = await api.post('/api/comments/', { + target_type: targetType, + target_id: targetId, + content, + }); + return { success: true, comment: response.data }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to create comment', + }; + } + } + + async fetchComments(targetType, targetId) { + try { + const response = await api.get('/api/comments/', { + params: { target_type: targetType, target_id: targetId }, + }); + this.comments = response.data.results || response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch comments'; + } + } + + async approveComment(commentId) { + try { + await api.post(`/api/comments/${commentId}/approve_ai/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to approve comment', + }; + } + } + + async rejectComment(commentId, reason) { + try { + await api.post(`/api/comments/${commentId}/reject_ai/`, { reason }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to reject comment', + }; + } + } + + // Ratings + async createRating(targetType, targetId, score) { + try { + const response = await api.post('/api/ratings/', { + target_type: targetType, + target_id: targetId, + score, + }); + return { success: true, rating: response.data }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to create rating', + }; + } + } + + async fetchRatings(params = {}) { + try { + const response = await api.get('/api/ratings/', { params }); + this.ratings = response.data.results || response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch ratings'; + } + } + + async fetchMyRatings() { + try { + const response = await api.get('/api/ratings/my_ratings/'); + this.ratings = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch my ratings'; + } + } + + // Likes + async toggleLike(targetType, targetId) { + try { + const response = await api.post('/api/likes/toggle/', { + target_type: targetType, + target_id: targetId, + }); + return response.data; + } catch (error) { + return null; + } + } + + async fetchMyLikes() { + try { + const response = await api.get('/api/likes/my_likes/'); + this.likes = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch my likes'; + } + } + + // Favorites + async toggleFavorite(targetType, targetId) { + try { + const response = await api.post('/api/favorites/toggle/', { + target_type: targetType, + target_id: targetId, + }); + return response.data; + } catch (error) { + return null; + } + } + + async fetchMyFavorites() { + try { + const response = await api.get('/api/favorites/my_favorites/'); + this.favorites = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch my favorites'; + } + } + + clearComments() { + this.comments = []; + } + + clearRatings() { + this.ratings = []; + } + + clearLikes() { + this.likes = []; + } + + clearFavorites() { + this.favorites = []; + } +} + +export default InteractionStore; \ No newline at end of file diff --git a/frontend/src/stores/RegionStore.js b/frontend/src/stores/RegionStore.js new file mode 100644 index 0000000..e2b2eac --- /dev/null +++ b/frontend/src/stores/RegionStore.js @@ -0,0 +1,139 @@ +import { makeAutoObservable } from 'mobx'; +import api from '../services/api'; + +class RegionStore { + regions = []; + currentRegion = null; + loading = false; + error = null; + + constructor() { + makeAutoObservable(this); + } + + async fetchRegions() { + this.loading = true; + this.error = null; + + try { + const response = await api.get('/api/regions/'); + this.regions = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch regions'; + } finally { + this.loading = false; + } + } + + async fetchRegion(id) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/regions/${id}/`); + this.currentRegion = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch region'; + } finally { + this.loading = false; + } + } + + async fetchProvinces() { + this.loading = true; + this.error = null; + + try { + const response = await api.get('/api/regions/provinces/'); + this.regions = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch provinces'; + } finally { + this.loading = false; + } + } + + async fetchChildren(parentId) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/regions/${parentId}/children/`); + return response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch children'; + return []; + } finally { + this.loading = false; + } + } + + async fetchRegionArticles(regionId) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/regions/${regionId}/articles/`); + return response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch region articles'; + return []; + } finally { + this.loading = false; + } + } + + async fetchRegionServices(regionId) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/regions/${regionId}/services/`); + return response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch region services'; + return []; + } finally { + this.loading = false; + } + } + + async rateRegion(regionId, score) { + try { + await api.post(`/api/regions/${regionId}/rate/`, { score }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to rate region', + }; + } + } + + async getRegionRating(regionId) { + try { + const response = await api.get(`/api/regions/${regionId}/my_rating/`); + return response.data.score; + } catch (error) { + return null; + } + } + + async favoriteRegion(regionId) { + try { + await api.post(`/api/regions/${regionId}/favorite/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to favorite region', + }; + } + } + + clearCurrentRegion() { + this.currentRegion = null; + } +} + +export default RegionStore; \ No newline at end of file diff --git a/frontend/src/stores/ServiceStore.js b/frontend/src/stores/ServiceStore.js new file mode 100644 index 0000000..7723838 --- /dev/null +++ b/frontend/src/stores/ServiceStore.js @@ -0,0 +1,164 @@ +import { makeAutoObservable } from 'mobx'; +import api from '../services/api'; + +class ServiceStore { + services = []; + currentService = null; + loading = false; + error = null; + + constructor() { + makeAutoObservable(this); + } + + async fetchServices(params = {}) { + this.loading = true; + this.error = null; + + try { + const response = await api.get('/api/services/', { params }); + this.services = response.data.results || response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch services'; + } finally { + this.loading = false; + } + } + + async fetchService(id) { + this.loading = true; + this.error = null; + + try { + const response = await api.get(`/api/services/${id}/`); + this.currentService = response.data; + } catch (error) { + this.error = error.response?.data || 'Failed to fetch service'; + } finally { + this.loading = false; + } + } + + async createService(data) { + this.loading = true; + this.error = null; + + try { + const response = await api.post('/api/services/', data); + return { success: true, service: response.data }; + } catch (error) { + this.error = error.response?.data || 'Failed to create service'; + return { success: false, error: this.error }; + } finally { + this.loading = false; + } + } + + async updateService(id, data) { + this.loading = true; + this.error = null; + + try { + const response = await api.put(`/api/services/${id}/`, data); + return { success: true, service: response.data }; + } catch (error) { + this.error = error.response?.data || 'Failed to update service'; + return { success: false, error: this.error }; + } finally { + this.loading = false; + } + } + + async deleteService(id) { + try { + await api.delete(`/api/services/${id}/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to delete service', + }; + } + } + + async submitService(id) { + try { + await api.post(`/api/services/${id}/submit/`); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to submit service', + }; + } + } + + async approveService(id, reason = '') { + try { + await api.post(`/api/services/${id}/approve/`, { action: 'approve', reason }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to approve service', + }; + } + } + + async rejectService(id, reason) { + try { + await api.post(`/api/services/${id}/reject/`, { action: 'reject', reason }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to reject service', + }; + } + } + + async likeService(id) { + try { + const response = await api.post(`/api/services/${id}/like/`); + return response.data; + } catch (error) { + return null; + } + } + + async rateService(id, score) { + try { + await api.post(`/api/services/${id}/rate/`, { score }); + return { success: true }; + } catch (error) { + return { + success: false, + error: error.response?.data || 'Failed to rate service', + }; + } + } + + async fetchServiceComments(id) { + try { + const response = await api.get(`/api/services/${id}/comments/`); + return response.data; + } catch (error) { + return []; + } + } + + async fetchServiceStats(id) { + try { + const response = await api.get(`/api/services/${id}/stats/`); + return response.data; + } catch (error) { + return null; + } + } + + clearCurrentService() { + this.currentService = null; + } +} + +export default ServiceStore; \ No newline at end of file