Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
e580f0ea44
|
@ -184,7 +184,34 @@ export class PostService extends BaseTreeService<Prisma.PostDelegate> {
|
||||||
where?: Prisma.PostWhereInput;
|
where?: Prisma.PostWhereInput;
|
||||||
orderBy?: OrderByArgs<(typeof db.post)['findMany']>;
|
orderBy?: OrderByArgs<(typeof db.post)['findMany']>;
|
||||||
select?: Prisma.PostSelect<DefaultArgs>;
|
select?: Prisma.PostSelect<DefaultArgs>;
|
||||||
}) {
|
}): Promise<{
|
||||||
|
items: {
|
||||||
|
id: string;
|
||||||
|
type: string | null;
|
||||||
|
level: string | null;
|
||||||
|
state: string | null;
|
||||||
|
title: string | null;
|
||||||
|
subTitle: string | null;
|
||||||
|
content: string | null;
|
||||||
|
important: boolean | null;
|
||||||
|
domainId: string | null;
|
||||||
|
order: number | null;
|
||||||
|
duration: number | null;
|
||||||
|
rating: number | null;
|
||||||
|
createdAt: Date;
|
||||||
|
views: number;
|
||||||
|
hates: number;
|
||||||
|
likes: number;
|
||||||
|
publishedAt: Date | null;
|
||||||
|
updatedAt: Date;
|
||||||
|
deletedAt: Date | null;
|
||||||
|
authorId: string | null;
|
||||||
|
parentId: string | null;
|
||||||
|
hasChildren: boolean | null;
|
||||||
|
meta: Prisma.JsonValue | null;
|
||||||
|
}[];
|
||||||
|
totalPages: number;
|
||||||
|
}> {
|
||||||
// super.updateOrder;
|
// super.updateOrder;
|
||||||
return super.findManyWithPagination(args);
|
return super.findManyWithPagination(args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -34,14 +34,16 @@ interface CoursesSectionProps {
|
||||||
description: string;
|
description: string;
|
||||||
initialVisibleCoursesCount?: number;
|
initialVisibleCoursesCount?: number;
|
||||||
postType:string;
|
postType:string;
|
||||||
render?:(post)=>ReactNode
|
render?:(post)=>ReactNode;
|
||||||
|
to:string
|
||||||
}
|
}
|
||||||
const CoursesSection: React.FC<CoursesSectionProps> = ({
|
const CoursesSection: React.FC<CoursesSectionProps> = ({
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
initialVisibleCoursesCount = 8,
|
initialVisibleCoursesCount = 8,
|
||||||
postType,
|
postType,
|
||||||
render
|
render,
|
||||||
|
to
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
|
const [selectedCategory, setSelectedCategory] = useState<string>("全部");
|
||||||
const gateGory: GetTaxonomyProps = useGetTaxonomy({
|
const gateGory: GetTaxonomyProps = useGetTaxonomy({
|
||||||
|
@ -102,7 +104,7 @@ const CoursesSection: React.FC<CoursesSectionProps> = ({
|
||||||
}}
|
}}
|
||||||
showPagination={false}
|
showPagination={false}
|
||||||
cols={4}></PostList>
|
cols={4}></PostList>
|
||||||
<LookForMore to={"/courses"}></LookForMore>
|
<LookForMore to={to}></LookForMore>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,10 @@ export default function LookForMore({to}:{to:string}) {
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={() => navigate(to)}
|
onClick={() => {
|
||||||
|
navigate(to)
|
||||||
|
window.scrollTo({top: 0,behavior: "smooth"});
|
||||||
|
}}
|
||||||
className="flex items-center gap-2 text-gray-600 hover:text-blue-600 font-medium transition-colors duration-300">
|
className="flex items-center gap-2 text-gray-600 hover:text-blue-600 font-medium transition-colors duration-300">
|
||||||
查看更多
|
查看更多
|
||||||
<ArrowRightOutlined />
|
<ArrowRightOutlined />
|
||||||
|
|
|
@ -16,12 +16,14 @@ const HomePage = () => {
|
||||||
description="深受追捧的思维导图,点亮你的智慧人生"
|
description="深受追捧的思维导图,点亮你的智慧人生"
|
||||||
postType={PostType.PATH}
|
postType={PostType.PATH}
|
||||||
render={(post)=><PathCard post={post}></PathCard>}
|
render={(post)=><PathCard post={post}></PathCard>}
|
||||||
|
to={"path"}
|
||||||
/>
|
/>
|
||||||
<CoursesSection
|
<CoursesSection
|
||||||
title="推荐课程"
|
title="推荐课程"
|
||||||
description="最受欢迎的精品课程,助你快速成长"
|
description="最受欢迎的精品课程,助你快速成长"
|
||||||
postType={PostType.COURSE}
|
postType={PostType.COURSE}
|
||||||
render={(post)=> <CourseCard post={post}></CourseCard>}
|
render={(post)=> <CourseCard post={post}></CourseCard>}
|
||||||
|
to={"/courses"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategorySection />
|
<CategorySection />
|
||||||
|
|
|
@ -25,7 +25,7 @@ const DeptInfo = ({ post }: { post: PostDto }) => {
|
||||||
<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