diff --git a/apps/server/src/models/department/department.router.ts b/apps/server/src/models/department/department.router.ts index 2cd168c..edec18e 100755 --- a/apps/server/src/models/department/department.router.ts +++ b/apps/server/src/models/department/department.router.ts @@ -1,21 +1,29 @@ import { Injectable } from '@nestjs/common'; import { TrpcService } from '@server/trpc/trpc.service'; import { DepartmentService } from './department.service'; // assuming it's in the same directory -import { DepartmentMethodSchema, Prisma, UpdateOrderSchema } from '@nice/common'; +import { + DepartmentMethodSchema, + Prisma, + UpdateOrderSchema, +} from '@nice/common'; import { z, ZodType } from 'zod'; import { DepartmentRowService } from './department.row.service'; -const DepartmentCreateArgsSchema: ZodType = z.any() -const DepartmentUpdateArgsSchema: ZodType = z.any() -const DepartmentFindFirstArgsSchema: ZodType = z.any() -const DepartmentFindManyArgsSchema: ZodType = z.any() +const DepartmentCreateArgsSchema: ZodType = + z.any(); +const DepartmentUpdateArgsSchema: ZodType = + z.any(); +const DepartmentFindFirstArgsSchema: ZodType = + z.any(); +const DepartmentFindManyArgsSchema: ZodType = + z.any(); @Injectable() export class DepartmentRouter { constructor( private readonly trpc: TrpcService, private readonly departmentService: DepartmentService, // 注入 DepartmentService - private readonly departmentRowService: DepartmentRowService - ) { } + private readonly departmentRowService: DepartmentRowService, + ) {} router = this.trpc.router({ // 创建部门 create: this.trpc.protectProcedure @@ -36,9 +44,11 @@ export class DepartmentRouter { return this.departmentService.softDeleteByIds(input.ids); }), // 更新部门顺序 - updateOrder: this.trpc.protectProcedure.input(UpdateOrderSchema).mutation(async ({ input }) => { - return this.departmentService.updateOrder(input) - }), + updateOrder: this.trpc.protectProcedure + .input(UpdateOrderSchema) + .mutation(async ({ input }) => { + return this.departmentService.updateOrder(input); + }), // 查询多个部门 findMany: this.trpc.procedure .input(DepartmentFindManyArgsSchema) // 假设 StaffMethodSchema.findMany 是根据关键字查找员工的 Zod schema @@ -53,13 +63,15 @@ export class DepartmentRouter { }), // 获取子部门的简单树结构 getChildSimpleTree: this.trpc.procedure - .input(DepartmentMethodSchema.getSimpleTree).query(async ({ input }) => { - return await this.departmentService.getChildSimpleTree(input) + .input(DepartmentMethodSchema.getSimpleTree) + .query(async ({ input }) => { + return await this.departmentService.getChildSimpleTree(input); }), // 获取父部门的简单树结构 getParentSimpleTree: this.trpc.procedure - .input(DepartmentMethodSchema.getSimpleTree).query(async ({ input }) => { - return await this.departmentService.getParentSimpleTree(input) + .input(DepartmentMethodSchema.getSimpleTree) + .query(async ({ input }) => { + return await this.departmentService.getParentSimpleTree(input); }), // 获取部门行数据 getRows: this.trpc.protectProcedure diff --git a/apps/server/src/models/staff/staff.router.ts b/apps/server/src/models/staff/staff.router.ts index 4768b16..9f227a4 100755 --- a/apps/server/src/models/staff/staff.router.ts +++ b/apps/server/src/models/staff/staff.router.ts @@ -61,6 +61,18 @@ export class StaffRouter { .query(async ({ input }) => { return await this.staffService.findMany(input); }), + findManyWithPagination: this.trpc.procedure + .input( + z.object({ + page: z.number().optional(), + pageSize: z.number().optional(), + where: StaffWhereInputSchema.optional(), + select: StaffSelectSchema.optional(), + }), + ) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.staffService.findManyWithPagination(input); + }), getRows: this.trpc.protectProcedure .input(StaffMethodSchema.getRows) .query(async ({ input, ctx }) => { diff --git a/apps/server/src/models/staff/staff.row.service.ts b/apps/server/src/models/staff/staff.row.service.ts index c2e92c2..7347725 100755 --- a/apps/server/src/models/staff/staff.row.service.ts +++ b/apps/server/src/models/staff/staff.row.service.ts @@ -1,13 +1,13 @@ import { Injectable } from '@nestjs/common'; import { - db, - ObjectType, - StaffMethodSchema, - UserProfile, - RolePerms, - ResPerm, - Staff, - RowModelRequest, + db, + ObjectType, + StaffMethodSchema, + UserProfile, + RolePerms, + ResPerm, + Staff, + RowModelRequest, } from '@nice/common'; import { DepartmentService } from '../department/department.service'; import { RowCacheService } from '../base/row-cache.service'; @@ -15,121 +15,116 @@ import { z } from 'zod'; import { isFieldCondition } from '../base/sql-builder'; @Injectable() export class StaffRowService extends RowCacheService { - constructor( - private readonly departmentService: DepartmentService, - ) { - super(ObjectType.STAFF, false); + constructor(private readonly departmentService: DepartmentService) { + super(ObjectType.STAFF, false); + } + createUnGroupingRowSelect(request?: RowModelRequest): string[] { + const result = super + .createUnGroupingRowSelect(request) + .concat([ + `${this.tableName}.id AS id`, + `${this.tableName}.username AS username`, + `${this.tableName}.showname AS showname`, + `${this.tableName}.avatar AS avatar`, + `${this.tableName}.officer_id AS officer_id`, + `${this.tableName}.phone_number AS phone_number`, + `${this.tableName}.order AS order`, + `${this.tableName}.enabled AS enabled`, + 'dept.name AS dept_name', + 'domain.name AS domain_name', + ]); + return result; + } + createJoinSql(request?: RowModelRequest): string[] { + return [ + `LEFT JOIN department dept ON ${this.tableName}.dept_id = dept.id`, + `LEFT JOIN department domain ON ${this.tableName}.domain_id = domain.id`, + ]; + } + protected createGetRowsFilters( + request: z.infer, + staff: UserProfile, + ) { + const condition = super.createGetRowsFilters(request); + const { domainId, includeDeleted = false } = request; + if (isFieldCondition(condition)) { + return; } - createUnGroupingRowSelect(request?: RowModelRequest): string[] { - const result = super.createUnGroupingRowSelect(request).concat([ - `${this.tableName}.id AS id`, - `${this.tableName}.username AS username`, - `${this.tableName}.showname AS showname`, - `${this.tableName}.avatar AS avatar`, - `${this.tableName}.officer_id AS officer_id`, - `${this.tableName}.phone_number AS phone_number`, - `${this.tableName}.order AS order`, - `${this.tableName}.enabled AS enabled`, - 'dept.name AS dept_name', - 'domain.name AS domain_name', - ]); - return result + if (domainId) { + condition.AND.push({ + field: `${this.tableName}.domain_id`, + value: domainId, + op: 'equals', + }); + } else { + condition.AND.push({ + field: `${this.tableName}.domain_id`, + op: 'blank', + }); } - createJoinSql(request?: RowModelRequest): string[] { - return [ - `LEFT JOIN department dept ON ${this.tableName}.dept_id = dept.id`, - `LEFT JOIN department domain ON ${this.tableName}.domain_id = domain.id`, - ]; + if (!includeDeleted) { + condition.AND.push({ + field: `${this.tableName}.deleted_at`, + type: 'date', + op: 'blank', + }); } - protected createGetRowsFilters( - request: z.infer, - staff: UserProfile, - ) { - const condition = super.createGetRowsFilters(request); - const { domainId, includeDeleted = false } = request; - if (isFieldCondition(condition)) { - return; - } - if (domainId) { - condition.AND.push({ - field: `${this.tableName}.domain_id`, - value: domainId, - op: 'equals', - }); - } else { - condition.AND.push({ - field: `${this.tableName}.domain_id`, - op: 'blank', - }); - } - if (!includeDeleted) { - condition.AND.push({ - field: `${this.tableName}.deleted_at`, - type: 'date', - op: 'blank', - }); - } - condition.OR = []; - if (!staff.permissions.includes(RolePerms.MANAGE_ANY_STAFF)) { - if (staff.permissions.includes(RolePerms.MANAGE_DOM_STAFF)) { - condition.OR.push({ - field: 'dept.id', - value: staff.domainId, - op: 'equals', - }); - } - } - - return condition; - } - - async getPermissionContext(id: string, staff: UserProfile) { - const data = await db.staff.findUnique({ - where: { id }, - select: { - deptId: true, - domainId: true, - }, + condition.OR = []; + if (!staff.permissions.includes(RolePerms.MANAGE_ANY_STAFF)) { + if (staff.permissions.includes(RolePerms.MANAGE_DOM_STAFF)) { + condition.OR.push({ + field: 'dept.id', + value: staff.domainId, + op: 'equals', }); - const deptId = data?.deptId; - const isFromSameDept = staff.deptIds?.includes(deptId); - const domainChildDeptIds = await this.departmentService.getDescendantIds( - staff.domainId, true - ); - const belongsToDomain = domainChildDeptIds.includes( - deptId, - ); - return { isFromSameDept, belongsToDomain }; - } - protected async setResPermissions( - data: Staff, - staff: UserProfile, - ) { - const permissions: ResPerm = {}; - const { isFromSameDept, belongsToDomain } = await this.getPermissionContext( - data.id, - staff, - ); - const setManagePermissions = (permissions: ResPerm) => { - Object.assign(permissions, { - read: true, - delete: true, - edit: true, - }); - }; - staff.permissions.forEach((permission) => { - switch (permission) { - case RolePerms.MANAGE_ANY_STAFF: - setManagePermissions(permissions); - break; - case RolePerms.MANAGE_DOM_STAFF: - if (belongsToDomain) { - setManagePermissions(permissions); - } - break; - } - }); - return { ...data, perm: permissions }; + } } + return condition; + } + + async getPermissionContext(id: string, staff: UserProfile) { + const data = await db.staff.findUnique({ + where: { id }, + select: { + deptId: true, + domainId: true, + }, + }); + const deptId = data?.deptId; + const isFromSameDept = staff.deptIds?.includes(deptId); + const domainChildDeptIds = await this.departmentService.getDescendantIds( + staff.domainId, + true, + ); + const belongsToDomain = domainChildDeptIds.includes(deptId); + return { isFromSameDept, belongsToDomain }; + } + protected async setResPermissions(data: Staff, staff: UserProfile) { + const permissions: ResPerm = {}; + const { isFromSameDept, belongsToDomain } = await this.getPermissionContext( + data.id, + staff, + ); + const setManagePermissions = (permissions: ResPerm) => { + Object.assign(permissions, { + read: true, + delete: true, + edit: true, + }); + }; + staff.permissions.forEach((permission) => { + switch (permission) { + case RolePerms.MANAGE_ANY_STAFF: + setManagePermissions(permissions); + break; + case RolePerms.MANAGE_DOM_STAFF: + if (belongsToDomain) { + setManagePermissions(permissions); + } + break; + } + }); + return { ...data, perm: permissions }; + } } diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index cf37549..57cddd2 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -14,7 +14,6 @@ import EventBus, { CrudOperation } from '@server/utils/event-bus'; @Injectable() export class StaffService extends BaseService { - constructor(private readonly departmentService: DepartmentService) { super(db, ObjectType.STAFF, true); } @@ -25,7 +24,10 @@ export class StaffService extends BaseService { */ async findByDept(data: z.infer) { const { deptId, domainId } = data; - const childDepts = await this.departmentService.getDescendantIds(deptId, true); + const childDepts = await this.departmentService.getDescendantIds( + deptId, + true, + ); const result = await db.staff.findMany({ where: { deptId: { in: childDepts }, @@ -50,7 +52,9 @@ export class StaffService extends BaseService { await this.validateUniqueFields(data, where.id); const updateData = { ...data, - ...(data.password && { password: await argon2.hash(data.password as string) }) + ...(data.password && { + password: await argon2.hash(data.password as string), + }), }; const result = await super.update({ ...args, data: updateData }); this.emitDataChangedEvent(result, CrudOperation.UPDATED); @@ -58,17 +62,26 @@ export class StaffService extends BaseService { } private async validateUniqueFields(data: any, excludeId?: string) { const uniqueFields = [ - { field: 'officerId', errorMsg: (val: string) => `证件号为${val}的用户已存在` }, - { field: 'phoneNumber', errorMsg: (val: string) => `手机号为${val}的用户已存在` }, - { field: 'username', errorMsg: (val: string) => `帐号为${val}的用户已存在` } + { + field: 'officerId', + errorMsg: (val: string) => `证件号为${val}的用户已存在`, + }, + { + field: 'phoneNumber', + errorMsg: (val: string) => `手机号为${val}的用户已存在`, + }, + { + field: 'username', + errorMsg: (val: string) => `帐号为${val}的用户已存在`, + }, ]; for (const { field, errorMsg } of uniqueFields) { if (data[field]) { const count = await db.staff.count({ where: { [field]: data[field], - ...(excludeId && { id: { not: excludeId } }) - } + ...(excludeId && { id: { not: excludeId } }), + }, }); if (count > 0) { throw new Error(errorMsg(data[field])); @@ -77,9 +90,8 @@ export class StaffService extends BaseService { } } - private emitDataChangedEvent(data: any, operation: CrudOperation) { - EventBus.emit("dataChanged", { + EventBus.emit('dataChanged', { type: this.objectType, operation, data, @@ -87,12 +99,12 @@ export class StaffService extends BaseService { } /** - * 更新员工DomainId - * @param data 包含domainId对象 - * @returns 更新后的员工记录 - */ + * 更新员工DomainId + * @param data 包含domainId对象 + * @returns 更新后的员工记录 + */ async updateUserDomain(data: { domainId?: string }, staff?: UserProfile) { - let { domainId } = data; + const { domainId } = data; if (staff.domainId !== domainId) { const result = await this.update({ where: { id: staff.id }, @@ -107,7 +119,6 @@ export class StaffService extends BaseService { } } - // /** // * 根据关键词或ID集合查找员工 // * @param data 包含关键词、域ID和ID集合的对象 @@ -176,5 +187,4 @@ export class StaffService extends BaseService { // return combinedResults; // } - } diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 7e3c3f1..932d4b3 100755 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -107,10 +107,10 @@ export async function updatePostViewCount(id: string, type: VisitType) { where: { id: course.id }, data: { [metaFieldMap[type]]: courseViews._sum.views || 0, - meta: { - ...((post?.meta as any) || {}), - [metaFieldMap[type]]: courseViews._sum.views || 0, - }, + // meta: { + // ...((post?.meta as any) || {}), + // [metaFieldMap[type]]: courseViews._sum.views || 0, + // }, }, }); } @@ -127,10 +127,10 @@ export async function updatePostViewCount(id: string, type: VisitType) { where: { id }, data: { [metaFieldMap[type]]: totalViews._sum.views || 0, - meta: { - ...((post?.meta as any) || {}), - [metaFieldMap[type]]: totalViews._sum.views || 0, - }, + // meta: { + // ...((post?.meta as any) || {}), + // [metaFieldMap[type]]: totalViews._sum.views || 0, + // }, }, }); } diff --git a/apps/web/src/components/common/editor/MindEditor.tsx b/apps/web/src/components/common/editor/MindEditor.tsx index e9a20b2..9518d4f 100755 --- a/apps/web/src/components/common/editor/MindEditor.tsx +++ b/apps/web/src/components/common/editor/MindEditor.tsx @@ -20,9 +20,11 @@ import { useTusUpload } from "@web/src/hooks/useTusUpload"; import { useNavigate } from "react-router-dom"; import { useAuth } from "@web/src/providers/auth-provider"; import { MIND_OPTIONS } from "./constant"; -import { SaveOutlined } from "@ant-design/icons"; +import { LinkOutlined, SaveOutlined } from "@ant-design/icons"; import JoinButton from "../../models/course/detail/CourseOperationBtns/JoinButton"; import { CourseDetailContext } from "../../models/course/detail/PostDetailContext"; +import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; export default function MindEditor({ id }: { id?: string }) { const containerRef = useRef(null); const { @@ -57,6 +59,17 @@ export default function MindEditor({ id }: { id?: string }) { }); const { handleFileUpload } = useTusUpload(); const [form] = Form.useForm(); + const CustomLinkIconPlugin = (mind) => { + mind.bus.addListener('operation', async () => { + const hyperLinkElement = await document.querySelectorAll('.hyper-link'); + console.log('hyperLinkElement', hyperLinkElement); + hyperLinkElement.forEach((item) => { + const hyperLinkDom = createRoot(item) + hyperLinkDom.render() + }); + + }); + }; useEffect(() => { if (post?.id && id) { read.mutateAsync({ @@ -98,6 +111,7 @@ export default function MindEditor({ id }: { id?: string }) { nodeMenu: canEdit, // 禁用节点右键菜单 keypress: canEdit, // 禁用键盘快捷键 }); + mind.install(CustomLinkIconPlugin); mind.init(MindElixir.new("新思维导图")); containerRef.current.hidden = true; //挂载实例 @@ -173,16 +187,13 @@ export default function MindEditor({ id }: { id?: string }) { } console.log(result); }, - (error) => {}, + (error) => { }, `mind-thumb-${new Date().toString()}` ); }; useEffect(() => { containerRef.current.style.height = `${Math.floor(window.innerHeight - 271)}px`; }, []); - useEffect(()=>{ - console.log(canEdit,user?.id,post?.author?.id) - }) return (
{taxonomies && ( diff --git a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx index f95c29d..6231436 100755 --- a/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx +++ b/apps/web/src/components/models/course/detail/CourseDetailDisplayArea.tsx @@ -1,6 +1,6 @@ // components/CourseDetailDisplayArea.tsx import { motion, useScroll, useTransform } from "framer-motion"; -import React, { useContext, useRef, useState } from "react"; +import React, { useContext, useEffect, useRef, useState } from "react"; import { VideoPlayer } from "@web/src/components/presentation/video-player/VideoPlayer"; import { CourseDetailDescription } from "./CourseDetailDescription"; import { Course, LectureType, PostType } from "@nice/common"; @@ -40,7 +40,7 @@ export const CourseDetailDisplayArea: React.FC = () => { opacity: videoOpacity, }} className="w-full bg-black rounded-lg "> -
+
diff --git a/apps/web/src/components/models/course/list/PostList.tsx b/apps/web/src/components/models/course/list/PostList.tsx index ce9da18..a276060 100755 --- a/apps/web/src/components/models/course/list/PostList.tsx +++ b/apps/web/src/components/models/course/list/PostList.tsx @@ -28,6 +28,7 @@ export default function PostList({ renderItem, }: PostListProps) { const [currentPage, setCurrentPage] = useState(params?.page || 1); + const { data, isLoading }: PostPagnationProps = api.post.findManyWithPagination.useQuery({ select: courseDetailSelect, diff --git a/apps/web/src/components/models/post/PostSelect/PostSelect.tsx b/apps/web/src/components/models/post/PostSelect/PostSelect.tsx index 63f4058..f97d148 100644 --- a/apps/web/src/components/models/post/PostSelect/PostSelect.tsx +++ b/apps/web/src/components/models/post/PostSelect/PostSelect.tsx @@ -91,6 +91,7 @@ export default function PostSelect({ dropdownStyle={{ minWidth: 200, // 设置合适的最小宽度 }} + autoClearSearchValue placeholder={placeholder} onChange={onChange} filterOption={false} diff --git a/apps/web/src/components/presentation/video-player/VideoDisplay.tsx b/apps/web/src/components/presentation/video-player/VideoDisplay.tsx index 32b1109..7260304 100755 --- a/apps/web/src/components/presentation/video-player/VideoDisplay.tsx +++ b/apps/web/src/components/presentation/video-player/VideoDisplay.tsx @@ -25,6 +25,7 @@ export const VideoDisplay: React.FC = ({ isDragging, setIsDragging, progressRef, + isPlaying } = useContext(VideoPlayerContext); // 处理进度条拖拽 @@ -191,9 +192,20 @@ export const VideoDisplay: React.FC = ({ }; }, [src, onError, autoPlay]); + const handleVideoClick = () => { + if (videoRef.current && isPlaying) { + videoRef.current.pause(); + setIsPlaying(false); + }else if (videoRef.current && !isPlaying) { + videoRef.current.play(); + setIsPlaying(true); + } + }; + return ( -
+