diff --git a/apps/server/src/models/base/base.service.ts b/apps/server/src/models/base/base.service.ts index d1b8b16..1408bfa 100644 --- a/apps/server/src/models/base/base.service.ts +++ b/apps/server/src/models/base/base.service.ts @@ -38,7 +38,7 @@ export class BaseService< protected prisma: PrismaClient, protected objectType: string, protected enableOrder: boolean = false, - ) {} + ) { } /** * Retrieves the name of the model dynamically. @@ -457,6 +457,7 @@ export class BaseService< try { // 获取总记录数 const total = (await this.getModel().count({ where })) as number; + // 获取分页数据 const items = (await this.getModel().findMany({ where, diff --git a/apps/server/src/models/post/post.router.ts b/apps/server/src/models/post/post.router.ts index 7766164..bcbca1b 100755 --- a/apps/server/src/models/post/post.router.ts +++ b/apps/server/src/models/post/post.router.ts @@ -17,7 +17,7 @@ export class PostRouter { constructor( private readonly trpc: TrpcService, private readonly postService: PostService, - ) {} + ) { } router = this.trpc.router({ create: this.trpc.protectProcedure .input(PostCreateArgsSchema) @@ -91,5 +91,15 @@ export class PostRouter { const { staff } = ctx; return await this.postService.findManyWithCursor(input, staff); }), + findManyWithPagination: this.trpc.procedure + .input(z.object({ + page: z.number(), + pageSize: z.number().optional(), + where: PostWhereInputSchema.optional(), + select: PostSelectSchema.optional() + })) // Assuming StaffMethodSchema.findMany is the Zod schema for finding staffs by keyword + .query(async ({ input }) => { + return await this.postService.findManyWithPagination(input); + }), }); } diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index 1dbe0fc..a2d69fe 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -11,6 +11,7 @@ import { z } from 'zod'; import { BaseService } from '../base/base.service'; import * as argon2 from 'argon2'; import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { DefaultArgs } from '@prisma/client/runtime/library'; @Injectable() export class StaffService extends BaseService { @@ -119,72 +120,14 @@ export class StaffService extends BaseService { } } - // /** - // * 根据关键词或ID集合查找员工 - // * @param data 包含关键词、域ID和ID集合的对象 - // * @returns 匹配的员工记录列表 - // */ - // async findMany(data: z.infer) { - // const { keyword, domainId, ids, deptId, limit = 30 } = data; - // const idResults = ids - // ? await db.staff.findMany({ - // where: { - // id: { in: ids }, - // deletedAt: null, - // domainId, - // deptId, - // }, - // select: { - // id: true, - // showname: true, - // username: true, - // deptId: true, - // domainId: true, - // department: true, - // domain: true, - // }, - // }) - // : []; - - // const mainResults = await db.staff.findMany({ - // where: { - // deletedAt: null, - // domainId, - // deptId, - // OR: (keyword || ids) && [ - // { showname: { contains: keyword } }, - // { - // username: { - // contains: keyword, - // }, - // }, - // { phoneNumber: { contains: keyword } }, - // // { - // // id: { in: ids }, - // // }, - // ], - // }, - // select: { - // id: true, - // showname: true, - // username: true, - // deptId: true, - // domainId: true, - // department: true, - // domain: true, - // }, - // orderBy: { order: 'asc' }, - // take: limit !== -1 ? limit : undefined, - // }); - // // Combine results, ensuring no duplicates - // const combinedResults = [ - // ...mainResults, - // ...idResults.filter( - // (idResult) => - // !mainResults.some((mainResult) => mainResult.id === idResult.id), - // ), - // ]; - - // return combinedResults; - // } + async findManyWithPagination(args: { page?: number; pageSize?: number; where?: Prisma.StaffWhereInput; select?: Prisma.StaffSelect; }) { + if (args.where.deptId && typeof args.where.deptId === 'string') { + const childDepts = await this.departmentService.getDescendantIds(args.where.deptId, true); + args.where.deptId = { + in: childDepts + } + } + + return super.findManyWithPagination(args) + } } diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index 6073f21..dd0190c 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -16,6 +16,7 @@ import { RoleRouter } from '@server/models/rbac/role.router'; @Injectable() export class TrpcRouter { + logger = new Logger(TrpcRouter.name); constructor( private readonly trpc: TrpcService, @@ -30,8 +31,11 @@ export class TrpcRouter { private readonly app_config: AppConfigRouter, private readonly message: MessageRouter, private readonly visitor: VisitRouter, - // private readonly websocketService: WebSocketService - ) {} + + ) { } + getRouter() { + return + } appRouter = this.trpc.router({ transform: this.transform.router, post: this.post.router, @@ -66,4 +70,3 @@ export class TrpcRouter { // }); } } -export type AppRouter = TrpcRouter[`appRouter`]; diff --git a/apps/server/src/trpc/types.ts b/apps/server/src/trpc/types.ts new file mode 100644 index 0000000..cd7e491 --- /dev/null +++ b/apps/server/src/trpc/types.ts @@ -0,0 +1,3 @@ +import { TrpcRouter } from "./trpc.router"; + +export type AppRouter = TrpcRouter[`appRouter`]; diff --git a/apps/server/tsconfig.json b/apps/server/tsconfig.json index 858dbf5..a0f0307 100755 --- a/apps/server/tsconfig.json +++ b/apps/server/tsconfig.json @@ -10,7 +10,6 @@ "target": "ES2020", "sourceMap": true, "outDir": "./dist", - "strict": true, "esModuleInterop": true, "incremental": true, // "skipLibCheck": true, diff --git a/apps/web/src/app/main/letter/write/LeaderCard.tsx b/apps/web/src/app/main/letter/write/SendCard.tsx similarity index 61% rename from apps/web/src/app/main/letter/write/LeaderCard.tsx rename to apps/web/src/app/main/letter/write/SendCard.tsx index a2ebdbf..7483167 100644 --- a/apps/web/src/app/main/letter/write/LeaderCard.tsx +++ b/apps/web/src/app/main/letter/write/SendCard.tsx @@ -1,14 +1,13 @@ import { motion } from 'framer-motion'; import { PaperAirplaneIcon } from '@heroicons/react/24/outline'; -import { Leader } from './types'; +import { StaffDto } from "@nice/common"; +import { Button } from 'antd'; -interface LeaderCardProps { - leader: Leader; - isSelected: boolean; - onSelect: () => void; +export interface SendCardProps { + staff: StaffDto; } -export default function LeaderCard({ leader, isSelected, onSelect }: LeaderCardProps) { +export function SendCard({ staff }: SendCardProps) { return (
{/* Image Container */} -
- {leader.name} +
+ {staff.meta?.photoUrl ? ( + {staff.showname} + ) : ( +
+ + + + 暂未上传照片 +
+ )}
{/* Content Container */} @@ -39,13 +54,13 @@ export default function LeaderCard({ leader, isSelected, onSelect }: LeaderCardP

- {leader.name} + {staff.showname}

- {leader.rank} + {staff.meta?.rank || '未设置职级'}
-

{leader.division}

+

{staff.department?.name || '未设置部门'}

{/* Contact Information */}
@@ -54,36 +69,32 @@ export default function LeaderCard({ leader, isSelected, onSelect }: LeaderCardP - {leader.email} + {staff.meta?.email || '未设置邮箱'}

- {leader.phone} + {staff.phoneNumber || '未设置电话'}

- {leader.office} + {staff.meta?.office || '未设置办公室'}

- + 发送信件 +
diff --git a/apps/web/src/app/main/letter/write/filter.tsx b/apps/web/src/app/main/letter/write/filter.tsx deleted file mode 100644 index fc6359e..0000000 --- a/apps/web/src/app/main/letter/write/filter.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { FunnelIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { useMemo, useState } from "react"; -import { Input, Select } from "antd"; -import { motion } from "framer-motion"; -import DepartmentSelect from "@web/src/components/models/department/department-select"; - -const { Search } = Input; - -interface FilterProps { - onSearch?: (query: string) => void; - onDivisionChange?: (division: string) => void; -} - -export default function Filter({ onSearch, onDivisionChange }: FilterProps) { - const [searchQuery, setSearchQuery] = useState(''); - const [selectedDivision, setSelectedDivision] = useState('all'); - - - const handleSearch = (value: string) => { - setSearchQuery(value); - onSearch?.(value); - }; - - const handleDivisionChange = (value: string) => { - setSelectedDivision(value); - onDivisionChange?.(value); - }; - - return ( - -
- - - Search -
- } - size="large" - value={searchQuery} - onChange={(e) => handleSearch(e.target.value)} - className="rounded-lg" - /> - - - -
- ); -} diff --git a/apps/web/src/app/main/letter/write/page.tsx b/apps/web/src/app/main/letter/write/page.tsx index ebfd340..1ed0640 100644 --- a/apps/web/src/app/main/letter/write/page.tsx +++ b/apps/web/src/app/main/letter/write/page.tsx @@ -1,50 +1,95 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Leader } from './types'; -import { leaders } from './mock'; import Header from './header'; -import Filter from './filter'; -import LeaderCard from './LeaderCard'; -import { Spin, Empty } from 'antd'; -import { api } from 'packages/client/dist'; - +import { SendCard } from './SendCard'; +import { Spin, Empty, Input, Alert, message, Pagination } from 'antd'; +import { api } from '@nice/client'; +import DepartmentSelect from '@web/src/components/models/department/department-select'; +import debounce from 'lodash/debounce'; +import { SearchOutlined } from '@ant-design/icons'; export default function WriteLetterPage() { - - const [selectedLeader, setSelectedLeader] = useState(null); const [searchQuery, setSearchQuery] = useState(''); - const [selectedDivision, setSelectedDivision] = useState('all'); + const [selectedDept, setSelectedDept] = useState(); + const [currentPage, setCurrentPage] = useState(1); + const pageSize = 10; - const filteredLeaders = useMemo(() => { - return leaders.filter(leader => { - const matchesSearch = leader.name.toLowerCase().includes(searchQuery.toLowerCase()) || - leader.rank.toLowerCase().includes(searchQuery.toLowerCase()); - const matchesDivision = selectedDivision === 'all' || leader.division === selectedDivision; - return matchesSearch && matchesDivision; - }); - }, [searchQuery, selectedDivision]); + const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({ + page: currentPage, + pageSize, + where: { + deptId: selectedDept, + OR: [{ + showname: { + contains: searchQuery + } + }, { + username: { + contains: searchQuery + } + }] + } + }); + useEffect(() => { + console.log(selectedDept) + console.log(data) + console.log(searchQuery) + }, [selectedDept, data, searchQuery]) + // Reset to first page when search query or department changes + useCallback(() => { + setCurrentPage(1); + }, [searchQuery, selectedDept]); return (
-
- +
+ {/* Search and Filter Section */} +
+
+ } + placeholder="搜索领导姓名或职级..." + onChange={debounce((e) => setSearchQuery(e.target.value), 300)} + className="w-full" + size="large" + /> +
+ +
+ + {error && ( + + )} +
- {filteredLeaders.length > 0 ? ( + {isLoading ? ( +
+ +
+ ) : data?.items.length > 0 ? ( - {filteredLeaders.map((leader) => ( - setSelectedLeader(leader)} + {data?.items.map((item) => ( + ))} @@ -55,10 +100,27 @@ export default function WriteLetterPage() { animate={{ opacity: 1 }} exit={{ opacity: 0 }} > - + )}
+ + {/* Pagination */} + {data?.items.length > 0 && ( +
+ setCurrentPage(page)} + showSizeChanger={false} + showTotal={(total) => `共 ${total} 条记录`} + /> +
+ )}
); diff --git a/apps/web/src/components/models/department/department-select.tsx b/apps/web/src/components/models/department/department-select.tsx index 9a23358..d692dbf 100644 --- a/apps/web/src/components/models/department/department-select.tsx +++ b/apps/web/src/components/models/department/department-select.tsx @@ -14,6 +14,7 @@ interface DepartmentSelectProps { domain?: boolean; disabled?: boolean; className?: string; + size?: "small" | "middle" | "large"; } export default function DepartmentSelect({ @@ -24,6 +25,7 @@ export default function DepartmentSelect({ placeholder = "选择单位", multiple = false, rootId = null, + size, disabled = false, domain = undefined, }: DepartmentSelectProps) { @@ -150,6 +152,7 @@ export default function DepartmentSelect({ disabled={disabled} showSearch allowClear + size={size} defaultValue={defaultValue} value={value} className={className} diff --git a/apps/web/src/components/models/post/list/PostList.tsx b/apps/web/src/components/models/post/list/PostList.tsx new file mode 100644 index 0000000..b0382d5 --- /dev/null +++ b/apps/web/src/components/models/post/list/PostList.tsx @@ -0,0 +1,5 @@ +import { api } from "@nice/client"; + +export default function LetterList(){ + +} \ No newline at end of file diff --git a/packages/client/src/api/hooks/useDepartment.ts b/packages/client/src/api/hooks/useDepartment.ts index 27e9c19..f2a1725 100755 --- a/packages/client/src/api/hooks/useDepartment.ts +++ b/packages/client/src/api/hooks/useDepartment.ts @@ -16,7 +16,7 @@ export function useDepartment() { const update = api.department.update.useMutation({ onSuccess: (result) => { - + queryClient.invalidateQueries({ queryKey }); emitDataChange(ObjectType.DEPARTMENT, result as any, CrudOperation.UPDATED) }, @@ -51,34 +51,6 @@ export function useDepartment() { }); }; - // const getTreeData = () => { - // const uniqueData: DepartmentDto[] = getCacheDataFromQuery( - // queryClient, - // api.department - // ); - // const treeData: DataNode[] = buildTree(uniqueData); - // return treeData; - // }; - // const getTreeData = () => { - // const cacheArray = queryClient.getQueriesData({ - // queryKey: getQueryKey(api.department.getChildren), - // }); - // const data: DepartmentDto[] = cacheArray - // .flatMap((cache) => cache.slice(1)) - // .flat() - // .filter((item) => item !== undefined) as any; - // const uniqueDataMap = new Map(); - - // data?.forEach((item) => { - // if (item && item.id) { - // uniqueDataMap.set(item.id, item); - // } - // }); - // // Convert the Map back to an array - // const uniqueData: DepartmentDto[] = Array.from(uniqueDataMap.values()); - // const treeData: DataNode[] = buildTree(uniqueData); - // return treeData; - // }; const getDept = (key: string) => { return findQueryData(queryClient, api.department, key); }; diff --git a/packages/client/src/api/hooks/useEntity.ts b/packages/client/src/api/hooks/useEntity.ts new file mode 100644 index 0000000..6cec411 --- /dev/null +++ b/packages/client/src/api/hooks/useEntity.ts @@ -0,0 +1,86 @@ +import type { UseTRPCMutationResult } from "@trpc/react-query/shared"; +import { api, type RouterInputs, type RouterOutputs } from "../trpc"; +/** + * 定义 MutationType 类型,用于提取指定实体类型的特定 mutation 方法名。 + * @template T - 实体类型的键名(如 'post', 'user') + * @description 该类型通过遍历 RouterInputs[T] 的键名,筛选出符合条件的 mutation 方法名(如 'create', 'update' 等)。 + */ +type MutationType = keyof { + [K in keyof RouterInputs[T]]: K extends "create" | "update" | "deleteMany" | "softDeleteByIds" | "restoreByIds" | "updateOrder" + ? RouterInputs[T][K] + : never +}; +/** + * 定义 MutationOptions 类型,用于配置特定 mutation 方法的回调函数。 + * @template T - 实体类型的键名 + * @template K - mutation 方法名 + * @description 该类型包含一个可选的 onSuccess 回调函数,用于在 mutation 成功时执行。 + */ +type MutationOptions> = { + onSuccess?: ( + data: RouterOutputs[T][K], // mutation 成功后的返回数据 + variables: RouterInputs[T][K], // mutation 的输入参数 + context?: unknown // 可选的上下文信息 + ) => void; +}; + +/** + * 定义 EntityOptions 类型,用于配置实体类型的所有 mutation 方法的回调函数。 + * @template T - 实体类型的键名 + * @description 该类型是一个对象,键为 mutation 方法名,值为对应的 MutationOptions 配置。 + */ +type EntityOptions = { + [K in MutationType]?: MutationOptions; +}; + +/** + * 工具类型:简化 UseTRPCMutationResult 的类型断言。 + * @template T - 实体类型的键名 + * @template K - mutation 方法名 + * @description 该类型用于简化 UseTRPCMutationResult 的类型定义,使其更易读。 + */ +type MutationResult> = UseTRPCMutationResult< + RouterOutputs[T][K], // mutation 成功后的返回数据 + unknown, // mutation 的错误类型 + RouterInputs[T][K], // mutation 的输入参数 + unknown // mutation 的上下文类型 +>; + +/** + * 自定义 Hook:用于处理实体的 mutation 操作,并内置缓存失效机制。 + * @template T - 实体类型的键名(如 'post', 'user') + * @param {T} key - 实体键名 + * @param {EntityOptions} [options] - 可选的 mutation 回调函数配置 + * @returns 返回一个包含多个 mutation 函数的对象 + * @description 该 Hook 封装了常见的 mutation 操作(如 create, update, deleteMany 等),并在每次 mutation 成功后自动失效相关缓存。 + */ +export function useEntity(key: T, options?: EntityOptions) { + const utils = api.useUtils(); // 获取 tRPC 的工具函数,用于操作缓存 + + /** + * 创建 mutation 处理函数。 + * @template K - mutation 方法名 + * @param {K} mutation - mutation 方法名 + * @returns 返回一个配置好的 mutation 函数 + * @description 该函数根据传入的 mutation 方法名,生成对应的 mutation 函数,并配置 onSuccess 回调。 + */ + const createMutationHandler = >(mutation: K) => { + const mutationFn = (api[key as any])[mutation]; // 获取对应的 tRPC mutation 函数 + return mutationFn.useMutation({ + onSuccess: (data, variables, context) => { + utils[key].invalidate(); // 失效指定实体的缓存 + options?.[mutation]?.onSuccess?.(data, variables, context); // 调用用户自定义的 onSuccess 回调 + }, + }); + }; + + // 返回包含多个 mutation 函数的对象 + return { + create: createMutationHandler("create") as MutationResult, // 创建实体的 mutation 函数 + update: createMutationHandler("update") as MutationResult, // 更新实体的 mutation 函数 + deleteMany: createMutationHandler("deleteMany") as MutationResult, // 批量删除实体的 mutation 函数 + softDeleteByIds: createMutationHandler("softDeleteByIds") as MutationResult, // 软删除实体的 mutation 函数 + restoreByIds: createMutationHandler("restoreByIds") as MutationResult, // 恢复软删除实体的 mutation 函数 + updateOrder: createMutationHandler("updateOrder") as MutationResult, // 更新实体顺序的 mutation 函数 + }; +} \ No newline at end of file diff --git a/packages/client/src/api/hooks/useMessage.ts b/packages/client/src/api/hooks/useMessage.ts index c620920..8f95f9b 100644 --- a/packages/client/src/api/hooks/useMessage.ts +++ b/packages/client/src/api/hooks/useMessage.ts @@ -1,18 +1,5 @@ -import { api } from "../trpc"; -import { useQueryClient } from "@tanstack/react-query"; -import { getQueryKey } from "@trpc/react-query"; -import { Prisma } from "packages/common/dist"; +import { useEntity } from "./useEntity"; export function useMessage() { - const queryClient = useQueryClient(); - const queryKey = getQueryKey(api.message); - const create:any = api.message.create.useMutation({ - onSuccess: () => { - queryClient.invalidateQueries({ queryKey }); - }, - }); - - return { - create - }; -} \ No newline at end of file + return useEntity("message") +} diff --git a/packages/client/src/api/hooks/usePost.ts b/packages/client/src/api/hooks/usePost.ts index 5006731..6839467 100644 --- a/packages/client/src/api/hooks/usePost.ts +++ b/packages/client/src/api/hooks/usePost.ts @@ -1,37 +1,4 @@ -import { api } from "../trpc"; - +import { useEntity } from "./useEntity"; export function usePost() { - const utils = api.useUtils(); - const create: any = api.post.create.useMutation({ - onSuccess: () => { - utils.post.invalidate(); - }, - }); - const update: any = api.post.update.useMutation({ - onSuccess: () => { - utils.post.invalidate(); - }, - }); - const deleteMany = api.post.deleteMany.useMutation({ - onSuccess: () => { - utils.post.invalidate(); - }, - }); - const softDeleteByIds: any = api.post.softDeleteByIds.useMutation({ - onSuccess: () => { - utils.post.invalidate(); - }, - }); - const restoreByIds: any = api.post.restoreByIds.useMutation({ - onSuccess: () => { - utils.post.invalidate(); - }, - }); - return { - create, - update, - deleteMany, - softDeleteByIds, - restoreByIds, - }; + return useEntity("post") } diff --git a/packages/client/src/api/hooks/useQueryApi.ts b/packages/client/src/api/hooks/useQueryApi.ts deleted file mode 100644 index fbe9d77..0000000 --- a/packages/client/src/api/hooks/useQueryApi.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { SkipToken } from "@tanstack/react-query"; -import type { TRPCClientErrorLike } from "@trpc/client"; -import type { - UseTRPCQueryOptions, - UseTRPCQueryResult, -} from "@trpc/react-query/shared"; -import type { DecoratedQuery } from "node_modules/@trpc/react-query/dist/createTRPCReact"; - -export const useQueryApi = < - T extends DecoratedQuery<{ - input: any; - output: any; - transformer: any; - errorShape: any; - }>, - U extends T extends DecoratedQuery ? R : never, ->( - query: T, - input: U["input"] | SkipToken, - opts?: UseTRPCQueryOptions< - U["output"], - U["input"], - TRPCClientErrorLike, - U["output"] - >, -): UseTRPCQueryResult> => - query.useQuery(input, opts); diff --git a/packages/client/src/api/hooks/useRoleMap.ts b/packages/client/src/api/hooks/useRoleMap.ts index 5d98116..61984dd 100755 --- a/packages/client/src/api/hooks/useRoleMap.ts +++ b/packages/client/src/api/hooks/useRoleMap.ts @@ -3,7 +3,15 @@ import { api } from "../trpc"; // Adjust path as necessary import { useQueryClient } from "@tanstack/react-query"; import { CrudOperation, emitDataChange, EventBus } from "../../event"; import { ObjectType } from "@nice/common"; -export function useRoleMap() { +type RoleMapHookReturn = { + create: ReturnType; + update: ReturnType; + setRoleForObjects: ReturnType; + deleteMany: ReturnType; + addRoleForObjects: ReturnType; +}; + +export function useRoleMap(): RoleMapHookReturn { const queryClient = useQueryClient(); const queryKey = getQueryKey(api.rolemap); @@ -30,10 +38,10 @@ export function useRoleMap() { }); const deleteMany = api.rolemap.deleteMany.useMutation({ onSuccess: (result) => { - + queryClient.invalidateQueries({ queryKey }); emitDataChange(ObjectType.ROLE_MAP, result as any, CrudOperation.DELETED) - + }, }); @@ -45,3 +53,4 @@ export function useRoleMap() { addRoleForObjects }; } + diff --git a/packages/client/src/api/hooks/useStaff.ts b/packages/client/src/api/hooks/useStaff.ts index fbdf571..c79977b 100755 --- a/packages/client/src/api/hooks/useStaff.ts +++ b/packages/client/src/api/hooks/useStaff.ts @@ -1,43 +1,29 @@ -import { getQueryKey } from "@trpc/react-query"; import { api } from "../trpc"; // Adjust path as necessary import { useQueryClient } from "@tanstack/react-query"; import { ObjectType, Staff } from "@nice/common"; import { findQueryData } from "../utils"; import { CrudOperation, emitDataChange } from "../../event"; +import { useEntity } from "./useEntity"; export function useStaff() { - const queryClient = useQueryClient(); - const queryKey = getQueryKey(api.staff); - - const create = api.staff.create.useMutation({ - onSuccess: (result) => { - queryClient.invalidateQueries({ queryKey }); - emitDataChange(ObjectType.STAFF, result as any, CrudOperation.CREATED) - }, - }); - const updateUserDomain = api.staff.updateUserDomain.useMutation({ - onSuccess: async (result) => { - queryClient.invalidateQueries({ queryKey }); - }, - }); - const update = api.staff.update.useMutation({ - onSuccess: (result) => { - queryClient.invalidateQueries({ queryKey }); - emitDataChange(ObjectType.STAFF, result as any, CrudOperation.UPDATED) - }, - }); - const softDeleteByIds = api.staff.softDeleteByIds.useMutation({ - onSuccess: (result, variables) => { - queryClient.invalidateQueries({ queryKey }); - }, - }); + const queryClient = useQueryClient() const getStaff = (key: string) => { return findQueryData(queryClient, api.staff, key); }; + return { - create, - update, - softDeleteByIds, - getStaff, - updateUserDomain - }; + ...useEntity("staff", { + create: { + onSuccess(result) { + emitDataChange(ObjectType.STAFF, result, CrudOperation.CREATED) + } + }, + update: { + onSuccess(result) { + emitDataChange(ObjectType.STAFF, result, CrudOperation.UPDATED) + }, + } + }), + getStaff + } + } diff --git a/packages/client/src/api/trpc.ts b/packages/client/src/api/trpc.ts index f414e9b..41e3ce9 100755 --- a/packages/client/src/api/trpc.ts +++ b/packages/client/src/api/trpc.ts @@ -1,4 +1,12 @@ -import { createTRPCReact } from '@trpc/react-query'; -import type { AppRouter } from '@server/trpc/trpc.router'; +import type { AppRouter } from '@server/trpc/types'; +import { + createTRPCReact, + type inferReactQueryProcedureOptions, +} from '@trpc/react-query'; +import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server'; +export type ReactQueryOptions = inferReactQueryProcedureOptions; +export type RouterInputs = inferRouterInputs; +export type RouterOutputs = inferRouterOutputs; + export const api = createTRPCReact(); diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 3379233..3071ad9 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -1,13 +1,13 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "esnext", - "module": "esnext", + "target": "es2022", + "module": "es2022", "allowJs": true, "esModuleInterop": true, "lib": [ "dom", - "esnext" + "es2022" ], "jsx": "react-jsx", "declaration": true, @@ -16,7 +16,9 @@ "outDir": "dist", "moduleResolution": "node", "incremental": true, - "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + "strict": true, + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", + "useDefineForClassFields": false }, "include": [ "src" diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index ded419e..4a3a901 100644 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -25,15 +25,15 @@ model Taxonomy { } model Term { - id String @id @default(cuid()) + id String @id @default(cuid()) name String - posts Post[] - taxonomy Taxonomy? @relation(fields: [taxonomyId], references: [id]) - taxonomyId String? @map("taxonomy_id") - order Float? @map("order") + posts Post[] + taxonomy Taxonomy? @relation(fields: [taxonomyId], references: [id]) + taxonomyId String? @map("taxonomy_id") + order Float? @map("order") description String? - parentId String? @map("parent_id") - + parentId String? @map("parent_id") + parent Term? @relation("ChildParent", fields: [parentId], references: [id], onDelete: Cascade) children Term[] @relation("ChildParent") ancestors TermAncestry[] @relation("DescendantToAncestor") @@ -96,6 +96,7 @@ model Staff { receivedMsgs Message[] @relation("message_receiver") registerToken String? ownedResources Resource[] + meta Json? @@index([officerId]) @@index([deptId]) @@ -185,15 +186,15 @@ model AppConfig { model Post { // 字符串类型字段 - id String @id @default(cuid()) // 帖子唯一标识,使用 cuid() 生成默认值 - type String? // 帖子类型,可为空 - state String? // 状态 : 未读、处理中、已回答 - title String? // 帖子标题,可为空 - content String? // 帖子内容,可为空 - + id String @id @default(cuid()) // 帖子唯一标识,使用 cuid() 生成默认值 + type String? // 帖子类型,可为空 + state String? // 状态 : 未读、处理中、已回答 + title String? // 帖子标题,可为空 + content String? // 帖子内容,可为空 + domainId String? @map("domain_id") - term Term? @relation(fields: [termId], references: [id]) - termId String? @map("term_id") + term Term? @relation(fields: [termId], references: [id]) + termId String? @map("term_id") // 日期时间类型字段 createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @map("updated_at") @@ -202,16 +203,16 @@ model Post { authorId String? @map("author_id") author Staff? @relation("post_author", fields: [authorId], references: [id]) // 帖子作者,关联 Staff 模型 visits Visit[] // 访问记录,关联 Visit 模型 - views Int @default(0) - likes Int @default(0) + views Int @default(0) + likes Int @default(0) - receivers Staff[] @relation("post_receiver") - parentId String? @map("parent_id") - parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型 - children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型 + receivers Staff[] @relation("post_receiver") + parentId String? @map("parent_id") + parent Post? @relation("PostChildren", fields: [parentId], references: [id]) // 父级帖子,关联 Post 模型 + children Post[] @relation("PostChildren") // 子级帖子列表,关联 Post 模型 resources Resource[] // 附件列表 isPublic Boolean? @default(true) @map("is_public") - meta Json? // 签名 和 IP 和 tags + meta Json? // 签名 和 IP 和 tags // 复合索引 @@index([type, domainId]) // 类型和域组合查询 @@ -247,8 +248,8 @@ model Visit { views Int @default(1) @map("views") // sourceIP String? @map("source_ip") // 关联关系 - visitorId String? @map("visitor_id") - visitor Staff? @relation(fields: [visitorId], references: [id]) + visitorId String? @map("visitor_id") + visitor Staff? @relation(fields: [visitorId], references: [id]) postId String? @map("post_id") post Post? @relation(fields: [postId], references: [id]) message Message? @relation(fields: [messageId], references: [id]) diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 0c35b89..b1d8fa8 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -38,6 +38,12 @@ export type AppLocalSettings = { export type StaffDto = Staff & { domain?: Department; department?: Department; + meta?: { + photoUrl?: string + office?: string + email?: string + rank?: string + } }; export interface AuthDto { token: string;