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/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/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..eb2791a 100644 --- a/apps/web/src/app/auth/register.tsx +++ b/apps/web/src/app/auth/register.tsx @@ -156,7 +156,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => { message: "请输入有效的证件号(5-12位数字)", }, ]}> - + = ({ }) => { 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 +58,9 @@ const AvatarUploader: React.FC = ({ progress: 100, status: "done", fileId: result.fileId, + url: result?.url, })); + setPreviewUrl(result?.url); resolve(result.fileId); }, (error) => { @@ -65,7 +69,7 @@ const AvatarUploader: React.FC = ({ file?.fileKey ); }); - setPreviewUrl(`${env.SERVER_IP}/uploads/${fileId}`); + setPreviewUrl(`http://${env.SERVER_IP}/uploads/${fileId}`); onChange?.(fileId); message.success("头像上传成功"); } catch (error) { @@ -90,6 +94,7 @@ const AvatarUploader: React.FC = ({ background: token.colorBgContainer, ...style, // 应用外部传入的样式 }}> +
{previewUrl}
+
diff --git a/apps/web/src/components/models/post/detail/PostCommentCard.tsx b/apps/web/src/components/models/post/detail/PostCommentCard.tsx index 3af549e..4442c08 100644 --- a/apps/web/src/components/models/post/detail/PostCommentCard.tsx +++ b/apps/web/src/components/models/post/detail/PostCommentCard.tsx @@ -6,10 +6,16 @@ import { Avatar } from "antd"; import { useVisitor } from "@nice/client"; import { useContext, useEffect, useRef, useState } from "react"; import { PostDetailContext } from "./context/PostDetailContext"; -import { CheckCircleOutlined, CheckOutlined, LikeFilled, LikeOutlined } from "@ant-design/icons"; +import { + CheckCircleOutlined, + CheckOutlined, + LikeFilled, + LikeOutlined, +} from "@ant-design/icons"; import PostLikeButton from "./PostHeader/PostLikeButton"; import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar"; import PostResources from "./PostResources"; +import PostHateButton from "./PostHeader/PostHateButton"; export default function PostCommentCard({ post, @@ -57,6 +63,8 @@ export default function PostCommentCard({ {`#${index + 1}`} + diff --git a/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx b/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx new file mode 100644 index 0000000..3d5cb18 --- /dev/null +++ b/apps/web/src/components/models/post/detail/PostHeader/PostHateButton.tsx @@ -0,0 +1,52 @@ +import { PostDto, VisitType } from "@nice/common"; +import { useVisitor } from "@nice/client"; +import { Button, Tooltip } from "antd"; +import { DislikeFilled, DislikeOutlined } from "@ant-design/icons"; +import { useAuth } from "@web/src/providers/auth-provider"; + +export default function PostHateButton({ post }: { post: PostDto }) { + const { user } = useAuth(); + const { hate, unHate } = useVisitor(); + function hateThisPost() { + if (!post?.hated) { + post.hates += 1; + post.hated = true; + hate.mutateAsync({ + data: { + visitorId: user?.id || null, + postId: post.id, + type: VisitType.HATE, + }, + }); + } else { + post.hates -= 1; + post.hated = false; + unHate.mutateAsync({ + where: { + visitorId: user?.id || null, + postId: post.id, + type: VisitType.HATE, + }, + }); + } + } + return ( + + ); +} diff --git a/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx b/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx index 64c7380..005da03 100644 --- a/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx +++ b/apps/web/src/components/models/post/detail/PostHeader/StatsSection.tsx @@ -10,6 +10,7 @@ import { Button, Tooltip } from "antd/lib"; import { PostDetailContext } from "../context/PostDetailContext"; import PostLikeButton from "./PostLikeButton"; import PostResources from "../PostResources"; +import PostHateButton from "./PostHateButton"; export function StatsSection() { const { post } = useContext(PostDetailContext); @@ -26,6 +27,7 @@ export function StatsSection() { 回复数{post?.commentsCount} + diff --git a/packages/client/src/api/hooks/useAppConfig.ts b/packages/client/src/api/hooks/useAppConfig.ts index 0b00ffd..3e468cb 100644 --- a/packages/client/src/api/hooks/useAppConfig.ts +++ b/packages/client/src/api/hooks/useAppConfig.ts @@ -3,15 +3,15 @@ import { AppConfigSlug, BaseSetting } from "@nice/common"; import { useCallback, useEffect, useMemo, useState } from "react"; export function useAppConfig() { - const utils = api.useUtils() + const utils = api.useUtils(); const [baseSetting, setBaseSetting] = useState(); const { data, isLoading }: { data: any; isLoading: boolean } = api.app_config.findFirst.useQuery({ - where: { slug: AppConfigSlug.BASE_SETTING } + where: { slug: AppConfigSlug.BASE_SETTING }, }); const handleMutationSuccess = useCallback(() => { - utils.app_config.invalidate() + utils.app_config.invalidate(); }, [utils]); // Use the generic success handler in mutations @@ -28,7 +28,6 @@ export function useAppConfig() { if (data?.meta) { setBaseSetting(JSON.parse(data?.meta)); } - }, [data, isLoading]); const splashScreen = useMemo(() => { return baseSetting?.appConfig?.splashScreen; @@ -36,8 +35,10 @@ export function useAppConfig() { const devDept = useMemo(() => { return baseSetting?.appConfig?.devDept; }, [baseSetting]); + const logo = useMemo(() => { + return baseSetting?.appConfig?.logo; + }, [baseSetting]); return { - create, deleteMany, update, @@ -45,5 +46,6 @@ export function useAppConfig() { splashScreen, devDept, isLoading, + logo, }; } diff --git a/packages/client/src/api/hooks/useVisitor.ts b/packages/client/src/api/hooks/useVisitor.ts index 37ce83e..86cdc58 100644 --- a/packages/client/src/api/hooks/useVisitor.ts +++ b/packages/client/src/api/hooks/useVisitor.ts @@ -171,5 +171,7 @@ export function useVisitor() { deleteStar, like, unLike, + hate, + unHate, }; } diff --git a/packages/common/src/select.ts b/packages/common/src/select.ts index 0e9e0c6..32271f6 100644 --- a/packages/common/src/select.ts +++ b/packages/common/src/select.ts @@ -8,6 +8,7 @@ export const postDetailSelect: Prisma.PostSelect = { content: true, views: true, likes: true, + hates: true, isPublic: true, resources: true, createdAt: true, diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index c1f8795..d754ae6 100755 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -39,11 +39,11 @@ export type StaffDto = Staff & { domain?: Department; department?: Department; meta?: { - photoUrl?: string - office?: string - email?: string - rank?: string - } + photoUrl?: string; + office?: string; + email?: string; + rank?: string; + }; }; export interface AuthDto { token: string; @@ -133,6 +133,7 @@ export type PostComment = { export type PostDto = Post & { readed: boolean; liked: boolean; + hated: boolean; readedCount: number; commentsCount: number; terms: TermDto[]; @@ -167,6 +168,7 @@ export interface BaseSetting { appConfig?: { splashScreen?: string; devDept?: string; + logo?: string; }; } export interface PostMeta {