add
This commit is contained in:
parent
7723891358
commit
1c3af15978
|
@ -1,5 +1,5 @@
|
||||||
# 基础镜像
|
# 基础镜像
|
||||||
FROM node:18.17-alpine as base
|
FROM node:18-alpine as base
|
||||||
# 更改 apk 镜像源为阿里云
|
# 更改 apk 镜像源为阿里云
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||||
# 设置 npm 镜像源
|
# 设置 npm 镜像源
|
||||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
4
apps/web/src/app/main/help/MoreContent.tsx → apps/web/src/app/main/help/ExampleContent.tsx
Normal file → Executable file
4
apps/web/src/app/main/help/MoreContent.tsx → apps/web/src/app/main/help/ExampleContent.tsx
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
||||||
export function MoreContent() {
|
export function ExampleContent() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[400px]">
|
<div className="min-h-[400px]">
|
||||||
<h2 className="text-xl font-bold mb-4">更多</h2>
|
<h2 className="text-xl font-bold mb-4">案例分析</h2>
|
||||||
{/* 这里放视频列表内容 */}
|
{/* 这里放视频列表内容 */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -1,35 +1,72 @@
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from "react";
|
||||||
import { Tabs, Button, message, Image, Row, Col, Modal, Input } from 'antd';
|
import {
|
||||||
|
Tabs,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Image,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Modal,
|
||||||
|
Input,
|
||||||
|
Alert,
|
||||||
|
Pagination,
|
||||||
|
} from "antd";
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
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 { api } from "@nice/client";
|
||||||
import dayjs from 'dayjs';
|
import dayjs from "dayjs";
|
||||||
import { env } from "@web/src/env";
|
import { env } from "@web/src/env";
|
||||||
import { getFileIcon } from "@web/src/components/models/post/detail/utils";
|
import { getFileIcon } from "@web/src/components/models/post/detail/utils";
|
||||||
import { formatFileSize, getCompressedImageUrl } from "@nice/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;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
export function MusicContent() {
|
export function MusicContent() {
|
||||||
|
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
const [uploaderKey, setUploaderKey] = useState<number>(0);
|
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");
|
||||||
|
}, [hasSomePermissions]);
|
||||||
|
|
||||||
// 获取资源列表
|
// 获取资源列表
|
||||||
const { data: resources,refetch } :{data:ResourceDto[],refetch:()=>void} = api.resource.findMany.useQuery({
|
const {
|
||||||
|
data: resources,
|
||||||
|
refetch,
|
||||||
|
}: { data: ResourceDto[]; refetch: () => void } =
|
||||||
|
api.resource.findMany.useQuery({
|
||||||
where: {
|
where: {
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
postId: null,
|
postId: null,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc'
|
createdAt: "desc",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理资源数据
|
// 处理资源数据
|
||||||
const { imageResources, fileResources } = useMemo(() => {
|
const { imageResources, fileResources, imagePagination, filePagination } =
|
||||||
if (!resources) return { imageResources: [], fileResources: [] };
|
useMemo(() => {
|
||||||
|
if (!resources)
|
||||||
|
return {
|
||||||
|
imageResources: [],
|
||||||
|
fileResources: [],
|
||||||
|
imagePagination: { total: 0, data: [] },
|
||||||
|
filePagination: { total: 0, data: [] },
|
||||||
|
};
|
||||||
|
|
||||||
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
|
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
|
||||||
|
|
||||||
const processedResources = resources.map(resource => {
|
const processedResources = resources
|
||||||
|
.map((resource) => {
|
||||||
if (!resource?.url) return null;
|
if (!resource?.url) return null;
|
||||||
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
|
const original = `http://${env.SERVER_IP}:${env.UPLOAD_PORT}/uploads/${resource.url}`;
|
||||||
const isImg = isImage(resource.url);
|
const isImg = isImage(resource.url);
|
||||||
|
@ -39,26 +76,50 @@ export function MusicContent() {
|
||||||
originalUrl: original,
|
originalUrl: original,
|
||||||
isImage: isImg,
|
isImage: isImg,
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
})
|
||||||
|
.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 {
|
return {
|
||||||
imageResources: processedResources.filter(res => res.isImage),
|
imageResources: allImageResources.slice(
|
||||||
fileResources: processedResources.filter(res => !res.isImage)
|
imageStart,
|
||||||
|
imageStart + pageSize
|
||||||
|
),
|
||||||
|
fileResources: allFileResources.slice(fileStart, fileStart + pageSize),
|
||||||
|
imagePagination: {
|
||||||
|
total: allImageResources.length,
|
||||||
|
data: allImageResources,
|
||||||
|
},
|
||||||
|
filePagination: {
|
||||||
|
total: allFileResources.length,
|
||||||
|
data: allFileResources,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [resources]);
|
}, [resources, imagePage, filePage]);
|
||||||
|
|
||||||
const createMutation = api.resource.create.useMutation({});
|
const createMutation = api.resource.create.useMutation({});
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
message.error("请先登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDomainAdmin) {
|
||||||
|
message.error("只有管理员才能上传文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!fileIds.length) {
|
if (!fileIds.length) {
|
||||||
message.error("请先上传文件");
|
message.error("请先上传文件");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!description.trim()) {
|
|
||||||
// message.error("请输入文件描述");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 逐个上传文件,而不是使用 Promise.all
|
// 逐个上传文件,而不是使用 Promise.all
|
||||||
for (const fileId of fileIds) {
|
for (const fileId of fileIds) {
|
||||||
|
@ -66,12 +127,14 @@ export function MusicContent() {
|
||||||
await createMutation.mutateAsync({
|
await createMutation.mutateAsync({
|
||||||
data: {
|
data: {
|
||||||
fileId,
|
fileId,
|
||||||
// description: description.trim(),
|
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message.includes('Unique constraint failed')) {
|
if (
|
||||||
|
error instanceof Error &&
|
||||||
|
error.message.includes("Unique constraint failed")
|
||||||
|
) {
|
||||||
console.warn(`文件 ${fileId} 已存在,跳过`);
|
console.warn(`文件 ${fileId} 已存在,跳过`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -80,8 +143,7 @@ export function MusicContent() {
|
||||||
}
|
}
|
||||||
message.success("上传成功!");
|
message.success("上传成功!");
|
||||||
setFileIds([]);
|
setFileIds([]);
|
||||||
// setDescription('');
|
setUploaderKey((prev) => prev + 1);
|
||||||
setUploaderKey(prev => prev + 1);
|
|
||||||
refetch(); // 刷新列表
|
refetch(); // 刷新列表
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error uploading:", error);
|
console.error("Error uploading:", error);
|
||||||
|
@ -89,19 +151,28 @@ export function MusicContent() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 删除资源
|
// 删除资源
|
||||||
const deleteMutation = api.resource.softDeleteByIds.useMutation();
|
const deleteMutation = api.resource.softDeleteByIds.useMutation();
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
message.error("请先登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDomainAdmin) {
|
||||||
|
message.error("只有域管理员才能删除文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Delete resource id:", id);
|
console.log("Delete resource id:", id);
|
||||||
try {
|
try {
|
||||||
const confirmed = await new Promise(resolve => {
|
const confirmed = await new Promise((resolve) => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '确认删除',
|
title: "确认删除",
|
||||||
content: '确定要删除这个文件吗?此操作不可恢复。',
|
content: "确定要删除这个文件吗?此操作不可恢复。",
|
||||||
okText: '确认',
|
okText: "确认",
|
||||||
cancelText: '取消',
|
cancelText: "取消",
|
||||||
onOk: () => resolve(true),
|
onOk: () => resolve(true),
|
||||||
onCancel: () => resolve(false),
|
onCancel: () => resolve(false),
|
||||||
});
|
});
|
||||||
|
@ -110,56 +181,68 @@ export function MusicContent() {
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
|
|
||||||
await deleteMutation.mutateAsync({
|
await deleteMutation.mutateAsync({
|
||||||
ids: [id]
|
ids: [id],
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete error:', error);
|
console.error("Delete error:", error);
|
||||||
message.error('删除失败,请重试');
|
message.error("删除失败,请重试");
|
||||||
}
|
}
|
||||||
|
|
||||||
refetch();
|
refetch();
|
||||||
message.success('删除成功');
|
message.success("删除成功");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<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">
|
<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">
|
<div className="flex flex-col space-b-1">
|
||||||
<h1>锦囊资源上传</h1>
|
<h1>资源上传</h1>
|
||||||
<p>支持视频、图片、文档、PPT等多种格式文件</p>
|
<p>支持视频、图片、文档、PPT等多种格式文件</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<div className="space-y-4">
|
||||||
<Tabs defaultActiveKey="1">
|
<Tabs defaultActiveKey="1">
|
||||||
<TabPane >
|
<TabPane>
|
||||||
<div className="relative rounded-xl border border-white hover:ring-1 ring-white transition-all duration-300 ease-in-out bg-slate-100">
|
<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
|
<TusUploader
|
||||||
key={uploaderKey}
|
key={uploaderKey}
|
||||||
value={fileIds}
|
value={fileIds}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
console.log('文件IDs:', value);
|
if (!isDomainAdmin) {
|
||||||
|
message.error("只有管理员才能上传文件");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setFileIds(value);
|
setFileIds(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabPane>
|
</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>
|
</Tabs>
|
||||||
{fileIds.length > 0 && (
|
{isDomainAdmin && fileIds.length > 0 && (
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -174,51 +257,16 @@ export function MusicContent() {
|
||||||
|
|
||||||
{/* 文件展示区域 */}
|
{/* 文件展示区域 */}
|
||||||
<div className="space-y-6">
|
<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);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Image.PreviewGroup>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
|
|
||||||
{/* 其他文件资源展示 */}
|
{/* 其他文件资源展示 */}
|
||||||
{fileResources?.length > 0 && (
|
{filePagination?.total > 0 && (
|
||||||
<div className="rounded-xl border p-4 bg-white">
|
<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">
|
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{fileResources.map((resource) => (
|
{fileResources.map((resource) => (
|
||||||
<div
|
<div
|
||||||
|
@ -230,7 +278,7 @@ export function MusicContent() {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="font-medium truncate">
|
<div className="font-medium truncate">
|
||||||
{resource.title || '未命名文件'}
|
{resource.title || "未命名文件"}
|
||||||
</div>
|
</div>
|
||||||
{resource.description && (
|
{resource.description && (
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
@ -238,8 +286,13 @@ export function MusicContent() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="text-xs text-gray-500 flex items-center gap-2">
|
<div className="text-xs text-gray-500 flex items-center gap-2">
|
||||||
<span>{dayjs(resource.createdAt).format('YYYY-MM-DD')}</span>
|
<span>
|
||||||
<span>{resource.meta?.size && formatFileSize(resource.meta.size)}</span>
|
{dayjs(resource.createdAt).format("YYYY-MM-DD")}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{resource.meta?.size &&
|
||||||
|
formatFileSize(resource.meta.size)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
@ -250,24 +303,39 @@ export function MusicContent() {
|
||||||
>
|
>
|
||||||
下载
|
下载
|
||||||
</Button>
|
</Button>
|
||||||
|
{isDomainAdmin && (
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation(); // 防止触发下载
|
e.stopPropagation();
|
||||||
handleDelete(resource.id);
|
handleDelete(resource.id);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
|
@ -7,46 +7,46 @@ import {
|
||||||
BookOutlined
|
BookOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { VideoContent } from './VideoContent';
|
import { VideoContent } from './VideoContent';
|
||||||
import { CourseContent } from './CourseContent';
|
|
||||||
import { MusicContent } from './MusicContent';
|
import { MusicContent } from './MusicContent';
|
||||||
import { ScienceContent } from './ScienceContent';
|
import { ScienceContent } from './ScienceContent';
|
||||||
import { MoreContent } from './MoreContent';
|
import { PublicityContent } from './PublicityContent';
|
||||||
|
import { ExampleContent } from './ExampleContent';
|
||||||
import './pt.css';
|
import './pt.css';
|
||||||
|
|
||||||
export function PsychologyNav() {
|
export function PsychologyNav() {
|
||||||
const items = [
|
const items = [
|
||||||
// {
|
{
|
||||||
// key: 'more',
|
key: 'publicity',
|
||||||
// label: (
|
label: (
|
||||||
// <span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
||||||
// <BookOutlined className="text-lg" />
|
<BookOutlined className="text-lg" />
|
||||||
// <span>宣传报道</span>
|
<span>宣传报道</span>
|
||||||
// </span>
|
</span>
|
||||||
// ),
|
),
|
||||||
// children: <VideoContent />
|
children: <PublicityContent />
|
||||||
// },
|
},
|
||||||
// {
|
|
||||||
// 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: 'science',
|
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: (
|
label: (
|
||||||
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
||||||
< FileTextOutlined className="text-lg" />
|
< FileTextOutlined className="text-lg" />
|
||||||
|
@ -56,7 +56,7 @@ export function PsychologyNav() {
|
||||||
children: <VideoContent />
|
children: <VideoContent />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'vedio',
|
key: 'music',
|
||||||
label: (
|
label: (
|
||||||
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
<span className="flex items-center gap-2 text-gray-700 hover:text-primary transition-colors">
|
||||||
<CustomerServiceOutlined className="text-lg" />
|
<CustomerServiceOutlined className="text-lg" />
|
||||||
|
@ -71,7 +71,7 @@ export function PsychologyNav() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full from bg-white rounded-lg shadow-md">
|
<div className="w-full from bg-white rounded-lg shadow-md">
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey="videos"
|
defaultActiveKey="publicity"
|
||||||
items={items}
|
items={items}
|
||||||
className="psychology-tabs"
|
className="psychology-tabs"
|
||||||
tabBarStyle={{
|
tabBarStyle={{
|
||||||
|
|
4
apps/web/src/app/main/help/CourseContent.tsx → apps/web/src/app/main/help/PublicityContent.tsx
Normal file → Executable file
4
apps/web/src/app/main/help/CourseContent.tsx → apps/web/src/app/main/help/PublicityContent.tsx
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
||||||
export function CourseContent() {
|
export function PublicityContent() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-[400px]">
|
<div className="min-h-[400px]">
|
||||||
<h2 className="text-xl font-bold mb-4">心理小课件</h2>
|
<h2 className="text-xl font-bold mb-4">宣传报道</h2>
|
||||||
{/* 这里放课件列表内容 */}
|
{/* 这里放课件列表内容 */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
Input,
|
Input,
|
||||||
Alert,
|
Alert,
|
||||||
|
Pagination,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||||
import { SendOutlined, DeleteOutlined, LoginOutlined } from "@ant-design/icons";
|
import { SendOutlined, DeleteOutlined, LoginOutlined } from "@ant-design/icons";
|
||||||
|
@ -25,11 +26,15 @@ export function VideoContent() {
|
||||||
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
const { isAuthenticated, user, hasSomePermissions } = useAuth();
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
const [uploaderKey, setUploaderKey] = useState<number>(0);
|
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(() => {
|
const isDomainAdmin = useMemo(() => {
|
||||||
return hasSomePermissions("MANAGE_DOM_STAFF", "MANAGE_ANY_STAFF"); // 使用权限检查而不是角色名称
|
return hasSomePermissions("MANAGE_DOM_STAFF", "MANAGE_ANY_STAFF");
|
||||||
}, [hasSomePermissions]);
|
}, [hasSomePermissions]);
|
||||||
|
|
||||||
// 获取资源列表
|
// 获取资源列表
|
||||||
|
@ -46,9 +51,18 @@ export function VideoContent() {
|
||||||
createdAt: "desc",
|
createdAt: "desc",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理资源数据
|
// 处理资源数据
|
||||||
const { imageResources, fileResources } = useMemo(() => {
|
const { imageResources, fileResources, imagePagination, filePagination } =
|
||||||
if (!resources) return { imageResources: [], fileResources: [] };
|
useMemo(() => {
|
||||||
|
if (!resources)
|
||||||
|
return {
|
||||||
|
imageResources: [],
|
||||||
|
fileResources: [],
|
||||||
|
imagePagination: { total: 0, data: [] },
|
||||||
|
filePagination: { total: 0, data: [] },
|
||||||
|
};
|
||||||
|
|
||||||
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
|
const isImage = (url: string) => /\.(png|jpg|jpeg|gif|webp)$/i.test(url);
|
||||||
|
|
||||||
const processedResources = resources
|
const processedResources = resources
|
||||||
|
@ -65,11 +79,29 @@ export function VideoContent() {
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.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 {
|
return {
|
||||||
imageResources: processedResources.filter((res) => res.isImage),
|
imageResources: allImageResources.slice(
|
||||||
fileResources: processedResources.filter((res) => !res.isImage),
|
imageStart,
|
||||||
|
imageStart + pageSize
|
||||||
|
),
|
||||||
|
fileResources: allFileResources.slice(fileStart, fileStart + pageSize),
|
||||||
|
imagePagination: {
|
||||||
|
total: allImageResources.length,
|
||||||
|
data: allImageResources,
|
||||||
|
},
|
||||||
|
filePagination: {
|
||||||
|
total: allFileResources.length,
|
||||||
|
data: allFileResources,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [resources]);
|
}, [resources, imagePage, filePage]);
|
||||||
|
|
||||||
const createMutation = api.resource.create.useMutation({});
|
const createMutation = api.resource.create.useMutation({});
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
@ -95,7 +127,6 @@ export function VideoContent() {
|
||||||
await createMutation.mutateAsync({
|
await createMutation.mutateAsync({
|
||||||
data: {
|
data: {
|
||||||
fileId,
|
fileId,
|
||||||
// description: description.trim(),
|
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -112,7 +143,6 @@ export function VideoContent() {
|
||||||
}
|
}
|
||||||
message.success("上传成功!");
|
message.success("上传成功!");
|
||||||
setFileIds([]);
|
setFileIds([]);
|
||||||
// setDescription('');
|
|
||||||
setUploaderKey((prev) => prev + 1);
|
setUploaderKey((prev) => prev + 1);
|
||||||
refetch(); // 刷新列表
|
refetch(); // 刷新列表
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -166,7 +196,7 @@ export function VideoContent() {
|
||||||
<div className="p-6">
|
<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">
|
<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">
|
<div className="flex flex-col space-b-1">
|
||||||
<h1>锦囊资源上传</h1>
|
<h1>资源上传</h1>
|
||||||
<p>支持视频、图片、文档、PPT等多种格式文件</p>
|
<p>支持视频、图片、文档、PPT等多种格式文件</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -209,13 +239,6 @@ export function VideoContent() {
|
||||||
setFileIds(value);
|
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>
|
</div>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -235,9 +258,15 @@ export function VideoContent() {
|
||||||
{/* 文件展示区域 */}
|
{/* 文件展示区域 */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* 图片资源展示 */}
|
{/* 图片资源展示 */}
|
||||||
{imageResources?.length > 0 && (
|
{imagePagination?.total > 0 && (
|
||||||
<div className="rounded-xl border p-4 bg-white">
|
<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]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Image.PreviewGroup>
|
<Image.PreviewGroup>
|
||||||
{imageResources.map((resource) => (
|
{imageResources.map((resource) => (
|
||||||
|
@ -275,13 +304,32 @@ export function VideoContent() {
|
||||||
))}
|
))}
|
||||||
</Image.PreviewGroup>
|
</Image.PreviewGroup>
|
||||||
</Row>
|
</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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 其他文件资源展示 */}
|
{/* 其他文件资源展示 */}
|
||||||
{fileResources?.length > 0 && (
|
{filePagination?.total > 0 && (
|
||||||
<div className="rounded-xl border p-4 bg-white">
|
<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">
|
<div className="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{fileResources.map((resource) => (
|
{fileResources.map((resource) => (
|
||||||
<div
|
<div
|
||||||
|
@ -334,6 +382,19 @@ export function VideoContent() {
|
||||||
</div>
|
</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>
|
||||||
|
|
|
@ -35,7 +35,7 @@ export default function PostCommentCard({
|
||||||
<div className="flex items-start space-x-2 gap-2">
|
<div className="flex items-start space-x-2 gap-2">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<a href={post.author?.meta?.photoUrl}>
|
<a href={post.author?.meta?.photoUrl}>
|
||||||
{post.author?.meta?.photoUrl}
|
{/* {post.author?.meta?.photoUrl} */}
|
||||||
</a>
|
</a>
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
src={post.author?.meta?.photoUrl}
|
src={post.author?.meta?.photoUrl}
|
||||||
|
|
Loading…
Reference in New Issue