|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useState, useEffect } from 'react';
|
|
|
+import React, { useState, useEffect, useRef } from 'react';
|
|
|
import {
|
|
|
Drawer,
|
|
|
Button,
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
Input,
|
|
|
Card,
|
|
|
Typography,
|
|
|
- Pagination,
|
|
|
Space,
|
|
|
message,
|
|
|
Modal,
|
|
|
@@ -19,7 +18,6 @@ import { useVideoCategoryOptions } from '../../hooks/useVideoCategoryOptions';
|
|
|
import { VideoSortType } from '@src/views/publishContent/weCom/components/videoSelectModal';
|
|
|
import { GzhPlanType } from '../../hooks/useGzhPlanList';
|
|
|
import { VideoSearchPlanType } from '@src/views/publishContent/weCom/type';
|
|
|
-import { enumToOptions } from '@src/utils/helper';
|
|
|
|
|
|
const { Paragraph, Text } = Typography;
|
|
|
|
|
|
@@ -35,16 +33,20 @@ interface VideoSelectModalProps {
|
|
|
const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible, onClose, onOk, initialSelectedIds = [], selectedVideos = [] }) => {
|
|
|
const { videoCategoryOptions } = useVideoCategoryOptions();
|
|
|
const [category, setCategory] = useState<string>();
|
|
|
- const [sort, setSort] = useState<VideoSortType>(VideoSortType.推荐指数);
|
|
|
+ const sort = VideoSortType.推荐指数;
|
|
|
+ const PAGE_SIZE = 10;
|
|
|
const [searchTerm, setSearchTerm] = useState<string>('');
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
|
- const [pageSize, setPageSize] = useState(10);
|
|
|
const [total, setTotal] = useState(0);
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
+ const [loadingMore, setLoadingMore] = useState(false);
|
|
|
+ const [hasMore, setHasMore] = useState(true);
|
|
|
const [videoList, setVideoList] = useState<VideoItem[]>([]);
|
|
|
const [videoListAll, setVideoListAll] = useState<VideoItem[]>([]);
|
|
|
const [selectedVideoIds, setSelectedVideoIds] = useState<Set<number>>(new Set(initialSelectedIds));
|
|
|
const [playingVideo, setPlayingVideo] = useState<VideoItem | null>(null);
|
|
|
+ const sentinelRef = useRef<HTMLDivElement>(null);
|
|
|
+ const loadingMoreRef = useRef(false);
|
|
|
const MAX_SELECTION = 3;
|
|
|
|
|
|
const getVideoListType = (planType: GzhPlanType) => {
|
|
|
@@ -57,29 +59,43 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const getVideoList = async (pageNum?: number, _pageSize?: number) => {
|
|
|
- setLoading(true);
|
|
|
- setCurrentPage(pageNum || currentPage);
|
|
|
- setPageSize(_pageSize || pageSize);
|
|
|
+ const getVideoList = async (pageNum: number, append: boolean) => {
|
|
|
+ if (append) {
|
|
|
+ if (loadingMoreRef.current) return;
|
|
|
+ loadingMoreRef.current = true;
|
|
|
+ setLoadingMore(true);
|
|
|
+ } else {
|
|
|
+ setLoading(true);
|
|
|
+ }
|
|
|
+ setCurrentPage(pageNum);
|
|
|
|
|
|
const requestParams = {
|
|
|
category,
|
|
|
title: searchTerm,
|
|
|
sort,
|
|
|
type: getVideoListType(planType),
|
|
|
- pageNum: pageNum || currentPage,
|
|
|
- pageSize: _pageSize || pageSize,
|
|
|
+ pageNum,
|
|
|
+ pageSize: PAGE_SIZE,
|
|
|
};
|
|
|
|
|
|
const res = await http.post<VideoListResponse>(getVideoContentListApi, requestParams).catch(() => {
|
|
|
message.error('获取视频列表失败');
|
|
|
}).finally(() => {
|
|
|
- setLoading(false);
|
|
|
+ if (append) {
|
|
|
+ loadingMoreRef.current = false;
|
|
|
+ setLoadingMore(false);
|
|
|
+ } else {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
});
|
|
|
if (res && res.code === 0) {
|
|
|
- setVideoList([...selectedVideos, ...res.data.objs.filter(v => !selectedVideos.find(ov => ov.videoId === v.videoId))]);
|
|
|
- setVideoListAll(old => [...old, ...res.data.objs.filter(v => !old.find(ov => ov.videoId === v.videoId))]);
|
|
|
+ const items = res.data.objs || [];
|
|
|
+ setVideoList(prev => append
|
|
|
+ ? [...prev, ...items.filter(v => !prev.find(p => p.videoId === v.videoId))]
|
|
|
+ : [...selectedVideos, ...items.filter(v => !selectedVideos.find(ov => ov.videoId === v.videoId))]);
|
|
|
+ setVideoListAll(old => [...old, ...items.filter(v => !old.find(ov => ov.videoId === v.videoId))]);
|
|
|
setTotal(res.data.totalSize);
|
|
|
+ setHasMore(pageNum * PAGE_SIZE < res.data.totalSize);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -87,7 +103,8 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
if (visible) {
|
|
|
setVideoList(selectedVideos);
|
|
|
setVideoListAll(selectedVideos);
|
|
|
- getVideoList(0);
|
|
|
+ setHasMore(true);
|
|
|
+ getVideoList(1, false);
|
|
|
}
|
|
|
}, [visible]);
|
|
|
|
|
|
@@ -97,11 +114,22 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
}
|
|
|
}, [visible, initialSelectedIds]);
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
+ if (!visible) return;
|
|
|
+ const sentinel = sentinelRef.current;
|
|
|
+ if (!sentinel) return;
|
|
|
+ const observer = new IntersectionObserver((entries) => {
|
|
|
+ if (entries[0].isIntersecting && hasMore && !loadingMoreRef.current && !loading) {
|
|
|
+ getVideoList(currentPage + 1, true);
|
|
|
+ }
|
|
|
+ }, { threshold: 0.1 });
|
|
|
+ observer.observe(sentinel);
|
|
|
+ return () => observer.disconnect();
|
|
|
+ }, [visible, hasMore, loading, currentPage]);
|
|
|
+
|
|
|
const handleSearch = () => {
|
|
|
- console.log('Searching for:', { category, searchTerm });
|
|
|
- const currentPage = 1
|
|
|
- setCurrentPage(currentPage);
|
|
|
- getVideoList(currentPage);
|
|
|
+ setHasMore(true);
|
|
|
+ getVideoList(1, false);
|
|
|
};
|
|
|
|
|
|
const handleSelectVideo = (videoId: number) => {
|
|
|
@@ -146,20 +174,7 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
styles={{ footer: { textAlign: 'right', padding: '10px 24px' } }}
|
|
|
footer={
|
|
|
<div className="flex justify-between items-center">
|
|
|
- <Pagination
|
|
|
- current={currentPage}
|
|
|
- pageSize={pageSize}
|
|
|
- total={total}
|
|
|
- onChange={(page, size) => {
|
|
|
- setCurrentPage(page);
|
|
|
- setPageSize(size);
|
|
|
- getVideoList(page, size);
|
|
|
- }}
|
|
|
- pageSizeOptions={['10', '20', '50']}
|
|
|
- size="small"
|
|
|
- showSizeChanger
|
|
|
- showTotal={(total) => `共 ${total} 条`}
|
|
|
- />
|
|
|
+ <Text type="secondary">已加载 {videoList.length} / 共 {total} 条</Text>
|
|
|
<Space>
|
|
|
<Text>已选 {selectedVideoIds.size} / {MAX_SELECTION} 条视频</Text>
|
|
|
<Button onClick={onClose}>取消</Button>
|
|
|
@@ -169,15 +184,6 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
}
|
|
|
>
|
|
|
<div className="flex flex-wrap gap-2 mb-6">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <span className="text-gray-600">排序选项:</span>
|
|
|
- <Select
|
|
|
- style={{ width: 120 }}
|
|
|
- value={sort}
|
|
|
- onChange={setSort}
|
|
|
- options={enumToOptions(VideoSortType)}
|
|
|
- />
|
|
|
- </div>
|
|
|
<div className="flex items-center gap-2">
|
|
|
<span className="text-gray-600">品类:</span>
|
|
|
<Select
|
|
|
@@ -245,6 +251,9 @@ const VideoSelectModal: React.FC<VideoSelectModalProps> = ({ planType, visible,
|
|
|
);
|
|
|
})}
|
|
|
</div>
|
|
|
+ <div ref={sentinelRef} className="text-center py-4 text-gray-400 text-xs">
|
|
|
+ {loadingMore ? '加载中...' : !hasMore && videoList.length > 0 ? '— 没有更多了 —' : ''}
|
|
|
+ </div>
|
|
|
</Drawer>
|
|
|
|
|
|
<Modal
|