feat: 添加前端 MobX Stores
- RegionStore(版块管理、查询、评分、收藏) - ArticleStore(文章管理、提交、审核、评论、点赞、统计) - ServiceStore(特色服务管理、提交、审核、评论、点赞、评分、统计) - InteractionStore(交互功能:评论、评分、点赞、收藏) - 更新 UserStore(保持原有) - 更新 AuthStore(保持原有) - 更新 index.js 导入所有 Stores
This commit is contained in:
@@ -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(
|
||||
|
||||
152
frontend/src/stores/ArticleStore.js
Normal file
152
frontend/src/stores/ArticleStore.js
Normal file
@@ -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;
|
||||
164
frontend/src/stores/InteractionStore.js
Normal file
164
frontend/src/stores/InteractionStore.js
Normal file
@@ -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;
|
||||
139
frontend/src/stores/RegionStore.js
Normal file
139
frontend/src/stores/RegionStore.js
Normal file
@@ -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;
|
||||
164
frontend/src/stores/ServiceStore.js
Normal file
164
frontend/src/stores/ServiceStore.js
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user