add
This commit is contained in:
parent
1266d076b1
commit
ed4b328047
|
@ -4,44 +4,48 @@ import { AppConfigService } from './app-config.service';
|
||||||
import { z, ZodType } from 'zod';
|
import { z, ZodType } from 'zod';
|
||||||
import { Prisma } from '@nice/common';
|
import { Prisma } from '@nice/common';
|
||||||
import { RealtimeServer } from '@server/socket/realtime/realtime.server';
|
import { RealtimeServer } from '@server/socket/realtime/realtime.server';
|
||||||
const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> = z.any()
|
const AppConfigUncheckedCreateInputSchema: ZodType<Prisma.AppConfigUncheckedCreateInput> =
|
||||||
const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any()
|
z.any();
|
||||||
const AppConfigDeleteManyArgsSchema: ZodType<Prisma.AppConfigDeleteManyArgs> = z.any()
|
const AppConfigUpdateArgsSchema: ZodType<Prisma.AppConfigUpdateArgs> = z.any();
|
||||||
const AppConfigFindFirstArgsSchema: ZodType<Prisma.AppConfigFindFirstArgs> = z.any()
|
const AppConfigDeleteManyArgsSchema: ZodType<Prisma.AppConfigDeleteManyArgs> =
|
||||||
|
z.any();
|
||||||
|
const AppConfigFindFirstArgsSchema: ZodType<Prisma.AppConfigFindFirstArgs> =
|
||||||
|
z.any();
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppConfigRouter {
|
export class AppConfigRouter {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly trpc: TrpcService,
|
private readonly trpc: TrpcService,
|
||||||
private readonly appConfigService: AppConfigService,
|
private readonly appConfigService: AppConfigService,
|
||||||
private readonly realtimeServer: RealtimeServer
|
private readonly realtimeServer: RealtimeServer,
|
||||||
) { }
|
) {}
|
||||||
router = this.trpc.router({
|
router = this.trpc.router({
|
||||||
create: this.trpc.protectProcedure
|
create: this.trpc.protectProcedure
|
||||||
.input(AppConfigUncheckedCreateInputSchema)
|
.input(AppConfigUncheckedCreateInputSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
const { staff } = ctx;
|
const { staff } = ctx;
|
||||||
return await this.appConfigService.create({ data: input });
|
return await this.appConfigService.create({ data: input });
|
||||||
}),
|
}),
|
||||||
update: this.trpc.protectProcedure
|
update: this.trpc.protectProcedure
|
||||||
.input(AppConfigUpdateArgsSchema)
|
.input(AppConfigUpdateArgsSchema)
|
||||||
.mutation(async ({ ctx, input }) => {
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const { staff } = ctx;
|
||||||
const { staff } = ctx;
|
return await this.appConfigService.update(input);
|
||||||
return await this.appConfigService.update(input);
|
}),
|
||||||
}),
|
deleteMany: this.trpc.protectProcedure
|
||||||
deleteMany: this.trpc.protectProcedure.input(AppConfigDeleteManyArgsSchema).mutation(async ({ input }) => {
|
.input(AppConfigDeleteManyArgsSchema)
|
||||||
return await this.appConfigService.deleteMany(input)
|
.mutation(async ({ input }) => {
|
||||||
}),
|
return await this.appConfigService.deleteMany(input);
|
||||||
findFirst: this.trpc.protectProcedure.input(AppConfigFindFirstArgsSchema).
|
}),
|
||||||
query(async ({ input }) => {
|
findFirst: this.trpc.protectProcedure
|
||||||
|
.input(AppConfigFindFirstArgsSchema)
|
||||||
return await this.appConfigService.findFirst(input)
|
.query(async ({ input }) => {
|
||||||
}),
|
return await this.appConfigService.findFirst(input);
|
||||||
clearRowCache: this.trpc.protectProcedure.mutation(async () => {
|
}),
|
||||||
return await this.appConfigService.clearRowCache()
|
clearRowCache: this.trpc.protectProcedure.mutation(async () => {
|
||||||
}),
|
return await this.appConfigService.clearRowCache();
|
||||||
getClientCount: this.trpc.protectProcedure.query(() => {
|
}),
|
||||||
return this.realtimeServer.getClientCount()
|
getClientCount: this.trpc.protectProcedure.query(() => {
|
||||||
})
|
return this.realtimeServer.getClientCount();
|
||||||
});
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import { db, ObjectType, Prisma } from '@nice/common';
|
||||||
db,
|
|
||||||
ObjectType,
|
|
||||||
Prisma,
|
|
||||||
} from '@nice/common';
|
|
||||||
|
|
||||||
|
|
||||||
import { BaseService } from '../base/base.service';
|
import { BaseService } from '../base/base.service';
|
||||||
import { deleteByPattern } from '@server/utils/redis/utils';
|
import { deleteByPattern } from '@server/utils/redis/utils';
|
||||||
|
@ -12,10 +7,10 @@ import { deleteByPattern } from '@server/utils/redis/utils';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppConfigService extends BaseService<Prisma.AppConfigDelegate> {
|
export class AppConfigService extends BaseService<Prisma.AppConfigDelegate> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(db, "appConfig");
|
super(db, 'appConfig');
|
||||||
}
|
}
|
||||||
async clearRowCache() {
|
async clearRowCache() {
|
||||||
await deleteByPattern("row-*")
|
await deleteByPattern('row-*');
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,6 +187,13 @@ export class VisitService extends BaseService<Prisma.VisitDelegate> {
|
||||||
visitType: VisitType.LIKE,
|
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;
|
return superDetele;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { db, PostState, PostType, VisitType } from '@nice/common';
|
import { db, PostState, PostType, VisitType } from '@nice/common';
|
||||||
export async function updatePostViewCount(id: string, type: VisitType) {
|
export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
|
console.log('updatePostViewCount', type);
|
||||||
const totalViews = await db.visit.aggregate({
|
const totalViews = await db.visit.aggregate({
|
||||||
_sum: {
|
_sum: {
|
||||||
views: true,
|
views: true,
|
||||||
|
@ -19,7 +20,6 @@ export async function updatePostViewCount(id: string, type: VisitType) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (type === VisitType.LIKE) {
|
} else if (type === VisitType.LIKE) {
|
||||||
console.log('totalViews._sum.view', totalViews._sum.views);
|
|
||||||
await db.post.update({
|
await db.post.update({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
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
|
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
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { updatePostViewCount } from '../models/post/utils';
|
||||||
const logger = new Logger('QueueWorker');
|
const logger = new Logger('QueueWorker');
|
||||||
export default async function processJob(job: Job<any, any, QueueJobType>) {
|
export default async function processJob(job: Job<any, any, QueueJobType>) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) {
|
if (job.name === QueueJobType.UPDATE_POST_VISIT_COUNT) {
|
||||||
await updatePostViewCount(job.data.id, job.data.type);
|
await updatePostViewCount(job.data.id, job.data.type);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,7 @@ import {
|
||||||
Term,
|
Term,
|
||||||
} from '@nice/common';
|
} from '@nice/common';
|
||||||
import EventBus from '@server/utils/event-bus';
|
import EventBus from '@server/utils/event-bus';
|
||||||
import {
|
import { capitalizeFirstLetter, DevDataCounts, getCounts } from './utils';
|
||||||
|
|
||||||
capitalizeFirstLetter,
|
|
||||||
DevDataCounts,
|
|
||||||
getCounts,
|
|
||||||
} from './utils';
|
|
||||||
import { StaffService } from '@server/models/staff/staff.service';
|
import { StaffService } from '@server/models/staff/staff.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GenDevService {
|
export class GenDevService {
|
||||||
|
@ -26,7 +21,7 @@ export class GenDevService {
|
||||||
deptStaffRecord: Record<string, Staff[]> = {};
|
deptStaffRecord: Record<string, Staff[]> = {};
|
||||||
terms: Record<TaxonomySlug, Term[]> = {
|
terms: Record<TaxonomySlug, Term[]> = {
|
||||||
[TaxonomySlug.CATEGORY]: [],
|
[TaxonomySlug.CATEGORY]: [],
|
||||||
[TaxonomySlug.TAG]: []
|
[TaxonomySlug.TAG]: [],
|
||||||
};
|
};
|
||||||
depts: Department[] = [];
|
depts: Department[] = [];
|
||||||
domains: Department[] = [];
|
domains: Department[] = [];
|
||||||
|
@ -39,7 +34,7 @@ export class GenDevService {
|
||||||
private readonly departmentService: DepartmentService,
|
private readonly departmentService: DepartmentService,
|
||||||
private readonly staffService: StaffService,
|
private readonly staffService: StaffService,
|
||||||
private readonly termService: TermService,
|
private readonly termService: TermService,
|
||||||
) { }
|
) {}
|
||||||
async genDataEvent() {
|
async genDataEvent() {
|
||||||
EventBus.emit('genDataEvent', { type: 'start' });
|
EventBus.emit('genDataEvent', { type: 'start' });
|
||||||
try {
|
try {
|
||||||
|
@ -47,7 +42,6 @@ export class GenDevService {
|
||||||
await this.generateDepartments(3, 6);
|
await this.generateDepartments(3, 6);
|
||||||
await this.generateTerms(1, 3);
|
await this.generateTerms(1, 3);
|
||||||
await this.generateStaffs(4);
|
await this.generateStaffs(4);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
}
|
}
|
||||||
|
@ -164,8 +158,8 @@ export class GenDevService {
|
||||||
showname: username,
|
showname: username,
|
||||||
username: username,
|
username: username,
|
||||||
deptId: dept.id,
|
deptId: dept.id,
|
||||||
domainId: domain.id
|
domainId: domain.id,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
// Update both deptStaffRecord and staffs array
|
// Update both deptStaffRecord and staffs array
|
||||||
this.deptStaffRecord[dept.id].push(staff);
|
this.deptStaffRecord[dept.id].push(staff);
|
||||||
|
@ -190,7 +184,7 @@ export class GenDevService {
|
||||||
name,
|
name,
|
||||||
isDomain: currentDepth === 1 ? true : false,
|
isDomain: currentDepth === 1 ? true : false,
|
||||||
parentId,
|
parentId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
return department;
|
return department;
|
||||||
}
|
}
|
||||||
|
@ -208,7 +202,9 @@ export class GenDevService {
|
||||||
throw new Error(`Taxonomy with slug ${taxonomySlug} not found`);
|
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;
|
let counter = 1;
|
||||||
const createTermTree = async (
|
const createTermTree = async (
|
||||||
parentId: string | null,
|
parentId: string | null,
|
||||||
|
@ -223,7 +219,7 @@ export class GenDevService {
|
||||||
taxonomyId: taxonomy!.id,
|
taxonomyId: taxonomy!.id,
|
||||||
domainId: domain?.id,
|
domainId: domain?.id,
|
||||||
parentId,
|
parentId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
this.terms[taxonomySlug].push(newTerm);
|
this.terms[taxonomySlug].push(newTerm);
|
||||||
await createTermTree(newTerm.id, currentDepth + 1);
|
await createTermTree(newTerm.id, currentDepth + 1);
|
||||||
|
|
|
@ -1,32 +1,25 @@
|
||||||
import {
|
import { AppConfigSlug, BaseSetting, RolePerms } from "@nice/common";
|
||||||
AppConfigSlug,
|
|
||||||
BaseSetting,
|
|
||||||
RolePerms,
|
|
||||||
} from "@nice/common";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import {
|
import { Button, Form, Input, message, theme } from "antd";
|
||||||
Button,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
message,
|
|
||||||
theme,
|
|
||||||
} from "antd";
|
|
||||||
import { useAppConfig } from "@nice/client";
|
import { useAppConfig } from "@nice/client";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { useForm } from "antd/es/form/Form";
|
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 AdminHeader from "@web/src/components/layout/admin/AdminHeader";
|
||||||
|
import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
|
||||||
|
|
||||||
export default function BaseSettingPage() {
|
export default function BaseSettingPage() {
|
||||||
const { update, baseSetting } = useAppConfig();
|
const { update, baseSetting } = useAppConfig();
|
||||||
const utils = api.useUtils()
|
const utils = api.useUtils();
|
||||||
const [form] = useForm()
|
const [form] = useForm();
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const { data: clientCount } = api.app_config.getClientCount.useQuery(undefined, {
|
const { data: clientCount } = api.app_config.getClientCount.useQuery(
|
||||||
refetchInterval: 3000,
|
undefined,
|
||||||
refetchIntervalInBackground: true
|
{
|
||||||
})
|
refetchInterval: 3000,
|
||||||
|
refetchIntervalInBackground: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
const [isFormChanged, setIsFormChanged] = useState(false);
|
const [isFormChanged, setIsFormChanged] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { user, hasSomePermissions } = useAuth();
|
const { user, hasSomePermissions } = useAuth();
|
||||||
|
@ -34,31 +27,27 @@ export default function BaseSettingPage() {
|
||||||
setIsFormChanged(true);
|
setIsFormChanged(true);
|
||||||
}
|
}
|
||||||
function onResetClick() {
|
function onResetClick() {
|
||||||
if (!form)
|
if (!form) return;
|
||||||
return
|
|
||||||
if (!baseSetting) {
|
if (!baseSetting) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
} else {
|
} else {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldsValue(baseSetting);
|
form.setFieldsValue(baseSetting);
|
||||||
|
|
||||||
}
|
}
|
||||||
setIsFormChanged(false);
|
setIsFormChanged(false);
|
||||||
}
|
}
|
||||||
function onSaveClick() {
|
function onSaveClick() {
|
||||||
if (form)
|
if (form) form.submit();
|
||||||
form.submit();
|
|
||||||
}
|
}
|
||||||
async function onSubmit(values: BaseSetting) {
|
async function onSubmit(values: BaseSetting) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await update.mutateAsync({
|
await update.mutateAsync({
|
||||||
where: {
|
where: {
|
||||||
slug: AppConfigSlug.BASE_SETTING,
|
slug: AppConfigSlug.BASE_SETTING,
|
||||||
},
|
},
|
||||||
data: { meta: JSON.stringify(values) }
|
data: { meta: JSON.stringify(values) },
|
||||||
});
|
});
|
||||||
setIsFormChanged(false);
|
setIsFormChanged(false);
|
||||||
message.success("已保存");
|
message.success("已保存");
|
||||||
|
@ -70,12 +59,11 @@ export default function BaseSettingPage() {
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (baseSetting && form) {
|
if (baseSetting && form) {
|
||||||
|
|
||||||
form.setFieldsValue(baseSetting);
|
form.setFieldsValue(baseSetting);
|
||||||
}
|
}
|
||||||
}, [baseSetting, form]);
|
}, [baseSetting, form]);
|
||||||
return (
|
return (
|
||||||
<div >
|
<div>
|
||||||
<AdminHeader>
|
<AdminHeader>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{isFormChanged &&
|
{isFormChanged &&
|
||||||
|
@ -101,7 +89,6 @@ export default function BaseSettingPage() {
|
||||||
!hasSomePermissions(RolePerms.MANAGE_BASE_SETTING)
|
!hasSomePermissions(RolePerms.MANAGE_BASE_SETTING)
|
||||||
}
|
}
|
||||||
onFinish={onSubmit}
|
onFinish={onSubmit}
|
||||||
|
|
||||||
onFieldsChange={handleFieldsChange}
|
onFieldsChange={handleFieldsChange}
|
||||||
layout="vertical">
|
layout="vertical">
|
||||||
{/* <div
|
{/* <div
|
||||||
|
@ -127,6 +114,17 @@ export default function BaseSettingPage() {
|
||||||
<Input></Input>
|
<Input></Input>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="p-2 grid grid-cols-8 gap-2 border-b">
|
||||||
|
<Form.Item
|
||||||
|
label="网站logo"
|
||||||
|
name={["appConfig", "logo"]}>
|
||||||
|
<AvatarUploader
|
||||||
|
style={{
|
||||||
|
width: 192,
|
||||||
|
height: 108,
|
||||||
|
}}></AvatarUploader>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
{/* <div
|
{/* <div
|
||||||
className="p-2 border-b flex items-center justify-between"
|
className="p-2 border-b flex items-center justify-between"
|
||||||
style={{
|
style={{
|
||||||
|
@ -171,17 +169,21 @@ export default function BaseSettingPage() {
|
||||||
清除行模型缓存
|
清除行模型缓存
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{<div
|
{
|
||||||
className="p-2 border-b text-primary flex justify-between items-center"
|
<div
|
||||||
style={{
|
className="p-2 border-b text-primary flex justify-between items-center"
|
||||||
fontSize: token.fontSize,
|
style={{
|
||||||
fontWeight: "bold",
|
fontSize: token.fontSize,
|
||||||
}}>
|
fontWeight: "bold",
|
||||||
<span>app在线人数</span>
|
}}>
|
||||||
<div>
|
<span>app在线人数</span>
|
||||||
{clientCount && clientCount > 0 ? `${clientCount}人在线` : '无人在线'}
|
<div>
|
||||||
|
{clientCount && clientCount > 0
|
||||||
|
? `${clientCount}人在线`
|
||||||
|
: "无人在线"}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -156,7 +156,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
||||||
message: "请输入有效的证件号(5-12位数字)",
|
message: "请输入有效的证件号(5-12位数字)",
|
||||||
},
|
},
|
||||||
]}>
|
]}>
|
||||||
<Input placeholder="证件号(可选)" />
|
<Input placeholder="证件号" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item noStyle name={"email"}>
|
<Form.Item noStyle name={"email"}>
|
||||||
<Input
|
<Input
|
||||||
|
|
|
@ -16,6 +16,7 @@ interface UploadingFile {
|
||||||
progress: number;
|
progress: number;
|
||||||
status: "uploading" | "done" | "error";
|
status: "uploading" | "done" | "error";
|
||||||
fileId?: string;
|
fileId?: string;
|
||||||
|
url?: string;
|
||||||
fileKey?: string;
|
fileKey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { handleFileUpload, uploadProgress } = useTusUpload();
|
const { handleFileUpload, uploadProgress } = useTusUpload();
|
||||||
const [file, setFile] = useState<UploadingFile | null>(null);
|
const [file, setFile] = useState<UploadingFile | null>(null);
|
||||||
|
|
||||||
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
|
const [previewUrl, setPreviewUrl] = useState<string>(value || "");
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
@ -56,7 +58,9 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
progress: 100,
|
progress: 100,
|
||||||
status: "done",
|
status: "done",
|
||||||
fileId: result.fileId,
|
fileId: result.fileId,
|
||||||
|
url: result?.url,
|
||||||
}));
|
}));
|
||||||
|
setPreviewUrl(result?.url);
|
||||||
resolve(result.fileId);
|
resolve(result.fileId);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
@ -65,7 +69,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
file?.fileKey
|
file?.fileKey
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
setPreviewUrl(`${env.SERVER_IP}/uploads/${fileId}`);
|
setPreviewUrl(`http://${env.SERVER_IP}/uploads/${fileId}`);
|
||||||
onChange?.(fileId);
|
onChange?.(fileId);
|
||||||
message.success("头像上传成功");
|
message.success("头像上传成功");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -90,6 +94,7 @@ const AvatarUploader: React.FC<AvatarUploaderProps> = ({
|
||||||
background: token.colorBgContainer,
|
background: token.colorBgContainer,
|
||||||
...style, // 应用外部传入的样式
|
...style, // 应用外部传入的样式
|
||||||
}}>
|
}}>
|
||||||
|
<div>{previewUrl}</div>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { PostDto, PostStateLabels } from "@nice/common";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import PostLikeButton from "./detail/PostHeader/PostLikeButton";
|
import PostLikeButton from "./detail/PostHeader/PostLikeButton";
|
||||||
import { LetterBadge } from "./LetterBadge";
|
import { LetterBadge } from "./LetterBadge";
|
||||||
|
import PostHateButton from "./detail/PostHeader/PostHateButton";
|
||||||
const { Title, Paragraph, Text } = Typography;
|
const { Title, Paragraph, Text } = Typography;
|
||||||
|
|
||||||
interface LetterCardProps {
|
interface LetterCardProps {
|
||||||
|
@ -117,6 +118,7 @@ export function LetterCard({ letter }: LetterCardProps) {
|
||||||
{letter.views}
|
{letter.views}
|
||||||
</Button>
|
</Button>
|
||||||
<PostLikeButton post={letter as any}></PostLikeButton>
|
<PostLikeButton post={letter as any}></PostLikeButton>
|
||||||
|
<PostHateButton post={letter as any}></PostHateButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,10 +6,16 @@ import { Avatar } from "antd";
|
||||||
import { useVisitor } from "@nice/client";
|
import { useVisitor } from "@nice/client";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { PostDetailContext } from "./context/PostDetailContext";
|
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 PostLikeButton from "./PostHeader/PostLikeButton";
|
||||||
import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar";
|
import { CustomAvatar } from "@web/src/components/presentation/CustomAvatar";
|
||||||
import PostResources from "./PostResources";
|
import PostResources from "./PostResources";
|
||||||
|
import PostHateButton from "./PostHeader/PostHateButton";
|
||||||
|
|
||||||
export default function PostCommentCard({
|
export default function PostCommentCard({
|
||||||
post,
|
post,
|
||||||
|
@ -57,6 +63,8 @@ export default function PostCommentCard({
|
||||||
<span className=" text-sm text-slate-500">{`#${index + 1}`}</span>
|
<span className=" text-sm text-slate-500">{`#${index + 1}`}</span>
|
||||||
<PostLikeButton
|
<PostLikeButton
|
||||||
post={post}></PostLikeButton>
|
post={post}></PostLikeButton>
|
||||||
|
<PostHateButton
|
||||||
|
post={post}></PostHateButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 (
|
||||||
|
<Button
|
||||||
|
title={post?.hated ? "取消点踩" : "点踩"}
|
||||||
|
type={post?.hated ? "primary" : "default"}
|
||||||
|
style={{
|
||||||
|
backgroundColor: post?.hated ? "#ff4d4f" : "#fff",
|
||||||
|
borderColor: "#ff4d4f",
|
||||||
|
color: post?.hated ? "#fff" : "#ff4d4f",
|
||||||
|
}}
|
||||||
|
shape="round"
|
||||||
|
icon={post?.hated ? <DislikeFilled /> : <DislikeOutlined />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
hateThisPost();
|
||||||
|
}}>
|
||||||
|
<span className="mr-1">不满意</span>
|
||||||
|
{post?.hates || 0}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import { Button, Tooltip } from "antd/lib";
|
||||||
import { PostDetailContext } from "../context/PostDetailContext";
|
import { PostDetailContext } from "../context/PostDetailContext";
|
||||||
import PostLikeButton from "./PostLikeButton";
|
import PostLikeButton from "./PostLikeButton";
|
||||||
import PostResources from "../PostResources";
|
import PostResources from "../PostResources";
|
||||||
|
import PostHateButton from "./PostHateButton";
|
||||||
export function StatsSection() {
|
export function StatsSection() {
|
||||||
const { post } = useContext(PostDetailContext);
|
const { post } = useContext(PostDetailContext);
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ export function StatsSection() {
|
||||||
<span className="mr-1">回复数</span>{post?.commentsCount}
|
<span className="mr-1">回复数</span>{post?.commentsCount}
|
||||||
</Button>
|
</Button>
|
||||||
<PostLikeButton post={post}></PostLikeButton>
|
<PostLikeButton post={post}></PostLikeButton>
|
||||||
|
<PostHateButton post={post}></PostHateButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,15 +3,15 @@ import { AppConfigSlug, BaseSetting } from "@nice/common";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
export function useAppConfig() {
|
export function useAppConfig() {
|
||||||
const utils = api.useUtils()
|
const utils = api.useUtils();
|
||||||
const [baseSetting, setBaseSetting] = useState<BaseSetting | undefined>();
|
const [baseSetting, setBaseSetting] = useState<BaseSetting | undefined>();
|
||||||
|
|
||||||
const { data, isLoading }: { data: any; isLoading: boolean } =
|
const { data, isLoading }: { data: any; isLoading: boolean } =
|
||||||
api.app_config.findFirst.useQuery({
|
api.app_config.findFirst.useQuery({
|
||||||
where: { slug: AppConfigSlug.BASE_SETTING }
|
where: { slug: AppConfigSlug.BASE_SETTING },
|
||||||
});
|
});
|
||||||
const handleMutationSuccess = useCallback(() => {
|
const handleMutationSuccess = useCallback(() => {
|
||||||
utils.app_config.invalidate()
|
utils.app_config.invalidate();
|
||||||
}, [utils]);
|
}, [utils]);
|
||||||
|
|
||||||
// Use the generic success handler in mutations
|
// Use the generic success handler in mutations
|
||||||
|
@ -28,7 +28,6 @@ export function useAppConfig() {
|
||||||
if (data?.meta) {
|
if (data?.meta) {
|
||||||
setBaseSetting(JSON.parse(data?.meta));
|
setBaseSetting(JSON.parse(data?.meta));
|
||||||
}
|
}
|
||||||
|
|
||||||
}, [data, isLoading]);
|
}, [data, isLoading]);
|
||||||
const splashScreen = useMemo(() => {
|
const splashScreen = useMemo(() => {
|
||||||
return baseSetting?.appConfig?.splashScreen;
|
return baseSetting?.appConfig?.splashScreen;
|
||||||
|
@ -36,8 +35,10 @@ export function useAppConfig() {
|
||||||
const devDept = useMemo(() => {
|
const devDept = useMemo(() => {
|
||||||
return baseSetting?.appConfig?.devDept;
|
return baseSetting?.appConfig?.devDept;
|
||||||
}, [baseSetting]);
|
}, [baseSetting]);
|
||||||
|
const logo = useMemo(() => {
|
||||||
|
return baseSetting?.appConfig?.logo;
|
||||||
|
}, [baseSetting]);
|
||||||
return {
|
return {
|
||||||
|
|
||||||
create,
|
create,
|
||||||
deleteMany,
|
deleteMany,
|
||||||
update,
|
update,
|
||||||
|
@ -45,5 +46,6 @@ export function useAppConfig() {
|
||||||
splashScreen,
|
splashScreen,
|
||||||
devDept,
|
devDept,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
logo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,5 +171,7 @@ export function useVisitor() {
|
||||||
deleteStar,
|
deleteStar,
|
||||||
like,
|
like,
|
||||||
unLike,
|
unLike,
|
||||||
|
hate,
|
||||||
|
unHate,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const postDetailSelect: Prisma.PostSelect = {
|
||||||
content: true,
|
content: true,
|
||||||
views: true,
|
views: true,
|
||||||
likes: true,
|
likes: true,
|
||||||
|
hates: true,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
resources: true,
|
resources: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
|
|
|
@ -39,11 +39,11 @@ export type StaffDto = Staff & {
|
||||||
domain?: Department;
|
domain?: Department;
|
||||||
department?: Department;
|
department?: Department;
|
||||||
meta?: {
|
meta?: {
|
||||||
photoUrl?: string
|
photoUrl?: string;
|
||||||
office?: string
|
office?: string;
|
||||||
email?: string
|
email?: string;
|
||||||
rank?: string
|
rank?: string;
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
export interface AuthDto {
|
export interface AuthDto {
|
||||||
token: string;
|
token: string;
|
||||||
|
@ -133,6 +133,7 @@ export type PostComment = {
|
||||||
export type PostDto = Post & {
|
export type PostDto = Post & {
|
||||||
readed: boolean;
|
readed: boolean;
|
||||||
liked: boolean;
|
liked: boolean;
|
||||||
|
hated: boolean;
|
||||||
readedCount: number;
|
readedCount: number;
|
||||||
commentsCount: number;
|
commentsCount: number;
|
||||||
terms: TermDto[];
|
terms: TermDto[];
|
||||||
|
@ -167,6 +168,7 @@ export interface BaseSetting {
|
||||||
appConfig?: {
|
appConfig?: {
|
||||||
splashScreen?: string;
|
splashScreen?: string;
|
||||||
devDept?: string;
|
devDept?: string;
|
||||||
|
logo?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export interface PostMeta {
|
export interface PostMeta {
|
||||||
|
|
Loading…
Reference in New Issue