CreatorCenter_OOIN/src/store/slices/creatorsSlice.js

566 lines
19 KiB
JavaScript
Raw Normal View History

2025-05-09 10:18:49 +08:00
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2025-05-23 09:22:51 +08:00
import api from '@/services/api';
2025-05-29 04:26:34 +08:00
import { setNotificationBarMessage } from './notificationBarSlice';
2025-05-23 09:22:51 +08:00
2025-05-21 22:49:54 +08:00
const mockVideos = [
{
id: 1,
title: 'Collagen + Biotin = your beauty routines new besties. For hair, skin, nails, and join...',
picture: 'https://api.dicebear.com/7.x/micah/svg?seed=1',
releaseTime: '2025-01-01',
views: 1000,
likes: 100,
},
{
id: 2,
title: 'Collagen + Biotin = your beauty routines new besties. For hair, skin, nails, and join...',
picture: 'https://api.dicebear.com/7.x/micah/svg?seed=2',
releaseTime: '2025-01-01',
views: 1000,
likes: 100,
},
{
id: 3,
title: 'Collagen + Biotin = your beauty routines new besties. For hair, skin, nails, and join...',
picture: 'https://api.dicebear.com/7.x/micah/svg?seed=3',
releaseTime: '2025-01-01',
views: 1000,
likes: 100,
},
];
2025-05-09 10:18:49 +08:00
// 模拟创作者数据实际项目中会从API获取
2025-05-23 05:00:59 +08:00
export const mockCreators = [
2025-05-09 10:18:49 +08:00
{
id: 1,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=1',
category: 'Phones & Electronics',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'L2',
exposure_level: 'KOC-1',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: true,
hasTiktok: true,
verified: true,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
{
id: 2,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=2',
category: 'Womenswear & Underwear',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'L3',
exposure_level: 'KOL-3',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: false,
hasTiktok: true,
verified: false,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
{
id: 3,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=3',
category: 'Sports & Outdoor',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'L4',
exposure_level: 'KOC-2',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: true,
hasTiktok: true,
verified: false,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
{
id: 4,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=4',
category: 'Food & Beverage',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'L1',
exposure_level: 'KOC-2',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: true,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: true,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
{
id: 5,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=5',
category: 'Health',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'L5',
exposure_level: 'KOL-2',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: false,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: true,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
{
id: 6,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=6',
category: 'Kitchenware',
2025-05-23 08:53:43 +08:00
e_commerce_level: 'New tag',
exposure_level: 'New tag',
2025-05-09 10:18:49 +08:00
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
2025-05-23 08:53:43 +08:00
avg_video_views: '1.9k',
2025-05-09 10:18:49 +08:00
hasEcommerce: true,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: false,
2025-05-21 22:49:54 +08:00
videos: mockVideos,
videosWithProduct: mockVideos,
2025-05-09 10:18:49 +08:00
},
];
// 模拟API获取数据的异步Thunk
2025-05-23 08:53:43 +08:00
// export const fetchCreators = createAsyncThunk('creators/fetchCreators', async ({ path }, { getState }) => {
// // 模拟API调用延迟
// await new Promise((resolve) => setTimeout(resolve, 500));
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 获取当前的筛选条件
// const state = getState();
// const filters = state.filters;
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 应用筛选逻辑(实际项目中可能在服务器端进行)
// let filteredCreators = [...mockCreators];
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 如果有选定的类别,进行筛选
// if (filters.category.length > 0) {
// filteredCreators = filteredCreators.filter((creator) => filters.category.includes(creator.category));
// }
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 如果有选定的电商评级,进行筛选
// if (filters.ecommerceRatings.length > 0) {
// filteredCreators = filteredCreators.filter((creator) =>
// filters.ecommerceRatings.includes(creator.e_commerce_level)
// );
// }
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 如果有选定的曝光评级,进行筛选
// if (filters.exposureRatings.length > 0) {
// filteredCreators = filteredCreators.filter((creator) =>
// filters.exposureRatings.includes(creator.exposure_level)
// );
// }
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// // 筛选观看量范围
// if (filters.viewsRange.length === 2) {
// const minViews = filters.viewsRange[0];
// const maxViews = filters.viewsRange[1];
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// filteredCreators = filteredCreators.filter((creator) => {
// // 将带k的字符串转换为数字
// const viewsStr = creator.avg_video_views;
// let views = parseFloat(viewsStr);
// if (viewsStr.includes('k')) {
// views *= 1000;
// } else if (viewsStr.includes('M')) {
// views *= 1000000;
// }
2025-05-09 10:18:49 +08:00
2025-05-23 08:53:43 +08:00
// return views >= minViews && views <= maxViews;
// });
// }
// return filteredCreators;
// });
2025-05-09 10:18:49 +08:00
const initialState = {
2025-05-23 08:53:43 +08:00
publicCreators: [],
privateCreators: [],
publicTiktokCreators: [],
publicInstagramCreators: [],
publicYoutubeCreators: [],
privateTiktokCreators: [],
privateInstagramCreators: [],
privateYoutubeCreators: [],
2025-05-09 10:18:49 +08:00
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
selectedCreators: [],
2025-05-16 09:22:08 +08:00
selectedCreator: null,
2025-05-23 08:53:43 +08:00
pagination: {
current_page: 1,
total_pages: 0,
total_count: 0,
has_next: false,
has_prev: false,
},
hasMore: true,
isLoadingMore: false,
2025-05-09 10:18:49 +08:00
};
2025-05-23 22:27:14 +08:00
export const fetchCreators = createAsyncThunk(
'creators/fetchCreators',
async ({ page = 1 }, { getState, rejectWithValue }) => {
try {
const state = getState();
2025-05-30 05:13:50 +08:00
const {pricing, ...filter} = state.filters;
2025-05-23 08:53:43 +08:00
2025-05-23 22:27:14 +08:00
const { code, data, message, pagination } = await api.post(
`/daren_detail/public/creators/filter/?page=${page}`,
2025-05-24 08:16:21 +08:00
{ filter }
2025-05-23 22:27:14 +08:00
);
if (code === 200) {
return { data, pagination };
} else {
throw new Error(message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
2025-05-23 08:53:43 +08:00
export const fetchPrivateCreators = createAsyncThunk(
'creators/fetchPrivateCreators',
2025-05-23 22:27:14 +08:00
async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => {
try {
const state = getState();
2025-05-29 04:26:34 +08:00
const filter = state.filters;
2025-05-23 08:53:43 +08:00
2025-05-23 22:27:14 +08:00
const { code, data, message, pagination } = await api.post(
`/daren_detail/private/pools/creators/filter/?page=${page}`,
2025-05-24 08:16:21 +08:00
{ pool_id: 1, filter }
2025-05-23 22:27:14 +08:00
);
if (code === 200) {
return { data, pagination };
} else {
throw new Error(message);
}
} catch (error) {
return rejectWithValue(error.message);
}
2025-05-23 08:53:43 +08:00
}
);
2025-05-29 04:26:34 +08:00
/**
* 搜索公有达人库达人
* @param {Object} params
* @param {string} params.q 搜索关键词
* @param {string} params.mode 搜索类型 OR AND 固定or
* @param {number} params.page 页码
* @param {number} params.page_size 每页条数
*/
export const searchCreators = createAsyncThunk(
'creators/searchPublicCreators',
async (params, { rejectWithValue, dispatch }) => {
try {
const response = await api.get(`/daren_detail/creators/search/`, { params });
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
/**
* 搜索私有达人库达人
* @param {Object} params
* @param {string} params.q 搜索关键词
* @param {string} params.mode 搜索类型 OR AND 固定or
* @param {number} params.page 页码
* @param {number} params.page_size 每页条数
*/
export const searchPrivateCreators = createAsyncThunk(
'creators/searchPrivateCreators',
async (params, { rejectWithValue, dispatch }) => {
try {
const response = await api.get(`/daren_detail/private/creators/search/`, { params });
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
2025-05-23 08:53:43 +08:00
export const fetchCreatorDetail = createAsyncThunk(
'creators/fetchCreatorDetail',
2025-05-24 22:40:29 +08:00
async ({ creatorId }, { dispatch, rejectWithValue }) => {
2025-05-24 08:16:21 +08:00
try {
const response = await api.get(`/daren_detail/creators/${creatorId}`);
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
2025-05-24 22:40:29 +08:00
} catch (error) {
console.log('fetchCreatorDetail.rejected', error);
return rejectWithValue(error.message);
}
}
);
export const fetchCreatorMetrics = createAsyncThunk(
'creators/fetchCreatorMetrics',
async ({ creatorId }, { rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creators/${creatorId}/metrics`);
if (response.code === 200 || response.code === 201) {
return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const fetchCreatorFollowers = createAsyncThunk(
'creators/fetchCreatorFollowers',
async ({ creatorId }, { rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creator/${creatorId}/followers`);
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const fetchCreatorTrends = createAsyncThunk(
'creators/fetchCreatorTrends',
async ({ creatorId }, { rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creator/${creatorId}/trends`);
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const fetchCreatorVideos = createAsyncThunk(
'creators/fetchCreatorVideos',
async ({ creatorId }, { rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creator/${creatorId}/videos`);
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
2025-05-24 08:16:21 +08:00
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const addCreatorsToCampaign = createAsyncThunk(
'creators/addCreatorsToCampaign',
async ({ creatorIds, campaignId }, { getState, rejectWithValue }) => {
try {
const response = await api.post(`/daren_detail/campaigns/add/`, {
creator_ids: creatorIds,
campaign_id: campaignId,
});
if (response.code === 200) {
2025-05-24 22:40:29 +08:00
dispatch(
setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' })
);
2025-05-24 08:16:21 +08:00
return response;
} else {
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
2025-05-23 08:53:43 +08:00
}
);
2025-05-09 10:18:49 +08:00
const creatorsSlice = createSlice({
name: 'creators',
initialState,
reducers: {
toggleCreatorSelection: (state, action) => {
const creatorId = action.payload;
const isSelected = state.selectedCreators.includes(creatorId);
if (isSelected) {
2025-05-16 09:22:08 +08:00
state.selectedCreators = state.selectedCreators.filter((id) => id.toString() !== creatorId.toString());
2025-05-09 10:18:49 +08:00
} else {
state.selectedCreators.push(creatorId);
}
},
2025-05-23 09:58:48 +08:00
selectAllCreators: (state, action) => {
if (action.payload === 'database') {
2025-05-24 08:16:21 +08:00
state.selectedCreators = state.publicCreators.map((creator) => creator.creator_id);
2025-05-23 09:58:48 +08:00
} else {
2025-05-24 08:16:21 +08:00
state.selectedCreators = state.privateCreators.map((creator) => creator.creator_id);
2025-05-23 09:58:48 +08:00
}
2025-05-09 10:18:49 +08:00
},
clearCreatorSelection: (state) => {
state.selectedCreators = [];
},
2025-05-16 09:22:08 +08:00
clearCreator: (state) => {
state.selectedCreator = null;
},
2025-05-23 08:53:43 +08:00
resetCreators: (state) => {
state.publicCreators = [];
state.privateCreators = [];
state.pagination = initialState.pagination;
state.hasMore = true;
state.isLoadingMore = false;
},
2025-05-09 10:18:49 +08:00
},
extraReducers: (builder) => {
builder
.addCase(fetchCreators.pending, (state) => {
2025-05-23 22:27:14 +08:00
state.status = 'loading';
2025-05-23 09:58:48 +08:00
if (state.publicCreators.length === 0) {
2025-05-23 08:53:43 +08:00
} else {
state.isLoadingMore = true;
}
2025-05-09 10:18:49 +08:00
})
.addCase(fetchCreators.fulfilled, (state, action) => {
state.status = 'succeeded';
2025-05-23 08:53:43 +08:00
state.isLoadingMore = false;
const { data, pagination } = action.payload;
if (pagination.current_page === 1) {
state.publicCreators = data;
} else {
state.publicCreators = [...state.publicCreators, ...data];
}
state.pagination = pagination;
state.hasMore = pagination.has_next;
2025-05-09 10:18:49 +08:00
})
.addCase(fetchCreators.rejected, (state, action) => {
2025-05-23 08:53:43 +08:00
state.status = 'failed';
state.isLoadingMore = false;
2025-05-23 22:27:14 +08:00
state.error = action.payload;
2025-05-23 08:53:43 +08:00
})
.addCase(fetchPrivateCreators.pending, (state) => {
2025-05-23 09:58:48 +08:00
if (state.privateCreators?.length === 0) {
2025-05-23 08:53:43 +08:00
state.status = 'loading';
} else {
state.isLoadingMore = true;
}
})
.addCase(fetchPrivateCreators.fulfilled, (state, action) => {
state.status = 'succeeded';
state.isLoadingMore = false;
const { data, pagination } = action.payload;
if (pagination.current_page === 1) {
state.privateCreators = data;
} else {
state.privateCreators = [...state.privateCreators, ...data];
}
state.pagination = pagination;
state.hasMore = pagination.has_next;
})
.addCase(fetchPrivateCreators.rejected, (state, action) => {
console.log('fetchPrivateCreators.rejected', action);
state.status = 'failed';
state.isLoadingMore = false;
2025-05-23 22:27:14 +08:00
state.error = action.payload;
2025-05-23 08:53:43 +08:00
})
.addCase(fetchCreatorDetail.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchCreatorDetail.fulfilled, (state, action) => {
state.status = 'succeeded';
2025-05-23 09:22:51 +08:00
const { data } = action.payload;
2025-05-23 08:53:43 +08:00
state.selectedCreator = data;
})
.addCase(fetchCreatorDetail.rejected, (state, action) => {
2025-05-09 10:18:49 +08:00
state.status = 'failed';
state.error = action.error.message;
2025-05-24 22:40:29 +08:00
})
.addCase(fetchCreatorMetrics.fulfilled, (state, action) => {
state.selectedCreator.metricsData = action.payload.data;
})
.addCase(fetchCreatorFollowers.fulfilled, (state, action) => {
state.selectedCreator.followerData = action.payload.data;
})
.addCase(fetchCreatorTrends.fulfilled, (state, action) => {
state.selectedCreator.trendsData = action.payload.data;
})
.addCase(fetchCreatorVideos.fulfilled, (state, action) => {
state.selectedCreator.videosData = action.payload.data;
2025-05-29 04:26:34 +08:00
})
.addCase(searchCreators.pending, (state) => {
state.status = 'loading';
})
.addCase(searchPrivateCreators.pending, (state) => {
state.status = 'loading';
})
.addCase(searchCreators.fulfilled, (state, action) => {
state.publicCreators = action.payload.data;
state.status = 'succeeded';
state.error = null;
})
.addCase(searchPrivateCreators.fulfilled, (state, action) => {
state.privateCreators = action.payload.data;
state.status = 'succeeded';
state.error = null;
})
.addCase(searchCreators.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload;
console.log('searchCreators.rejected', action);
})
.addCase(searchPrivateCreators.rejected, (state, action) => {
state.status = 'failed';
state.error = action.payload;
2025-05-09 10:18:49 +08:00
});
},
});
2025-05-23 08:53:43 +08:00
export const {
toggleCreatorSelection,
selectAllCreators,
clearCreatorSelection,
clearCreator,
setCreators,
resetCreators,
} = creatorsSlice.actions;
2025-05-09 10:18:49 +08:00
export default creatorsSlice.reducer;