add
This commit is contained in:
parent
9d6e9136cf
commit
4ff101ab7b
|
@ -200,6 +200,8 @@ export class PostService extends BaseTreeService<Prisma.PostDelegate> {
|
||||||
rating: number | null;
|
rating: number | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
views: number;
|
views: number;
|
||||||
|
hates: number;
|
||||||
|
likes: number;
|
||||||
publishedAt: Date | null;
|
publishedAt: Date | null;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
deletedAt: Date | null;
|
deletedAt: Date | null;
|
||||||
|
|
|
@ -15,13 +15,13 @@ export class VisitRouter {
|
||||||
private readonly visitService: VisitService,
|
private readonly visitService: VisitService,
|
||||||
) {}
|
) {}
|
||||||
router = this.trpc.router({
|
router = this.trpc.router({
|
||||||
create: this.trpc.protectProcedure
|
create: this.trpc.procedure
|
||||||
.input(VisitCreateArgsSchema)
|
.input(VisitCreateArgsSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { staff } = ctx;
|
const { staff } = ctx;
|
||||||
return await this.visitService.create(input, staff);
|
return await this.visitService.create(input, staff);
|
||||||
}),
|
}),
|
||||||
createMany: this.trpc.protectProcedure
|
createMany: this.trpc.procedure
|
||||||
.input(z.array(VisitCreateManyInputSchema))
|
.input(z.array(VisitCreateManyInputSchema))
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { staff } = ctx;
|
const { staff } = ctx;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
export async function updateTotalCourseViewCount(type: VisitType) {
|
export async function updateTotalCourseViewCount(type: VisitType) {
|
||||||
const posts = await db.post.findMany({
|
const posts = await db.post.findMany({
|
||||||
where: {
|
where: {
|
||||||
type: { in: [PostType.COURSE, PostType.LECTURE] },
|
// type: { in: [PostType.COURSE, PostType.LECTURE,] },
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
},
|
},
|
||||||
select: { id: true, type: true },
|
select: { id: true, type: true },
|
||||||
|
@ -66,27 +66,34 @@ export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
where: { id },
|
where: { id },
|
||||||
select: { id: true, meta: true, type: true },
|
select: { id: true, meta: true, type: true },
|
||||||
});
|
});
|
||||||
|
console.log(post?.type);
|
||||||
|
console.log('updatePostViewCount');
|
||||||
const metaFieldMap = {
|
const metaFieldMap = {
|
||||||
[VisitType.READED]: 'views',
|
[VisitType.READED]: 'views',
|
||||||
[VisitType.LIKE]: 'likes',
|
[VisitType.LIKE]: 'likes',
|
||||||
[VisitType.HATE]: 'hates',
|
[VisitType.HATE]: 'hates',
|
||||||
};
|
};
|
||||||
if (post?.type === PostType.LECTURE) {
|
if (post?.type === PostType.LECTURE) {
|
||||||
const course = await db.postAncestry.findFirst({
|
const courseAncestry = await db.postAncestry.findFirst({
|
||||||
where: {
|
where: {
|
||||||
descendantId: post?.id,
|
descendantId: post?.id,
|
||||||
ancestor: {
|
ancestor: {
|
||||||
type: PostType.COURSE,
|
type: PostType.COURSE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
select: { id: true },
|
select: { id: true, ancestorId: true },
|
||||||
});
|
});
|
||||||
const lectures = await db.postAncestry.findMany({
|
const course = { id: courseAncestry.ancestorId };
|
||||||
|
const lecturesAncestry = await db.postAncestry.findMany({
|
||||||
where: { ancestorId: course.id, descendant: { type: PostType.LECTURE } },
|
where: { ancestorId: course.id, descendant: { type: PostType.LECTURE } },
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
|
descendantId: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const lectures = lecturesAncestry.map((ancestry) => ({
|
||||||
|
id: ancestry.descendantId,
|
||||||
|
}));
|
||||||
const courseViews = await db.visit.aggregate({
|
const courseViews = await db.visit.aggregate({
|
||||||
_sum: {
|
_sum: {
|
||||||
views: true,
|
views: true,
|
||||||
|
@ -98,9 +105,11 @@ export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
type: type,
|
type: type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(courseViews);
|
||||||
await db.post.update({
|
await db.post.update({
|
||||||
where: { id: course.id },
|
where: { id: course.id },
|
||||||
data: {
|
data: {
|
||||||
|
[metaFieldMap[type]]: courseViews._sum.views || 0,
|
||||||
meta: {
|
meta: {
|
||||||
...((post?.meta as any) || {}),
|
...((post?.meta as any) || {}),
|
||||||
[metaFieldMap[type]]: courseViews._sum.views || 0,
|
[metaFieldMap[type]]: courseViews._sum.views || 0,
|
||||||
|
@ -117,9 +126,11 @@ export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
type: type,
|
type: type,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log('totalViews', totalViews);
|
||||||
await db.post.update({
|
await db.post.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
[metaFieldMap[type]]: totalViews._sum.views || 0,
|
||||||
meta: {
|
meta: {
|
||||||
...((post?.meta as any) || {}),
|
...((post?.meta as any) || {}),
|
||||||
[metaFieldMap[type]]: totalViews._sum.views || 0,
|
[metaFieldMap[type]]: totalViews._sum.views || 0,
|
||||||
|
|
|
@ -24,7 +24,7 @@ const DeptInfo = ({ post }: { post: PostDto }) => {
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="gap-1 text-xs font-medium text-gray-500 flex items-center">
|
<span className="gap-1 text-xs font-medium text-gray-500 flex items-center">
|
||||||
<EyeOutlined />
|
<EyeOutlined />
|
||||||
{`${post?.meta?.views || 0}`}
|
{`${post?.views || 0}`}
|
||||||
</span>
|
</span>
|
||||||
{post?.studentIds && post?.studentIds?.length > 0 && (
|
{post?.studentIds && post?.studentIds?.length > 0 && (
|
||||||
<span className="gap-1 text-xs font-medium text-gray-500 flex items-center">
|
<span className="gap-1 text-xs font-medium text-gray-500 flex items-center">
|
||||||
|
|
|
@ -80,6 +80,7 @@ export function CourseDetailProvider({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lecture?.id) {
|
if (lecture?.id) {
|
||||||
|
console.log(123);
|
||||||
read.mutateAsync({
|
read.mutateAsync({
|
||||||
data: {
|
data: {
|
||||||
visitorId: user?.id || null,
|
visitorId: user?.id || null,
|
||||||
|
|
|
@ -36,11 +36,11 @@ export const CourseDetailDescription: React.FC = () => {
|
||||||
{!selectedLectureId && (
|
{!selectedLectureId && (
|
||||||
<div className="relative mb-4 overflow-hidden flex justify-center items-center">
|
<div className="relative mb-4 overflow-hidden flex justify-center items-center">
|
||||||
{
|
{
|
||||||
<Image
|
<div
|
||||||
src={course.meta.thumbnail}
|
className="w-full rounded-xl aspect-video bg-cover bg-center z-0"
|
||||||
preview={false}
|
style={{
|
||||||
className="w-full h-full object-cover z-0"
|
backgroundImage: `url(${course?.meta?.thumbnail || "/placeholder.webp"})`,
|
||||||
fallback="/placeholder.webp"
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
|
@ -59,7 +59,7 @@ export const CourseDetailDescription: React.FC = () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="w-full h-full absolute top-0 z-10 bg-[rgba(0,0,0,0.3)] transition-all duration-300 ease-in-out hover:bg-[rgba(0,0,0,0.7)] cursor-pointer group">
|
className="absolute rounded-xl top-0 left-0 right-0 bottom-0 z-10 bg-[rgba(0,0,0,0.3)] transition-all duration-300 ease-in-out hover:bg-[rgba(0,0,0,0.7)] cursor-pointer group">
|
||||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-4xl z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-4xl z-10 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
点击进入学习
|
点击进入学习
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,11 +12,12 @@ import dayjs from "dayjs";
|
||||||
import CourseOperationBtns from "./JoinLearingButton";
|
import CourseOperationBtns from "./JoinLearingButton";
|
||||||
|
|
||||||
export default function CourseDetailTitle() {
|
export default function CourseDetailTitle() {
|
||||||
const { course } = useContext(CourseDetailContext);
|
const { course, lecture, selectedLectureId } =
|
||||||
|
useContext(CourseDetailContext);
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center flex-col items-center gap-2 w-full my-2 px-6">
|
<div className="flex justify-center flex-col items-center gap-2 w-full my-2 px-6">
|
||||||
<div className="flex justify-start w-full text-2xl font-bold">
|
<div className="flex justify-start w-full text-2xl font-bold">
|
||||||
{course?.title}
|
{!selectedLectureId ? course?.title : lecture?.title}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-600 flex w-full justify-start items-center gap-5">
|
<div className="text-gray-600 flex w-full justify-start items-center gap-5">
|
||||||
{course?.author?.showname && (
|
{course?.author?.showname && (
|
||||||
|
@ -36,15 +37,25 @@ export default function CourseDetailTitle() {
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<CalendarOutlined></CalendarOutlined>
|
<CalendarOutlined></CalendarOutlined>
|
||||||
{"发布于:"}
|
{"发布于:"}
|
||||||
{dayjs(course?.createdAt).format("YYYY年M月D日")}
|
{dayjs(
|
||||||
|
!selectedLectureId
|
||||||
|
? course?.createdAt
|
||||||
|
: lecture?.createdAt
|
||||||
|
).format("YYYY年M月D日")}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
{"最后更新:"}
|
{"最后更新:"}
|
||||||
{dayjs(course?.updatedAt).format("YYYY年M月D日")}
|
{dayjs(
|
||||||
|
!selectedLectureId
|
||||||
|
? course?.updatedAt
|
||||||
|
: lecture?.updatedAt
|
||||||
|
).format("YYYY年M月D日")}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<EyeOutlined></EyeOutlined>
|
<EyeOutlined></EyeOutlined>
|
||||||
<div>{`观看次数${course?.meta?.views || 0}`}</div>
|
<div>{`观看次数${
|
||||||
|
!selectedLectureId ? course?.views : lecture?.views || 0
|
||||||
|
}`}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1">
|
<div className="flex gap-1">
|
||||||
<BookOutlined />
|
<BookOutlined />
|
||||||
|
|
|
@ -45,7 +45,9 @@ export const LectureItem: React.FC<LectureItemProps> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-grow flex justify-between items-center w-2/3 realative">
|
<div className="flex-grow flex justify-between items-center w-2/3 realative">
|
||||||
<h4 className="font-medium text-gray-800 w-4/5">{lecture.title}</h4>
|
<h4 className="font-medium text-gray-800 w-4/5">
|
||||||
|
{lecture.title}
|
||||||
|
</h4>
|
||||||
{lecture.subTitle && (
|
{lecture.subTitle && (
|
||||||
<span className="text-sm text-gray-500 mt-1 w-4/5">
|
<span className="text-sm text-gray-500 mt-1 w-4/5">
|
||||||
{lecture.subTitle}
|
{lecture.subTitle}
|
||||||
|
@ -53,7 +55,9 @@ export const LectureItem: React.FC<LectureItemProps> = ({
|
||||||
)}
|
)}
|
||||||
<div className="text-gray-500 whitespace-normal">
|
<div className="text-gray-500 whitespace-normal">
|
||||||
<EyeOutlined></EyeOutlined>
|
<EyeOutlined></EyeOutlined>
|
||||||
<span className="ml-2">{lecture?.meta?.views ? lecture?.meta?.views : 0}</span>
|
<span className="ml-2">
|
||||||
|
{lecture?.views ? lecture?.views : 0}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -101,13 +101,17 @@ export function CourseFormProvider({
|
||||||
terms:
|
terms:
|
||||||
termIds?.length > 0
|
termIds?.length > 0
|
||||||
? {
|
? {
|
||||||
set: termIds.map((id) => ({ id })), // 转换成 connect 格式
|
[editId ? "set" : "connect"]: termIds.map((id) => ({
|
||||||
|
id,
|
||||||
|
})), // 转换成 connect 格式
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
depts:
|
depts:
|
||||||
deptIds?.length > 0
|
deptIds?.length > 0
|
||||||
? {
|
? {
|
||||||
set: deptIds.map((id) => ({ id })),
|
[editId ? "set" : "connect"]: deptIds.map((id) => ({
|
||||||
|
id,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
|
|
|
@ -115,7 +115,7 @@ export const SortableLecture: React.FC<SortableLectureProps> = ({
|
||||||
resources:
|
resources:
|
||||||
[videoUrlId, ...fileIds].filter(Boolean)?.length > 0
|
[videoUrlId, ...fileIds].filter(Boolean)?.length > 0
|
||||||
? {
|
? {
|
||||||
connect: [videoUrlId, ...fileIds]
|
set: [videoUrlId, ...fileIds]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((fileId) => ({
|
.map((fileId) => ({
|
||||||
fileId,
|
fileId,
|
||||||
|
|
|
@ -207,6 +207,8 @@ model Post {
|
||||||
students Staff[] @relation("post_student")
|
students Staff[] @relation("post_student")
|
||||||
depts Department[] @relation("post_dept")
|
depts Department[] @relation("post_dept")
|
||||||
views Int @default(0) @map("views")
|
views Int @default(0) @map("views")
|
||||||
|
hates Int @default(0) @map("hates")
|
||||||
|
likes Int @default(0) @map("likes")
|
||||||
// 索引
|
// 索引
|
||||||
// 日期时间类型字段
|
// 日期时间类型字段
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
|
@ -45,11 +45,13 @@ export const postDetailSelect: Prisma.PostSelect = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
meta: true,
|
meta: true,
|
||||||
|
views: true,
|
||||||
};
|
};
|
||||||
export const postUnDetailSelect: Prisma.PostSelect = {
|
export const postUnDetailSelect: Prisma.PostSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
type: true,
|
type: true,
|
||||||
title: true,
|
title: true,
|
||||||
|
views: true,
|
||||||
parent: true,
|
parent: true,
|
||||||
parentId: true,
|
parentId: true,
|
||||||
content: true,
|
content: true,
|
||||||
|
@ -79,6 +81,7 @@ export const messageDetailSelect: Prisma.MessageSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
sender: true,
|
sender: true,
|
||||||
content: true,
|
content: true,
|
||||||
|
|
||||||
title: true,
|
title: true,
|
||||||
url: true,
|
url: true,
|
||||||
option: true,
|
option: true,
|
||||||
|
@ -88,6 +91,7 @@ export const courseDetailSelect: Prisma.PostSelect = {
|
||||||
id: true,
|
id: true,
|
||||||
title: true,
|
title: true,
|
||||||
subTitle: true,
|
subTitle: true,
|
||||||
|
views: true,
|
||||||
type: true,
|
type: true,
|
||||||
author: true,
|
author: true,
|
||||||
authorId: true,
|
authorId: true,
|
||||||
|
@ -124,6 +128,7 @@ export const lectureDetailSelect: Prisma.PostSelect = {
|
||||||
subTitle: true,
|
subTitle: true,
|
||||||
content: true,
|
content: true,
|
||||||
resources: true,
|
resources: true,
|
||||||
|
views: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
// 关联表选择
|
// 关联表选择
|
||||||
|
|
Loading…
Reference in New Issue