From 3d02643de3629f4c9c5622d0a25e280b14cf8ee5 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 08:46:22 +0800 Subject: [PATCH 1/8] add --- apps/web/src/app/admin/base-setting/page.tsx | 4 +-- .../common/uploader/TusUploader.tsx | 23 ++++++++++----- .../CourseContentForm/SortableLecture.tsx | 28 +++++++++++++++---- packages/common/src/constants.ts | 14 ++++++++++ 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/apps/web/src/app/admin/base-setting/page.tsx b/apps/web/src/app/admin/base-setting/page.tsx index 1155868..1f8d42f 100755 --- a/apps/web/src/app/admin/base-setting/page.tsx +++ b/apps/web/src/app/admin/base-setting/page.tsx @@ -24,7 +24,8 @@ export default function BaseSettingPage() { const [isFormChanged, setIsFormChanged] = useState(false); const [loading, setLoading] = useState(false); const { user, hasSomePermissions } = useAuth(); - const { pageWidth } = useContext?.(MainLayoutContext); + const context = useContext(MainLayoutContext); + const pageWidth = context?.pageWidth; function handleFieldsChange() { setIsFormChanged(true); } @@ -43,7 +44,6 @@ export default function BaseSettingPage() { } async function onSubmit(values: BaseSetting) { setLoading(true); - try { await update.mutateAsync({ where: { diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index e9fa45b..1811135 100755 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -11,6 +11,7 @@ export interface TusUploaderProps { value?: string[]; onChange?: (value: string[]) => void; multiple?: boolean; + allowTypes?: string[]; } interface UploadingFile { @@ -25,8 +26,8 @@ export const TusUploader = ({ value = [], onChange, multiple = true, + allowTypes = undefined, }: TusUploaderProps) => { - const { handleFileUpload, uploadProgress } = useTusUpload(); const [uploadingFiles, setUploadingFiles] = useState([]); const [completedFiles, setCompletedFiles] = useState( @@ -61,7 +62,10 @@ export const TusUploader = ({ const handleBeforeUpload = useCallback( (file: File) => { - + if (allowTypes && !allowTypes.includes(file.type)) { + toast.error(`文件类型 ${file.type} 不在允许范围内`); + return Upload.LIST_IGNORE; // 使用 antd 的官方阻止方式 + } const fileKey = `${file.name}-${Date.now()}`; setUploadingFiles((prev) => [ @@ -136,10 +140,10 @@ export const TusUploader = ({ return (

@@ -149,6 +153,11 @@ export const TusUploader = ({

{multiple ? "支持单个或批量上传文件" : "仅支持上传单个文件"} + {allowTypes && ( + + 允许类型: {allowTypes.join(", ")} + + )}

@@ -165,10 +174,10 @@ export const TusUploader = ({ file.status === "done" ? 100 : Math.round( - uploadProgress?.[ - file.fileKey! - ] || 0 - ) + uploadProgress?.[ + file.fileKey! + ] || 0 + ) } status={ file.status === "error" diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx index 7a23629..cb662a3 100755 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx @@ -10,7 +10,13 @@ import { useSortable } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor"; import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; -import { Lecture, LectureType, LessonTypeLabel, PostType } from "@nice/common"; +import { + Lecture, + LectureType, + LessonTypeLabel, + PostType, + videoMimeTypes, +} from "@nice/common"; import { usePost } from "@nice/client"; import toast from "react-hot-toast"; @@ -134,7 +140,9 @@ export const SortableLecture: React.FC = ({ name="title" initialValue={field?.title} className="mb-0 flex-1" - rules={[{ required: true }]}> + rules={[ + { required: true, message: "请输入课时标题" }, + ]}> = ({ - + rules={[ + { + required: true, + message: "请输入课时标题", + }, + ]}> + ) : ( + rules={[ + { required: true, message: "请输入内容" }, + ]}> )} diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index 7e9174f..9a36b03 100755 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -81,3 +81,17 @@ export const InitAppConfigs: Prisma.AppConfigCreateInput[] = [ description: "", }, ]; +export const videoMimeTypes = [ + "video/*", // 通配符 (部分浏览器可能不支持) + "video/mp4", // .mp4 + "video/quicktime", // .mov + "video/x-msvideo", // .avi + "video/x-matroska", // .mkv + "video/webm", // .webm + "video/ogg", // .ogv + "video/mpeg", // .mpeg + "video/3gpp", // .3gp + "video/3gpp2", // .3g2 + "video/x-flv", // .flv + "video/x-ms-wmv", // .wmv +]; From 8991b1ff8d864e8e53b46c9563bed8566825236a Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 08:47:08 +0800 Subject: [PATCH 2/8] add --- .../course/editor/form/CourseContentForm/SortableLecture.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx index cb662a3..d421ba2 100755 --- a/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx +++ b/apps/web/src/components/models/course/editor/form/CourseContentForm/SortableLecture.tsx @@ -169,7 +169,7 @@ export const SortableLecture: React.FC = ({ rules={[ { required: true, - message: "请输入课时标题", + message: "请传入视频", }, ]}> Date: Tue, 25 Feb 2025 08:52:14 +0800 Subject: [PATCH 3/8] aff --- docker-compose.example.yml | 263 +++++++++++++++++++------------------ 1 file changed, 132 insertions(+), 131 deletions(-) diff --git a/docker-compose.example.yml b/docker-compose.example.yml index ee99507..ab857dc 100755 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -1,135 +1,136 @@ version: "3.8" services: - db: - image: postgres:latest - ports: - - "5432:5432" - environment: - - POSTGRES_DB=app - - POSTGRES_USER=root - - POSTGRES_PASSWORD=Letusdoit000 - volumes: - - ./volumes/postgres:/var/lib/postgresql/data - # minio: - # image: minio/minio - # ports: - # - "9000:9000" - # - "9001:9001" - # volumes: - # - ./volumes/minio:/minio_data - # environment: - # - MINIO_ACCESS_KEY=minioadmin - # - MINIO_SECRET_KEY=minioadmin - # command: minio server /minio_data --console-address ":9001" -address ":9000" - # healthcheck: - # test: - # [ - # "CMD", - # "curl", - # "-f", - # "http://192.168.2.1:9001/minio/health/live" - # ] - # interval: 30s - # timeout: 20s - # retries: 3 - pgadmin: - image: dpage/pgadmin4 - ports: - - "8082:80" - environment: - - PGADMIN_DEFAULT_EMAIL=insiinc@outlook.com - - PGADMIN_DEFAULT_PASSWORD=Letusdoit000 - # tusd: - # image: tusproject/tusd - # ports: - # - "8080:8080" - # environment: - # - AWS_REGION=cn-north-1 - # - AWS_ACCESS_KEY_ID=minioadmin - # - AWS_SECRET_ACCESS_KEY=minioadmin - # command: -verbose -s3-bucket app -s3-endpoint http://minio:9000 -hooks-http http://host.docker.internal:3000/upload/hook - # volumes: - # - ./volumes/tusd:/data - # extra_hosts: - # - "host.docker.internal:host-gateway" - # depends_on: - # - minio - # tusd: - # image: tusproject/tusd - # ports: - # - "8080:8080" - # command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook - # volumes: - # - ./uploads:/data - # extra_hosts: - # - "host.docker.internal:host-gateway" - nginx: - image: nice-nginx:latest - ports: - - "80:80" - volumes: - - ./config/nginx/conf.d:/etc/nginx/conf.d - - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf - - ./uploads:/data/uploads # tusd 上传目录 - - ./web-dist:/usr/share/nginx/html # 添加前端构建文件的挂载 - - ./config/nginx/entrypoint.sh:/docker-entrypoint.sh - environment: - - SERVER_IP=host.docker.internal - - SERVER_PORT=3000 - entrypoint: ["/docker-entrypoint.sh"] - extra_hosts: - - "host.docker.internal:host-gateway" - redis: - image: redis:latest - ports: - - "6379:6379" - volumes: - - ./config/redis.conf:/usr/local/etc/redis/redis.conf - - ./volumes/redis:/data - command: ["redis-server", "/usr/local/etc/redis/redis.conf"] - # restic: - # image: restic/restic:latest - # environment: - # - RESTIC_REPOSITORY=/backup - # - RESTIC_PASSWORD=Letusdoit000 - # volumes: - # - ./volumes/postgres:/data - # - ./volumes/restic-cache:/root/.cache/restic - # - ./backup:/backup # 本地目录挂载到容器内的 /backup - # - ./config/backup.sh:/usr/local/bin/backup.sh # Mount your script inside the container - # entrypoint: /usr/local/bin/backup.sh - # depends_on: - # - db - # web: - # image: td-web:latest - # ports: - # - "80:80" - # environment: - # - VITE_APP_SERVER_IP=192.168.79.77 - # - VITE_APP_VERSION=0.3.0 - # - VITE_APP_APP_NAME=两道防线管理后台 - # server: - # image: td-server:latest - # ports: - # - "3000:3000" - # - "3001:3001" - # environment: - # - DATABASE_URL=postgresql://root:Letusdoit000@db:5432/app?schema=public - # - REDIS_HOST=redis - # - REDIS_PORT=6379 - # - REDIS_PASSWORD=Letusdoit000 - # - TUS_URL=http://192.168.2.1:8080 - # - JWT_SECRET=/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA= - # - PUSH_URL=http://dns:9092 - # - PUSH_APPID=123 - # - PUSH_APPSECRET=123 - # - MINIO_HOST=minio - # - ADMIN_PHONE_NUMBER=13258117304 - # - DEADLINE_CRON=0 0 8 * * * - # depends_on: - # - db - # - redis + db: + image: postgres:latest + ports: + - "5432:5432" + environment: + - POSTGRES_DB=app + - POSTGRES_USER=root + - POSTGRES_PASSWORD=Letusdoit000 + volumes: + - ./volumes/postgres:/var/lib/postgresql/data + # minio: + # image: minio/minio + # ports: + # - "9000:9000" + # - "9001:9001" + # volumes: + # - ./volumes/minio:/minio_data + # environment: + # - MINIO_ACCESS_KEY=minioadmin + # - MINIO_SECRET_KEY=minioadmin + # command: minio server /minio_data --console-address ":9001" -address ":9000" + # healthcheck: + # test: + # [ + # "CMD", + # "curl", + # "-f", + # "http://192.168.2.1:9001/minio/health/live" + # ] + # interval: 30s + # timeout: 20s + # retries: 3 + pgadmin: + image: dpage/pgadmin4 + ports: + - "8082:80" + environment: + - PGADMIN_DEFAULT_EMAIL=insiinc@outlook.com + - PGADMIN_DEFAULT_PASSWORD=Letusdoit000 + # tusd: + # image: tusproject/tusd + # ports: + # - "8080:8080" + # environment: + # - AWS_REGION=cn-north-1 + # - AWS_ACCESS_KEY_ID=minioadmin + # - AWS_SECRET_ACCESS_KEY=minioadmin + # command: -verbose -s3-bucket app -s3-endpoint http://minio:9000 -hooks-http http://host.docker.internal:3000/upload/hook + # volumes: + # - ./volumes/tusd:/data + # extra_hosts: + # - "host.docker.internal:host-gateway" + # depends_on: + # - minio + # tusd: + # image: tusproject/tusd + # ports: + # - "8080:8080" + # command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook + # volumes: + # - ./uploads:/data + # extra_hosts: + # - "host.docker.internal:host-gateway" + nginx: + image: nice-nginx:2.0 + ports: + - "80:80" + volumes: + - ./config/nginx/conf.d:/etc/nginx/conf.d + - ./config/nginx/nginx.conf:/etc/nginx/nginx.conf + - ./uploads:/data/uploads # tusd 上传目录 + - ./web-dist:/usr/share/nginx/html # 添加前端构建文件的挂载 + - ./config/nginx/entrypoint.sh:/docker-entrypoint.sh + environment: + - SERVER_IP=host.docker.internal + - SERVER_PORT=3000 + entrypoint: ["/docker-entrypoint.sh"] + extra_hosts: + - "host.docker.internal:host-gateway" + + redis: + image: redis:latest + ports: + - "6379:6379" + volumes: + - ./config/redis.conf:/usr/local/etc/redis/redis.conf + - ./volumes/redis:/data + command: ["redis-server", "/usr/local/etc/redis/redis.conf"] + # restic: + # image: restic/restic:latest + # environment: + # - RESTIC_REPOSITORY=/backup + # - RESTIC_PASSWORD=Letusdoit000 + # volumes: + # - ./volumes/postgres:/data + # - ./volumes/restic-cache:/root/.cache/restic + # - ./backup:/backup # 本地目录挂载到容器内的 /backup + # - ./config/backup.sh:/usr/local/bin/backup.sh # Mount your script inside the container + # entrypoint: /usr/local/bin/backup.sh + # depends_on: + # - db + # web: + # image: td-web:latest + # ports: + # - "80:80" + # environment: + # - VITE_APP_SERVER_IP=192.168.79.77 + # - VITE_APP_VERSION=0.3.0 + # - VITE_APP_APP_NAME=两道防线管理后台 + # server: + # image: td-server:latest + # ports: + # - "3000:3000" + # - "3001:3001" + # environment: + # - DATABASE_URL=postgresql://root:Letusdoit000@db:5432/app?schema=public + # - REDIS_HOST=redis + # - REDIS_PORT=6379 + # - REDIS_PASSWORD=Letusdoit000 + # - TUS_URL=http://192.168.2.1:8080 + # - JWT_SECRET=/yT9MnLm/r6NY7ee2Fby6ihCHZl+nFx4OQFKupivrhA= + # - PUSH_URL=http://dns:9092 + # - PUSH_APPID=123 + # - PUSH_APPSECRET=123 + # - MINIO_HOST=minio + # - ADMIN_PHONE_NUMBER=13258117304 + # - DEADLINE_CRON=0 0 8 * * * + # depends_on: + # - db + # - redis networks: - default: - name: remooc + default: + name: remooc From f8f3e7985e507ccd3484cc34bce861c7ebc3275f Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 09:11:15 +0800 Subject: [PATCH 4/8] add --- apps/server/src/models/visit/visit.service.ts | 7 +++++++ apps/server/src/queue/models/post/utils.ts | 2 +- apps/web/src/app/main/layout/UserMenu/types.ts | 3 ++- .../models/course/detail/CourseDetailDescription.tsx | 2 +- .../detail/CourseDetailHeader/CourseDetailHeader.tsx | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/server/src/models/visit/visit.service.ts b/apps/server/src/models/visit/visit.service.ts index 4742c61..20607e3 100755 --- a/apps/server/src/models/visit/visit.service.ts +++ b/apps/server/src/models/visit/visit.service.ts @@ -40,7 +40,11 @@ export class VisitService extends BaseService { id: postId, visitType: args.data.type, // 直接复用传入的类型 }); + EventBus.emit('updateTotalCourseViewCount', { + visitType: args.data.type, // 直接复用传入的类型 + }); } + return result; } async createMany(args: Prisma.VisitCreateManyArgs, staff?: UserProfile) { @@ -138,6 +142,9 @@ export class VisitService extends BaseService { id: args?.where?.postId as string, visitType: args.where.type as any, // 直接复用传入的类型 }); + EventBus.emit('updateTotalCourseViewCount', { + visitType: args.where.type as any, // 直接复用传入的类型 + }); } } return superDetele; diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 115c323..7f2b69d 100644 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -38,7 +38,7 @@ export async function updateTotalCourseViewCount(type: VisitType) { data: { meta: { ...baseSeting, - reads: totalViews, + reads: totalViews._sum.views, }, }, }); diff --git a/apps/web/src/app/main/layout/UserMenu/types.ts b/apps/web/src/app/main/layout/UserMenu/types.ts index 9809554..f21be44 100644 --- a/apps/web/src/app/main/layout/UserMenu/types.ts +++ b/apps/web/src/app/main/layout/UserMenu/types.ts @@ -1,5 +1,6 @@ +import React from "react"; export interface MenuItemType { - icon: JSX.Element; + icon: React.; label: string; action: () => void; } diff --git a/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx b/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx index e33e445..feba41d 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailDescription.tsx @@ -47,7 +47,7 @@ export const CourseDetailDescription: React.FC = () => {
{course?.subTitle}
-
{course?.meta?.views}
+
{course?.meta?.views || 0}
diff --git a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx index 554e5b7..7e708e1 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx @@ -21,7 +21,7 @@ export function CourseDetailHeader() { return (
-
+
{ From 3432b1af791ec3eb7824fa2da08f16ee5def8e66 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 09:55:36 +0800 Subject: [PATCH 5/8] add --- apps/server/src/queue/models/post/utils.ts | 29 +++++++++++++++---- .../app/main/home/components/HeroSection.tsx | 27 +++++++++-------- .../web/src/app/main/layout/UserMenu/types.ts | 4 +-- .../course/detail/CourseDetailSkeleton.tsx | 1 + apps/web/src/hooks/useTusUpload.ts | 2 +- packages/client/src/api/hooks/useAppConfig.ts | 15 +++++++++- packages/common/src/types.ts | 7 ++++- 7 files changed, 61 insertions(+), 24 deletions(-) diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 7f2b69d..0b25170 100644 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -7,11 +7,18 @@ import { VisitType, } from '@nice/common'; export async function updateTotalCourseViewCount(type: VisitType) { - const courses = await db.post.findMany({ - where: { type: PostType.COURSE }, - select: { id: true }, + const posts = await db.post.findMany({ + where: { + type: { in: [PostType.COURSE, PostType.LECTURE] }, + deletedAt: null, + }, + select: { id: true, type: true }, }); - const courseIds = courses.map((course) => course.id); + + const courseIds = posts + .filter((post) => post.type === PostType.COURSE) + .map((course) => course.id); + const lectures = posts.filter((post) => post.type === PostType.LECTURE); const totalViews = await db.visit.aggregate({ _sum: { views: true, @@ -30,6 +37,10 @@ export async function updateTotalCourseViewCount(type: VisitType) { meta: true, }, }); + const staffs = await db.staff.count({ + where: { deletedAt: null }, + }); + const baseSeting = appConfig.meta as BaseSetting; await db.appConfig.update({ where: { @@ -38,7 +49,15 @@ export async function updateTotalCourseViewCount(type: VisitType) { data: { meta: { ...baseSeting, - reads: totalViews._sum.views, + appConfig: { + ...(baseSeting?.appConfig || {}), + statistics: { + reads: totalViews._sum.views || 0, + courses: courseIds?.length || 0, + staffs: staffs || 0, + lectures: lectures?.length || 0, + }, + }, }, }, }); diff --git a/apps/web/src/app/main/home/components/HeroSection.tsx b/apps/web/src/app/main/home/components/HeroSection.tsx index 5a58d12..0399c32 100755 --- a/apps/web/src/app/main/home/components/HeroSection.tsx +++ b/apps/web/src/app/main/home/components/HeroSection.tsx @@ -45,16 +45,16 @@ const carouselItems: CarouselItem[] = [ }, ]; -const platformStats: PlatformStat[] = [ - { icon: , value: "50,000+", label: "注册学员" }, - { icon: , value: "1,000+", label: "精品课程" }, - // { icon: , value: '98%', label: '好评度' }, - { icon: , value: "100万+", label: "观看次数" }, -]; - const HeroSection = () => { const carouselRef = useRef(null); + const { statistics, baseSetting } = useAppConfig(); + const platformStats: PlatformStat[] = [ + { icon: , value: "50,000+", label: "注册学员" }, + { icon: , value: "1,000+", label: "精品课程" }, + // { icon: , value: '98%', label: '好评度' }, + { icon: , value: "4552", label: "观看次数" }, + ]; const handlePrev = useCallback(() => { carouselRef.current?.prev(); }, []); @@ -74,8 +74,8 @@ const HeroSection = () => { dots={{ className: "carousel-dots !bottom-32 !z-20", }}> - {Array.isArray(carouselItems)? - (carouselItems.map((item, index) => ( + {Array.isArray(carouselItems) ? ( + carouselItems.map((item, index) => (
{ {/* Content Container */}
- ))) - :( -
- ) - } + )) + ) : ( +
+ )} {/* Navigation Buttons */} diff --git a/apps/web/src/app/main/layout/UserMenu/types.ts b/apps/web/src/app/main/layout/UserMenu/types.ts index f21be44..dfe4b00 100644 --- a/apps/web/src/app/main/layout/UserMenu/types.ts +++ b/apps/web/src/app/main/layout/UserMenu/types.ts @@ -1,6 +1,6 @@ -import React from "react"; +import React, { ReactNode } from "react"; export interface MenuItemType { - icon: React.; + icon: ReactNode; label: string; action: () => void; } diff --git a/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx b/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx index 95b1049..ad58cb8 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailSkeleton.tsx @@ -2,6 +2,7 @@ import { SkeletonItem, SkeletonSection, } from "@web/src/components/presentation/Skeleton"; +import { api } from "packages/client/dist"; export const CourseDetailSkeleton = () => { return ( diff --git a/apps/web/src/hooks/useTusUpload.ts b/apps/web/src/hooks/useTusUpload.ts index a4589a5..3f6d038 100755 --- a/apps/web/src/hooks/useTusUpload.ts +++ b/apps/web/src/hooks/useTusUpload.ts @@ -15,7 +15,7 @@ export function useTusUpload() { >({}); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); - + const getFileId = (url: string) => { const parts = url.split("/"); const uploadIndex = parts.findIndex((part) => part === "upload"); diff --git a/packages/client/src/api/hooks/useAppConfig.ts b/packages/client/src/api/hooks/useAppConfig.ts index 5be578e..f38b6de 100755 --- a/packages/client/src/api/hooks/useAppConfig.ts +++ b/packages/client/src/api/hooks/useAppConfig.ts @@ -10,6 +10,7 @@ export function useAppConfig() { api.app_config.findFirst.useQuery({ where: { slug: AppConfigSlug.BASE_SETTING }, }); + const handleMutationSuccess = useCallback(() => { utils.app_config.invalidate(); }, [utils]); @@ -26,7 +27,8 @@ export function useAppConfig() { }); useEffect(() => { if (data?.meta) { - setBaseSetting(JSON.parse(data?.meta)); + // console.log(JSON.parse(data?.meta)); + setBaseSetting(data?.meta); } }, [data, isLoading]); const splashScreen = useMemo(() => { @@ -38,6 +40,16 @@ export function useAppConfig() { const slides = useMemo(() => { return baseSetting?.appConfig?.slides || []; }, [baseSetting]); + const statistics = useMemo(() => { + return ( + baseSetting?.appConfig?.statistics || { + reads: 0, + staffs: 0, + courses: 0, + lectures: 0, + } + ); + }, [baseSetting]); return { create, deleteMany, @@ -47,5 +59,6 @@ export function useAppConfig() { devDept, isLoading, slides, + statistics, }; } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 2a081c0..0207fb7 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -44,7 +44,12 @@ export interface BaseSetting { splashScreen?: string; devDept?: string; slides?: []; - reads?: number; + statistics?: { + reads?: number; + courses?: number; + lectures?: number; + staffs?: number; + }; }; } export type RowModelResult = { From 6959424af362cc2cf2a26c0955669e827c07ca9c Mon Sep 17 00:00:00 2001 From: wfc <2146706290@qq.com> Date: Tue, 25 Feb 2025 09:59:22 +0800 Subject: [PATCH 6/8] UserMenu --- apps/server/src/auth/utils.ts | 1 + apps/web/src/app/main/layout/MainFooter.tsx | 2 +- apps/web/src/app/main/layout/UserMenu/UserForm.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/server/src/auth/utils.ts b/apps/server/src/auth/utils.ts index 5e8cd9a..b1b2a88 100755 --- a/apps/server/src/auth/utils.ts +++ b/apps/server/src/auth/utils.ts @@ -155,6 +155,7 @@ export class UserProfileService { where: { id }, select: { id: true, + avatar:true, deptId: true, department: true, domainId: true, diff --git a/apps/web/src/app/main/layout/MainFooter.tsx b/apps/web/src/app/main/layout/MainFooter.tsx index 1356335..e37d149 100755 --- a/apps/web/src/app/main/layout/MainFooter.tsx +++ b/apps/web/src/app/main/layout/MainFooter.tsx @@ -8,7 +8,7 @@ export function MainFooter() { {/* 开发组织信息 */}

- 创新高地 软件小组 + 软件与数据小组

提供技术支持 diff --git a/apps/web/src/app/main/layout/UserMenu/UserForm.tsx b/apps/web/src/app/main/layout/UserMenu/UserForm.tsx index e17bffa..efbf12c 100644 --- a/apps/web/src/app/main/layout/UserMenu/UserForm.tsx +++ b/apps/web/src/app/main/layout/UserMenu/UserForm.tsx @@ -84,7 +84,7 @@ export default function StaffForm() { form.setFieldValue("officerId", data.officerId); form.setFieldValue("phoneNumber", data.phoneNumber); form.setFieldValue("enabled", data.enabled); - form.setFieldValue("enabled", data.avatar); + form.setFieldValue("avatar", data.avatar); } }, [data]); // useEffect(() => { From d5aa2f02d11a6cb5548cb33faa2e7fdbe5135a03 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 10:00:02 +0800 Subject: [PATCH 7/8] add --- .../components/models/staff/staff-form.tsx | 75 ++++++++++++------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/web/src/components/models/staff/staff-form.tsx b/apps/web/src/components/models/staff/staff-form.tsx index 1dc4920..513f08c 100755 --- a/apps/web/src/components/models/staff/staff-form.tsx +++ b/apps/web/src/components/models/staff/staff-form.tsx @@ -1,10 +1,11 @@ import { Button, Form, Input, Spin, Switch, message } from "antd"; -import { useContext, useEffect} from "react"; +import { useContext, useEffect } from "react"; import { useStaff } from "@nice/client"; import DepartmentSelect from "../department/department-select"; -import { api } from "@nice/client" +import { api } from "@nice/client"; import { StaffEditorContext } from "./staff-editor"; import { useAuth } from "@web/src/providers/auth-provider"; +import AvatarUploader from "../../common/uploader/AvatarUploader"; export default function StaffForm() { const { create, update } = useStaff(); // Ensure you have these methods in your hooks const { @@ -21,6 +22,7 @@ export default function StaffForm() { { where: { id: editId } }, { enabled: !!editId } ); + const { isRoot } = useAuth(); async function handleFinish(values: any) { const { @@ -31,8 +33,9 @@ export default function StaffForm() { password, phoneNumber, officerId, - enabled - } = values + enabled, + avatar, + } = values; setFormLoading(true); try { if (data && editId) { @@ -46,8 +49,9 @@ export default function StaffForm() { password, phoneNumber, officerId, - enabled - } + enabled, + avatar, + }, }); } else { await create.mutateAsync({ @@ -58,8 +62,9 @@ export default function StaffForm() { domainId: fieldDomainId ? fieldDomainId : domainId, password, officerId, - phoneNumber - } + phoneNumber, + avatar, + }, }); form.resetFields(); if (deptId) form.setFieldValue("deptId", deptId); @@ -77,13 +82,14 @@ export default function StaffForm() { useEffect(() => { form.resetFields(); if (data && editId) { - form.setFieldValue("username", data.username); - form.setFieldValue("showname", data.showname); - form.setFieldValue("domainId", data.domainId); - form.setFieldValue("deptId", data.deptId); - form.setFieldValue("officerId", data.officerId); - form.setFieldValue("phoneNumber", data.phoneNumber); - form.setFieldValue("enabled", data.enabled) + form.setFieldValue("username", data?.username); + form.setFieldValue("showname", data?.showname); + form.setFieldValue("domainId", data?.domainId); + form.setFieldValue("deptId", data?.deptId); + form.setFieldValue("officerId", data?.officerId); + form.setFieldValue("phoneNumber", data?.phoneNumber); + form.setFieldValue("enabled", data?.enabled); + form.setFieldValue("avatar", data?.avatar); } }, [data]); useEffect(() => { @@ -99,6 +105,7 @@ export default function StaffForm() {

)} +
+ + + {canManageAnyStaff && ( - @@ -136,7 +147,8 @@ export default function StaffForm() { rules={[{ required: true }]} name={"showname"} label="姓名"> - @@ -146,8 +158,8 @@ export default function StaffForm() { { required: false, pattern: /^\d{5,18}$/, - message: "请输入正确的证件号(数字)" - } + message: "请输入正确的证件号(数字)", + }, ]} name={"officerId"} label="证件号"> @@ -158,20 +170,29 @@ export default function StaffForm() { { required: false, pattern: /^\d{6,11}$/, - message: "请输入正确的手机号(数字)" - } + message: "请输入正确的手机号(数字)", + }, ]} name={"phoneNumber"} label="手机号"> - + - + - {editId && - - } + {editId && ( + + + + )}
); From 79f51e1b55216214db36cd8d1d6e4b63f7b5270f Mon Sep 17 00:00:00 2001 From: Li1304553726 <1304553726@qq.com> Date: Tue, 25 Feb 2025 10:02:40 +0800 Subject: [PATCH 8/8] add. --- .../main/home/components/CategorySection.tsx | 3 - .../main/home/components/CoursesSection.tsx | 79 +++++++++++-------- apps/web/src/app/main/home/page.tsx | 6 +- 3 files changed, 49 insertions(+), 39 deletions(-) diff --git a/apps/web/src/app/main/home/components/CategorySection.tsx b/apps/web/src/app/main/home/components/CategorySection.tsx index c2eff20..81b201a 100755 --- a/apps/web/src/app/main/home/components/CategorySection.tsx +++ b/apps/web/src/app/main/home/components/CategorySection.tsx @@ -84,9 +84,6 @@ const CategorySection = () => { // description: term.description // })) || []; // },[data]) - - - const handleMouseEnter = useCallback((index: number) => { setHoveredIndex(index); }, []); diff --git a/apps/web/src/app/main/home/components/CoursesSection.tsx b/apps/web/src/app/main/home/components/CoursesSection.tsx index 1b7edd7..efa7270 100755 --- a/apps/web/src/app/main/home/components/CoursesSection.tsx +++ b/apps/web/src/app/main/home/components/CoursesSection.tsx @@ -1,6 +1,6 @@ import React, { useState, useMemo, useEffect } from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; -import { Button, Card, Typography, Tag, Progress, Spin } from 'antd'; +import { Button, Card, Typography, Tag, Progress, Spin, Empty } from 'antd'; import { PlayCircleOutlined, UserOutlined, @@ -17,7 +17,6 @@ interface GetTaxonomyProps { categories: string[]; isLoading: boolean; } - function useGetTaxonomy({ type }): GetTaxonomyProps { const { data, isLoading }: { data: TermDto[], isLoading: boolean } = api.term.findMany.useQuery({ where: { @@ -40,8 +39,11 @@ function useGetTaxonomy({ type }): GetTaxonomyProps { }, [data]); return { categories, isLoading } } +// 修改useGetName中的查询条件 +// 不同分类跳转 + const { Title, Text } = Typography; interface Course { @@ -63,6 +65,25 @@ interface CoursesSectionProps { isLoading:boolean initialVisibleCoursesCount?: number; } +function useFetchCoursesByCategory(category: string) { + const isAll = category === '全部'; + const { data, isLoading } = api.post.findMany.useQuery({ + where: isAll?{}:{ + terms: { + some: { + name:category + }, + }, + }, + take: 8, + include:{ + terms:true + } + }); + + return { data, isLoading }; +} + const CoursesSection: React.FC = ({ title, @@ -77,39 +98,22 @@ const CoursesSection: React.FC = ({ const gateGory: GetTaxonomyProps = useGetTaxonomy({ type: TaxonomySlug.CATEGORY, }) - // const { data } = api.post.findMany.useQuery({ - // where: {}, // 必选参数 - // include: { // 关联查询 - // instructors: true - // }, - // orderBy: { // 排序规则 - // createdAt: 'desc' - // }, - // take: 8 - // }) - // useEffect(() => { - // // 添加空值保护 - // if (data && data.length > 0) { - // console.log('有效数据:', data) - // // 执行后续操作 - // } else { - // console.log('无数据或加载中') - // } - // }, [data]) - // const formatted = data?.map(course => ({ - // id: course.id, - // title: course.title - // })); - const handleClick = (course: Course) => { - navigate(`/course?courseId=${course.id}/detail`); - } + const handleClick = (course: Course) => { + navigate(`/course/${course.id}/detail`); + } + const { data } = useFetchCoursesByCategory(selectedCategory); + + useEffect(()=>{ + console.log('data:', data) + }) const filteredCourses = useMemo(() => { return selectedCategory === '全部' ? courses : courses.filter((course) => course.category === selectedCategory); }, [selectedCategory, courses]); + const displayedCourses = isLoading ? [] : filteredCourses.slice(0, visibleCourses); return (
@@ -140,12 +144,22 @@ const CoursesSection: React.FC = ({ : 'bg-white text-gray-600 hover:bg-gray-100' }`} >全部 - { + {gateGory.categories.length === 0 && ( + + )}:{ gateGory.categories.map((category) => ( setSelectedCategory(category)} + onClick={() => { + setSelectedCategory(category) + // console.log(gateGory) + } + } + className={`px-6 py-2 text-base cursor-pointer rounded-full transition-all duration-300 ${selectedCategory === category ? 'bg-blue-600 text-white shadow-lg' : 'bg-white text-gray-600 hover:bg-gray-100' @@ -155,6 +169,7 @@ const CoursesSection: React.FC = ({ )) } + ) } @@ -229,7 +244,7 @@ const CoursesSection: React.FC = ({
- 观看次数{course.progress}次 + 观看次数{course.progress}
@@ -262,7 +277,7 @@ const CoursesSection: React.FC = ({
- )} +)}
); diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index 3a7b2af..b1a2b82 100755 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -127,12 +127,10 @@ const HomePage = () => { }); useEffect(() => { if (data) { - console.log('mockCourses data:', data); + console.log('Courses data:', data); } }, [data]); - - // 数据处理逻辑 -// 修正依赖数组 + return (