diff --git a/apps/server/src/models/app-config/app-config.router.ts b/apps/server/src/models/app-config/app-config.router.ts index ece1b35..882fa16 100644 --- a/apps/server/src/models/app-config/app-config.router.ts +++ b/apps/server/src/models/app-config/app-config.router.ts @@ -4,44 +4,48 @@ import { AppConfigService } from './app-config.service'; import { z, ZodType } from 'zod'; import { Prisma } from '@nice/common'; import { RealtimeServer } from '@server/socket/realtime/realtime.server'; -const AppConfigUncheckedCreateInputSchema: ZodType = z.any() -const AppConfigUpdateArgsSchema: ZodType = z.any() -const AppConfigDeleteManyArgsSchema: ZodType = z.any() -const AppConfigFindFirstArgsSchema: ZodType = z.any() +const AppConfigUncheckedCreateInputSchema: ZodType = + z.any(); +const AppConfigUpdateArgsSchema: ZodType = z.any(); +const AppConfigDeleteManyArgsSchema: ZodType = + z.any(); +const AppConfigFindFirstArgsSchema: ZodType = + z.any(); @Injectable() export class AppConfigRouter { - constructor( - private readonly trpc: TrpcService, - private readonly appConfigService: AppConfigService, - private readonly realtimeServer: RealtimeServer - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(AppConfigUncheckedCreateInputSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.appConfigService.create({ data: input }); - }), - update: this.trpc.protectProcedure - .input(AppConfigUpdateArgsSchema) - .mutation(async ({ ctx, input }) => { - - const { staff } = ctx; - return await this.appConfigService.update(input); - }), - deleteMany: this.trpc.protectProcedure.input(AppConfigDeleteManyArgsSchema).mutation(async ({ input }) => { - return await this.appConfigService.deleteMany(input) - }), - findFirst: this.trpc.protectProcedure.input(AppConfigFindFirstArgsSchema). - query(async ({ input }) => { - - return await this.appConfigService.findFirst(input) - }), - clearRowCache: this.trpc.protectProcedure.mutation(async () => { - return await this.appConfigService.clearRowCache() - }), - getClientCount: this.trpc.protectProcedure.query(() => { - return this.realtimeServer.getClientCount() - }) - }); + constructor( + private readonly trpc: TrpcService, + private readonly appConfigService: AppConfigService, + private readonly realtimeServer: RealtimeServer, + ) {} + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(AppConfigUncheckedCreateInputSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.appConfigService.create({ data: input }); + }), + update: this.trpc.protectProcedure + .input(AppConfigUpdateArgsSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.appConfigService.update(input); + }), + deleteMany: this.trpc.protectProcedure + .input(AppConfigDeleteManyArgsSchema) + .mutation(async ({ input }) => { + return await this.appConfigService.deleteMany(input); + }), + findFirst: this.trpc.protectProcedure + .input(AppConfigFindFirstArgsSchema) + .query(async ({ input }) => { + return await this.appConfigService.findFirst(input); + }), + clearRowCache: this.trpc.protectProcedure.mutation(async () => { + return await this.appConfigService.clearRowCache(); + }), + getClientCount: this.trpc.protectProcedure.query(() => { + return this.realtimeServer.getClientCount(); + }), + }); } diff --git a/apps/server/src/models/app-config/app-config.service.ts b/apps/server/src/models/app-config/app-config.service.ts index 733e620..bd003d7 100644 --- a/apps/server/src/models/app-config/app-config.service.ts +++ b/apps/server/src/models/app-config/app-config.service.ts @@ -1,10 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { - db, - ObjectType, - Prisma, -} from '@nice/common'; - +import { db, ObjectType, Prisma } from '@nice/common'; import { BaseService } from '../base/base.service'; import { deleteByPattern } from '@server/utils/redis/utils'; @@ -12,10 +7,10 @@ import { deleteByPattern } from '@server/utils/redis/utils'; @Injectable() export class AppConfigService extends BaseService { constructor() { - super(db, "appConfig"); + super(db, 'appConfig'); } async clearRowCache() { - await deleteByPattern("row-*") - return true + await deleteByPattern('row-*'); + return true; } } diff --git a/apps/server/src/models/rbac/rolemap.router.ts b/apps/server/src/models/rbac/rolemap.router.ts index 72ae5a4..e610cc2 100755 --- a/apps/server/src/models/rbac/rolemap.router.ts +++ b/apps/server/src/models/rbac/rolemap.router.ts @@ -1,9 +1,6 @@ import { Injectable } from '@nestjs/common'; import { TrpcService } from '@server/trpc/trpc.service'; -import { - ObjectType, - RoleMapMethodSchema, -} from '@nice/common'; +import { ObjectType, RoleMapMethodSchema } from '@nice/common'; import { RoleMapService } from './rolemap.service'; @Injectable() @@ -11,7 +8,7 @@ export class RoleMapRouter { constructor( private readonly trpc: TrpcService, private readonly roleMapService: RoleMapService, - ) { } + ) {} router = this.trpc.router({ deleteAllRolesForObject: this.trpc.protectProcedure .input(RoleMapMethodSchema.deleteWithObject) @@ -67,5 +64,10 @@ export class RoleMapRouter { .query(async ({ input }) => { return this.roleMapService.getStaffsNotMap(input); }), + getStaffIdsByRoleNames: this.trpc.procedure + .input(RoleMapMethodSchema.getStaffIdsByRoleNames) + .query(async ({ input }) => { + return this.roleMapService.getStaffIdsByRoleNames(input); + }), }); } diff --git a/apps/server/src/models/rbac/rolemap.service.ts b/apps/server/src/models/rbac/rolemap.service.ts index d3c971a..9b2c7db 100755 --- a/apps/server/src/models/rbac/rolemap.service.ts +++ b/apps/server/src/models/rbac/rolemap.service.ts @@ -44,7 +44,7 @@ export class RoleMapService extends RowModelService { ) { const { roleId, domainId } = request; // Base conditions - let condition = super.createGetRowsFilters(request, staff); + const condition = super.createGetRowsFilters(request, staff); if (isFieldCondition(condition)) return; // Adding conditions based on parameters existence if (roleId) { @@ -64,10 +64,7 @@ export class RoleMapService extends RowModelService { return condition; } - protected async getRowDto( - row: any, - staff?: UserProfile, - ): Promise { + protected async getRowDto(row: any, staff?: UserProfile): Promise { if (!row.id) return row; return row; } @@ -126,15 +123,17 @@ export class RoleMapService extends RowModelService { data: roleMaps, }); }); - const wrapResult = Promise.all(result.map(async item => { - const staff = await db.staff.findMany({ - include: { department: true }, - where: { - id: item.objectId - } - }) - return { ...item, staff } - })) + const wrapResult = Promise.all( + result.map(async (item) => { + const staff = await db.staff.findMany({ + include: { department: true }, + where: { + id: item.objectId, + }, + }); + return { ...item, staff }; + }), + ); return wrapResult; } async addRoleForObjects( @@ -187,11 +186,11 @@ export class RoleMapService extends RowModelService { { objectId: staffId, objectType: ObjectType.STAFF }, ...(deptId || ancestorDeptIds.length > 0 ? [ - { - objectId: { in: [deptId, ...ancestorDeptIds].filter(Boolean) }, - objectType: ObjectType.DEPARTMENT, - }, - ] + { + objectId: { in: [deptId, ...ancestorDeptIds].filter(Boolean) }, + objectType: ObjectType.DEPARTMENT, + }, + ] : []), ]; // Helper function to fetch roles based on domain ID. @@ -260,7 +259,9 @@ export class RoleMapService extends RowModelService { // const processedItems = await Promise.all(items.map(item => this.genRoleMapDto(item))); return { items, totalCount }; } - async getStaffsNotMap(data: z.infer) { + async getStaffsNotMap( + data: z.infer, + ) { const { domainId, roleId } = data; let staffs = await db.staff.findMany({ where: { @@ -280,6 +281,35 @@ export class RoleMapService extends RowModelService { ); return staffs; } + async getStaffIdsByRoleNames( + data: z.infer, + ) { + const { roleNames } = data; + const roles = await db.role.findMany({ + where: { + name: { + in: roleNames, + }, + }, + select: { + id: true, + }, + }); + const roleMaps = await db.roleMap.findMany({ + where: { + roleId: { + in: roles.map((role) => role.id), + }, + objectType: ObjectType.STAFF, + }, + select: { + id: true, + objectId: true, + }, + }); + const staffIds = roleMaps.map((roleMap) => roleMap.objectId); + return staffIds; + } /** * 更新角色映射 * @param data 包含更新信息的数据 @@ -300,7 +330,9 @@ export class RoleMapService extends RowModelService { * @param data 包含角色ID和域ID的数据 * @returns 角色映射详情,包含部门ID和员工ID列表 */ - async getRoleMapDetail(data: z.infer) { + async getRoleMapDetail( + data: z.infer, + ) { const { roleId, domainId } = data; const res = await db.roleMap.findMany({ where: { roleId, domainId } }); diff --git a/apps/server/src/models/resource/resource.module.ts b/apps/server/src/models/resource/resource.module.ts index 153bc6e..a2b9dbf 100644 --- a/apps/server/src/models/resource/resource.module.ts +++ b/apps/server/src/models/resource/resource.module.ts @@ -4,7 +4,7 @@ import { ResourceService } from './resource.service'; import { TrpcService } from '@server/trpc/trpc.service'; @Module({ - exports: [ResourceRouter, ResourceService], - providers: [ResourceRouter, ResourceService, TrpcService], + exports: [ResourceRouter, ResourceService], + providers: [ResourceRouter, ResourceService, TrpcService], }) -export class ResourceModule { } +export class ResourceModule {} diff --git a/apps/server/src/models/resource/resource.router.ts b/apps/server/src/models/resource/resource.router.ts index 6d4290f..30af7b5 100644 --- a/apps/server/src/models/resource/resource.router.ts +++ b/apps/server/src/models/resource/resource.router.ts @@ -3,68 +3,75 @@ import { TrpcService } from '@server/trpc/trpc.service'; import { Prisma, UpdateOrderSchema } from '@nice/common'; import { ResourceService } from './resource.service'; import { z, ZodType } from 'zod'; -const ResourceCreateArgsSchema: ZodType = z.any() -const ResourceCreateManyInputSchema: ZodType = z.any() -const ResourceDeleteManyArgsSchema: ZodType = z.any() -const ResourceFindManyArgsSchema: ZodType = z.any() -const ResourceFindFirstArgsSchema: ZodType = z.any() -const ResourceWhereInputSchema: ZodType = z.any() -const ResourceSelectSchema: ZodType = z.any() +const ResourceCreateArgsSchema: ZodType = z.any(); +const ResourceCreateManyInputSchema: ZodType = + z.any(); +const ResourceDeleteManyArgsSchema: ZodType = + z.any(); +const ResourceFindManyArgsSchema: ZodType = + z.any(); +const ResourceFindFirstArgsSchema: ZodType = + z.any(); +const ResourceWhereInputSchema: ZodType = z.any(); +const ResourceSelectSchema: ZodType = z.any(); @Injectable() export class ResourceRouter { - constructor( - private readonly trpc: TrpcService, - private readonly resourceService: ResourceService, - ) { } - router = this.trpc.router({ - create: this.trpc.protectProcedure - .input(ResourceCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.resourceService.create(input, { staff }); - }), - createMany: this.trpc.protectProcedure.input(z.array(ResourceCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; + constructor( + private readonly trpc: TrpcService, + private readonly resourceService: ResourceService, + ) {} + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(ResourceCreateArgsSchema) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.resourceService.create(input, { staff }); + }), + createMany: this.trpc.protectProcedure + .input(z.array(ResourceCreateManyInputSchema)) + .mutation(async ({ ctx, input }) => { + const { staff } = ctx; - return await this.resourceService.createMany({ data: input }, staff); - }), - deleteMany: this.trpc.procedure - .input(ResourceDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.resourceService.deleteMany(input); - }), - findFirst: this.trpc.procedure - .input(ResourceFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.resourceService.findFirst(input); - }), - softDeleteByIds: this.trpc.protectProcedure - .input(z.object({ ids: z.array(z.string()) })) // expect input according to the schema - .mutation(async ({ input }) => { - return this.resourceService.softDeleteByIds(input.ids); - }), - updateOrder: this.trpc.protectProcedure - .input(UpdateOrderSchema) - .mutation(async ({ input }) => { - return this.resourceService.updateOrder(input); - }), - findMany: this.trpc.procedure - .input(ResourceFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword - .query(async ({ input }) => { - return await this.resourceService.findMany(input); - }), - findManyWithCursor: this.trpc.protectProcedure - .input(z.object({ - cursor: z.any().nullish(), - take: z.number().nullish(), - where: ResourceWhereInputSchema.nullish(), - select: ResourceSelectSchema.nullish() - })) - .query(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.resourceService.findManyWithCursor(input); - }), - }); + return await this.resourceService.createMany({ data: input }, staff); + }), + deleteMany: this.trpc.procedure + .input(ResourceDeleteManyArgsSchema) + .mutation(async ({ input }) => { + return await this.resourceService.deleteMany(input); + }), + findFirst: this.trpc.procedure + .input(ResourceFindFirstArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.resourceService.findFirst(input); + }), + softDeleteByIds: this.trpc.protectProcedure + .input(z.object({ ids: z.array(z.string()) })) // expect input according to the schema + .mutation(async ({ input }) => { + return this.resourceService.softDeleteByIds(input.ids); + }), + updateOrder: this.trpc.protectProcedure + .input(UpdateOrderSchema) + .mutation(async ({ input }) => { + return this.resourceService.updateOrder(input); + }), + findMany: this.trpc.procedure + .input(ResourceFindManyArgsSchema) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.resourceService.findMany(input); + }), + findManyWithCursor: this.trpc.protectProcedure + .input( + z.object({ + cursor: z.any().nullish(), + take: z.number().nullish(), + where: ResourceWhereInputSchema.nullish(), + select: ResourceSelectSchema.nullish(), + }), + ) + .query(async ({ ctx, input }) => { + const { staff } = ctx; + return await this.resourceService.findManyWithCursor(input); + }), + }); } diff --git a/apps/server/src/models/staff/staff.module.ts b/apps/server/src/models/staff/staff.module.ts index fa681dc..c9e787f 100755 --- a/apps/server/src/models/staff/staff.module.ts +++ b/apps/server/src/models/staff/staff.module.ts @@ -12,4 +12,4 @@ import { StaffRowService } from './staff.row.service'; exports: [StaffService, StaffRouter, StaffRowService], controllers: [StaffController], }) -export class StaffModule { } +export class StaffModule {} diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index a5868aa..9844c37 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -44,7 +44,7 @@ export class StaffService extends BaseService { ...data, password: await argon2.hash((data.password || '123456') as string), }; - + const result = await super.create({ ...args, data: createData }); this.emitDataChangedEvent(result, CrudOperation.CREATED); return result; diff --git a/apps/server/src/models/visit/visit.service.ts b/apps/server/src/models/visit/visit.service.ts index 3d3a49a..6e0fb44 100644 --- a/apps/server/src/models/visit/visit.service.ts +++ b/apps/server/src/models/visit/visit.service.ts @@ -187,6 +187,13 @@ export class VisitService extends BaseService { visitType: VisitType.LIKE, }); } + if (args.where.type === VisitType.HATE) { + EventBus.emit('updateVisitCount', { + objectType: ObjectType.POST, + id: args?.where?.postId as string, + visitType: VisitType.HATE, + }); + } } return superDetele; } diff --git a/apps/server/src/queue/models/post/utils.ts b/apps/server/src/queue/models/post/utils.ts index 6d43be2..4c620a2 100644 --- a/apps/server/src/queue/models/post/utils.ts +++ b/apps/server/src/queue/models/post/utils.ts @@ -1,5 +1,6 @@ import { db, PostState, PostType, VisitType } from '@nice/common'; export async function updatePostViewCount(id: string, type: VisitType) { + console.log('updatePostViewCount', type); const totalViews = await db.visit.aggregate({ _sum: { views: true, @@ -19,7 +20,6 @@ export async function updatePostViewCount(id: string, type: VisitType) { }, }); } else if (type === VisitType.LIKE) { - console.log('totalViews._sum.view', totalViews._sum.views); await db.post.update({ where: { id: id, @@ -28,5 +28,14 @@ export async function updatePostViewCount(id: string, type: VisitType) { likes: totalViews._sum.views || 0, // Use 0 if no visits exist }, }); + } else if (type === VisitType.HATE) { + await db.post.update({ + where: { + id: id, + }, + data: { + hates: totalViews._sum.views || 0, // Use 0 if no visits exist + }, + }); } } diff --git a/apps/server/src/queue/worker/processor.ts b/apps/server/src/queue/worker/processor.ts index 74fe414..76c4b89 100755 --- a/apps/server/src/queue/worker/processor.ts +++ b/apps/server/src/queue/worker/processor.ts @@ -8,7 +8,6 @@ import { updatePostViewCount } from '../models/post/utils'; const logger = new Logger('QueueWorker'); export default async function processJob(job: Job) { try { - if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) { await updatePostViewCount(job.data.id, job.data.type); } diff --git a/apps/server/src/tasks/init/gendev.service.ts b/apps/server/src/tasks/init/gendev.service.ts index 313c004..5bc2cc0 100644 --- a/apps/server/src/tasks/init/gendev.service.ts +++ b/apps/server/src/tasks/init/gendev.service.ts @@ -12,12 +12,7 @@ import { Term, } from '@nice/common'; import EventBus from '@server/utils/event-bus'; -import { - - capitalizeFirstLetter, - DevDataCounts, - getCounts, -} from './utils'; +import { capitalizeFirstLetter, DevDataCounts, getCounts } from './utils'; import { StaffService } from '@server/models/staff/staff.service'; @Injectable() export class GenDevService { @@ -26,7 +21,7 @@ export class GenDevService { deptStaffRecord: Record = {}; terms: Record = { [TaxonomySlug.CATEGORY]: [], - [TaxonomySlug.TAG]: [] + [TaxonomySlug.TAG]: [], }; depts: Department[] = []; domains: Department[] = []; @@ -39,7 +34,7 @@ export class GenDevService { private readonly departmentService: DepartmentService, private readonly staffService: StaffService, private readonly termService: TermService, - ) { } + ) {} async genDataEvent() { EventBus.emit('genDataEvent', { type: 'start' }); try { @@ -47,7 +42,6 @@ export class GenDevService { await this.generateDepartments(3, 6); await this.generateTerms(1, 3); await this.generateStaffs(4); - } catch (err) { this.logger.error(err); } @@ -164,8 +158,8 @@ export class GenDevService { showname: username, username: username, deptId: dept.id, - domainId: domain.id - } + domainId: domain.id, + }, }); // Update both deptStaffRecord and staffs array this.deptStaffRecord[dept.id].push(staff); @@ -190,7 +184,7 @@ export class GenDevService { name, isDomain: currentDepth === 1 ? true : false, parentId, - } + }, }); return department; } @@ -208,7 +202,9 @@ export class GenDevService { throw new Error(`Taxonomy with slug ${taxonomySlug} not found`); } - this.logger.log(`Creating terms for taxonomy: ${taxonomy.name} (${taxonomy.slug})`); + this.logger.log( + `Creating terms for taxonomy: ${taxonomy.name} (${taxonomy.slug})`, + ); let counter = 1; const createTermTree = async ( parentId: string | null, @@ -223,7 +219,7 @@ export class GenDevService { taxonomyId: taxonomy!.id, domainId: domain?.id, parentId, - } + }, }); this.terms[taxonomySlug].push(newTerm); await createTermTree(newTerm.id, currentDepth + 1); diff --git a/apps/server/src/trpc/trpc.module.ts b/apps/server/src/trpc/trpc.module.ts index 978c0af..222c9c9 100755 --- a/apps/server/src/trpc/trpc.module.ts +++ b/apps/server/src/trpc/trpc.module.ts @@ -15,6 +15,8 @@ import { WebSocketModule } from '@server/socket/websocket.module'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; import { TransformModule } from '@server/models/transform/transform.module'; +import { ResourceModule } from '@server/models/resource/resource.module'; + @Module({ imports: [ AuthModule, @@ -30,6 +32,7 @@ import { TransformModule } from '@server/models/transform/transform.module'; PostModule, VisitModule, WebSocketModule, + ResourceModule, ], controllers: [], providers: [TrpcService, TrpcRouter, Logger], diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index dd0190c..7550867 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -13,10 +13,10 @@ import { VisitRouter } from '@server/models/visit/visit.router'; import { RoleMapRouter } from '@server/models/rbac/rolemap.router'; import { TransformRouter } from '@server/models/transform/transform.router'; import { RoleRouter } from '@server/models/rbac/role.router'; +import { ResourceRouter } from '../models/resource/resource.router'; @Injectable() export class TrpcRouter { - logger = new Logger(TrpcRouter.name); constructor( private readonly trpc: TrpcService, @@ -31,10 +31,10 @@ export class TrpcRouter { private readonly app_config: AppConfigRouter, private readonly message: MessageRouter, private readonly visitor: VisitRouter, - - ) { } + private readonly resource: ResourceRouter, + ) {} getRouter() { - return + return; } appRouter = this.trpc.router({ transform: this.transform.router, @@ -48,6 +48,7 @@ export class TrpcRouter { message: this.message.router, app_config: this.app_config.router, visitor: this.visitor.router, + resource: this.resource.router, }); wss: WebSocketServer = undefined; diff --git a/apps/server/src/trpc/types.ts b/apps/server/src/trpc/types.ts index cd7e491..fc6ce2c 100644 --- a/apps/server/src/trpc/types.ts +++ b/apps/server/src/trpc/types.ts @@ -1,3 +1,3 @@ -import { TrpcRouter } from "./trpc.router"; +import { TrpcRouter } from './trpc.router'; export type AppRouter = TrpcRouter[`appRouter`]; diff --git a/apps/web/src/app/admin/base-setting/page.tsx b/apps/web/src/app/admin/base-setting/page.tsx index 693b11b..7c6fe27 100644 --- a/apps/web/src/app/admin/base-setting/page.tsx +++ b/apps/web/src/app/admin/base-setting/page.tsx @@ -1,32 +1,25 @@ -import { - AppConfigSlug, - BaseSetting, - RolePerms, -} from "@nice/common"; +import { AppConfigSlug, BaseSetting, RolePerms } from "@nice/common"; import { useContext, useEffect, useState } from "react"; -import { - Button, - Form, - Input, - message, - theme, -} from "antd"; +import { Button, Form, Input, message, theme } from "antd"; import { useAppConfig } from "@nice/client"; import { useAuth } from "@web/src/providers/auth-provider"; import { useForm } from "antd/es/form/Form"; -import { api } from "@nice/client" +import { api } from "@nice/client"; import AdminHeader from "@web/src/components/layout/admin/AdminHeader"; - +import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader"; export default function BaseSettingPage() { const { update, baseSetting } = useAppConfig(); - const utils = api.useUtils() - const [form] = useForm() + const utils = api.useUtils(); + const [form] = useForm(); const { token } = theme.useToken(); - const { data: clientCount } = api.app_config.getClientCount.useQuery(undefined, { - refetchInterval: 3000, - refetchIntervalInBackground: true - }) + const { data: clientCount } = api.app_config.getClientCount.useQuery( + undefined, + { + refetchInterval: 3000, + refetchIntervalInBackground: true, + } + ); const [isFormChanged, setIsFormChanged] = useState(false); const [loading, setLoading] = useState(false); const { user, hasSomePermissions } = useAuth(); @@ -34,31 +27,27 @@ export default function BaseSettingPage() { setIsFormChanged(true); } function onResetClick() { - if (!form) - return + if (!form) return; if (!baseSetting) { form.resetFields(); } else { form.resetFields(); form.setFieldsValue(baseSetting); - } setIsFormChanged(false); } function onSaveClick() { - if (form) - form.submit(); + if (form) form.submit(); } async function onSubmit(values: BaseSetting) { setLoading(true); try { - await update.mutateAsync({ where: { slug: AppConfigSlug.BASE_SETTING, }, - data: { meta: JSON.stringify(values) } + data: { meta: JSON.stringify(values) }, }); setIsFormChanged(false); message.success("已保存"); @@ -70,12 +59,11 @@ export default function BaseSettingPage() { } useEffect(() => { if (baseSetting && form) { - form.setFieldsValue(baseSetting); } }, [baseSetting, form]); return ( -
+
{isFormChanged && @@ -101,7 +89,6 @@ export default function BaseSettingPage() { !hasSomePermissions(RolePerms.MANAGE_BASE_SETTING) } onFinish={onSubmit} - onFieldsChange={handleFieldsChange} layout="vertical"> {/*
+
+ + + +
{/*
- {
- app在线人数 -
- {clientCount && clientCount > 0 ? `${clientCount}人在线` : '无人在线'} + { +
+ app在线人数 +
+ {clientCount && clientCount > 0 + ? `${clientCount}人在线` + : "无人在线"} +
-
} + }
); diff --git a/apps/web/src/app/auth/register.tsx b/apps/web/src/app/auth/register.tsx index 3337d32..bfbfeca 100644 --- a/apps/web/src/app/auth/register.tsx +++ b/apps/web/src/app/auth/register.tsx @@ -134,12 +134,12 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => { rules={[ { required: true, message: "请输入密码" }, { min: 8, message: "密码至少需要8个字符" }, - { - pattern: - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, - message: - "密码必须包含大小写字母、数字和特殊字符", - }, + // { + // pattern: + // /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, + // message: + // "密码必须包含大小写字母、数字和特殊字符", + // }, ]}> @@ -150,13 +150,13 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => { label="证件号" noStyle rules={[ - { required: true, message: "请输入证件号" }, + { message: "请输入证件号" }, { pattern: /^\d{5,12}$/, message: "请输入有效的证件号(5-12位数字)", }, ]}> - + +
diff --git a/apps/web/src/app/main/letter/write/page.tsx b/apps/web/src/app/main/letter/write/page.tsx index c7da2f7..de44eae 100644 --- a/apps/web/src/app/main/letter/write/page.tsx +++ b/apps/web/src/app/main/letter/write/page.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from "react"; +import { useState, useCallback, useEffect, useMemo } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useSearchParams } from "react-router-dom"; @@ -9,136 +9,157 @@ import DepartmentSelect from "@web/src/components/models/department/department-s import debounce from "lodash/debounce"; import { SearchOutlined } from "@ant-design/icons"; import WriteHeader from "./WriteHeader"; +import { ObjectType, RoleName } from "@nice/common"; export default function WriteLetterPage() { - const [searchParams] = useSearchParams(); - const termId = searchParams.get("termId"); - const [searchQuery, setSearchQuery] = useState(""); - const [selectedDept, setSelectedDept] = useState(); - const [currentPage, setCurrentPage] = useState(1); - const pageSize = 10; - const { getTerm } = useTerm(); + const [searchParams] = useSearchParams(); + const termId = searchParams.get("termId"); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedDept, setSelectedDept] = useState(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; + const { getTerm } = useTerm(); + const { data: enabledStaffIds, isLoading: roleMapIsLoading } = + api.rolemap.getStaffIdsByRoleNames.useQuery({ + roleNames: [RoleName.Leader, RoleName.Organization], + }); + // eslint-disable-next-line react-hooks/exhaustive-deps - const { data, isLoading, error } = - api.staff.findManyWithPagination.useQuery({ - page: currentPage, - pageSize, - where: { - deptId: selectedDept, - OR: [ - { - showname: { - contains: searchQuery, - }, - }, - { - username: { - contains: searchQuery, - }, - }, + const { data, isLoading, error } = + api.staff.findManyWithPagination.useQuery( + { + page: currentPage, + pageSize, + where: { + id: enabledStaffIds + ? { + in: enabledStaffIds, + } + : undefined, + deptId: selectedDept, + OR: [ + { + showname: { + contains: searchQuery, + }, + }, + { + username: { + contains: searchQuery, + }, + }, + { + meta: { + path: ["rank"], // 指定 JSON 字段的路径 + string_contains: searchQuery, // 对 rank 字段进行模糊搜索 + }, + }, + ], + }, + orderBy: { + order: "asc", + }, + }, + { + enabled: !roleMapIsLoading, + } + ); - ], - }, - orderBy: { - order: "desc", - } - }); + const resetPage = useCallback(() => { + setCurrentPage(1); + }, []); - const resetPage = useCallback(() => { - setCurrentPage(1); - }, []); + // Reset page when search or department changes + useEffect(() => { + resetPage(); + }, [searchQuery, selectedDept, resetPage]); - // Reset page when search or department changes - useEffect(() => { - resetPage(); - }, [searchQuery, selectedDept, resetPage]); + return ( +
+ +
+
+
+ + + } + placeholder="搜索领导姓名或职级..." + onChange={debounce( + (e) => setSearchQuery(e.target.value), + 300 + )} + size="large" + /> +
+ {error && ( + + )} +
+ + {isLoading ? ( +
+ +
+ ) : data?.items.length > 0 ? ( + + {data?.items.map((item: any) => ( + + ))} + + ) : ( + + + + )} +
- return ( -
- -
-
-
- - - } - placeholder="搜索领导姓名或职级..." - onChange={debounce( - (e) => setSearchQuery(e.target.value), - 300 - )} - size="large" - /> -
- {error && ( - - )} -
- - {isLoading ? ( -
- -
- ) : data?.items.length > 0 ? ( - - {data?.items.map((item: any) => ( - - ))} - - ) : ( - - - - )} -
- - {/* Pagination */} - {data?.items.length > 0 && ( -
- { - setCurrentPage(page); - window.scrollTo(0, 0); - }} - showSizeChanger={false} - showTotal={(total) => `共 ${total} 条记录`} - /> -
- )} -
-
- ); + {/* Pagination */} + {data?.items.length > 0 && ( +
+ { + setCurrentPage(page); + window.scrollTo(0, 0); + }} + showSizeChanger={false} + showTotal={(total) => `共 ${total} 条记录`} + /> +
+ )} +
+
+ ); } diff --git a/apps/web/src/components/common/uploader/AvatarUploader.tsx b/apps/web/src/components/common/uploader/AvatarUploader.tsx index 6efc6c9..ae831a3 100644 --- a/apps/web/src/components/common/uploader/AvatarUploader.tsx +++ b/apps/web/src/components/common/uploader/AvatarUploader.tsx @@ -2,6 +2,7 @@ import { env } from "@web/src/env"; import { message, Progress, Spin, theme } from "antd"; import React, { useState, useEffect, useRef } from "react"; import { useTusUpload } from "@web/src/hooks/useTusUpload"; +import { Avatar } from "antd/lib"; export interface AvatarUploaderProps { value?: string; @@ -16,6 +17,7 @@ interface UploadingFile { progress: number; status: "uploading" | "done" | "error"; fileId?: string; + url?: string; fileKey?: string; } @@ -28,6 +30,7 @@ const AvatarUploader: React.FC = ({ }) => { const { handleFileUpload, uploadProgress } = useTusUpload(); const [file, setFile] = useState(null); + const [previewUrl, setPreviewUrl] = useState(value || ""); const [uploading, setUploading] = useState(false); const inputRef = useRef(null); @@ -56,7 +59,9 @@ const AvatarUploader: React.FC = ({ progress: 100, status: "done", fileId: result.fileId, + url: result?.url, })); + setPreviewUrl(result?.url); resolve(result.fileId); }, (error) => { @@ -65,7 +70,6 @@ const AvatarUploader: React.FC = ({ file?.fileKey ); }); - setPreviewUrl(`${env.SERVER_IP}/uploads/${fileId}`); onChange?.(fileId); message.success("头像上传成功"); } catch (error) { @@ -98,9 +102,9 @@ const AvatarUploader: React.FC = ({ style={{ display: "none" }} /> {previewUrl ? ( - Avatar ) : ( diff --git a/apps/web/src/components/common/uploader/TusUploader.tsx b/apps/web/src/components/common/uploader/TusUploader.tsx index 47034e6..3aa7ad0 100644 --- a/apps/web/src/components/common/uploader/TusUploader.tsx +++ b/apps/web/src/components/common/uploader/TusUploader.tsx @@ -4,14 +4,16 @@ import { CheckCircleOutlined, DeleteOutlined, } from "@ant-design/icons"; -import { Upload, message, Progress, Button } from "antd"; +import { Upload, Progress, Button } from "antd"; import type { UploadFile } from "antd"; import { useTusUpload } from "@web/src/hooks/useTusUpload"; +import toast from "react-hot-toast"; export interface TusUploaderProps { value?: string[]; onChange?: (value: string[]) => void; } + interface UploadingFile { name: string; progress: number; @@ -32,98 +34,82 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { fileId, })) || [] ); + // 恢复使用 uploadResults 状态跟踪最新结果 const [uploadResults, setUploadResults] = useState(value || []); - const handleRemoveFile = useCallback( (fileId: string) => { setCompletedFiles((prev) => prev.filter((f) => f.fileId !== fileId) ); - const newResults = uploadResults.filter((id) => id !== fileId); - setUploadResults(newResults); - onChange?.(newResults); + // 使用函数式更新保证获取最新状态 + setUploadResults((prev) => { + const newValue = prev.filter((id) => id !== fileId); + onChange?.(newValue); // 同步更新父组件 + return newValue; + }); }, - [uploadResults, onChange] + [onChange] ); - const handleChange = useCallback( - async (fileList: UploadFile | UploadFile[]) => { - const files = Array.isArray(fileList) ? fileList : [fileList]; - console.log("文件", files); + const handleBeforeUpload = useCallback( + (file: File) => { + const fileKey = `${file.name}-${Date.now()}`; - if (!files.every((f) => f instanceof File)) { - message.error("无效的文件格式"); - return false; - } + setUploadingFiles((prev) => [ + ...prev, + { + name: file.name, + progress: 0, + status: "uploading", + fileKey, + }, + ]); - const newFiles: UploadingFile[] = files.map((f) => ({ - name: f.name, - progress: 0, - status: "uploading" as const, - fileKey: `${f.name}-${Date.now()}`, // 为每个文件创建唯一标识 - })); + handleFileUpload( + file, + (result) => { + setCompletedFiles((prev) => [ + ...prev, + { + name: file.name, + progress: 100, + status: "done", + fileId: result.fileId, + }, + ]); - setUploadingFiles((prev) => [...prev, ...newFiles]); - - const newUploadResults: string[] = []; - try { - for (const [index, f] of files.entries()) { - if (!f) { - throw new Error(`文件 ${f.name} 无效`); - } - const fileKey = newFiles[index].fileKey!; - const fileId = await new Promise( - (resolve, reject) => { - handleFileUpload( - f as File, - (result) => { - console.log("上传成功:", result); - const completedFile = { - name: f.name, - progress: 100, - status: "done" as const, - fileId: result.fileId, - }; - setCompletedFiles((prev) => [ - ...prev, - completedFile, - ]); - setUploadingFiles((prev) => - prev.filter( - (file) => file.fileKey !== fileKey - ) - ); - resolve(result.fileId); - }, - (error) => { - console.error("上传错误:", error); - reject(error); - }, - fileKey - ); - } + setUploadingFiles((prev) => + prev.filter((f) => f.fileKey !== fileKey) ); - newUploadResults.push(fileId); - } - const newValue = Array.from( - new Set([...uploadResults, ...newUploadResults]) - ); - setUploadResults(newValue); - onChange?.(newValue); - message.success(`${files.length} 个文件上传成功`); - } catch (error) { - console.error("上传错误详情:", error); - message.error( - `上传失败: ${error instanceof Error ? error.message : "未知错误"}` - ); - setUploadingFiles((prev) => - prev.map((f) => ({ ...f, status: "error" })) - ); - } + // 正确的状态更新方式 + setUploadResults((prev) => { + const newValue = [...prev, result.fileId]; + onChange?.(newValue); // 传递值而非函数 + return newValue; + }); + }, + (error) => { + console.error("上传错误:", error); + toast.error( + `上传失败: ${ + error instanceof Error ? error.message : "未知错误" + }` + ); + setUploadingFiles((prev) => + prev.map((f) => + f.fileKey === fileKey + ? { ...f, status: "error" } + : f + ) + ); + }, + fileKey + ); + return false; }, - [uploadResults, onChange, handleFileUpload] + [handleFileUpload, onChange] ); return ( @@ -132,8 +118,8 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { name="files" multiple showUploadList={false} - style={{ background: "white", borderStyle: "solid" }} - beforeUpload={handleChange}> + style={{ background: "transparent", borderStyle: "none" }} + beforeUpload={handleBeforeUpload}>

@@ -141,64 +127,58 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => { 点击或拖拽文件到此区域进行上传

支持单个或批量上传文件

- {/* 正在上传的文件 */} - {(uploadingFiles.length > 0 || completedFiles.length > 0) && ( -
- {uploadingFiles.map((file) => ( -
-
-
{file.name}
-
- + {/* 上传状态展示 */} +
+ {/* 上传中的文件 */} + {uploadingFiles.map((file) => ( +
+
+ {file.name}
- ))} - {completedFiles.length > 0 && - completedFiles.map((file, index) => ( -
-
- -
- {file.name} -
-
-
- ))} -
- )} + +
+ ))} + + {/* 已完成的文件 */} + {completedFiles.map((file) => ( +
+
+ + {file.name} +
+
+ ))} +
); diff --git a/apps/web/src/components/layout/main/Header.tsx b/apps/web/src/components/layout/main/Header.tsx index 04babf8..8c5ae80 100644 --- a/apps/web/src/components/layout/main/Header.tsx +++ b/apps/web/src/components/layout/main/Header.tsx @@ -7,25 +7,29 @@ import { UserOutlined } from "@ant-design/icons"; import { UserMenu } from "../element/usermenu/usermenu"; export const Header = memo(function Header() { - const { isAuthenticated } = useAuth(); + const { isAuthenticated } = useAuth(); - return ( -
-
-
-
-
- 首长机关信箱 -

聆怀若水,应语如风;纾难化困,践诺成春

-
-
- -
-
- {!isAuthenticated ? ( - +
+
+
+
+ + 首长机关信箱 + +

+ 聆怀若水,应语如风;纾难化困,践诺成春 +

+
+
+ +
+
+ {!isAuthenticated ? ( + - - + - 登录 - - ) : ( - - )} -
-
-
- -
-
- ); + 登录 + + ) : ( + + )} +
+
+
+ + + + ); }); diff --git a/apps/web/src/components/layout/main/navigation.tsx b/apps/web/src/components/layout/main/navigation.tsx index 27a1302..66517b6 100644 --- a/apps/web/src/components/layout/main/navigation.tsx +++ b/apps/web/src/components/layout/main/navigation.tsx @@ -28,7 +28,7 @@ export default function Navigation({ className }: NavigationProps) { return (