feat: 本次提交更新内容如下
记录列表搞好了
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
Search,
|
||||
RefreshCw,
|
||||
ThumbsUp,
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
} from "lucide-react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -209,7 +209,7 @@ export default function AutoLike() {
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button variant="ghost" size="icon" onClick={() => navigate(-1)}>
|
||||
<ChevronDown className="h-5 w-5" />
|
||||
<ChevronLeft className="h-5 w-5" />
|
||||
</Button>
|
||||
<h1 className="text-lg font-medium">自动点赞</h1>
|
||||
</div>
|
||||
|
||||
@@ -1,455 +1,248 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Edit,
|
||||
Trash2,
|
||||
Copy,
|
||||
ThumbsUp,
|
||||
Settings,
|
||||
Calendar,
|
||||
Eye,
|
||||
RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import Layout from '@/components/Layout';
|
||||
import PageHeader from '@/components/PageHeader';
|
||||
import BottomNav from '@/components/BottomNav';
|
||||
import { useToast } from '@/components/ui/toast';
|
||||
import '@/components/Layout.css';
|
||||
import {
|
||||
fetchAutoLikeTaskDetail,
|
||||
toggleAutoLikeTask,
|
||||
deleteAutoLikeTask,
|
||||
copyAutoLikeTask,
|
||||
fetchLikeRecords,
|
||||
LikeTask,
|
||||
LikeRecord,
|
||||
} from '@/api/autoLike';
|
||||
|
||||
export default function AutoLikeDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { toast } = useToast();
|
||||
const [task, setTask] = useState<LikeTask | null>(null);
|
||||
const [records, setRecords] = useState<LikeRecord[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [recordsLoading, setRecordsLoading] = useState(false);
|
||||
|
||||
const fetchTaskDetail = useCallback(async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
const taskData = await fetchAutoLikeTaskDetail(id);
|
||||
if (taskData) {
|
||||
setTask(taskData);
|
||||
} else {
|
||||
toast({
|
||||
title: '任务不存在',
|
||||
description: '该任务可能已被删除',
|
||||
variant: 'destructive',
|
||||
});
|
||||
navigate('/workspace/auto-like');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取任务详情失败:', error);
|
||||
toast({
|
||||
title: '获取失败',
|
||||
description: '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [id, toast, navigate]);
|
||||
|
||||
const fetchRecords = useCallback(async () => {
|
||||
if (!id) return;
|
||||
|
||||
setRecordsLoading(true);
|
||||
try {
|
||||
const response = await fetchLikeRecords(id, 1, 20);
|
||||
setRecords(response.list || []);
|
||||
} catch (error) {
|
||||
console.error('获取点赞记录失败:', error);
|
||||
} finally {
|
||||
setRecordsLoading(false);
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchTaskDetail();
|
||||
fetchRecords();
|
||||
}
|
||||
}, [id, fetchTaskDetail, fetchRecords]);
|
||||
|
||||
const handleToggleStatus = async () => {
|
||||
if (!task) return;
|
||||
|
||||
// 先更新本地状态
|
||||
const newStatus = (Number(task.status) === 1 ? 2 : 1) as 1 | 2;
|
||||
const originalStatus = Number(task.status) as 1 | 2;
|
||||
setTask(prev => prev ? { ...prev, status: newStatus } : null);
|
||||
|
||||
try {
|
||||
const response = await toggleAutoLikeTask(task.id, String(newStatus));
|
||||
|
||||
if (response.code === 200) {
|
||||
toast({ title: '操作成功' });
|
||||
// 成功时保持本地状态,不重新获取数据
|
||||
} else {
|
||||
// 请求失败,回退本地状态
|
||||
setTask(prev => prev ? { ...prev, status: originalStatus } : null);
|
||||
toast({
|
||||
title: '操作失败',
|
||||
description: response.msg || '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
// 请求异常,回退本地状态
|
||||
setTask(prev => prev ? { ...prev, status: originalStatus } : null);
|
||||
toast({
|
||||
title: '操作失败',
|
||||
description: '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!task || !window.confirm('确定要删除该任务吗?')) return;
|
||||
|
||||
try {
|
||||
const response = await deleteAutoLikeTask(task.id);
|
||||
if (response.code === 200) {
|
||||
toast({ title: '删除成功' });
|
||||
navigate('/workspace/auto-like');
|
||||
} else {
|
||||
toast({
|
||||
title: '删除失败',
|
||||
description: response.msg || '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
toast({
|
||||
title: '删除失败',
|
||||
description: '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
if (!task) return;
|
||||
|
||||
try {
|
||||
const response = await copyAutoLikeTask(task.id);
|
||||
if (response.code === 200) {
|
||||
toast({ title: '复制成功' });
|
||||
navigate('/workspace/auto-like');
|
||||
} else {
|
||||
toast({
|
||||
title: '复制失败',
|
||||
description: response.msg || '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('复制失败:', error);
|
||||
toast({
|
||||
title: '复制失败',
|
||||
description: '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: number) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'bg-green-100 text-green-800';
|
||||
case 2:
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: number) => {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return '进行中';
|
||||
case 2:
|
||||
return '已暂停';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Layout
|
||||
header={<PageHeader title="任务详情" defaultBackPath="/workspace/auto-like" />}
|
||||
footer={<BottomNav />}
|
||||
>
|
||||
<div className="bg-gray-50 min-h-screen pb-20">
|
||||
<div className="p-4">
|
||||
<Card className="p-8 text-center">
|
||||
<RefreshCw className="h-12 w-12 text-gray-300 mx-auto mb-3 animate-spin" />
|
||||
<p className="text-gray-500 text-lg font-medium mb-2">加载中...</p>
|
||||
<p className="text-gray-400 text-sm">请稍候,正在获取任务详情</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<PageHeader
|
||||
title="任务详情"
|
||||
defaultBackPath="/workspace/auto-like"
|
||||
rightContent={
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
操作
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => navigate(`/workspace/auto-like/${task.id}/edit`)}>
|
||||
<Edit className="h-4 w-4 mr-2" />
|
||||
编辑
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleCopy}>
|
||||
<Copy className="h-4 w-4 mr-2" />
|
||||
复制
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleDelete}>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
删除
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
}
|
||||
/>
|
||||
}
|
||||
footer={<BottomNav />}
|
||||
>
|
||||
<div className="bg-gray-50 min-h-screen pb-20">
|
||||
<div className="p-4 space-y-4">
|
||||
{/* 任务基本信息 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center">
|
||||
<ThumbsUp className="h-5 w-5 mr-2" />
|
||||
{task.name}
|
||||
</CardTitle>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge className={getStatusColor(Number(task.status))}>
|
||||
{getStatusText(Number(task.status))}
|
||||
</Badge>
|
||||
<Switch
|
||||
checked={Number(task.status) === 1}
|
||||
onCheckedChange={handleToggleStatus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">创建时间</div>
|
||||
<div className="font-medium">{task.createTime}</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">创建人</div>
|
||||
<div className="font-medium">{task.creator}</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">执行设备</div>
|
||||
<div className="font-medium">{task.deviceCount} 个</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">目标人群</div>
|
||||
<div className="font-medium">{task.targetGroup}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 执行进度 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Calendar className="h-5 w-5 mr-2" />
|
||||
执行进度
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-500">今日已点赞</span>
|
||||
<span className="font-medium">
|
||||
{task.todayLikeCount} / {task.maxLikesPerDay}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={(task.todayLikeCount / task.maxLikesPerDay) * 100}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<div className="text-gray-500">总点赞数</div>
|
||||
<div className="font-medium">{task.totalLikeCount} 次</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-500">上次点赞</div>
|
||||
<div className="font-medium">{task.lastLikeTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 任务配置 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center">
|
||||
<Settings className="h-5 w-5 mr-2" />
|
||||
任务配置
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">点赞间隔</div>
|
||||
<div className="font-medium">{task.likeInterval} 秒</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">每日最大点赞数</div>
|
||||
<div className="font-medium">{task.maxLikesPerDay} 次</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">单个好友最大点赞数</div>
|
||||
<div className="font-medium">{task.friendMaxLikes} 次</div>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<div className="text-gray-500">执行时间段</div>
|
||||
<div className="font-medium">
|
||||
{task.timeRange.start} - {task.timeRange.end}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-gray-500">内容类型</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{task.contentTypes.map((type) => (
|
||||
<Badge key={type} variant="outline">
|
||||
{type === 'text' ? '文字' : type === 'image' ? '图片' : type === 'video' ? '视频' : '链接'}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{task.targetTags.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-gray-500">目标标签</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{task.targetTags.map((tag) => (
|
||||
<Badge key={tag} variant="outline">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{task.enableFriendTags && task.friendTags && (
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm text-gray-500">好友标签</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Badge variant="outline">{task.friendTags}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 点赞记录 */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center">
|
||||
<Eye className="h-5 w-5 mr-2" />
|
||||
点赞记录
|
||||
</CardTitle>
|
||||
<Button variant="outline" size="sm" onClick={fetchRecords}>
|
||||
<RefreshCw className="h-4 w-4 mr-2" />
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{recordsLoading ? (
|
||||
<div className="text-center py-4">
|
||||
<RefreshCw className="h-6 w-6 text-gray-400 mx-auto animate-spin" />
|
||||
<p className="text-gray-500 text-sm mt-2">加载中...</p>
|
||||
</div>
|
||||
) : records.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<ThumbsUp className="h-12 w-12 text-gray-300 mx-auto mb-3" />
|
||||
<p className="text-gray-500">暂无点赞记录</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{records.slice(0, 10).map((record) => (
|
||||
<div key={record.id} className="flex items-center space-x-3 p-3 border rounded-lg">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<ThumbsUp className="h-4 w-4 text-blue-600" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium truncate">
|
||||
{record.friendName}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{record.content}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{record.likeTime}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import {
|
||||
ThumbsUp,
|
||||
RefreshCw,
|
||||
Search,
|
||||
Filter,
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import Layout from '@/components/Layout';
|
||||
import PageHeader from '@/components/PageHeader';
|
||||
import { useToast } from '@/components/ui/toast';
|
||||
import '@/components/Layout.css';
|
||||
import {
|
||||
fetchLikeRecords,
|
||||
LikeRecord,
|
||||
} from '@/api/autoLike';
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch (error) {
|
||||
return dateString;
|
||||
}
|
||||
};
|
||||
|
||||
export default function AutoLikeDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { toast } = useToast();
|
||||
const [records, setRecords] = useState<LikeRecord[]>([]);
|
||||
const [recordsLoading, setRecordsLoading] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const pageSize = 10;
|
||||
|
||||
const fetchRecords = useCallback(async (page: number = 1, keyword?: string) => {
|
||||
if (!id) return;
|
||||
setRecordsLoading(true);
|
||||
try {
|
||||
const response = await fetchLikeRecords(id, page, pageSize, keyword);
|
||||
setRecords(response.list || []);
|
||||
setTotal(response.total || 0);
|
||||
setCurrentPage(page);
|
||||
} catch (error) {
|
||||
console.error('获取点赞记录失败:', error);
|
||||
toast({
|
||||
title: '获取点赞记录失败',
|
||||
description: '请稍后重试',
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
setRecordsLoading(false);
|
||||
}
|
||||
}, [id, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
fetchRecords(1);
|
||||
}
|
||||
}, [id, fetchRecords]); // 加上 fetchRecords 依赖
|
||||
|
||||
const handleSearch = () => {
|
||||
setCurrentPage(1);
|
||||
fetchRecords(1, searchTerm);
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchRecords(currentPage, searchTerm);
|
||||
};
|
||||
|
||||
const handlePageChange = (newPage: number) => {
|
||||
fetchRecords(newPage, searchTerm);
|
||||
};
|
||||
|
||||
return (
|
||||
<Layout
|
||||
header={
|
||||
<PageHeader
|
||||
title="点赞记录"
|
||||
defaultBackPath="/workspace/auto-like"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="bg-gray-50 min-h-screen pb-20">
|
||||
<div className="p-4 space-y-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索好友昵称或内容"
|
||||
className="pl-9"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" onClick={handleRefresh} disabled={recordsLoading}>
|
||||
<RefreshCw className={`h-4 w-4 ${recordsLoading ? 'animate-spin' : ''}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{recordsLoading ? (
|
||||
<div className="space-y-4">
|
||||
{Array.from({ length: 3 }).map((_, index) => (
|
||||
<Card key={index} className="p-4">
|
||||
<div className="flex items-center space-x-3 mb-3">
|
||||
<Skeleton className="h-10 w-10 rounded-full" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-3 w-16" />
|
||||
</div>
|
||||
</div>
|
||||
<Separator className="my-3" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<div className="flex space-x-2 mt-3">
|
||||
<Skeleton className="h-20 w-20" />
|
||||
<Skeleton className="h-20 w-20" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : records.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<ThumbsUp className="h-12 w-12 text-gray-300 mx-auto mb-3" />
|
||||
<p className="text-gray-500">暂无点赞记录</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{records.map((record) => (
|
||||
<div key={record.id} className="p-4 mb-4 bg-white rounded-2xl shadow-sm">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-center space-x-3 max-w-[65%]">
|
||||
<Avatar>
|
||||
<img
|
||||
src={record.friendAvatar || "https://api.dicebear.com/7.x/avataaars/svg?seed=fallback"}
|
||||
alt={record.friendName}
|
||||
className="w-10 h-10 rounded-full"
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium truncate" title={record.friendName}>
|
||||
{record.friendName}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">内容发布者</div>
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="outline" className="bg-blue-50 whitespace-nowrap shrink-0">
|
||||
{formatDate(record.momentTime || record.likeTime)}
|
||||
</Badge>
|
||||
</div>
|
||||
<Separator className="my-3" />
|
||||
<div className="mb-3">
|
||||
{record.content && (
|
||||
<p className="text-gray-700 mb-3 whitespace-pre-line">
|
||||
{record.content}
|
||||
</p>
|
||||
)}
|
||||
{Array.isArray(record.resUrls) && record.resUrls.length > 0 && (
|
||||
<div className={`grid gap-2 ${
|
||||
record.resUrls.length === 1 ? "grid-cols-1" :
|
||||
record.resUrls.length === 2 ? "grid-cols-2" :
|
||||
record.resUrls.length <= 3 ? "grid-cols-3" :
|
||||
record.resUrls.length <= 6 ? "grid-cols-3 grid-rows-2" :
|
||||
"grid-cols-3 grid-rows-3"
|
||||
}`}>
|
||||
{record.resUrls.slice(0, 9).map((image: string, idx: number) => (
|
||||
<div key={idx} className="relative aspect-square rounded-md overflow-hidden">
|
||||
<img
|
||||
src={image}
|
||||
alt={`内容图片 ${idx + 1}`}
|
||||
className="object-cover w-full h-full"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center mt-4 p-2 bg-gray-50 rounded-md">
|
||||
<Avatar className="h-8 w-8 mr-2 shrink-0">
|
||||
<img
|
||||
src={record.operatorAvatar || "https://api.dicebear.com/7.x/avataaars/svg?seed=operator"}
|
||||
alt={record.operatorName}
|
||||
className="w-8 h-8 rounded-full"
|
||||
/>
|
||||
</Avatar>
|
||||
<div className="text-sm min-w-0">
|
||||
<span className="font-medium truncate inline-block max-w-full" title={record.operatorName}>
|
||||
{record.operatorName}
|
||||
</span>
|
||||
<span className="text-gray-500 ml-2">点赞了这条内容</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{/* 分页 */}
|
||||
{records.length > 0 && total > pageSize && (
|
||||
<div className="flex justify-center mt-6">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage === 1}
|
||||
onClick={() => handlePageChange(currentPage - 1)}
|
||||
className="mx-1"
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<span className="mx-4 py-2 text-sm text-gray-500">
|
||||
第 {currentPage} 页,共 {Math.ceil(total / pageSize)} 页
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={currentPage >= Math.ceil(total / pageSize)}
|
||||
onClick={() => handlePageChange(currentPage + 1)}
|
||||
className="mx-1"
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user