CreatorCenter_OOIN/src/store/slices/creatorsSlice.js
2025-05-29 17:13:50 -04:00

566 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import api from '@/services/api';
import { setNotificationBarMessage } from './notificationBarSlice';
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,
},
];
// 模拟创作者数据实际项目中会从API获取
export const mockCreators = [
{
id: 1,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=1',
category: 'Phones & Electronics',
e_commerce_level: 'L2',
exposure_level: 'KOC-1',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: true,
hasTiktok: true,
verified: true,
videos: mockVideos,
videosWithProduct: mockVideos,
},
{
id: 2,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=2',
category: 'Womenswear & Underwear',
e_commerce_level: 'L3',
exposure_level: 'KOL-3',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: false,
hasTiktok: true,
verified: false,
videos: mockVideos,
videosWithProduct: mockVideos,
},
{
id: 3,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=3',
category: 'Sports & Outdoor',
e_commerce_level: 'L4',
exposure_level: 'KOC-2',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: true,
hasTiktok: true,
verified: false,
videos: mockVideos,
videosWithProduct: mockVideos,
},
{
id: 4,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=4',
category: 'Food & Beverage',
e_commerce_level: 'L1',
exposure_level: 'KOC-2',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: true,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: true,
videos: mockVideos,
videosWithProduct: mockVideos,
},
{
id: 5,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=5',
category: 'Health',
e_commerce_level: 'L5',
exposure_level: 'KOL-2',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: false,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: true,
videos: mockVideos,
videosWithProduct: mockVideos,
},
{
id: 6,
name: 'name',
avatar: 'https://api.dicebear.com/7.x/micah/svg?seed=6',
category: 'Kitchenware',
e_commerce_level: 'New tag',
exposure_level: 'New tag',
followers: '162.2k',
gmv: '$534.1k',
soldPercentage: '18.1%',
avg_video_views: '1.9k',
hasEcommerce: true,
hasTiktok: true,
hasInstagram: true,
hasYoutube: true,
verified: false,
videos: mockVideos,
videosWithProduct: mockVideos,
},
];
// 模拟API获取数据的异步Thunk
// export const fetchCreators = createAsyncThunk('creators/fetchCreators', async ({ path }, { getState }) => {
// // 模拟API调用延迟
// await new Promise((resolve) => setTimeout(resolve, 500));
// // 获取当前的筛选条件
// const state = getState();
// const filters = state.filters;
// // 应用筛选逻辑(实际项目中可能在服务器端进行)
// let filteredCreators = [...mockCreators];
// // 如果有选定的类别,进行筛选
// if (filters.category.length > 0) {
// filteredCreators = filteredCreators.filter((creator) => filters.category.includes(creator.category));
// }
// // 如果有选定的电商评级,进行筛选
// if (filters.ecommerceRatings.length > 0) {
// filteredCreators = filteredCreators.filter((creator) =>
// filters.ecommerceRatings.includes(creator.e_commerce_level)
// );
// }
// // 如果有选定的曝光评级,进行筛选
// if (filters.exposureRatings.length > 0) {
// filteredCreators = filteredCreators.filter((creator) =>
// filters.exposureRatings.includes(creator.exposure_level)
// );
// }
// // 筛选观看量范围
// if (filters.viewsRange.length === 2) {
// const minViews = filters.viewsRange[0];
// const maxViews = filters.viewsRange[1];
// 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;
// }
// return views >= minViews && views <= maxViews;
// });
// }
// return filteredCreators;
// });
const initialState = {
publicCreators: [],
privateCreators: [],
publicTiktokCreators: [],
publicInstagramCreators: [],
publicYoutubeCreators: [],
privateTiktokCreators: [],
privateInstagramCreators: [],
privateYoutubeCreators: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null,
selectedCreators: [],
selectedCreator: null,
pagination: {
current_page: 1,
total_pages: 0,
total_count: 0,
has_next: false,
has_prev: false,
},
hasMore: true,
isLoadingMore: false,
};
export const fetchCreators = createAsyncThunk(
'creators/fetchCreators',
async ({ page = 1 }, { getState, rejectWithValue }) => {
try {
const state = getState();
const {pricing, ...filter} = state.filters;
const { code, data, message, pagination } = await api.post(
`/daren_detail/public/creators/filter/?page=${page}`,
{ filter }
);
if (code === 200) {
return { data, pagination };
} else {
throw new Error(message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const fetchPrivateCreators = createAsyncThunk(
'creators/fetchPrivateCreators',
async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => {
try {
const state = getState();
const filter = state.filters;
const { code, data, message, pagination } = await api.post(
`/daren_detail/private/pools/creators/filter/?page=${page}`,
{ pool_id: 1, filter }
);
if (code === 200) {
return { data, pagination };
} else {
throw new Error(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 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);
}
}
);
export const fetchCreatorDetail = createAsyncThunk(
'creators/fetchCreatorDetail',
async ({ creatorId }, { dispatch, rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creators/${creatorId}`);
if (response.code === 200) {
return response;
} else {
throw new Error(response.message);
}
} 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);
}
} 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) {
dispatch(
setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' })
);
return response;
} else {
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
const creatorsSlice = createSlice({
name: 'creators',
initialState,
reducers: {
toggleCreatorSelection: (state, action) => {
const creatorId = action.payload;
const isSelected = state.selectedCreators.includes(creatorId);
if (isSelected) {
state.selectedCreators = state.selectedCreators.filter((id) => id.toString() !== creatorId.toString());
} else {
state.selectedCreators.push(creatorId);
}
},
selectAllCreators: (state, action) => {
if (action.payload === 'database') {
state.selectedCreators = state.publicCreators.map((creator) => creator.creator_id);
} else {
state.selectedCreators = state.privateCreators.map((creator) => creator.creator_id);
}
},
clearCreatorSelection: (state) => {
state.selectedCreators = [];
},
clearCreator: (state) => {
state.selectedCreator = null;
},
resetCreators: (state) => {
state.publicCreators = [];
state.privateCreators = [];
state.pagination = initialState.pagination;
state.hasMore = true;
state.isLoadingMore = false;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchCreators.pending, (state) => {
state.status = 'loading';
if (state.publicCreators.length === 0) {
} else {
state.isLoadingMore = true;
}
})
.addCase(fetchCreators.fulfilled, (state, action) => {
state.status = 'succeeded';
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;
})
.addCase(fetchCreators.rejected, (state, action) => {
state.status = 'failed';
state.isLoadingMore = false;
state.error = action.payload;
})
.addCase(fetchPrivateCreators.pending, (state) => {
if (state.privateCreators?.length === 0) {
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;
state.error = action.payload;
})
.addCase(fetchCreatorDetail.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchCreatorDetail.fulfilled, (state, action) => {
state.status = 'succeeded';
const { data } = action.payload;
state.selectedCreator = data;
})
.addCase(fetchCreatorDetail.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
})
.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;
})
.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;
});
},
});
export const {
toggleCreatorSelection,
selectAllCreators,
clearCreatorSelection,
clearCreator,
setCreators,
resetCreators,
} = creatorsSlice.actions;
export default creatorsSlice.reducer;