| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 |
- import { Text, View, ScrollView } from "@tarojs/components";
- import { Heart, HeartFill, Plus } from "@nutui/icons-react-taro";
- import { Message } from "@nutui/icons-react-taro";
- import avatar from "../../asset/img/avatar.png";
- import {
- Grid,
- GridItem,
- Image,
- ImagePreview,
- Toast,
- } from "@nutui/nutui-react-taro";
- import { createPortal } from "react-dom";
- import MoreButton from "../fllower-button";
- import { circlePostListApi } from "../../api/post";
- import {
- useEffect,
- useState,
- useCallback,
- useImperativeHandle,
- forwardRef,
- } from "react";
- import { strSlice } from "../../utils/index";
- import { followApi, interactionApi, unfollowApi } from "../../api/interaction";
- import { Dialog } from "@nutui/nutui-react-taro";
- import { getStorageSync, navigateTo } from "@tarojs/taro";
- interface PostListProps {
- style?: any;
- }
- export interface PostListRef {
- loadMore: () => void;
- refresh: () => void;
- hasMore: boolean;
- loading: boolean;
- }
- const PostList = forwardRef<PostListRef, PostListProps>(({ style }, ref) => {
- const [postList, setPostList] = useState<any[]>([]);
- const [showPreview, setShowPreview] = useState(false);
- const [init, setInit] = useState(0);
- const [mediaUrl, setMediaUrl] = useState<any[]>([]);
- // 分页相关状态
- const [currentPage, setCurrentPage] = useState(1);
- const [hasMore, setHasMore] = useState(true);
- const [loading, setLoading] = useState(false);
- const [refreshing, setRefreshing] = useState(false);
- const pageSize = 10;
- const getPostList = useCallback(
- async (page = 1, isRefresh = false) => {
- if (isRefresh) {
- setRefreshing(true);
- } else {
- setLoading(true);
- }
- try {
- let circleInfo = getStorageSync("circleInfo");
- const res: any = await circlePostListApi({
- circleId: circleInfo.id,
- page,
- pageSize: pageSize,
- });
- if (res.code === 200) {
- const newList = res.data.list || [];
- if (isRefresh || page === 1) {
- setPostList(newList);
- } else {
- setPostList((prev) => [...prev, ...newList]);
- }
- // 判断是否还有更多数据
- setHasMore(res.data.has_more);
- setCurrentPage(page);
- }
- } catch (error) {
- Toast.show("load_error", {
- content: "加载失败,请重试",
- duration: 2,
- });
- } finally {
- setLoading(false);
- setRefreshing(false);
- }
- },
- [pageSize]
- );
- // 下拉刷新
- const handleRefresh = useCallback(async () => {
- await getPostList(1, true);
- }, [getPostList]);
- // 上拉加载更多
- const handleLoadMore = useCallback(async () => {
- if (hasMore && !loading && !refreshing) {
- await getPostList(currentPage + 1, false);
- }
- }, [hasMore, currentPage, getPostList, loading, refreshing]);
- // ScrollView 滚动到底部事件
- const handleScrollToLower = useCallback(() => {
- handleLoadMore();
- }, [handleLoadMore]);
- // ScrollView 下拉刷新事件
- const handleRefresherRefresh = useCallback(async () => {
- await handleRefresh();
- }, [handleRefresh]);
- const handleMoreClick = (postIndex: number) => {
- const data = [...postList];
- data.splice(postIndex, 1);
- setPostList(data);
- };
- // 点赞
- const handleInteraction = async (postId: number, type) => {
- const res: any = await interactionApi({
- target_type: "post",
- target_id: postId,
- interaction_type: type,
- });
- if (res.code === 200) {
- const data = [...postList];
- data.forEach((item: any) => {
- if (item.id === postId) {
- item.is_praised = type === "praise" ? true : false;
- item.like_count =
- type === "praise" ? item.like_count + 1 : item.like_count - 1;
- }
- });
- console.log(data);
- setPostList(data);
- }
- };
- // 关注/不关注
- const handleFollowClick = (followeeId: number, is_followed) => {
- if (is_followed) {
- unfollowApi({ followee_id: followeeId }).then(() => {
- Toast.show("follow_success", {
- content: "取消关注成功",
- duration: 2,
- lockScroll: true,
- });
- const data = [...postList];
- data.forEach((item: any) => {
- item.is_followed = false;
- });
- setPostList(data);
- Dialog.close("followed_dialog");
- });
- } else {
- followApi({ followee_id: followeeId }).then(() => {
- Toast.show("follow_success", {
- content: "关注成功",
- duration: 2,
- lockScroll: true,
- });
- const data = [...postList];
- data.forEach((item: any) => {
- item.is_followed = true;
- });
- setPostList(data);
- });
- }
- };
- // 暴露方法给父组件
- useImperativeHandle(ref, () => ({
- loadMore: handleLoadMore,
- refresh: handleRefresh,
- hasMore,
- loading,
- }));
- useEffect(() => {
- getPostList(1, true);
- }, [getPostList]);
- return (
- <>
- <ScrollView
- className="post-list bg-[#f8f8f8]"
- scrollY
- refresherEnabled
- refresherTriggered={refreshing}
- onRefresherRefresh={handleRefresherRefresh}
- onScrollToLower={handleScrollToLower}
- lowerThreshold={50}
- style={style}
- >
- {postList.map((item: any, index: number) => {
- return (
- <View
- key={item.id}
- className="post-list-item bg-[#fff] p-[10px] mb-[10px]"
- >
- <View
- className="flex items-center justify-between"
- onClick={() => {
- navigateTo({
- url: `/pages/self/index?id=${item.user_id}`,
- });
- }}
- >
- <View className="flex items-center">
- <View
- className="w-[40px] h-[40px] rounded-[50%] bg-[#000] overflow-hidden bg-cover bg-center"
- style={{
- backgroundImage: item.user.avatar_url
- ? `url('${item.user.avatar_url}')`
- : `url('${avatar}')`,
- }}
- ></View>
- <View className="ml-[5px]">
- <View className="text-[14px] font-[800] text-[#9c9dee]">
- {item.user.nickname || item.user.username || "微信用户"}
- </View>
- {item.user.is_official ? (
- <View className="text-[12px] text-[#949494]">
- 官方账号
- </View>
- ) : (
- <></>
- )}
- </View>
- </View>
- <View className="flex items-center">
- <View
- className="flex items-center justify-center mr-[12px] text-[12px] bg-[#f8f8f8] text-[#949494] rounded-[20px] h-[25px] w-[60px] text-center"
- onClick={(e) => {
- e.stopPropagation();
- handleFollowClick(item.user_id, item.is_followed);
- }}
- >
- {!item.is_followed && <Plus color="#1874d0" />}
- <Text>{item.is_followed ? "已关注" : "关注"}</Text>
- </View>
- {!item.user.is_official && (
- <MoreButton
- postIndex={index}
- followeeId={item.user_id}
- postId={item.id}
- postItem={item}
- onClick={handleMoreClick}
- />
- )}
- </View>
- </View>
- <View
- onClick={() =>
- navigateTo({ url: `/pages/detail/index?id=${item.id}` })
- }
- >
- <View className="text-[14px] text-[#333] pt-[20px]">
- {item.type === "1" ? (
- <>
- {strSlice(item.content, 100)}
- {item.content.length > 100 && (
- <Text className="text-[#1874d0]">全文</Text>
- )}
- </>
- ) : (
- <>
- <View
- className="text-[14px] text-[#333]"
- dangerouslySetInnerHTML={{
- __html: strSlice(item.content, 100),
- }}
- ></View>
- {item.content.length > 100 && (
- <Text className="text-[#1874d0]">全文</Text>
- )}
- </>
- )}
- </View>
- <View className="mt-[10px]">
- <Grid
- columns={item.media_url.length < 3 ? 2 : 3}
- gap={7}
- style={
- {
- "--nutui-grid-item-content-padding": "0px",
- "--nutui-grid-item-content-margin": "0px",
- "--nutui-grid-border-color": "transparent",
- } as any
- }
- >
- {item.media_url.map((ite: any, index: number) => {
- return (
- <GridItem>
- <Image
- src={ite.src}
- mode="scaleToFill"
- onClick={(e) => {
- e.preventDefault();
- e.stopPropagation();
- setMediaUrl(item.media_url);
- setInit(index + 1);
- setShowPreview(true);
- }}
- />
- </GridItem>
- );
- })}
- </Grid>
- </View>
- <View className="mt-[10px] pl-[20px] pr-[20px] flex items-center justify-between">
- <View className="flex items-center">
- <Message size={16} color="#949494" />
- <Text className="ml-[5px] text-[16px] text-[#949494]">
- {item.comment_count}
- </Text>
- </View>
- <View
- className="flex items-center"
- onClick={(e) => {
- e.stopPropagation();
- handleInteraction(
- item.id,
- item.is_praised ? "dispraise" : "praise"
- );
- }}
- >
- {item.is_praised ? (
- <HeartFill size={16} color="#ec4342" />
- ) : (
- <Heart size={16} color="#949494" />
- )}
- <Text className="ml-[5px] text-[16px] text-[#949494]">
- {item.like_count}
- </Text>
- </View>
- </View>
- </View>
- </View>
- );
- })}
- {/* 空状态提示 */}
- {postList.length === 0 && !loading && (
- <View className="flex flex-col items-center justify-center py-[50px]">
- <Text className="text-[#999] text-[14px]">暂无帖子</Text>
- </View>
- )}
- {/* 加载更多提示 */}
- {hasMore && (
- <View className="p-[20px] text-center">
- <Text className="text-[14px] text-[#999]">
- {loading ? "加载中..." : "上拉加载更多"}
- </Text>
- </View>
- )}
- {!hasMore && postList.length > 0 && (
- <View className="p-[20px] text-center">
- <Text className="text-[14px] text-[#999]">没有更多了</Text>
- </View>
- )}
- </ScrollView>
- <Toast id="follow_success" />
- <Toast id="load_error" />
- <Dialog id="followed_dialog" />
- {showPreview &&
- createPortal(
- <ImagePreview
- images={mediaUrl}
- visible={showPreview}
- defaultValue={init}
- onClose={() => setShowPreview(false)}
- indicator
- />,
- document.body
- )}
- </>
- );
- });
- PostList.displayName = "PostList";
- export default PostList;
|