diff --git a/src/components/CreatorList.jsx b/src/components/CreatorList.jsx index 72f2eb0..843ed26 100644 --- a/src/components/CreatorList.jsx +++ b/src/components/CreatorList.jsx @@ -25,6 +25,7 @@ export default function CreatorList({ path }) { hasMore, isLoadingMore, pagination, + error, } = useSelector((state) => state.creators); const { sortBy, sortDirection } = useSelector((state) => state.filters); const observer = useRef(); @@ -49,7 +50,8 @@ export default function CreatorList({ path }) { }, [path, dispatch]); useEffect(() => { - }, [publicCreators]); + console.log(publicCreators, status); + }, [publicCreators, status]); // 处理全选/取消全选 const handleSelectAll = (e) => { @@ -67,11 +69,13 @@ export default function CreatorList({ path }) { // 处理排序 const handleSort = (field) => { + return; dispatch(setSortBy(field)); }; // 渲染排序图标 const renderSortIcon = (field) => { + return; if (sortBy === field) { return sortDirection === 'asc' ? : ; } @@ -93,7 +97,7 @@ export default function CreatorList({ path }) { }; // 如果正在加载且没有数据,显示加载中 - if (status === 'loading' && (!publicCreators || publicCreators.length === 0)) { + if (status === 'loading' && !isLoadingMore) { return (
@@ -105,7 +109,7 @@ export default function CreatorList({ path }) { // 如果加载失败,显示错误信息 if (status === 'failed') { - return
Failed to load creators. Please try again later.
; + return
{error || 'Failed to load creators. Please try again later.'}
; } return ( @@ -117,7 +121,9 @@ export default function CreatorList({ path }) { 0} + checked={ + selectedCreators.length === publicCreators.length && publicCreators.length > 0 + } onChange={handleSelectAll} /> diff --git a/src/components/DatabaseFilter.jsx b/src/components/DatabaseFilter.jsx index 02e8578..409803a 100644 --- a/src/components/DatabaseFilter.jsx +++ b/src/components/DatabaseFilter.jsx @@ -10,8 +10,9 @@ import { toggleGmvRange, setViewsRange, setPricingRange, + setPlatform, } from '../store/slices/filtersSlice'; -import { fetchCreators } from '../store/slices/creatorsSlice'; +import { fetchCreators, resetCreators } from '../store/slices/creatorsSlice'; import '../styles/DatabaseFilter.scss'; export default function DatabaseFilter({ path, pageType = 'database' }) { @@ -63,23 +64,23 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { }; // 本地状态用于表单控制 - const [minViews, setMinViews] = useState(filters.viewsRange[0]); - const [maxViews, setMaxViews] = useState(filters.viewsRange[1]); - const [minPricing, setMinPricing] = useState(filters.pricingRange[0]); - const [maxPricing, setMaxPricing] = useState(filters.pricingRange[1]); + const [minViews, setMinViews] = useState(filters.views_range[0]); + const [maxViews, setMaxViews] = useState(filters.views_range[1]); + const [minPricing, setMinPricing] = useState(filters.pricing[0]); + const [maxPricing, setMaxPricing] = useState(filters.pricing[1]); // 监听Redux状态变化,更新本地表单状态 useEffect(() => { - setMinViews(filters.viewsRange[0]); - setMaxViews(filters.viewsRange[1]); - setMinPricing(filters.pricingRange[0]); - setMaxPricing(filters.pricingRange[1]); - }, [filters.viewsRange, filters.pricingRange]); + setMinViews(filters.views_range[0]); + setMaxViews(filters.views_range[1]); + setMinPricing(filters.pricing[0]); + setMaxPricing(filters.pricing[1]); + }, [filters.views_range, filters.pricing]); // 组件加载时获取数据 useEffect(() => { - - }, [dispatch, filters, pageType, path]); + dispatch(setPlatform(path)); + }, [dispatch, path]); // 处理类别选择 const handleCategorySelect = (category) => { @@ -104,6 +105,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { // 处理视图范围更新 const handleViewsRangeChange = (newRange) => { dispatch(setViewsRange(newRange)); + resetAndFetch(); }; // 处理min input变更 @@ -120,6 +122,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { const handlePricingRangeChange = (newRange) => { dispatch(setPricingRange(newRange)); + resetAndFetch(); }; // 处理min pricing input变更 @@ -145,7 +148,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { const finalValue = Math.min(discreteValue, maxViews); setMinViews(finalValue); - dispatch(setViewsRange([finalValue, filters.viewsRange[1]])); + dispatch(setViewsRange([finalValue, filters.views_range[1]])); } else { // 找到最接近的离散值 const closestIndex = findClosestDiscreteIndex(maxViews); @@ -155,7 +158,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { const finalValue = Math.max(discreteValue, minViews); setMaxViews(finalValue); - dispatch(setViewsRange([filters.viewsRange[0], finalValue])); + dispatch(setViewsRange([filters.views_range[0], finalValue])); } }; @@ -169,7 +172,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { const finalValue = Math.min(discreteValue, maxPricing); setMinPricing(finalValue); - dispatch(setPricingRange([finalValue, filters.pricingRange[1]])); + dispatch(setPricingRange([finalValue, filters.pricing[1]])); } else { const closestIndex = findClosestDiscreteIndex(maxPricing); const discreteValue = discretePricingValues[closestIndex]; @@ -178,7 +181,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { const finalValue = Math.max(discreteValue, minPricing); setMaxPricing(finalValue); - dispatch(setPricingRange([filters.pricingRange[0], finalValue])); + dispatch(setPricingRange([filters.pricing[0], finalValue])); } }; @@ -193,6 +196,43 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { return value; }; + const resetAndFetch = () => { + dispatch(resetCreators()); + if (pageType === 'private') { + dispatch(fetchPrivateCreators({ page: 1 })); + } else { + dispatch(fetchCreators({ page: 1 })); + } + }; + + const onChange = (e) => { + const name = e.target.name; + const value = e.target.value; + switch (name) { + case 'category': + handleCategorySelect(value); + break; + case 'e_commerce_level': + handleEcommerceRatingSelect(value); + break; + case 'exposure_level': + handleExposureRatingSelect(value); + break; + case 'gmv_range': + handleGmvRangeSelect(value); + break; + case 'views_range': + handleViewsRangeChange(value); + break; + case 'pricing': + handlePricingRangeChange(value); + break; + default: + break; + } + resetAndFetch(); + }; + return (
@@ -207,7 +247,9 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { key={category} variant={filters.category.includes(category) ? 'primary' : 'light'} className='rounded-pill' - onClick={() => handleCategorySelect(category)} + onClick={onChange} + name='category' + value={category} > {category} @@ -222,9 +264,11 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { {ecommerceRatings.map((rating) => ( @@ -239,9 +283,11 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { {exposureRatings.map((rating) => ( @@ -256,9 +302,11 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { {gmvRanges.map((range) => ( @@ -273,7 +321,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { @@ -313,7 +361,7 @@ export default function DatabaseFilter({ path, pageType = 'database' }) { diff --git a/src/components/NotificationBar.jsx b/src/components/NotificationBar.jsx new file mode 100644 index 0000000..bd9ab7f --- /dev/null +++ b/src/components/NotificationBar.jsx @@ -0,0 +1,21 @@ +import { Check, CircleAlert, Info, X } from 'lucide-react'; +import { useDispatch } from 'react-redux'; + +export default function NotificationBar() { + const { message, type, show } = useSelector((state) => state.notificationBar); + const dispatch = useDispatch(); + + return ( +
+ {type === 'success' && } + {type === 'warning' && } + {type === 'error' && } + {type === 'info' && } +
{message}
+ +
+ ); +} diff --git a/src/components/PrivateCreatorList.jsx b/src/components/PrivateCreatorList.jsx index 2c2f001..ae12900 100644 --- a/src/components/PrivateCreatorList.jsx +++ b/src/components/PrivateCreatorList.jsx @@ -15,7 +15,7 @@ import { Link } from 'react-router-dom'; export default function PrivateCreatorList({ path }) { const dispatch = useDispatch(); - const { privateCreators, status, selectedCreators, hasMore, isLoadingMore, pagination } = useSelector( + const { privateCreators, status, selectedCreators, hasMore, isLoadingMore, pagination, error } = useSelector( (state) => state.creators ); const { sortBy, sortDirection } = useSelector((state) => state.filters); @@ -60,11 +60,13 @@ export default function PrivateCreatorList({ path }) { // 处理排序 const handleSort = (field) => { + return; dispatch(setSortBy(field)); }; // 渲染排序图标 const renderSortIcon = (field) => { + return; if (sortBy === field) { return sortDirection === 'asc' ? : ; } @@ -86,7 +88,7 @@ export default function PrivateCreatorList({ path }) { }; // 如果正在加载且没有数据,显示加载中 - if (status === 'loading' && (!privateCreators || privateCreators.length === 0)) { + if (status === 'loading' && !isLoadingMore) { return (
@@ -98,7 +100,7 @@ export default function PrivateCreatorList({ path }) { // 如果加载失败,显示错误信息 if (status === 'failed') { - return
Failed to load creators. Please try again later.
; + return
{error || 'Failed to load creators. Please try again later.'}
; } return ( diff --git a/src/store/slices/creatorsSlice.js b/src/store/slices/creatorsSlice.js index 51587ff..7125708 100644 --- a/src/store/slices/creatorsSlice.js +++ b/src/store/slices/creatorsSlice.js @@ -216,24 +216,47 @@ const initialState = { isLoadingMore: false, }; -export const fetchCreators = createAsyncThunk('creators/fetchCreators', async ({ path, page = 1 }, { getState }) => { - const state = getState(); - const filters = state.filters; +export const fetchCreators = createAsyncThunk( + 'creators/fetchCreators', + async ({ page = 1 }, { getState, rejectWithValue }) => { + try { + const state = getState(); + const filters = state.filters; - const response = await api.get(`/daren_detail/public/creators`, { params: { page } }); - console.log(response); - return response; -}); + const { code, data, message, pagination } = await api.post( + `/daren_detail/public/creators/filter/?page=${page}`, + filters + ); + if (code === 200) { + return { data, pagination }; + } else { + throw new Error(message); + } + } catch (error) { + return rejectWithValue(error.message); + } + } +); export const fetchPrivateCreators = createAsyncThunk( 'creators/fetchPrivateCreators', - async ({ path, page = 1 }, { getState }) => { - const state = getState(); - const filters = state.filters; + async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => { + try { + const state = getState(); + const filters = state.filters; - const queryParams = { pool_id: 1, page }; - const response = await api.get(`/daren_detail/private/pools/creators`, { params: queryParams }); - return response; + const { code, data, message, pagination } = await api.post( + `/daren_detail/private/pools/creators/filter/?page=${page}`, + { pool_id: 1, filters } + ); + if (code === 200) { + return { data, pagination }; + } else { + throw new Error(message); + } + } catch (error) { + return rejectWithValue(error.message); + } } ); @@ -283,8 +306,8 @@ const creatorsSlice = createSlice({ extraReducers: (builder) => { builder .addCase(fetchCreators.pending, (state) => { + state.status = 'loading'; if (state.publicCreators.length === 0) { - state.status = 'loading'; } else { state.isLoadingMore = true; } @@ -304,7 +327,7 @@ const creatorsSlice = createSlice({ .addCase(fetchCreators.rejected, (state, action) => { state.status = 'failed'; state.isLoadingMore = false; - state.error = action.error.message; + state.error = action.payload; }) .addCase(fetchPrivateCreators.pending, (state) => { if (state.privateCreators?.length === 0) { @@ -330,7 +353,7 @@ const creatorsSlice = createSlice({ console.log('fetchPrivateCreators.rejected', action); state.status = 'failed'; state.isLoadingMore = false; - state.error = action.error.message; + state.error = action.payload; }) .addCase(fetchCreatorDetail.pending, (state) => { state.status = 'loading'; diff --git a/src/store/slices/filtersSlice.js b/src/store/slices/filtersSlice.js index 0b9c9a9..2ffac92 100644 --- a/src/store/slices/filtersSlice.js +++ b/src/store/slices/filtersSlice.js @@ -2,13 +2,14 @@ import { createSlice } from '@reduxjs/toolkit'; const initialState = { category: ['Homes Supplies'], - ecommerceRatings: ['L2', 'L3'], - exposureRatings: [], - gmvRanges: ['$5k - $25k', '$25k - $60k'], - viewsRange: [0, 100000], - pricingRange: [0, 3000], + e_commerce_level: ['L2', 'L3'], + exposure_level: [], + gmv_range: ['$5k - $25k', '$25k - $60k'], + views_range: [0, 100000], + pricing: [0, 3000], sortBy: 'followers', sortDirection: 'desc', + platform: '', }; const filtersSlice = createSlice({ name: 'filters', @@ -24,33 +25,33 @@ const filtersSlice = createSlice({ }, toggleEcommerceRating: (state, action) => { const rating = action.payload; - if (state.ecommerceRatings.includes(rating)) { - state.ecommerceRatings = state.ecommerceRatings.filter((r) => r !== rating); + if (state.e_commerce_level.includes(rating)) { + state.e_commerce_level = state.e_commerce_level.filter((r) => r !== rating); } else { - state.ecommerceRatings.push(rating); + state.e_commerce_level.push(rating); } }, toggleExposureRating: (state, action) => { const rating = action.payload; - if (state.exposureRatings.includes(rating)) { - state.exposureRatings = state.exposureRatings.filter((r) => r !== rating); + if (state.exposure_level.includes(rating)) { + state.exposure_level = state.exposure_level.filter((r) => r !== rating); } else { - state.exposureRatings.push(rating); + state.exposure_level.push(rating); } }, toggleGmvRange: (state, action) => { const range = action.payload; - if (state.gmvRanges.includes(range)) { - state.gmvRanges = state.gmvRanges.filter((r) => r !== range); + if (state.gmv_range.includes(range)) { + state.gmv_range = state.gmv_range.filter((r) => r !== range); } else { - state.gmvRanges.push(range); + state.gmv_range.push(range); } }, setViewsRange: (state, action) => { - state.viewsRange = action.payload; + state.views_range = action.payload; }, setPricingRange: (state, action) => { - state.pricingRange = action.payload; + state.pricing = action.payload; }, setSortBy: (state, action) => { // 如果选择了当前已激活的排序项,则切换排序方向 @@ -62,6 +63,9 @@ const filtersSlice = createSlice({ state.sortDirection = 'desc'; } }, + setPlatform: (state, action) => { + state.platform = action.payload; + }, resetFilters: () => { return initialState; }, @@ -76,6 +80,7 @@ export const { setViewsRange, setPricingRange, setSortBy, + setPlatform, resetFilters, } = filtersSlice.actions; diff --git a/src/store/slices/notificationBarSlice.js b/src/store/slices/notificationBarSlice.js new file mode 100644 index 0000000..4ae76f2 --- /dev/null +++ b/src/store/slices/notificationBarSlice.js @@ -0,0 +1,27 @@ +const initialState = { + message: '', + show: false, + type: 'success', // success, warning, error, info +}; + +const notificationBarSlice = createSlice({ + name: 'notificationBar', + initialState, + reducers: { + setNotificationBarMessage: (state, action) => { + state.message = action.payload; + }, + setNotificationBarShow: (state, action) => { + state.show = action.payload; + }, + resetNotificationBar: (state) => { + state.message = ''; + state.show = false; + state.type = 'success'; + }, + }, +}); + +export const { setNotificationBarMessage, setNotificationBarShow, resetNotificationBar } = notificationBarSlice.actions; + +export default notificationBarSlice.reducer;