This commit is contained in:
Li1304553726 2025-05-05 19:51:44 +08:00
parent 7723891358
commit 1c3af15978
9 changed files with 451 additions and 322 deletions

View File

@ -1,5 +1,5 @@
# 基础镜像
FROM node:18.17-alpine as base
FROM node:18-alpine as base
# 更改 apk 镜像源为阿里云
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
# 设置 npm 镜像源

0
apps/web/public/vite.svg Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,7 +1,7 @@
export function MoreContent() {
export function ExampleContent() {
return (
<div className="min-h-[400px]">
<h2 className="text-xl font-bold mb-4"></h2>
<h2 className="text-xl font-bold mb-4"></h2>
{/* 这里放视频列表内容 */}
</div>
);

560
apps/web/src/app/main/help/MusicContent.tsx Normal file → Executable file
View File

@ -1,273 +1,341 @@
import React, { useState, useMemo } from 'react';
import { Tabs, Button, message, Image, Row, Col, Modal, Input } from 'antd';
import React, { useState, useMemo } from "react";
import {
Tabs,
Button,
message,
Image,
Row,
Col,
Modal,
Input,
Alert,
Pagination,
} from "antd";
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
import { SendOutlined, DeleteOutlined } from "@ant-design/icons";
import { SendOutlined, DeleteOutlined, LoginOutlined } from "@ant-design/icons";
import { api } from "@nice/client";
import dayjs from 'dayjs';
import dayjs from "dayjs";
import { env } from "@web/src/env";
import { getFileIcon } from "@web/src/components/models/post/detail/utils";
import { formatFileSize, getCompressedImageUrl } from "@nice/utils";
import { ResourceDto } from 'packages/common/dist';
import { ResourceDto, RoleName } from "packages/common/dist";
import { useAuth } from "@web/src/providers/auth-provider";
const { TabPane } = Tabs;
export function MusicContent() {
const [fileIds, setFileIds] = useState<string[]>([]);
const [uploaderKey, setUploaderKey] = useState<number>(0);
// const [description, setDescription] = useState<string>('');
// 获取资源列表
const { data: resources,refetch } :{data:ResourceDto[],refetch:()=>void} = api.resource.findMany.useQuery({
where: {
deletedAt: null,
postId: null,
},
orderBy: {
createdAt: 'desc'
}
const { isAuthenticated, user, hasSomePermissions } = useAuth();
const [fileIds, setFileIds] = useState<string[]>([]);
const [uploaderKey, setUploaderKey] = useState<number>(0);
// 分页状态
const [imagePage, setImagePage] = useState(1);
const [filePage, setFilePage] = useState(1);
const pageSize = 12; // 每页显示的数量
// 检查是否为域管理员
const isDomainAdmin = useMemo(() => {
return hasSomePermissions("MANAGE_DOM_STAFF", "MANAGE_ANY_STAFF");
}, [hasSomePermissions]);
// 获取资源列表
const {
data: resources,
refetch,
}: { data: ResourceDto[]; refetch: () => void } =
api.resource.findMany.useQuery({
where: {
deletedAt: null,
postId: null,
},
orderBy: {
createdAt: "desc",
},
});
// 处理资源数据
const { imageResources, fileResources } = useMemo(() => {
if (!resources) return { imageResources: [], fileResources: [] };
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
const processedResources = resources.map(resource => {
if (!resource?.url) return null;
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
const isImg = isImage(resource.url);
return {
...resource,
url: isImg ? getCompressedImageUrl(original) : original,
originalUrl: original,
isImage: isImg,
};
}).filter(Boolean);
// 处理资源数据
const { imageResources, fileResources, imagePagination, filePagination } =
useMemo(() => {
if (!resources)
return {
imageResources: processedResources.filter(res => res.isImage),
fileResources: processedResources.filter(res => !res.isImage)
imageResources: [],
fileResources: [],
imagePagination: { total: 0, data: [] },
filePagination: { total: 0, data: [] },
};
}, [resources]);
const createMutation = api.resource.create.useMutation({});
const handleSubmit = async () => {
if (!fileIds.length) {
message.error("请先上传文件");
return;
}
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
// if (!description.trim()) {
// message.error("请输入文件描述");
// return;
// }
const processedResources = resources
.map((resource) => {
if (!resource?.url) return null;
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
const isImg = isImage(resource.url);
return {
...resource,
url: isImg ? getCompressedImageUrl(original) : original,
originalUrl: original,
isImage: isImg,
};
})
.filter(Boolean);
const allImageResources = processedResources.filter((res) => res.isImage);
const allFileResources = processedResources.filter((res) => !res.isImage);
// 分页处理
const imageStart = (imagePage - 1) * pageSize;
const fileStart = (filePage - 1) * pageSize;
return {
imageResources: allImageResources.slice(
imageStart,
imageStart + pageSize
),
fileResources: allFileResources.slice(fileStart, fileStart + pageSize),
imagePagination: {
total: allImageResources.length,
data: allImageResources,
},
filePagination: {
total: allFileResources.length,
data: allFileResources,
},
};
}, [resources, imagePage, filePage]);
const createMutation = api.resource.create.useMutation({});
const handleSubmit = async () => {
if (!isAuthenticated) {
message.error("请先登录");
return;
}
if (!isDomainAdmin) {
message.error("只有管理员才能上传文件");
return;
}
if (!fileIds.length) {
message.error("请先上传文件");
return;
}
try {
// 逐个上传文件,而不是使用 Promise.all
for (const fileId of fileIds) {
try {
// 逐个上传文件,而不是使用 Promise.all
for (const fileId of fileIds) {
try {
await createMutation.mutateAsync({
data: {
fileId,
// description: description.trim(),
isPublic: true,
}
});
} catch (error) {
if (error instanceof Error && error.message.includes('Unique constraint failed')) {
console.warn(`文件 ${fileId} 已存在,跳过`);
continue;
}
throw error;
}
}
message.success("上传成功!");
setFileIds([]);
// setDescription('');
setUploaderKey(prev => prev + 1);
refetch(); // 刷新列表
await createMutation.mutateAsync({
data: {
fileId,
isPublic: true,
},
});
} catch (error) {
console.error("Error uploading:", error);
message.error("上传失败,请稍后重试");
if (
error instanceof Error &&
error.message.includes("Unique constraint failed")
) {
console.warn(`文件 ${fileId} 已存在,跳过`);
continue;
}
throw error;
}
};
}
message.success("上传成功!");
setFileIds([]);
setUploaderKey((prev) => prev + 1);
refetch(); // 刷新列表
} catch (error) {
console.error("Error uploading:", error);
message.error("上传失败,请稍后重试");
}
};
// 删除资源
const deleteMutation = api.resource.softDeleteByIds.useMutation();
// 删除资源
const deleteMutation = api.resource.softDeleteByIds.useMutation();
const handleDelete = async (id: string) => {
if (!isAuthenticated) {
message.error("请先登录");
return;
}
const handleDelete = async (id: string) => {
console.log("Delete resource id:", id);
try {
const confirmed = await new Promise(resolve => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个文件吗?此操作不可恢复。',
okText: '确认',
cancelText: '取消',
onOk: () => resolve(true),
onCancel: () => resolve(false),
});
});
if (!confirmed) return;
await deleteMutation.mutateAsync({
ids: [id]
});
} catch (error) {
console.error('Delete error:', error);
message.error('删除失败,请重试');
}
if (!isDomainAdmin) {
message.error("只有域管理员才能删除文件");
return;
}
refetch();
message.success('删除成功');
};
console.log("Delete resource id:", id);
try {
const confirmed = await new Promise((resolve) => {
Modal.confirm({
title: "确认删除",
content: "确定要删除这个文件吗?此操作不可恢复。",
okText: "确认",
cancelText: "取消",
onOk: () => resolve(true),
onCancel: () => resolve(false),
});
});
return (
<div className="p-6">
<header className="rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6 relative mb-4">
<div className="flex flex-col space-b-1">
<h1></h1>
<p>PPT等多种格式文件</p>
</div>
</header>
{/* 上传区域 */}
<div className="space-y-4">
<Tabs defaultActiveKey="1">
<TabPane >
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
<TusUploader
key={uploaderKey}
value={fileIds}
onChange={(value) => {
console.log('文件IDs:', value);
setFileIds(value);
}}
/>
</div>
</TabPane>
{/* <TabPane tab="" key="2">
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100 p-4">
<Input.TextArea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="请输入文件描述信息"
autoSize={{ minRows: 3, maxRows: 6 }}
className="bg-transparent border-none focus:ring-0"
/>
<div className="mt-2 text-sm text-gray-500">
<p>便</p>
</div>
</div>
</TabPane> */}
</Tabs>
{fileIds.length > 0 && (
<div className="flex items-center justify-end gap-2">
<Button
type="primary"
onClick={handleSubmit}
className="flex items-center space-x-2 bg-primary"
icon={<SendOutlined />}
>
</Button>
</div>
)}
if (!confirmed) return;
{/* 文件展示区域 */}
<div className="space-y-6">
{/* 图片资源展示 */}
{/* {imageResources?.length > 0 && (
<div className="rounded-xl border p-4 bg-white">
<h3 className="text-lg font-medium mb-4"></h3>
<Row gutter={[16, 16]}>
<Image.PreviewGroup>
{imageResources.map((resource) => (
<Col key={resource.url} xs={12} sm={8} md={6} lg={4}>
<div className="relative aspect-square rounded-lg overflow-hidden flex items-center justify-center bg-gray-100">
<Image
src={resource.url}
alt={resource.title}
preview={{
src: resource.originalUrl,
}}
className="object-contain w-full h-full"
/>
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent">
<div className="flex items-center justify-between text-white">
<span className="text-sm truncate">{resource.title}</span>
<Button
type="text"
size="small"
className="text-white hover:text-red-500"
icon={<DeleteOutlined />}
onClick={(e) => {
e.stopPropagation(); // 防止触发图片预览
handleDelete(resource.id);
}}
/>
await deleteMutation.mutateAsync({
ids: [id],
});
} catch (error) {
console.error("Delete error:", error);
message.error("删除失败,请重试");
}
</div>
</div>
</div>
</Col>
))}
</Image.PreviewGroup>
</Row>
</div>
)} */}
refetch();
message.success("删除成功");
};
{/* 其他文件资源展示 */}
{fileResources?.length > 0 && (
<div className="rounded-xl border p-4 bg-white">
<h3 className="text-lg font-medium mb-4"></h3>
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{fileResources.map((resource) => (
<div
key={resource.url}
className="flex items-center p-3 rounded-lg border hover:shadow-md transition-shadow"
>
<div className="text-primary-600 text-2xl mr-3">
{getFileIcon(resource.url)}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{resource.title || '未命名文件'}
</div>
{resource.description && (
<div className="text-xs text-gray-500 mt-1">
: {resource.description}
</div>
)}
<div className="text-xs text-gray-500 flex items-center gap-2">
<span>{dayjs(resource.createdAt).format('YYYY-MM-DD')}</span>
<span>{resource.meta?.size && formatFileSize(resource.meta.size)}</span>
</div>
</div>
<div className="flex gap-2">
<Button
type="text"
size="small"
onClick={() => window.open(resource.url)}
>
</Button>
<Button
type="text"
size="small"
danger
icon={<DeleteOutlined />}
onClick={(e) => {
e.stopPropagation(); // 防止触发下载
handleDelete(resource.id);
}}
/>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
return (
<div className="p-6">
<header className="rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6 relative mb-4">
<div className="flex flex-col space-b-1">
<h1></h1>
<p>PPT等多种格式文件</p>
</div>
);
};
</header>
{!isAuthenticated && (
<Alert
message="请先登录"
description="您需要登录后才能查看和管理文件资源"
type="warning"
showIcon
className="mb-4"
/>
)}
{isAuthenticated && !isDomainAdmin && (
<Alert
message="权限不足"
description="只有管理员才能上传和管理文件资源"
type="warning"
showIcon
className="mb-4"
/>
)}
{/* 上传区域 */}
<div className="space-y-4">
<Tabs defaultActiveKey="1">
<TabPane>
<div
className={`relative rounded-xl border hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100 ${!isDomainAdmin ? " pointer-events-none" : ""}`}
>
<TusUploader
key={uploaderKey}
value={fileIds}
onChange={(value) => {
if (!isDomainAdmin) {
message.error("只有管理员才能上传文件");
return;
}
setFileIds(value);
}}
/>
</div>
</TabPane>
</Tabs>
{isDomainAdmin && fileIds.length > 0 && (
<div className="flex items-center justify-end gap-2">
<Button
type="primary"
onClick={handleSubmit}
className="flex items-center space-x-2 bg-primary"
icon={<SendOutlined />}
>
</Button>
</div>
)}
{/* 文件展示区域 */}
<div className="space-y-6">
{/* 其他文件资源展示 */}
{filePagination?.total > 0 && (
<div className="rounded-xl border p-4 bg-white">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
<span className="text-gray-500 text-sm">
{filePagination.total}
</span>
</div>
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{fileResources.map((resource) => (
<div
key={resource.url}
className="flex items-center p-3 rounded-lg border hover:shadow-md transition-shadow"
>
<div className="text-primary-600 text-2xl mr-3">
{getFileIcon(resource.url)}
</div>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{resource.title || "未命名文件"}
</div>
{resource.description && (
<div className="text-xs text-gray-500 mt-1">
: {resource.description}
</div>
)}
<div className="text-xs text-gray-500 flex items-center gap-2">
<span>
{dayjs(resource.createdAt).format("YYYY-MM-DD")}
</span>
<span>
{resource.meta?.size &&
formatFileSize(resource.meta.size)}
</span>
</div>
</div>
<div className="flex gap-2">
<Button
type="text"
size="small"
onClick={() => window.open(resource.url)}
>
</Button>
{isDomainAdmin && (
<Button
type="text"
size="small"
danger
icon={<DeleteOutlined />}
onClick={(e) => {
e.stopPropagation();
handleDelete(resource.id);
}}
/>
)}
</div>
</div>
))}
</div>
{/* 文件分页 */}
{filePagination.total > pageSize && (
<div className="flex justify-center mt-6">
<Pagination
current={filePage}
pageSize={pageSize}
total={filePagination.total}
onChange={(page) => setFilePage(page)}
showSizeChanger={false}
/>
</div>
)}
</div>
)}
</div>
</div>
</div>
);
}

68
apps/web/src/app/main/help/PsychologyNav.tsx Normal file → Executable file
View File

@ -7,46 +7,46 @@ import {
BookOutlined
} from '@ant-design/icons';
import { VideoContent } from './VideoContent';
import { CourseContent } from './CourseContent';
import { MusicContent } from './MusicContent';
import { ScienceContent } from './ScienceContent';
import { MoreContent } from './MoreContent';
import { PublicityContent } from './PublicityContent';
import { ExampleContent } from './ExampleContent';
import './pt.css';
export function PsychologyNav() {
const items = [
// {
// key: 'more',
// label: (
// <span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
// <BookOutlined className="text-lg" />
// <span>宣传报道</span>
// </span>
// ),
// children: <VideoContent />
// },
// {
// key: 'music',
// label: (
// <span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
// < ReadOutlined className="text-lg" />
// <span>常识科普</span>
// </span>
// ),
// children: <VideoContent />
// },
// {
// key: 'courses',
// label: (
// <span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
// <VideoCameraOutlined className="text-lg" />
// <span>案例分析</span>
// </span>
// ),
// children: <VideoContent />
// },
{
key: 'publicity',
label: (
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
<BookOutlined className="text-lg" />
<span></span>
</span>
),
children: <PublicityContent />
},
{
key: 'science',
label: (
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
< ReadOutlined className="text-lg" />
<span></span>
</span>
),
children: <ScienceContent />
},
{
key: 'example',
label: (
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
<VideoCameraOutlined className="text-lg" />
<span></span>
</span>
),
children: <ExampleContent />
},
{
key: 'video',
label: (
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
< FileTextOutlined className="text-lg" />
@ -56,7 +56,7 @@ export function PsychologyNav() {
children: <VideoContent />
},
{
key: 'vedio',
key: 'music',
label: (
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
<CustomerServiceOutlined className="text-lg" />
@ -71,7 +71,7 @@ export function PsychologyNav() {
return (
<div className="w-full from bg-white rounded-lg shadow-md">
<Tabs
defaultActiveKey="videos"
defaultActiveKey="publicity"
items={items}
className="psychology-tabs"
tabBarStyle={{

View File

@ -1,7 +1,7 @@
export function CourseContent() {
export function PublicityContent() {
return (
<div className="min-h-[400px]">
<h2 className="text-xl font-bold mb-4"></h2>
<h2 className="text-xl font-bold mb-4"></h2>
{/* 这里放课件列表内容 */}
</div>
);

0
apps/web/src/app/main/help/ScienceContent.tsx Normal file → Executable file
View File

133
apps/web/src/app/main/help/VideoContent.tsx Normal file → Executable file
View File

@ -9,6 +9,7 @@ import {
Modal,
Input,
Alert,
Pagination,
} from "antd";
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
import { SendOutlined, DeleteOutlined, LoginOutlined } from "@ant-design/icons";
@ -25,11 +26,15 @@ export function VideoContent() {
const { isAuthenticated, user, hasSomePermissions } = useAuth();
const [fileIds, setFileIds] = useState<string[]>([]);
const [uploaderKey, setUploaderKey] = useState<number>(0);
// const [description, setDescription] = useState<string>('');
// 分页状态
const [imagePage, setImagePage] = useState(1);
const [filePage, setFilePage] = useState(1);
const pageSize = 12; // 每页显示的数量
// 检查是否为域管理员
const isDomainAdmin = useMemo(() => {
return hasSomePermissions("MANAGE_DOM_STAFF", "MANAGE_ANY_STAFF"); // 使用权限检查而不是角色名称
return hasSomePermissions("MANAGE_DOM_STAFF", "MANAGE_ANY_STAFF");
}, [hasSomePermissions]);
// 获取资源列表
@ -46,30 +51,57 @@ export function VideoContent() {
createdAt: "desc",
},
});
// 处理资源数据
const { imageResources, fileResources } = useMemo(() => {
if (!resources) return { imageResources: [], fileResources: [] };
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
const processedResources = resources
.map((resource) => {
if (!resource?.url) return null;
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
const isImg = isImage(resource.url);
const { imageResources, fileResources, imagePagination, filePagination } =
useMemo(() => {
if (!resources)
return {
...resource,
url: isImg ? getCompressedImageUrl(original) : original,
originalUrl: original,
isImage: isImg,
imageResources: [],
fileResources: [],
imagePagination: { total: 0, data: [] },
filePagination: { total: 0, data: [] },
};
})
.filter(Boolean);
return {
imageResources: processedResources.filter((res) => res.isImage),
fileResources: processedResources.filter((res) => !res.isImage),
};
}, [resources]);
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
const processedResources = resources
.map((resource) => {
if (!resource?.url) return null;
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
const isImg = isImage(resource.url);
return {
...resource,
url: isImg ? getCompressedImageUrl(original) : original,
originalUrl: original,
isImage: isImg,
};
})
.filter(Boolean);
const allImageResources = processedResources.filter((res) => res.isImage);
const allFileResources = processedResources.filter((res) => !res.isImage);
// 分页处理
const imageStart = (imagePage - 1) * pageSize;
const fileStart = (filePage - 1) * pageSize;
return {
imageResources: allImageResources.slice(
imageStart,
imageStart + pageSize
),
fileResources: allFileResources.slice(fileStart, fileStart + pageSize),
imagePagination: {
total: allImageResources.length,
data: allImageResources,
},
filePagination: {
total: allFileResources.length,
data: allFileResources,
},
};
}, [resources, imagePage, filePage]);
const createMutation = api.resource.create.useMutation({});
const handleSubmit = async () => {
@ -95,7 +127,6 @@ export function VideoContent() {
await createMutation.mutateAsync({
data: {
fileId,
// description: description.trim(),
isPublic: true,
},
});
@ -112,7 +143,6 @@ export function VideoContent() {
}
message.success("上传成功!");
setFileIds([]);
// setDescription('');
setUploaderKey((prev) => prev + 1);
refetch(); // 刷新列表
} catch (error) {
@ -166,7 +196,7 @@ export function VideoContent() {
<div className="p-6">
<header className="rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6 relative mb-4">
<div className="flex flex-col space-b-1">
<h1></h1>
<h1></h1>
<p>PPT等多种格式文件</p>
</div>
</header>
@ -209,13 +239,6 @@ export function VideoContent() {
setFileIds(value);
}}
/>
{/* {!isDomainAdmin && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-20 rounded-xl">
<span className="text-white font-medium">
使
</span>
</div>
)} */}
</div>
</TabPane>
</Tabs>
@ -235,9 +258,15 @@ export function VideoContent() {
{/* 文件展示区域 */}
<div className="space-y-6">
{/* 图片资源展示 */}
{imageResources?.length > 0 && (
{imagePagination?.total > 0 && (
<div className="rounded-xl border p-4 bg-white">
<h3 className="text-lg font-medium mb-4"></h3>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
<span className="text-gray-500 text-sm">
{imagePagination.total}
</span>
</div>
<Row gutter={[16, 16]}>
<Image.PreviewGroup>
{imageResources.map((resource) => (
@ -275,13 +304,32 @@ export function VideoContent() {
))}
</Image.PreviewGroup>
</Row>
{/* 图片分页 */}
{imagePagination.total > pageSize && (
<div className="flex justify-center mt-6">
<Pagination
current={imagePage}
pageSize={pageSize}
total={imagePagination.total}
onChange={(page) => setImagePage(page)}
showSizeChanger={false}
/>
</div>
)}
</div>
)}
{/* 其他文件资源展示 */}
{fileResources?.length > 0 && (
{filePagination?.total > 0 && (
<div className="rounded-xl border p-4 bg-white">
<h3 className="text-lg font-medium mb-4"></h3>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
<span className="text-gray-500 text-sm">
{filePagination.total}
</span>
</div>
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
{fileResources.map((resource) => (
<div
@ -334,6 +382,19 @@ export function VideoContent() {
</div>
))}
</div>
{/* 文件分页 */}
{filePagination.total > pageSize && (
<div className="flex justify-center mt-6">
<Pagination
current={filePage}
pageSize={pageSize}
total={filePagination.total}
onChange={(page) => setFilePage(page)}
showSizeChanger={false}
/>
</div>
)}
</div>
)}
</div>

View File

@ -35,7 +35,7 @@ export default function PostCommentCard({
<div className="flex items-start space-x-2 gap-2">
<div className="flex-shrink-0">
<a href={post.author?.meta?.photoUrl}>
{post.author?.meta?.photoUrl}
{/* {post.author?.meta?.photoUrl} */}
</a>
<CustomAvatar
src={post.author?.meta?.photoUrl}