From dd8f88e65075e557406dcd57f889a377fbf04c4e Mon Sep 17 00:00:00 2001 From: ditiqi Date: Mon, 24 Feb 2025 21:49:31 +0800 Subject: [PATCH 01/11] add --- packages/client/src/api/hooks/useAppConfig.ts | 12 +++++++----- packages/common/src/types.ts | 1 + 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/client/src/api/hooks/useAppConfig.ts b/packages/client/src/api/hooks/useAppConfig.ts index 0b00ffd..5be578e 100755 --- a/packages/client/src/api/hooks/useAppConfig.ts +++ b/packages/client/src/api/hooks/useAppConfig.ts @@ -3,15 +3,15 @@ import { AppConfigSlug, BaseSetting } from "@nice/common"; import { useCallback, useEffect, useMemo, useState } from "react"; export function useAppConfig() { - const utils = api.useUtils() + const utils = api.useUtils(); const [baseSetting, setBaseSetting] = useState(); const { data, isLoading }: { data: any; isLoading: boolean } = api.app_config.findFirst.useQuery({ - where: { slug: AppConfigSlug.BASE_SETTING } + where: { slug: AppConfigSlug.BASE_SETTING }, }); const handleMutationSuccess = useCallback(() => { - utils.app_config.invalidate() + utils.app_config.invalidate(); }, [utils]); // Use the generic success handler in mutations @@ -28,7 +28,6 @@ export function useAppConfig() { if (data?.meta) { setBaseSetting(JSON.parse(data?.meta)); } - }, [data, isLoading]); const splashScreen = useMemo(() => { return baseSetting?.appConfig?.splashScreen; @@ -36,8 +35,10 @@ export function useAppConfig() { const devDept = useMemo(() => { return baseSetting?.appConfig?.devDept; }, [baseSetting]); + const slides = useMemo(() => { + return baseSetting?.appConfig?.slides || []; + }, [baseSetting]); return { - create, deleteMany, update, @@ -45,5 +46,6 @@ export function useAppConfig() { splashScreen, devDept, isLoading, + slides, }; } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 084316a..2d6a2ef 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -43,6 +43,7 @@ export interface BaseSetting { appConfig?: { splashScreen?: string; devDept?: string; + slides?: []; }; } export type RowModelResult = { From ad61c5207b5131132da3ce356435b8b4988b56ec Mon Sep 17 00:00:00 2001 From: Rao <1227431568@qq.com> Date: Mon, 24 Feb 2025 22:00:30 +0800 Subject: [PATCH 02/11] rht02242200 --- apps/web/src/app/main/home/components/CoursesSection.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/main/home/components/CoursesSection.tsx b/apps/web/src/app/main/home/components/CoursesSection.tsx index fe2653c..904f758 100755 --- a/apps/web/src/app/main/home/components/CoursesSection.tsx +++ b/apps/web/src/app/main/home/components/CoursesSection.tsx @@ -128,11 +128,15 @@ const CoursesSection: React.FC = ({ setSelectedCategory(category)} + onClick={() => { + setSelectedCategory(category) + console.log(category) + }} 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' }`} + > {category} From 286b90511b2b5ebbfa6f4d4722ba4b3f7a22f7e3 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 08:25:54 +0800 Subject: [PATCH 03/11] add --- .../queue/models/post/post.queue.service.ts | 16 ++ apps/server/src/queue/models/post/utils.ts | 46 +++- apps/server/src/queue/types.ts | 1 + apps/server/src/queue/worker/processor.ts | 8 +- apps/server/src/utils/event-bus.ts | 3 + apps/web/src/app/admin/base-setting/page.tsx | 7 + apps/web/src/app/main/layout/MainHeader.tsx | 23 +- apps/web/src/app/main/layout/UserMenu.tsx | 70 ----- .../main/layout/UserMenu/UserEditModal.tsx | 27 ++ .../src/app/main/layout/UserMenu/UserForm.tsx | 170 ++++++++++++ .../src/app/main/layout/UserMenu/UserMenu.tsx | 252 ++++++++++++++++++ .../web/src/app/main/layout/UserMenu/types.ts | 5 + .../course/detail/CourseDetailContext.tsx | 1 + .../CourseDetailHeader/CourseDetailHeader.tsx | 2 +- packages/common/src/models/select.ts | 23 +- packages/common/src/models/staff.ts | 46 ++-- packages/common/src/types.ts | 1 + 17 files changed, 578 insertions(+), 123 deletions(-) delete mode 100755 apps/web/src/app/main/layout/UserMenu.tsx create mode 100644 apps/web/src/app/main/layout/UserMenu/UserEditModal.tsx create mode 100644 apps/web/src/app/main/layout/UserMenu/UserForm.tsx create mode 100755 apps/web/src/app/main/layout/UserMenu/UserMenu.tsx create mode 100644 apps/web/src/app/main/layout/UserMenu/types.ts diff --git a/apps/server/src/queue/models/post/post.queue.service.ts b/apps/server/src/queue/models/post/post.queue.service.ts index f7370df..8e49a81 100644 --- a/apps/server/src/queue/models/post/post.queue.service.ts +++ b/apps/server/src/queue/models/post/post.queue.service.ts @@ -22,6 +22,12 @@ export class PostQueueService implements OnModuleInit { EventBus.on('updatePostState', ({ id }) => { this.addUpdatePostState({ id }); }); + EventBus.on('updatePostState', ({ id }) => { + this.addUpdatePostState({ id }); + }); + EventBus.on('updateTotalCourseViewCount', ({ visitType }) => { + this.addUpdateTotalCourseViewCount({ visitType }); + }); } async addUpdateVisitCountJob(data: updateVisitCountJobData) { this.logger.log(`update post view count ${data.id}`); @@ -37,4 +43,14 @@ export class PostQueueService implements OnModuleInit { debounce: { id: `${QueueJobType.UPDATE_POST_STATE}_${data.id}` }, }); } + async addUpdateTotalCourseViewCount({ visitType }) { + this.logger.log(`update post state ${visitType}`); + await this.generalQueue.add( + QueueJobType.UPDATE_TOTAL_COURSE_VIEW_COUNT, + { type: visitType }, + { + debounce: { id: `${QueueJobType.UPDATE_POST_STATE}_${visitType}` }, + }, + ); + } } diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 98eb43c..115c323 100644 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -1,4 +1,48 @@ -import { db, VisitType } from '@nice/common'; +import { + AppConfigSlug, + BaseSetting, + db, + PostType, + TaxonomySlug, + VisitType, +} from '@nice/common'; +export async function updateTotalCourseViewCount(type: VisitType) { + const courses = await db.post.findMany({ + where: { type: PostType.COURSE }, + select: { id: true }, + }); + const courseIds = courses.map((course) => course.id); + const totalViews = await db.visit.aggregate({ + _sum: { + views: true, + }, + where: { + postId: { in: courseIds }, + type: type, + }, + }); + const appConfig = await db.appConfig.findFirst({ + where: { + slug: AppConfigSlug.BASE_SETTING, + }, + select: { + id: true, + meta: true, + }, + }); + const baseSeting = appConfig.meta as BaseSetting; + await db.appConfig.update({ + where: { + slug: AppConfigSlug.BASE_SETTING, + }, + data: { + meta: { + ...baseSeting, + reads: totalViews, + }, + }, + }); +} export async function updatePostViewCount(id: string, type: VisitType) { const post = await db.post.findFirst({ where: { id }, diff --git a/apps/server/src/queue/types.ts b/apps/server/src/queue/types.ts index 7e0f308..11119ee 100755 --- a/apps/server/src/queue/types.ts +++ b/apps/server/src/queue/types.ts @@ -4,6 +4,7 @@ export enum QueueJobType { FILE_PROCESS = 'file_process', UPDATE_POST_VISIT_COUNT = 'updatePostVisitCount', UPDATE_POST_STATE = 'updatePostState', + UPDATE_TOTAL_COURSE_VIEW_COUNT = 'updateTotalCourseViewCount', } export type updateVisitCountJobData = { id: string; diff --git a/apps/server/src/queue/worker/processor.ts b/apps/server/src/queue/worker/processor.ts index a968179..86d428b 100755 --- a/apps/server/src/queue/worker/processor.ts +++ b/apps/server/src/queue/worker/processor.ts @@ -11,7 +11,10 @@ import { updateCourseReviewStats, updateParentLectureStats, } from '@server/models/post/utils'; -import { updatePostViewCount } from '../models/post/utils'; +import { + updatePostViewCount, + updateTotalCourseViewCount, +} from '../models/post/utils'; const logger = new Logger('QueueWorker'); export default async function processJob(job: Job) { try { @@ -51,6 +54,9 @@ export default async function processJob(job: Job) { if (job.name === QueueJobType.UPDATE_POST_STATE) { await updatePostViewCount(job.data.id, job.data.type); } + if (job.name === QueueJobType.UPDATE_TOTAL_COURSE_VIEW_COUNT) { + await updateTotalCourseViewCount(job.data.type); + } } catch (error: any) { logger.error( `Error processing stats update job: ${error.message}`, diff --git a/apps/server/src/utils/event-bus.ts b/apps/server/src/utils/event-bus.ts index dfb3409..3f96688 100755 --- a/apps/server/src/utils/event-bus.ts +++ b/apps/server/src/utils/event-bus.ts @@ -21,6 +21,9 @@ type Events = { updatePostState: { id: string; }; + updateTotalCourseViewCount: { + visitType: VisitType | string; + }; onMessageCreated: { data: Partial }; dataChanged: { type: string; operation: CrudOperation; data: any }; }; diff --git a/apps/web/src/app/admin/base-setting/page.tsx b/apps/web/src/app/admin/base-setting/page.tsx index 82601ee..1155868 100755 --- a/apps/web/src/app/admin/base-setting/page.tsx +++ b/apps/web/src/app/admin/base-setting/page.tsx @@ -116,6 +116,13 @@ export default function BaseSettingPage() { +
+ + + +
{/*
setSearchValue(e.target.value)} - onPressEnter={(e)=>{ + onPressEnter={(e) => { //console.log(e) - setSearchValue('') - navigate(`/courses/?searchValue=${searchValue}`) + setSearchValue(""); + navigate( + `/courses/?searchValue=${searchValue}` + ); window.scrollTo({ top: 0, behavior: "smooth" }); }} /> @@ -54,18 +56,7 @@ export function MainHeader() { )} {isAuthenticated ? ( - } - trigger={["click"]} - placement="bottomRight"> - - {(user?.showname || - user?.username || - "")[0]?.toUpperCase()} - - + ) : ( + ))} +
+ + )} + + + + + ); +} diff --git a/apps/web/src/app/main/layout/UserMenu/types.ts b/apps/web/src/app/main/layout/UserMenu/types.ts new file mode 100644 index 0000000..9809554 --- /dev/null +++ b/apps/web/src/app/main/layout/UserMenu/types.ts @@ -0,0 +1,5 @@ +export interface MenuItemType { + icon: JSX.Element; + label: string; + action: () => void; +} diff --git a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx index 890a331..8702d19 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailContext.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailContext.tsx @@ -58,6 +58,7 @@ export function CourseDetailProvider({ ); useEffect(() => { if (course) { + console.log("read"); read.mutateAsync({ data: { visitorId: user?.id || null, 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 71ec0e0..554e5b7 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailHeader/CourseDetailHeader.tsx @@ -8,7 +8,7 @@ import { } from "@ant-design/icons"; import { useAuth } from "@web/src/providers/auth-provider"; import { useNavigate } from "react-router-dom"; -import { UserMenu } from "@web/src/app/main/layout/UserMenu"; +import { UserMenu } from "@web/src/app/main/layout/UserMenu/UserMenu"; import { CourseDetailContext } from "../CourseDetailContext"; const { Header } = Layout; diff --git a/packages/common/src/models/select.ts b/packages/common/src/models/select.ts index 36e072d..a591134 100755 --- a/packages/common/src/models/select.ts +++ b/packages/common/src/models/select.ts @@ -70,21 +70,22 @@ export const courseDetailSelect: Prisma.PostSelect = { title: true, subTitle: true, content: true, + depts: true, // isFeatured: true, createdAt: true, updatedAt: true, // 关联表选择 - terms:{ - select:{ - id:true, - name:true, - taxonomy:{ - select:{ - id:true, - slug:true - } - } - } + terms: { + select: { + id: true, + name: true, + taxonomy: { + select: { + id: true, + slug: true, + }, + }, + }, }, enrollments: { select: { diff --git a/packages/common/src/models/staff.ts b/packages/common/src/models/staff.ts index a6ace02..565f847 100755 --- a/packages/common/src/models/staff.ts +++ b/packages/common/src/models/staff.ts @@ -2,37 +2,37 @@ import { Staff, Department } from "@prisma/client"; import { RolePerms } from "../enum"; export type StaffRowModel = { - avatar: string; - dept_name: string; - officer_id: string; - phone_number: string; - showname: string; - username: string; + avatar: string; + dept_name: string; + officer_id: string; + phone_number: string; + showname: string; + username: string; }; export type UserProfile = Staff & { - permissions: RolePerms[]; - deptIds: string[]; - parentDeptIds: string[]; - domain: Department; - department: Department; + permissions: RolePerms[]; + deptIds: string[]; + parentDeptIds: string[]; + domain: Department; + department: Department; }; export type StaffDto = Staff & { - domain?: Department; - department?: Department; + domain?: Department; + department?: Department; }; export interface AuthDto { - token: string; - staff: StaffDto; - refreshToken: string; - perms: string[]; + token: string; + staff: StaffDto; + refreshToken: string; + perms: string[]; } export interface JwtPayload { - sub: string; - username: string; + sub: string; + username: string; } export interface TokenPayload { - id: string; - phoneNumber: string; - name: string; -} \ No newline at end of file + id: string; + phoneNumber: string; + name: string; +} diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 2d6a2ef..2a081c0 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -44,6 +44,7 @@ export interface BaseSetting { splashScreen?: string; devDept?: string; slides?: []; + reads?: number; }; } export type RowModelResult = { From 2548fadfeebc61cfb0407d5c11fd2cb7b23ced0e Mon Sep 17 00:00:00 2001 From: Rao <1227431568@qq.com> Date: Tue, 25 Feb 2025 08:25:59 +0800 Subject: [PATCH 04/11] rht02250825 --- .../app/main/home/components/HeroSection.tsx | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/apps/web/src/app/main/home/components/HeroSection.tsx b/apps/web/src/app/main/home/components/HeroSection.tsx index 9dae829..5a58d12 100755 --- a/apps/web/src/app/main/home/components/HeroSection.tsx +++ b/apps/web/src/app/main/home/components/HeroSection.tsx @@ -10,6 +10,7 @@ import { EyeOutlined, } from "@ant-design/icons"; import type { CarouselRef } from "antd/es/carousel"; +import { useAppConfig } from "@nice/client"; const { Title, Text } = Typography; @@ -53,7 +54,7 @@ const platformStats: PlatformStat[] = [ const HeroSection = () => { const carouselRef = useRef(null); - + const handlePrev = useCallback(() => { carouselRef.current?.prev(); }, []); @@ -61,7 +62,7 @@ const HeroSection = () => { const handleNext = useCallback(() => { carouselRef.current?.next(); }, []); - + //const {slides:carouselItems} = useAppConfig() return (
@@ -73,24 +74,30 @@ const HeroSection = () => { dots={{ className: "carousel-dots !bottom-32 !z-20", }}> - {carouselItems.map((item, index) => ( -
-
-
-
+ {Array.isArray(carouselItems)? + (carouselItems.map((item, index) => ( +
+
+
+
- {/* Content Container */} -
-
- ))} + {/* Content Container */} +
+
+ ))) + :( +
+ ) + } {/* Navigation Buttons */} From 3d02643de3629f4c9c5622d0a25e280b14cf8ee5 Mon Sep 17 00:00:00 2001 From: ditiqi Date: Tue, 25 Feb 2025 08:46:22 +0800 Subject: [PATCH 05/11] 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 06/11] 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 07/11] 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 08/11] 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 09/11] 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 10/11] 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 11/11] 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 && ( + + + + )}
);