This commit is contained in:
ditiqi 2025-03-05 21:21:03 +08:00
parent 2ed15c9b6b
commit eafd308db2
5 changed files with 180 additions and 150 deletions

View File

@ -1,21 +1,29 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TrpcService } from '@server/trpc/trpc.service'; import { TrpcService } from '@server/trpc/trpc.service';
import { DepartmentService } from './department.service'; // assuming it's in the same directory 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 { z, ZodType } from 'zod';
import { DepartmentRowService } from './department.row.service'; import { DepartmentRowService } from './department.row.service';
const DepartmentCreateArgsSchema: ZodType<Prisma.DepartmentCreateArgs> = z.any() const DepartmentCreateArgsSchema: ZodType<Prisma.DepartmentCreateArgs> =
const DepartmentUpdateArgsSchema: ZodType<Prisma.DepartmentUpdateArgs> = z.any() z.any();
const DepartmentFindFirstArgsSchema: ZodType<Prisma.DepartmentFindFirstArgs> = z.any() const DepartmentUpdateArgsSchema: ZodType<Prisma.DepartmentUpdateArgs> =
const DepartmentFindManyArgsSchema: ZodType<Prisma.DepartmentFindManyArgs> = z.any() z.any();
const DepartmentFindFirstArgsSchema: ZodType<Prisma.DepartmentFindFirstArgs> =
z.any();
const DepartmentFindManyArgsSchema: ZodType<Prisma.DepartmentFindManyArgs> =
z.any();
@Injectable() @Injectable()
export class DepartmentRouter { export class DepartmentRouter {
constructor( constructor(
private readonly trpc: TrpcService, private readonly trpc: TrpcService,
private readonly departmentService: DepartmentService, // 注入 DepartmentService private readonly departmentService: DepartmentService, // 注入 DepartmentService
private readonly departmentRowService: DepartmentRowService private readonly departmentRowService: DepartmentRowService,
) { } ) {}
router = this.trpc.router({ router = this.trpc.router({
// 创建部门 // 创建部门
create: this.trpc.protectProcedure create: this.trpc.protectProcedure
@ -36,9 +44,11 @@ export class DepartmentRouter {
return this.departmentService.softDeleteByIds(input.ids); return this.departmentService.softDeleteByIds(input.ids);
}), }),
// 更新部门顺序 // 更新部门顺序
updateOrder: this.trpc.protectProcedure.input(UpdateOrderSchema).mutation(async ({ input }) => { updateOrder: this.trpc.protectProcedure
return this.departmentService.updateOrder(input) .input(UpdateOrderSchema)
}), .mutation(async ({ input }) => {
return this.departmentService.updateOrder(input);
}),
// 查询多个部门 // 查询多个部门
findMany: this.trpc.procedure findMany: this.trpc.procedure
.input(DepartmentFindManyArgsSchema) // 假设 StaffMethodSchema.findMany 是根据关键字查找员工的 Zod schema .input(DepartmentFindManyArgsSchema) // 假设 StaffMethodSchema.findMany 是根据关键字查找员工的 Zod schema
@ -53,13 +63,15 @@ export class DepartmentRouter {
}), }),
// 获取子部门的简单树结构 // 获取子部门的简单树结构
getChildSimpleTree: this.trpc.procedure getChildSimpleTree: this.trpc.procedure
.input(DepartmentMethodSchema.getSimpleTree).query(async ({ input }) => { .input(DepartmentMethodSchema.getSimpleTree)
return await this.departmentService.getChildSimpleTree(input) .query(async ({ input }) => {
return await this.departmentService.getChildSimpleTree(input);
}), }),
// 获取父部门的简单树结构 // 获取父部门的简单树结构
getParentSimpleTree: this.trpc.procedure getParentSimpleTree: this.trpc.procedure
.input(DepartmentMethodSchema.getSimpleTree).query(async ({ input }) => { .input(DepartmentMethodSchema.getSimpleTree)
return await this.departmentService.getParentSimpleTree(input) .query(async ({ input }) => {
return await this.departmentService.getParentSimpleTree(input);
}), }),
// 获取部门行数据 // 获取部门行数据
getRows: this.trpc.protectProcedure getRows: this.trpc.protectProcedure

View File

@ -61,6 +61,18 @@ export class StaffRouter {
.query(async ({ input }) => { .query(async ({ input }) => {
return await this.staffService.findMany(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 getRows: this.trpc.protectProcedure
.input(StaffMethodSchema.getRows) .input(StaffMethodSchema.getRows)
.query(async ({ input, ctx }) => { .query(async ({ input, ctx }) => {

View File

@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import {
db, db,
ObjectType, ObjectType,
StaffMethodSchema, StaffMethodSchema,
UserProfile, UserProfile,
RolePerms, RolePerms,
ResPerm, ResPerm,
Staff, Staff,
RowModelRequest, RowModelRequest,
} from '@nice/common'; } from '@nice/common';
import { DepartmentService } from '../department/department.service'; import { DepartmentService } from '../department/department.service';
import { RowCacheService } from '../base/row-cache.service'; import { RowCacheService } from '../base/row-cache.service';
@ -15,121 +15,116 @@ import { z } from 'zod';
import { isFieldCondition } from '../base/sql-builder'; import { isFieldCondition } from '../base/sql-builder';
@Injectable() @Injectable()
export class StaffRowService extends RowCacheService { export class StaffRowService extends RowCacheService {
constructor( constructor(private readonly departmentService: DepartmentService) {
private readonly departmentService: DepartmentService, super(ObjectType.STAFF, false);
) { }
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<typeof StaffMethodSchema.getRows>,
staff: UserProfile,
) {
const condition = super.createGetRowsFilters(request);
const { domainId, includeDeleted = false } = request;
if (isFieldCondition(condition)) {
return;
} }
createUnGroupingRowSelect(request?: RowModelRequest): string[] { if (domainId) {
const result = super.createUnGroupingRowSelect(request).concat([ condition.AND.push({
`${this.tableName}.id AS id`, field: `${this.tableName}.domain_id`,
`${this.tableName}.username AS username`, value: domainId,
`${this.tableName}.showname AS showname`, op: 'equals',
`${this.tableName}.avatar AS avatar`, });
`${this.tableName}.officer_id AS officer_id`, } else {
`${this.tableName}.phone_number AS phone_number`, condition.AND.push({
`${this.tableName}.order AS order`, field: `${this.tableName}.domain_id`,
`${this.tableName}.enabled AS enabled`, op: 'blank',
'dept.name AS dept_name', });
'domain.name AS domain_name',
]);
return result
} }
createJoinSql(request?: RowModelRequest): string[] { if (!includeDeleted) {
return [ condition.AND.push({
`LEFT JOIN department dept ON ${this.tableName}.dept_id = dept.id`, field: `${this.tableName}.deleted_at`,
`LEFT JOIN department domain ON ${this.tableName}.domain_id = domain.id`, type: 'date',
]; op: 'blank',
});
} }
protected createGetRowsFilters( condition.OR = [];
request: z.infer<typeof StaffMethodSchema.getRows>, if (!staff.permissions.includes(RolePerms.MANAGE_ANY_STAFF)) {
staff: UserProfile, if (staff.permissions.includes(RolePerms.MANAGE_DOM_STAFF)) {
) { condition.OR.push({
const condition = super.createGetRowsFilters(request); field: 'dept.id',
const { domainId, includeDeleted = false } = request; value: staff.domainId,
if (isFieldCondition(condition)) { op: 'equals',
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,
},
}); });
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 };
}
} }

View File

@ -14,7 +14,6 @@ import EventBus, { CrudOperation } from '@server/utils/event-bus';
@Injectable() @Injectable()
export class StaffService extends BaseService<Prisma.StaffDelegate> { export class StaffService extends BaseService<Prisma.StaffDelegate> {
constructor(private readonly departmentService: DepartmentService) { constructor(private readonly departmentService: DepartmentService) {
super(db, ObjectType.STAFF, true); super(db, ObjectType.STAFF, true);
} }
@ -25,7 +24,10 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
*/ */
async findByDept(data: z.infer<typeof StaffMethodSchema.findByDept>) { async findByDept(data: z.infer<typeof StaffMethodSchema.findByDept>) {
const { deptId, domainId } = data; 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({ const result = await db.staff.findMany({
where: { where: {
deptId: { in: childDepts }, deptId: { in: childDepts },
@ -50,7 +52,9 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
await this.validateUniqueFields(data, where.id); await this.validateUniqueFields(data, where.id);
const updateData = { const updateData = {
...data, ...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 }); const result = await super.update({ ...args, data: updateData });
this.emitDataChangedEvent(result, CrudOperation.UPDATED); this.emitDataChangedEvent(result, CrudOperation.UPDATED);
@ -58,17 +62,26 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
} }
private async validateUniqueFields(data: any, excludeId?: string) { private async validateUniqueFields(data: any, excludeId?: string) {
const uniqueFields = [ const uniqueFields = [
{ field: 'officerId', errorMsg: (val: string) => `证件号为${val}的用户已存在` }, {
{ field: 'phoneNumber', errorMsg: (val: string) => `手机号为${val}的用户已存在` }, field: 'officerId',
{ field: 'username', errorMsg: (val: string) => `帐号为${val}的用户已存在` } errorMsg: (val: string) => `证件号为${val}的用户已存在`,
},
{
field: 'phoneNumber',
errorMsg: (val: string) => `手机号为${val}的用户已存在`,
},
{
field: 'username',
errorMsg: (val: string) => `帐号为${val}的用户已存在`,
},
]; ];
for (const { field, errorMsg } of uniqueFields) { for (const { field, errorMsg } of uniqueFields) {
if (data[field]) { if (data[field]) {
const count = await db.staff.count({ const count = await db.staff.count({
where: { where: {
[field]: data[field], [field]: data[field],
...(excludeId && { id: { not: excludeId } }) ...(excludeId && { id: { not: excludeId } }),
} },
}); });
if (count > 0) { if (count > 0) {
throw new Error(errorMsg(data[field])); throw new Error(errorMsg(data[field]));
@ -77,9 +90,8 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
} }
} }
private emitDataChangedEvent(data: any, operation: CrudOperation) { private emitDataChangedEvent(data: any, operation: CrudOperation) {
EventBus.emit("dataChanged", { EventBus.emit('dataChanged', {
type: this.objectType, type: this.objectType,
operation, operation,
data, data,
@ -87,12 +99,12 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
} }
/** /**
* DomainId * DomainId
* @param data domainId对象 * @param data domainId对象
* @returns * @returns
*/ */
async updateUserDomain(data: { domainId?: string }, staff?: UserProfile) { async updateUserDomain(data: { domainId?: string }, staff?: UserProfile) {
let { domainId } = data; const { domainId } = data;
if (staff.domainId !== domainId) { if (staff.domainId !== domainId) {
const result = await this.update({ const result = await this.update({
where: { id: staff.id }, where: { id: staff.id },
@ -107,7 +119,6 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
} }
} }
// /** // /**
// * 根据关键词或ID集合查找员工 // * 根据关键词或ID集合查找员工
// * @param data 包含关键词、域ID和ID集合的对象 // * @param data 包含关键词、域ID和ID集合的对象
@ -176,5 +187,4 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
// return combinedResults; // return combinedResults;
// } // }
} }

View File

@ -28,6 +28,7 @@ export default function PostList({
renderItem, renderItem,
}: PostListProps) { }: PostListProps) {
const [currentPage, setCurrentPage] = useState<number>(params?.page || 1); const [currentPage, setCurrentPage] = useState<number>(params?.page || 1);
const { data, isLoading }: PostPagnationProps = const { data, isLoading }: PostPagnationProps =
api.post.findManyWithPagination.useQuery({ api.post.findManyWithPagination.useQuery({
select: courseDetailSelect, select: courseDetailSelect,