From 6c26d26c2ab224f80026fc2d7631676da0b8ecf8 Mon Sep 17 00:00:00 2001 From: Rao <1227431568@qq.com> Date: Mon, 21 Apr 2025 22:52:10 +0800 Subject: [PATCH] rht --- .../models/share-code/share-code.module.ts | 14 - .../models/share-code/share-code.router.ts | 70 -- .../models/share-code/share-code.service.ts | 610 ------------------ apps/server/src/models/visit/visit.module.ts | 10 - apps/server/src/models/visit/visit.router.ts | 37 -- apps/server/src/models/visit/visit.service.ts | 152 ----- apps/server/src/trpc/trpc.module.ts | 4 - apps/server/src/trpc/trpc.router.ts | 6 - .../admin/code-manage/CodeManageContext.tsx | 116 ---- .../admin/code-manage/CodeManageLayout.tsx | 26 - .../code-manage/components/CodeManageEdit.tsx | 72 --- .../components/CodeManageSearchBase.tsx | 68 -- .../components/CodeMangeDisplay.tsx | 87 --- .../code-manage/components/ShareCodeList.tsx | 41 -- .../components/ShareCodeListCard.tsx | 79 --- .../app/admin/dashboard/DashboardContext.tsx | 77 --- .../app/admin/dashboard/DashboardLayout.tsx | 13 - .../admin/dashboard/components/Activate.tsx | 40 -- .../dashboard/components/ActivityItem.tsx | 34 - .../app/admin/dashboard/components/Board.tsx | 157 ----- .../admin/quick-file/components/header.tsx | 41 -- apps/web/src/app/admin/quick-file/manage.tsx | 86 --- apps/web/src/app/admin/quick-file/page.tsx | 55 -- .../app/admin/quick-file/quickFileContext.tsx | 127 ---- .../sharecode/ShareCodeCenerator.module.css | 68 -- .../sharecode/ShareCodeValidator.module.css | 12 - .../admin/sharecode/components/CodeRecord.tsx | 89 --- .../admin/sharecode/sharecodegenerator.tsx | 203 ------ .../admin/sharecode/sharecodevalidator.tsx | 102 --- apps/web/src/routes/index.tsx | 58 +- config/nginx/conf.d/web.conf | 4 +- packages/client/src/api/hooks/index.ts | 1 - packages/client/src/api/hooks/useVisitor.ts | 178 ----- 33 files changed, 4 insertions(+), 2733 deletions(-) delete mode 100644 apps/server/src/models/share-code/share-code.module.ts delete mode 100644 apps/server/src/models/share-code/share-code.router.ts delete mode 100755 apps/server/src/models/share-code/share-code.service.ts delete mode 100755 apps/server/src/models/visit/visit.module.ts delete mode 100755 apps/server/src/models/visit/visit.router.ts delete mode 100755 apps/server/src/models/visit/visit.service.ts delete mode 100644 apps/web/src/app/admin/code-manage/CodeManageContext.tsx delete mode 100644 apps/web/src/app/admin/code-manage/CodeManageLayout.tsx delete mode 100644 apps/web/src/app/admin/code-manage/components/CodeManageEdit.tsx delete mode 100644 apps/web/src/app/admin/code-manage/components/CodeManageSearchBase.tsx delete mode 100644 apps/web/src/app/admin/code-manage/components/CodeMangeDisplay.tsx delete mode 100644 apps/web/src/app/admin/code-manage/components/ShareCodeList.tsx delete mode 100644 apps/web/src/app/admin/code-manage/components/ShareCodeListCard.tsx delete mode 100644 apps/web/src/app/admin/dashboard/DashboardContext.tsx delete mode 100644 apps/web/src/app/admin/dashboard/DashboardLayout.tsx delete mode 100644 apps/web/src/app/admin/dashboard/components/Activate.tsx delete mode 100644 apps/web/src/app/admin/dashboard/components/ActivityItem.tsx delete mode 100644 apps/web/src/app/admin/dashboard/components/Board.tsx delete mode 100644 apps/web/src/app/admin/quick-file/components/header.tsx delete mode 100644 apps/web/src/app/admin/quick-file/manage.tsx delete mode 100755 apps/web/src/app/admin/quick-file/page.tsx delete mode 100644 apps/web/src/app/admin/quick-file/quickFileContext.tsx delete mode 100755 apps/web/src/app/admin/sharecode/ShareCodeCenerator.module.css delete mode 100755 apps/web/src/app/admin/sharecode/ShareCodeValidator.module.css delete mode 100644 apps/web/src/app/admin/sharecode/components/CodeRecord.tsx delete mode 100755 apps/web/src/app/admin/sharecode/sharecodegenerator.tsx delete mode 100755 apps/web/src/app/admin/sharecode/sharecodevalidator.tsx delete mode 100755 packages/client/src/api/hooks/useVisitor.ts diff --git a/apps/server/src/models/share-code/share-code.module.ts b/apps/server/src/models/share-code/share-code.module.ts deleted file mode 100644 index 6782366..0000000 --- a/apps/server/src/models/share-code/share-code.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; - -import { TrpcService } from '@server/trpc/trpc.service'; -import { ResourceModule } from '../resource/resource.module'; -import { ShareCodeService } from './share-code.service'; -import { ShareCodeRouter } from './share-code.router'; - -@Module({ - imports: [ResourceModule], - providers: [TrpcService, ShareCodeService, ShareCodeRouter], - exports: [ShareCodeService, ShareCodeRouter], - controllers: [], -}) -export class ShareCodeModule { } diff --git a/apps/server/src/models/share-code/share-code.router.ts b/apps/server/src/models/share-code/share-code.router.ts deleted file mode 100644 index 1924d2d..0000000 --- a/apps/server/src/models/share-code/share-code.router.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { z, ZodType } from "zod"; -import { ShareCodeService } from "./share-code.service"; -import { TrpcService } from "@server/trpc/trpc.service"; -import { Injectable } from "@nestjs/common"; -import { Prisma } from "@nice/common"; -const ShareCodeWhereInputSchema: ZodType = z.any(); -const ShareCodeFindManyArgsSchema: ZodType = z.any(); -@Injectable() -export class ShareCodeRouter { - constructor( - private readonly shareCodeService: ShareCodeService, - private readonly trpc: TrpcService - ) { } - - router = this.trpc.router({ - getFileByShareCode: this.trpc.procedure - .input(z.object({ code: z.string() })) - .query(async ({ input }) => { - return this.shareCodeService.getFileByShareCode(input.code); - }), - generateShareCodeByFileId: this.trpc.procedure - .input(z.object({ fileId: z.string(), expiresAt: z.date(), canUseTimes: z.number() })) - .mutation(async ({ input, ctx }) => { - return this.shareCodeService.generateShareCodeByFileId(input.fileId, input.expiresAt, input.canUseTimes, ctx.ip); - }), - getShareCodesWithResources: this.trpc.procedure - .input(z.object({ page: z.number(), pageSize: z.number(), where: ShareCodeWhereInputSchema.optional() })) - .query(async ({ input }) => { - return this.shareCodeService.getShareCodesWithResources(input); - }), - softDeleteShareCodes: this.trpc.procedure - .input(z.object({ ids: z.array(z.string()) })) - .mutation(async ({ input }) => { - return this.shareCodeService.softDeleteShareCodes(input.ids); - }), - updateShareCode: this.trpc.procedure - .input(z.object({ - id: z.string(), - data: z.object({ - expiresAt: z.date().optional(), - canUseTimes: z.number().optional(), - }) - })) - .mutation(async ({ input }) => { - return this.shareCodeService.updateShareCode(input.id, input.data); - }), - getShareCodeResourcesTotalSize: this.trpc.procedure - .query(async () => { - return this.shareCodeService.getShareCodeResourcesTotalSize(); - }), - getShareCodeResourcesSizeByDateRange: this.trpc.procedure - .input(z.object({ dateType: z.enum(['today', 'yesterday']) })) - .query(async ({ input }) => { - return this.shareCodeService.getShareCodeResourcesSizeByDateRange(input.dateType); - }), - countDistinctUploadIPs: this.trpc.procedure - .query(async () => { - return this.shareCodeService.countDistinctUploadIPs(); - }), - findShareCodes: this.trpc.procedure - .input(ShareCodeFindManyArgsSchema) - .query(async ({ input }) => { - return this.shareCodeService.findShareCodes(input); - }), - getAllreadlyDeletedShareCodes:this.trpc.procedure - .query(async () => { - return this.shareCodeService.getAllreadlyDeletedShareCodes(); - }) - }); -} \ No newline at end of file diff --git a/apps/server/src/models/share-code/share-code.service.ts b/apps/server/src/models/share-code/share-code.service.ts deleted file mode 100755 index a8d8214..0000000 --- a/apps/server/src/models/share-code/share-code.service.ts +++ /dev/null @@ -1,610 +0,0 @@ -import { Injectable, Logger, NotFoundException } from '@nestjs/common'; -import { customAlphabet } from 'nanoid-cjs'; -import { db, ObjectType, Prisma, Resource } from '@nice/common'; -import { Cron, CronExpression } from '@nestjs/schedule'; -import { ResourceService } from '@server/models/resource/resource.service'; -import * as fs from 'fs' -import * as path from 'path' -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; -import { BaseService } from '../base/base.service'; -dayjs.extend(utc); -dayjs.extend(timezone); -export interface ShareCode { - id: string; - code: string; - fileId: string; - createdAt: Date; - expiresAt: Date; - isUsed: boolean; - fileName?: string | null; - canUseTimes: number | null; - uploadIp?: string; -} -export interface GenerateShareCodeResponse { - id?: string; - code: string; - expiresAt: Date; - canUseTimes: number; - fileName?: string; - resource: { - id: string; - type: string; - url: string; - meta: ResourceMeta - } - uploadIp?: string; - createdAt?: Date; -} - -interface ResourceMeta { - filename: string; - filetype: string; - size: string; -} - -const ShareCodeSelect = { - id: true, - code: true, - fileId: true, - expiresAt: true, - fileName: true, - canUseTimes: true, - resource: { - select: { - id: true, - type: true, - url: true, - meta: true, - } - }, - uploadIp: true, - createdAt: true, -} - -@Injectable() -export class ShareCodeService extends BaseService { - private readonly logger = new Logger(ShareCodeService.name); - // 生成8位分享码,使用易读的字符 - private readonly generateCode = customAlphabet( - '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ', - 5, - ); - - constructor(private readonly resourceService: ResourceService) { - super(db, ObjectType.SHARE_CODE, false); - } - // 根据文件ID生成分享码 - async generateShareCodeByFileId(fileId: string, expiresAt: Date, canUseTimes: number, ip: string) { - try { - this.logger.log('收到生成分享码请求,fileId:', fileId); - this.logger.log('客户端IP:', ip); - const result = await this.generateShareCode(fileId, expiresAt, canUseTimes, undefined, ip); - this.logger.log('生成分享码结果:', result); - return result; - } catch (error) { - this.logger.error('生成分享码错误:', error); - return error - } - } - - async generateShareCode( - fileId: string, - expiresAt: Date, - canUseTimes: number, - fileName?: string, - uploadIp?: string, - ): Promise { - try { - // 检查文件是否存在 - const resource = await this.resourceService.findUnique({ - where: { fileId }, - }); - this.logger.log('完整 resource:', resource); - if (!resource) { - throw new NotFoundException('文件不存在'); - } - const { filename, filetype, size } = resource.meta as any as ResourceMeta - // 生成分享码(修改的逻辑保证分享码的唯一性) - let code = this.generateCode(); - let existingShareCode; - do { - // 查找是否已有相同 shareCode 或者相同 FileID 的分享码记录 - existingShareCode = await super.findFirst({ - where: { - OR: [ - { code }, - { fileId } - ] - }, - }); - // 如果找到的是已经被删除的码,则可以使用并更新其他信息,否则重新生成 - if(!existingShareCode){ - break - } - if (existingShareCode.deleteAt !== null) { - break - } - if (existingShareCode && existingShareCode.code === code) { - code = this.generateCode(); - } - } while (existingShareCode && existingShareCode.code === code); - - if (existingShareCode) { - // 更新现有记录,但保留原有文件名 - await super.update({ - where: { id: existingShareCode.id }, - data: { - code, - expiresAt, - canUseTimes, - isUsed: false, - fileId, - fileName: filename || "downloaded_file", - uploadIp, - createdAt: new Date(), - deletedAt: null - }, - }); - } else { - // 创建新记录 - await super.create({ - data: { - code, - fileId, - expiresAt, - canUseTimes, - isUsed: false, - fileName: filename || "downloaded_file", - uploadIp, - }, - }); - } - this.logger.log(`Generated share code ${code} for file ${fileId} canUseTimes: ${canUseTimes}`); - return { - code, - expiresAt, - canUseTimes, - fileName: filename || "downloaded_file", - resource: { - id: resource.id, - type: resource.type, - url: resource.url, - meta: { - filename, - filetype, - size, - } - }, - uploadIp, - createdAt: new Date() - }; - } catch (error) { - this.logger.error('Failed to generate share code', error); - throw error; - } - } - - async validateAndUseCode(code: string): Promise { - try { - this.logger.log(`尝试验证分享码: ${code}`); - - // 查找有效的分享码 - const shareCode = await super.findFirst({ - where: { - code, - isUsed: false, - expiresAt: { gt: dayjs().tz('Asia/Shanghai').toDate() }, - deletedAt: null - }, - }); - if (shareCode.canUseTimes <= 0) { - this.logger.log('分享码已使用次数超过限制'); - return null; - } - //更新已使用次数 - await super.update({ - where: { id: shareCode.id }, - data: { canUseTimes: shareCode.canUseTimes - 1 }, - }); - this.logger.log('查询结果:', shareCode); - - if (!shareCode) { - this.logger.log('分享码无效或已过期'); - return null; - } - // 记录使用日志 - this.logger.log(`Share code ${code} used for file ${shareCode.fileId}`); - // 返回完整的分享码信息,包括文件名 - return shareCode; - } catch (error) { - this.logger.error('Failed to validate share code', error); - return null; - } - } - - // 每天清理过期的分享码 - //@Cron('*/30 * * * * *') - @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) - async cleanupExpiredShareCodes() { - try { - const shareCodes = await super.findMany({ - where: { - OR: [ - { expiresAt: { lt: dayjs().tz('Asia/Shanghai').toDate() } }, - { isUsed: true }, - { canUseTimes: { lte: 0 } } - ], - } - }) - this.logger.log('需要清理的分享码:', shareCodes); - //文件资源硬删除 - shareCodes.forEach(code => { - this.cleanupUploadFolder(code.fileId); - }) - //数据库资源软删除 - const result = await super.softDeleteByIds( - [...shareCodes.map(code => code.id)] - ); - const deleteResource = await this.resourceService.updateMany({ - where: { - fileId: { - in: shareCodes.map(code => code.fileId) - } - }, - data: { - deletedAt: new Date() - } - }) - this.logger.log(`Cleaned up ${result} ${deleteResource.count} expired share codes`); - } catch (error) { - this.logger.error('Failed to cleanup expired share codes', error); - } - } - - async cleanupUploadFolder(file?: string) { - //const uploadDir = path.join(__dirname, '../../../uploads'); - const uploadDir = path.join('/data/uploads/', file || ''); - this.logger.log('uploadDir:', uploadDir); - try { - if (!fs.existsSync(uploadDir)) { - this.logger.warn(`Upload directory does not exist: ${uploadDir}`); - return; - } - // 递归删除文件夹及其内容 - this.deleteFolderRecursive(uploadDir); - this.logger.log(`Cleaned up upload folder: ${uploadDir}`); - } catch (error) { - this.logger.error('读取上传目录失败:', error); - return; - } - } - private deleteFolderRecursive(dirPath: string) { - if (fs.existsSync(dirPath)) { - fs.readdirSync(dirPath).forEach((file) => { - const filePath = path.join(dirPath, file); - if (fs.statSync(filePath).isDirectory()) { - // 递归删除子目录 - this.deleteFolderRecursive(filePath); - } else { - // 删除文件 - fs.unlinkSync(filePath); - this.logger.log(`Deleted file: ${filePath}`); - } - }); - // 删除空文件夹 - fs.rmdirSync(dirPath); - this.logger.log(`Deleted folder: ${dirPath}`); - } - } - - // 根据分享码获取文件 - async getFileByShareCode(code: string) { - this.logger.log('收到验证分享码请求,code:', code); - const shareCode = await this.validateAndUseCode(code); - this.logger.log('验证分享码结果:', shareCode); - - if (!shareCode) { - this.logger.log('分享码无效或已过期'); - return null - } - - // 获取文件信息 - const resource = await this.resourceService.findUnique({ - where: { fileId: shareCode.fileId }, - }); - this.logger.log('获取到的资源信息:', resource); - const { filename, filetype, size } = resource.meta as any as ResourceMeta - const fileUrl = resource?.url - if (!resource) { - throw new NotFoundException('文件不存在'); - } - - // 直接返回正确的数据结构 - const response = { - id: shareCode.id, - code: shareCode.code, - fileName: filename || 'downloaded_file', - expiresAt: shareCode.expiresAt, - canUseTimes: shareCode.canUseTimes - 1, - resource: { - id: resource.id, - type: resource.type, - url: resource.url, - meta: { - filename, filetype, size - } - }, - uploadIp: shareCode.uploadIp, - createdAt: shareCode.createdAt - }; - - this.logger.log('返回给前端的数据:', response); // 添加日志 - return response; - } - - - async getShareCodesWithResources(args: { - page?: number; - pageSize?: number; - where?: Prisma.ShareCodeWhereInput; - }): Promise<{ - items: Array; - totalPages: number; - }> { - try { - // 使用include直接关联查询Resource - const { items, totalPages } = await super.findManyWithPagination({ - ...args, - select: ShareCodeSelect - }); - return { - items, - totalPages - }; - } catch (error) { - this.logger.error('Failed to get share codes with resources', error); - throw error; - } - } - async softDeleteShareCodes(ids: string[]): Promise { - try { - this.logger.log(`尝试软删除分享码,IDs: ${ids.join(', ')}`); - const result = await super.softDeleteByIds(ids); - this.logger.log(`软删除分享码成功,数量: ${result.length}`); - return result; - } catch (error) { - this.logger.error('软删除分享码失败', error); - throw error; - } - } - async updateShareCode(id: string, data: Partial): Promise { - try { - this.logger.log(`尝试更新分享码,ID: ${id},数据:`, data); - const result = await super.updateById(id, data); - this.logger.log(`更新分享码成功:`, result); - return result; - } catch (error) { - this.logger.error('更新分享码失败', error); - throw error; - } - } - - /** - * 获取所有分享码关联资源的总大小 - * @returns 返回所有资源的总大小和资源数量 - */ - async getShareCodeResourcesTotalSize(): Promise<{ totalSize: number; resourceCount: number }> { - try { - this.logger.log('获取所有分享码关联资源的总大小'); - - // 查询所有有效的分享码及其关联的资源 - const shareCodes = await super.findMany({ - where: { - deletedAt: null - }, - select: { ...ShareCodeSelect } - }); - const {totalSize, resourceCount} = this.calculateTotalSize(shareCodes as any as GenerateShareCodeResponse[]); - - this.logger.log(`资源总大小: ${totalSize}, 资源数量: ${resourceCount}`); - return { totalSize, resourceCount }; - } catch (error) { - this.logger.error('获取分享码资源总大小失败', error); - throw error; - } - } - /** - * 根据日期范围获取分享码关联资源的大小 - * @param dateType 日期类型: 'today' 或 'yesterday' - * @returns 返回特定日期范围内的资源总大小和资源数量 - */ - async getShareCodeResourcesSizeByDateRange(dateType: 'today' | 'yesterday'): Promise<{ totalSize: number; resourceCount: number }> { - try { - let startDate: Date; - let endDate: Date; - const now = dayjs().tz('Asia/Shanghai'); - - if (dateType === 'today') { - startDate = now.startOf('day').toDate(); - endDate = now.endOf('day').toDate(); - this.logger.log(`获取今天创建的分享码资源大小, 日期范围: ${startDate} 到 ${endDate}`); - } else { - startDate = now.subtract(1, 'day').startOf('day').toDate(); - endDate = now.subtract(1, 'day').endOf('day').toDate(); - this.logger.log(`获取昨天创建的分享码资源大小, 日期范围: ${startDate} 到 ${endDate}`); - } - - // 查询特定日期范围内创建的分享码及其关联的资源 - const shareCodes = await super.findMany({ - where: { - createdAt: { - gte: startDate, - lte: endDate - }, - deletedAt: null - }, - select: { - ...ShareCodeSelect - } - }); - const {totalSize, resourceCount} = this.calculateTotalSize(shareCodes as any as GenerateShareCodeResponse[]); - this.logger.log(`${dateType}资源总大小: ${totalSize}, 资源数量: ${resourceCount}`); - return { totalSize, resourceCount }; - } catch (error) { - this.logger.error(`获取${dateType === 'today' ? '今天' : '昨天'}的分享码资源大小失败`, error); - throw error; - } - } - /** - * 统计不同时间段内独立uploadIp的数量 - * @returns 返回本周和上周的不同uploadIp数量 - */ - async countDistinctUploadIPs(): Promise<{ thisWeek: number; lastWeek: number; all: number }> { - try { - const now = dayjs().tz('Asia/Shanghai'); - - // 本周的开始和结束 - const thisWeekStart = now.startOf('week').toDate(); - const thisWeekEnd = now.endOf('week').toDate(); - - // 上周的开始和结束 - const lastWeekStart = now.subtract(1, 'week').startOf('week').toDate(); - const lastWeekEnd = now.subtract(1, 'week').endOf('week').toDate(); - - this.logger.log(`统计本周IP数量, 日期范围: ${thisWeekStart} 到 ${thisWeekEnd}`); - this.logger.log(`统计上周IP数量, 日期范围: ${lastWeekStart} 到 ${lastWeekEnd}`); - // 查询所有不同IP - const allIPs = await super.findMany({ - where: { - deletedAt: null, - uploadIp: { - not: null - } - }, - select: { - uploadIp: true - }, - distinct: ['uploadIp'] - }); - // 查询本周的不同IP - const thisWeekIPs = await super.findMany({ - where: { - createdAt: { - gte: thisWeekStart, - lte: thisWeekEnd - }, - uploadIp: { - not: null - }, - deletedAt: null - }, - select: { - uploadIp: true - }, - distinct: ['uploadIp'] - }); - - // 查询上周的不同IP - const lastWeekIPs = await super.findMany({ - where: { - createdAt: { - gte: lastWeekStart, - lte: lastWeekEnd - }, - uploadIp: { - not: null - }, - deletedAt: null - }, - select: { - uploadIp: true - }, - distinct: ['uploadIp'] - }); - - const thisWeekCount = thisWeekIPs.length; - const lastWeekCount = lastWeekIPs.length; - const allCount = allIPs.length; - - this.logger.log(`本周不同IP数量: ${thisWeekCount}, 上周不同IP数量: ${lastWeekCount}, 所有不同IP数量: ${allCount}`); - - return { thisWeek: thisWeekCount, lastWeek: lastWeekCount, all: allCount }; - } catch (error) { - this.logger.error('统计不同uploadIp数量失败', error); - throw error; - } - } - - /** - * 获取分享码列表,使用ShareCodeSelect并按创建时间倒序排序 - * @param args 查询参数 - * @returns 返回分享码列表 - */ - async findShareCodes(args?: Omit): Promise { - try { - const result = await super.findMany({ - ...args, - select: ShareCodeSelect, - }); - this.logger.log(`获取分享码列表成功, 数量: ${result.length}`); - return result as unknown as GenerateShareCodeResponse[]; - } catch (error) { - this.logger.error('获取分享码列表失败', error); - throw error; - } - } - async getAllreadlyDeletedShareCodes(args?: Omit):Promise<{ totalSize: number; resourceCount: number; }> { - try { - const result = await super.findMany({ - ...args, - where: { - deletedAt: { - not: null - } - }, - select: ShareCodeSelect, - }); - // 计算总大小和资源数量 - const { totalSize, resourceCount } = this.calculateTotalSize(result as unknown as GenerateShareCodeResponse[]); - this.logger.log(`获取已删除分享码列表成功, 数量: ${resourceCount}, 总大小: ${totalSize}`); - return {totalSize,resourceCount} - } catch (err) { - this.logger.error('获取已删除分享码列表失败', err) - throw err - } - - } - calculateTotalSize(shareCodes: GenerateShareCodeResponse[]): { totalSize: number; resourceCount: number } { - let totalSize = 0; - let resourceCount = 0; - shareCodes.forEach(shareCode => { - if ((shareCode as any as GenerateShareCodeResponse).resource && (shareCode as any as GenerateShareCodeResponse).resource.meta) { - const meta = (shareCode as any as GenerateShareCodeResponse).resource.meta as any; - if (meta.size) { - // 如果size是字符串格式(如 "1024"或"1 MB"),需要转换 - let sizeValue: number; - if (typeof meta.size === 'string') { - // 尝试直接解析数字 - sizeValue = parseInt(meta.size, 10); - // 如果解析失败,可能需要更复杂的处理 - if (isNaN(sizeValue)) { - // 简单处理,实际应用中可能需要更复杂的单位转换 - this.logger.warn(`无法解析资源大小: ${meta.size}`); - sizeValue = 0; - } - } else if (typeof meta.size === 'number') { - sizeValue = meta.size; - } else { - sizeValue = 0; - } - - totalSize += sizeValue; - resourceCount++; - } - } - }) - return { totalSize, resourceCount } - } -} diff --git a/apps/server/src/models/visit/visit.module.ts b/apps/server/src/models/visit/visit.module.ts deleted file mode 100755 index 0d3e2ec..0000000 --- a/apps/server/src/models/visit/visit.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { VisitService } from './visit.service'; -import { VisitRouter } from './visit.router'; -import { TrpcService } from '@server/trpc/trpc.service'; - -@Module({ - providers: [VisitService, VisitRouter, TrpcService], - exports: [VisitRouter] -}) -export class VisitModule { } diff --git a/apps/server/src/models/visit/visit.router.ts b/apps/server/src/models/visit/visit.router.ts deleted file mode 100755 index e600ff2..0000000 --- a/apps/server/src/models/visit/visit.router.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { Prisma } from '@nice/common'; - -import { VisitService } from './visit.service'; -import { z, ZodType } from 'zod'; -const VisitCreateArgsSchema: ZodType = z.any(); -const VisitCreateManyInputSchema: ZodType = - z.any(); -const VisitDeleteManyArgsSchema: ZodType = z.any(); -@Injectable() -export class VisitRouter { - constructor( - private readonly trpc: TrpcService, - private readonly visitService: VisitService, - ) {} - router = this.trpc.router({ - create: this.trpc.procedure - .input(VisitCreateArgsSchema) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - return await this.visitService.create(input, staff); - }), - createMany: this.trpc.procedure - .input(z.array(VisitCreateManyInputSchema)) - .mutation(async ({ ctx, input }) => { - const { staff } = ctx; - - return await this.visitService.createMany({ data: input }, staff); - }), - deleteMany: this.trpc.procedure - .input(VisitDeleteManyArgsSchema) - .mutation(async ({ input }) => { - return await this.visitService.deleteMany(input); - }), - }); -} diff --git a/apps/server/src/models/visit/visit.service.ts b/apps/server/src/models/visit/visit.service.ts deleted file mode 100755 index ba6ed20..0000000 --- a/apps/server/src/models/visit/visit.service.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { BaseService } from '../base/base.service'; -import { UserProfile, db, ObjectType, Prisma, VisitType } from '@nice/common'; -import EventBus from '@server/utils/event-bus'; -@Injectable() -export class VisitService extends BaseService { - constructor() { - super(db, ObjectType.VISIT); - } - async create(args: Prisma.VisitCreateArgs, staff?: UserProfile) { - const { postId, lectureId, messageId } = args.data; - const visitorId = args.data?.visitorId || staff?.id; - let result; - const existingVisit = await db.visit.findFirst({ - where: { - type: args.data.type, - // visitorId: visitorId ? visitorId : null, - OR: [{ postId }, { messageId }], - }, - }); - if (!existingVisit) { - result = await super.create(args); - } else if (args.data.type === VisitType.READED) { - result = await super.update({ - where: { id: existingVisit.id }, - data: { - ...args.data, - views: existingVisit.views + 1, - }, - }); - } - - if ( - [VisitType.READED, VisitType.LIKE, VisitType.HATE].includes( - args.data.type as VisitType, - ) - ) { - EventBus.emit('updateVisitCount', { - objectType: ObjectType.POST, - id: postId, - visitType: args.data.type, // 直接复用传入的类型 - }); - EventBus.emit('updateTotalCourseViewCount', { - visitType: args.data.type, // 直接复用传入的类型 - }); - } - - return result; - } - async createMany(args: Prisma.VisitCreateManyArgs, staff?: UserProfile) { - const data = Array.isArray(args.data) ? args.data : [args.data]; - const updatePromises: any[] = []; - const createData: Prisma.VisitCreateManyInput[] = []; - await Promise.all( - data.map(async (item) => { - if (staff && !item.visitorId) item.visitorId = staff.id; - const { postId, lectureId, messageId, visitorId } = item; - const existingVisit = await db.visit.findFirst({ - where: { - visitorId, - OR: [{ postId }, { lectureId }, { messageId }], - }, - }); - - if (existingVisit) { - updatePromises.push( - super.update({ - where: { id: existingVisit.id }, - data: { - ...item, - views: existingVisit.views + 1, - }, - }), - ); - } else { - createData.push(item); - } - }), - ); - // Execute all updates in parallel - await Promise.all(updatePromises); - // Create new visits for those not existing - if (createData.length > 0) { - return super.createMany({ - ...args, - data: createData, - }); - } - - return { count: updatePromises.length }; // Return the number of updates if no new creates - } - async deleteMany(args: Prisma.VisitDeleteManyArgs, staff?: UserProfile) { - // const where = Array.isArray(args.where) ? args.where : [args.where]; - // const updatePromises: any[] = []; - // const createData: Prisma.VisitCreateManyInput[] = []; - // super - // await Promise.all( - // data.map(async (item) => { - // if (staff && !item.visitorId) item.visitorId = staff.id; - // const { postId, messageId, visitorId } = item; - // const existingVisit = await db.visit.findFirst({ - // where: { - // visitorId, - // OR: [{ postId }, { messageId }], - // }, - // }); - - // if (existingVisit) { - // updatePromises.push( - // super.update({ - // where: { id: existingVisit.id }, - // data: { - // ...item, - // views: existingVisit.views + 1, - // }, - // }), - // ); - // } else { - // createData.push(item); - // } - // }), - // ); - // // Execute all updates in parallel - // await Promise.all(updatePromises); - // // Create new visits for those not existing - // if (createData.length > 0) { - // return super.createMany({ - // ...args, - // data: createData, - // }); - // } - // return { count: updatePromises.length }; // Return the number of updates if no new creates - const superDetele = super.deleteMany(args, staff); - if (args?.where?.postId) { - if ( - [VisitType.READED, VisitType.LIKE, VisitType.HATE].includes( - args.where.type as any, - ) - ) { - EventBus.emit('updateVisitCount', { - objectType: ObjectType.POST, - id: args?.where?.postId as string, - visitType: args.where.type as any, // 直接复用传入的类型 - }); - EventBus.emit('updateTotalCourseViewCount', { - visitType: args.where.type as any, // 直接复用传入的类型 - }); - } - } - return superDetele; - } -} diff --git a/apps/server/src/trpc/trpc.module.ts b/apps/server/src/trpc/trpc.module.ts index eab1b6c..0eee4c9 100755 --- a/apps/server/src/trpc/trpc.module.ts +++ b/apps/server/src/trpc/trpc.module.ts @@ -10,11 +10,9 @@ import { AuthModule } from '@server/auth/auth.module'; import { AppConfigModule } from '@server/models/app-config/app-config.module'; import { MessageModule } from '@server/models/message/message.module'; import { PostModule } from '@server/models/post/post.module'; -import { VisitModule } from '@server/models/visit/visit.module'; import { WebSocketModule } from '@server/socket/websocket.module'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; import { TransformModule } from '@server/models/transform/transform.module'; -import { ShareCodeModule } from '@server/models/share-code/share-code.module'; import { ResourceModule } from '@server/models/resource/resource.module'; @Module({ @@ -30,10 +28,8 @@ import { ResourceModule } from '@server/models/resource/resource.module'; MessageModule, AppConfigModule, PostModule, - VisitModule, WebSocketModule, ResourceModule, - ShareCodeModule, ], controllers: [], providers: [TrpcService, TrpcRouter, Logger], diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index 14fc2c0..c898b6b 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -9,12 +9,10 @@ import ws, { WebSocketServer } from 'ws'; import { AppConfigRouter } from '@server/models/app-config/app-config.router'; import { MessageRouter } from '@server/models/message/message.router'; import { PostRouter } from '@server/models/post/post.router'; -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'; -import { ShareCodeRouter } from '@server/models/share-code/share-code.router'; @Injectable() export class TrpcRouter { logger = new Logger(TrpcRouter.name); @@ -30,9 +28,7 @@ export class TrpcRouter { private readonly transform: TransformRouter, private readonly app_config: AppConfigRouter, private readonly message: MessageRouter, - private readonly visitor: VisitRouter, private readonly resource: ResourceRouter, - private readonly shareCode: ShareCodeRouter, ) {} getRouter() { return; @@ -48,9 +44,7 @@ export class TrpcRouter { rolemap: this.rolemap.router, message: this.message.router, app_config: this.app_config.router, - visitor: this.visitor.router, resource: this.resource.router, - shareCode: this.shareCode.router, }); wss: WebSocketServer = undefined; diff --git a/apps/web/src/app/admin/code-manage/CodeManageContext.tsx b/apps/web/src/app/admin/code-manage/CodeManageContext.tsx deleted file mode 100644 index 6a4d591..0000000 --- a/apps/web/src/app/admin/code-manage/CodeManageContext.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Form, FormInstance, message } from "antd"; -import { api } from "@nice/client"; -import { createContext, useContext, useState } from "react"; -import { useQueryClient } from "@tanstack/react-query"; -import { getQueryKey } from "@trpc/react-query"; -import { ShareCodeResponse } from "../quick-file/quickFileContext"; - -interface CodeManageContextType { - editForm: FormInstance; - isLoading: boolean; - currentShareCodes: ShareCodeWithResource; - currentPage: number; - setCurrentPage: (page: number) => void; - pageSize: number; - deletShareCode: (id: string) => void; - updateCode: (expiresAt: Date, canUseTimes: number) => void - setCurrentCodeId: (id: string) => void, - currentCodeId: string | null, - searchRefetch: () => void, - setSearchKeyword: (keyword: string) => void, - currentCode: string | null, - setCurrentCode: (code: string) => void, - searchKeyword: string -} - -interface ShareCodeWithResource { - items: ShareCodeResponse[], - totalPages: number -} - -export const CodeManageContext = createContext(null); - -export const CodeManageProvider = ({ children }: { children: React.ReactNode }) => { - const [editForm] = Form.useForm(); - const [currentPage, setCurrentPage] = useState(1); - const [currentCodeId, setCurrentCodeId] = useState() - const [currentCode, setCurrentCode] = useState() - const queryClient = useQueryClient(); - const pageSize = 8; - // 在组件顶部添加 - const [searchKeyword, setSearchKeyword] = useState(''); - // 构建查询条件 - const whereCondition = { - deletedAt: null, - ...(searchKeyword ? { - OR: [ - { fileName: { contains: searchKeyword } }, - { code: { contains: searchKeyword } }, - { uploadIp: { contains: searchKeyword } } - ] - } : {}) - }; - const { data: currentShareCodes, refetch: searchRefetch }: { data: ShareCodeWithResource, refetch: () => void } = api.shareCode.getShareCodesWithResources.useQuery( - { - page: currentPage, - pageSize: pageSize, - where: whereCondition, - }, - { - enabled: true, - refetchOnWindowFocus: false, - } - ) - const { mutate: softDeleteShareCode } = api.shareCode.softDeleteShareCodes.useMutation({ - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: getQueryKey(api.shareCode) }); - }, - onError: () => { - message.error('删除失败') - } - }) - const { mutate: updateShareCode } = api.shareCode.updateShareCode.useMutation({ - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: getQueryKey(api.shareCode) }); - }, - onError: () => { - message.error('更新失败') - } - }) - const deletShareCode = (id: string) => { - softDeleteShareCode({ ids: [id] }) - } - const updateCode = (expiresAt: Date, canUseTimes: number) => { - if (currentCodeId) updateShareCode({ id: currentCodeId, data: { expiresAt, canUseTimes } }) - } - const [isLoading, setIsLoading] = useState(false); - return <> - - {children} - - -}; - -export const useCodeManageContext = () => { - const context = useContext(CodeManageContext); - if (!context) { - throw new Error("useCodeManageContext must be used within a CodeManageProvider"); - } - return context; -}; \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/CodeManageLayout.tsx b/apps/web/src/app/admin/code-manage/CodeManageLayout.tsx deleted file mode 100644 index ebca1b5..0000000 --- a/apps/web/src/app/admin/code-manage/CodeManageLayout.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useSearchParams } from "react-router-dom"; -import CodeManageSearchBase from "./components/CodeManageSearchBase"; -import CodeManageDisplay from "./components/CodeMangeDisplay"; -import { useCodeManageContext } from "./CodeManageContext"; -import { useEffect, useState } from "react"; -export default function CodeManageLayout() { - const [searchParams] = useSearchParams(); - const { setSearchKeyword, searchRefetch, setCurrentPage } = useCodeManageContext(); - const [localKeyword, setLocalKeyword] = useState(""); - - useEffect(() => { - const keyword = searchParams.get('keyword'); - if (keyword) { - setSearchKeyword(keyword); - setLocalKeyword(keyword); - setCurrentPage(1); - searchRefetch(); - } - }, [searchParams, setSearchKeyword, setCurrentPage, searchRefetch]); - return ( -
- - -
- ) -} \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/components/CodeManageEdit.tsx b/apps/web/src/app/admin/code-manage/components/CodeManageEdit.tsx deleted file mode 100644 index 9ed7c68..0000000 --- a/apps/web/src/app/admin/code-manage/components/CodeManageEdit.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { useCodeManageContext } from "../CodeManageContext"; -import { Form, DatePicker, Input, Button } from "antd"; -import dayjs from "dayjs"; -import { useState } from "react"; - -export default function CodeManageEdit() { - const { editForm } = useCodeManageContext(); - // 验证数字输入只能是大于等于0的整数 - const validatePositiveInteger = (_: any, value: string) => { - const num = parseInt(value, 10); - if (isNaN(num) || num < 0 || num !== parseFloat(value)) { - return Promise.reject("请输入大于等于0的整数"); - } - return Promise.resolve(); - }; - - return ( -
-
- 分享码有效期} - name="expiresAt" - rules={[{ required: true, message: "请选择有效期" }]} - className="mb-5" - > - current && current < dayjs().startOf('day')} - disabledTime={(current) => { - if (current && current.isSame(dayjs(), 'day')) { - return { - disabledHours: () => [...Array(dayjs().hour()).keys()], - disabledMinutes: (selectedHour) => { - if (selectedHour === dayjs().hour()) { - return [...Array(dayjs().minute()).keys()]; - } - return []; - } - }; - } - return {}; - }} - /> - - - 使用次数} - name="canUseTimes" - rules={[ - { required: true, message: "请输入使用次数" }, - { validator: validatePositiveInteger } - ]} - className="mb-5" - > - - -
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/components/CodeManageSearchBase.tsx b/apps/web/src/app/admin/code-manage/components/CodeManageSearchBase.tsx deleted file mode 100644 index 9efb8e5..0000000 --- a/apps/web/src/app/admin/code-manage/components/CodeManageSearchBase.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Button, Form, Input } from 'antd'; -import { useCodeManageContext } from '../CodeManageContext'; -import { ChangeEvent, useEffect, useRef } from 'react'; -import { useSearchParams } from "react-router-dom"; - -export default function CodeManageSearchBase({ keyword }: { keyword?: string }) { - const { setCurrentPage, searchRefetch, setSearchKeyword, searchKeyword } = useCodeManageContext(); - const debounceTimer = useRef(null); - const formRef = Form.useForm()[0]; // 获取表单实例 - const [searchParams, setSearchParams] = useSearchParams(); - - // 当 keyword 属性变化时更新表单值 - useEffect(() => { - if (keyword) { - formRef.setFieldsValue({ search: keyword }); - } - }, [keyword, formRef]); - - // 监听 searchKeyword 变化,如果 URL 参数中有 keyword 且 searchKeyword 发生变化,则清空 URL 参数 - useEffect(() => { - const urlKeyword = searchParams.get('keyword'); - if (urlKeyword && searchKeyword !== urlKeyword) { - // 创建一个新的 URLSearchParams 对象,不包含 keyword 参数 - const newParams = new URLSearchParams(searchParams); - newParams.delete('keyword'); - setSearchParams(newParams); - } - }, [searchKeyword, searchParams, setSearchParams]); - - const onSearch = (value: string) => { - console.log(value); - setSearchKeyword(value); - setCurrentPage(1) - searchRefetch() - }; - - const onChange = (e: React.ChangeEvent) => { - // 设置表单值 - setSearchKeyword(e.target.value); - // 设置页码为1,确保从第一页开始显示搜索结果 - setCurrentPage(1); - // 使用防抖处理,避免频繁发送请求 - if (debounceTimer.current) { - clearTimeout(debounceTimer.current); - } - debounceTimer.current = setTimeout(() => { - // 触发查询 - searchRefetch(); - }, 300); // 300毫秒的防抖延迟 - }; - - return <> -
-
- - - -
-
- -} \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/components/CodeMangeDisplay.tsx b/apps/web/src/app/admin/code-manage/components/CodeMangeDisplay.tsx deleted file mode 100644 index a1f4482..0000000 --- a/apps/web/src/app/admin/code-manage/components/CodeMangeDisplay.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { message, Modal, Pagination } from "antd"; -import ShareCodeList from "./ShareCodeList"; -import { useCodeManageContext } from "../CodeManageContext"; -import { useEffect, useState } from "react"; -import { ExclamationCircleFilled } from "@ant-design/icons"; -import CodeManageEdit from "./CodeManageEdit"; -import dayjs from "dayjs"; -export default function CodeMangeDisplay() { - const { isLoading, currentShareCodes, pageSize, currentPage, - setCurrentPage, deletShareCode, editForm, - updateCode, setCurrentCodeId, setCurrentCode, currentCode - } = useCodeManageContext(); - const [modalOpen, setModalOpen] = useState(false); - const [formLoading, setFormLoading] = useState(false); - const { confirm } = Modal; - const handleEdit = (id: string, expiresAt: Date, canUseTimes: number, code: string) => { - console.log('编辑分享码:', id); - setCurrentCodeId(id) - setModalOpen(true); - setCurrentCode(code) - editForm.setFieldsValue({ - expiresAt: dayjs(expiresAt), - canUseTimes: canUseTimes - }); - }; - const handleEditOk = () => { - const expiresAt = editForm.getFieldsValue().expiresAt.tz('Asia/Shanghai').toDate() - const canUseTimes = Number(editForm.getFieldsValue().canUseTimes) - updateCode(expiresAt, canUseTimes) - message.success('分享码已更新') - setModalOpen(false) - } - const handleDelete = (id: string) => { - console.log('删除分享码:', id); - confirm({ - title: '确定删除该分享码吗', - icon: , - content: '', - okText: '删除', - okType: 'danger', - cancelText: '取消', - async onOk() { - deletShareCode(id) - message.success('分享码已删除') - }, - onCancel() { - }, - }); - }; - useEffect(() => { - console.log('currentShareCodes:', currentShareCodes); - }, [currentShareCodes]); - return <> -
- -
-
- { - setCurrentPage(page); - }} - /> -
- { - handleEditOk() - }} - centered - open={modalOpen} - confirmLoading={formLoading} - onCancel={() => { - setModalOpen(false); - }} - title={`编辑分享码:${currentCode}`}> - - - -} \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/components/ShareCodeList.tsx b/apps/web/src/app/admin/code-manage/components/ShareCodeList.tsx deleted file mode 100644 index 6f92693..0000000 --- a/apps/web/src/app/admin/code-manage/components/ShareCodeList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { List,} from 'antd'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; -import ShareCodeListCard from './ShareCodeListCard'; -import { ShareCodeResponse } from '../../quick-file/quickFileContext'; - -dayjs.extend(utc); -dayjs.extend(timezone); - - -interface ShareCodeListProps { - data: ShareCodeResponse[]; - loading?: boolean; - onEdit?: (id: string,expiresAt:Date,canUseTimes:number,code:string) => void; - onDelete?: (id: string) => void; -} - -const ShareCodeList: React.FC = ({ - data, - loading, - onEdit, - onDelete -}) => { - - return ( - ( - - - - )} - /> - ); -}; - -export default ShareCodeList; \ No newline at end of file diff --git a/apps/web/src/app/admin/code-manage/components/ShareCodeListCard.tsx b/apps/web/src/app/admin/code-manage/components/ShareCodeListCard.tsx deleted file mode 100644 index 474252e..0000000 --- a/apps/web/src/app/admin/code-manage/components/ShareCodeListCard.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { DeleteOutlined, DownloadOutlined, EditOutlined } from "@ant-design/icons"; -import { Button, Card, Typography } from "antd"; -import dayjs from "dayjs"; -import { useEffect } from "react"; -import { ShareCodeResponse } from "../../quick-file/quickFileContext"; - -export default function ShareCodeListCard({ item, onEdit, onDelete, styles, onDownload }: - { - item: ShareCodeResponse, - styles?: string, - onDelete: (id: string) => void, - onEdit?: (id: string, expiresAt: Date, canUseTimes: number, code: string) => void, - onDownload?: (id: string) => void - }) { - useEffect(() => { - console.log('item:', item); - }, [item]); - return
- - {item.code} - - } - hoverable - actions={[ - onEdit && ( -
-} \ No newline at end of file diff --git a/apps/web/src/app/admin/dashboard/DashboardContext.tsx b/apps/web/src/app/admin/dashboard/DashboardContext.tsx deleted file mode 100644 index f76ece3..0000000 --- a/apps/web/src/app/admin/dashboard/DashboardContext.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { api } from "@nice/client"; -import { createContext, useContext, useState } from "react"; -import { ShareCodeResponse } from "../quick-file/quickFileContext"; -interface DashboardContextType { - shareCodeAll: ShareCodeResourcesSizeByDateRange; - shareCodeToday: ShareCodeResourcesSizeByDateRange; - shareCodeYesterday: ShareCodeResourcesSizeByDateRange; - isShareCodeAllLoading: boolean; - isShareCodeTodayLoading: boolean; - isShareCodeYesterdayLoading: boolean; - distinctUploadIPs: { thisWeek: number; lastWeek: number; all: number }; - isDistinctUploadIPsLoading: boolean; - shareCodeList: ShareCodeResponse[]; - isShareCodeListLoading: boolean; - deletedData: { totalSize?: number; resourceCount?: number; }; - isDeletedLoading: boolean; -} -interface ShareCodeResourcesSizeByDateRange { - totalSize: number; - resourceCount: number; -} -export const DashboardContext = createContext(null); - -export const DashboardProvider = ({ children }: { children: React.ReactNode }) => { - const { data: shareCodeAll, isLoading: isShareCodeAllLoading }: - { data: ShareCodeResourcesSizeByDateRange, isLoading: boolean } - = api.shareCode.getShareCodeResourcesTotalSize.useQuery() - const { data: shareCodeToday, isLoading: isShareCodeTodayLoading }: - { data: ShareCodeResourcesSizeByDateRange, isLoading: boolean } - = api.shareCode.getShareCodeResourcesSizeByDateRange.useQuery( - { dateType: "today" }) - const { data: shareCodeYesterday, isLoading: isShareCodeYesterdayLoading }: - { data: ShareCodeResourcesSizeByDateRange, isLoading: boolean } - = api.shareCode.getShareCodeResourcesSizeByDateRange.useQuery( - { dateType: "yesterday" }) - const { data: distinctUploadIPs, isLoading: isDistinctUploadIPsLoading }: - { data: { thisWeek: number; lastWeek: number; all: number }, isLoading: boolean } - = api.shareCode.countDistinctUploadIPs.useQuery() - const { data: shareCodeList, isLoading: isShareCodeListLoading }: - { data: ShareCodeResponse[], isLoading: boolean } - = api.shareCode.findShareCodes.useQuery({ - where: { - deletedAt: null - }, - take: 8, - orderBy: { - createdAt: 'desc', - }, - }) - const {data:deletedData , isLoading:isDeletedLoading} = api.shareCode.getAllreadlyDeletedShareCodes.useQuery() - return <> - - {children} - - -}; - -export const useDashboardContext = () => { - const context = useContext(DashboardContext); - if (!context) { - throw new Error("useDashboardContext must be used within a DashboardProvider"); - } - return context; -}; \ No newline at end of file diff --git a/apps/web/src/app/admin/dashboard/DashboardLayout.tsx b/apps/web/src/app/admin/dashboard/DashboardLayout.tsx deleted file mode 100644 index 1a13b91..0000000 --- a/apps/web/src/app/admin/dashboard/DashboardLayout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import Board from './components/Board'; -import Activate from './components/Activate'; - -export default function DashboardLayout() { - return ( -
- - {/* 最近活动 */} - -
- ); -} - diff --git a/apps/web/src/app/admin/dashboard/components/Activate.tsx b/apps/web/src/app/admin/dashboard/components/Activate.tsx deleted file mode 100644 index 589a62f..0000000 --- a/apps/web/src/app/admin/dashboard/components/Activate.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Typography, Space, Skeleton } from 'antd'; -import { FileOutlined } from '@ant-design/icons'; -import DashboardCard from '@web/src/components/presentation/dashboard-card'; -import ActivityItem from './ActivityItem'; -import { useDashboardContext } from '../DashboardContext'; -import { ShareCodeResponse } from '../../quick-file/quickFileContext'; -import { useNavigate } from 'react-router-dom'; -const { Title, Text } = Typography; -export default function Activate() { - const { shareCodeList, isShareCodeListLoading } = useDashboardContext(); - const navigate = useNavigate() - const handleClick = (item: ShareCodeResponse) => { - navigate(`/manage/share-code?keyword=${encodeURIComponent(item.code)}`) - } - return <> -
- 最新上传活动 - - - {isShareCodeListLoading ? - - : - shareCodeList.map((item) => ( -
handleClick(item)}> - } - time={item.createdAt?.toLocaleString()} - ip={item.uploadIp} - filename={item.fileName} - filesize={Math.max(Number(item.resource.meta.size) / 1024 / 1024, 0.01).toFixed(2)} - code={item.code} - /> -
- ))} -
-
-
- -} \ No newline at end of file diff --git a/apps/web/src/app/admin/dashboard/components/ActivityItem.tsx b/apps/web/src/app/admin/dashboard/components/ActivityItem.tsx deleted file mode 100644 index 8dc0915..0000000 --- a/apps/web/src/app/admin/dashboard/components/ActivityItem.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { motion } from 'framer-motion'; -// 活动项组件 -interface ActivityItemProps { - icon: React.ReactNode; - time: string; - ip: string; - filename: string; - filesize: string; - code: string; -} - -export default function ActivityItem({ icon, time, ip ,filename, filesize, code }: ActivityItemProps) { - return ( - -
- {icon} -
-
-
- 来自{ip}的用户 - 上传了文件:"{filename}", - 文件大小:{filesize}MB, - 分享码:{code} -
-
{time}
-
-
- ); -} \ No newline at end of file diff --git a/apps/web/src/app/admin/dashboard/components/Board.tsx b/apps/web/src/app/admin/dashboard/components/Board.tsx deleted file mode 100644 index 10e3178..0000000 --- a/apps/web/src/app/admin/dashboard/components/Board.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { Row, Col, Typography, Space, Tooltip, Statistic, Spin } from 'antd'; -import { FileOutlined, HddOutlined, UserOutlined, CheckCircleOutlined, HistoryOutlined } from '@ant-design/icons'; -import DashboardCard from '@web/src/components/presentation/dashboard-card'; -import { useDashboardContext } from '../DashboardContext'; -import { useEffect, useState } from 'react'; -const { Title, Text } = Typography; -export default function Board() { - const { shareCodeAll, shareCodeToday, shareCodeYesterday, - isShareCodeAllLoading, isShareCodeTodayLoading, isShareCodeYesterdayLoading, - distinctUploadIPs, isDistinctUploadIPsLoading,deletedData,isDeletedLoading } = useDashboardContext(); - const [serverUptime, setServerUptime] = useState(''); - useEffect(() => { - const calculateTimeDifference = () => { - const now = new Date(); - const targetDate = new Date('2025-04-09T15:00:00'); - const diffMs = now.getTime() - targetDate.getTime(); - - // 如果是负数,表示目标日期已过 - if (diffMs < 0) { - setServerUptime('0天0小时0分钟'); - return; - } - - // 计算天数、小时数和分钟数 - const days = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); - const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); - - setServerUptime(`${days}天${hours}小时${minutes}分钟`); - }; - - calculateTimeDifference(); - // 每分钟更新一次 - const timer = setInterval(calculateTimeDifference, 60000); - - return () => clearInterval(timer); - }, []); - return <> - 仪表盘 - - {/* 总文件数卡片 */} - - - - 总文件数 - - } - className="h-full" - > -
- - { - isShareCodeTodayLoading || isShareCodeYesterdayLoading - ? - : (
- 昨天: {shareCodeYesterday?.resourceCount} 今天: {shareCodeToday?.resourceCount} -
) - } -
-
- - - {/* 存储空间卡片 */} - - - - 已使用的存储空间 - - } - className="h-full" - > -
- - { - isShareCodeTodayLoading || isShareCodeYesterdayLoading - ? - : (
- 昨天: {(shareCodeYesterday?.totalSize / 1024 / 1024).toFixed(2)}MB 今天: {(shareCodeToday?.totalSize / 1024 / 1024).toFixed(2)}MB -
) - } -
-
- - - {/* 活跃用户卡片 */} - - - - 全部用户 - - } - className="h-full" - > -
- - -
- 上周使用用户: {distinctUploadIPs?.lastWeek} 本周使用用户: {distinctUploadIPs?.thisWeek} - { - distinctUploadIPs?.lastWeek ? ( - distinctUploadIPs?.lastWeek > distinctUploadIPs?.thisWeek ? - ↓{((distinctUploadIPs?.lastWeek - distinctUploadIPs?.thisWeek) / distinctUploadIPs?.lastWeek * 100).toFixed(2)}% - : ↑ {((distinctUploadIPs?.thisWeek - distinctUploadIPs?.lastWeek) / distinctUploadIPs?.lastWeek * 100).toFixed(2)}% - ) : null - } -
-
-
- - - {/* 系统状态卡片 */} - - - - 系统状态 - - } - className="h-full" - > -
- -
- 服务器运行时间: {serverUptime} -
-
-
- - {/* 删除状态卡片 */} - {/* - - - 已清理文件数 - - } - className="h-full" - > -
- -
- 已经清理文件大小: {isDeletedLoading? 0 : `${(deletedData.totalSize / 1024 / 1024 / 1024).toFixed(2)}GB`} -
-
-
- */} -
- -} \ No newline at end of file diff --git a/apps/web/src/app/admin/quick-file/components/header.tsx b/apps/web/src/app/admin/quick-file/components/header.tsx deleted file mode 100644 index 6871577..0000000 --- a/apps/web/src/app/admin/quick-file/components/header.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useAuth } from "@web/src/providers/auth-provider"; -import { Button } from "antd"; -import { RolePerms } from "@nice/common"; -import { useNavigate } from "react-router-dom"; - -interface HeaderProps { - showLoginButton?: boolean; -} - -export const Header: React.FC = ({ showLoginButton = true }) => { - const navigate = useNavigate(); - const {isAuthenticated , hasEveryPermissions} = useAuth(); - const isAdmin = hasEveryPermissions(RolePerms.MANAGE_ANY_POST) && isAuthenticated; - - return ( -
-
- - 烽火快传 -
- - {showLoginButton && ( - - )} -
- ); -}; - -export default Header; \ No newline at end of file diff --git a/apps/web/src/app/admin/quick-file/manage.tsx b/apps/web/src/app/admin/quick-file/manage.tsx deleted file mode 100644 index 3ebb84b..0000000 --- a/apps/web/src/app/admin/quick-file/manage.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { useState, useEffect } from "react"; -import { Layout, Menu, Button, Space } from "antd"; -import { FileOutlined, DashboardOutlined, HomeOutlined, LogoutOutlined } from "@ant-design/icons"; -import { NavLink, Outlet, useNavigate, useLocation } from "react-router-dom"; -import { useAuth } from "@web/src/providers/auth-provider"; - -const { Sider, Content } = Layout; - -export default function QuickFileManage() { - const [collapsed, setCollapsed] = useState(false); - const navigate = useNavigate(); - const location = useLocation(); - const [currentKey, setCurrentKey] = useState("1"); - const { logout } = useAuth(); - - useEffect(() => { - // 根据当前URL路径设置currentKey - if (location.pathname.includes("/manage/dashboard")) { - setCurrentKey("1"); - } else if (location.pathname.includes("/manage/share-code")) { - setCurrentKey("2"); - } - }, [location.pathname]); - - return ( - - setCollapsed(value)} - theme="light" - style={{ - borderRight: "1px solid #f0f0f0", - display: "flex", - flexDirection: "column" - }} - > -
- - {!collapsed && 烽火快传} -
- , - label: 数据看板, - }, - { - key: "2", - icon: , - label: 所有文件, - }, - ]} - /> -
- - -
- - - - - - ); -} \ No newline at end of file diff --git a/apps/web/src/app/admin/quick-file/page.tsx b/apps/web/src/app/admin/quick-file/page.tsx deleted file mode 100755 index ace4874..0000000 --- a/apps/web/src/app/admin/quick-file/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ - -import { ShareCodeGenerator } from "../sharecode/sharecodegenerator"; -import { ShareCodeValidator } from "../sharecode/sharecodevalidator"; -import { Form } from "antd"; -import { TusUploader } from "@web/src/components/common/uploader/TusUploader"; -import CodeRecord from "../sharecode/components/CodeRecord"; -import Header from "./components/header"; -import { useState } from "react"; -import { MainFooter } from "../../main/layout/MainFooter"; -export default function QuickUploadPage() { - const [form] = Form.useForm(); - const uploadFileId = Form.useWatch(["file"], form)?.[0] - const [fileMsg, setFileMsg] = useState(null) - return ( -
-
-
-
-
- 使用分享码下载文件 - -
-
- -
-
- -
-
- 上传文件并生成分享码 - -
-
- -
-
-
- - { - setFileMsg(file) - }} - /> - -
-
-
-
- -
- ) - -} \ No newline at end of file diff --git a/apps/web/src/app/admin/quick-file/quickFileContext.tsx b/apps/web/src/app/admin/quick-file/quickFileContext.tsx deleted file mode 100644 index 78ded68..0000000 --- a/apps/web/src/app/admin/quick-file/quickFileContext.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { message } from "antd"; -import dayjs from "dayjs"; -import { createContext, useContext, useState } from "react"; -import { env } from "@web/src/env"; -import { api } from "@nice/client"; -interface QuickFileContextType { - saveCodeRecord: (data: ShareCodeResponse, recordName: string) => void; - handleValidSuccess: (fileUrl: string, fileName: string) => void; - isGetingFileId: boolean; - downloadCode: string | null; - setDownloadCode: (code: string | null) => void; - refetchShareCodeWithResource: () => Promise<{ data: any }>; - isLoading: boolean; - downloadResult: ShareCodeResponse | null; -} -export interface ShareCodeResponse { - id?: string; - code?: string; - fileName?: string; - expiresAt?: Date; - canUseTimes?: number; - resource?: { - id: string; - type: string; - url: string; - meta: ResourceMeta - } - uploadIp?: string; - createdAt?: Date; -} -interface ResourceMeta { - filename: string; - filetype: string; - size: string; -} - -export const QuickFileContext = createContext(null); - -export const QuickFileProvider = ({ children }: { children: React.ReactNode }) => { - const [isGetingFileId, setIsGetingFileId] = useState(false); - const [downloadCode, setDownloadCode] = useState(null); - const saveCodeRecord = (data: ShareCodeResponse, recordName: string) => { - if (data.canUseTimes == 0) { - const existingGeneratorRecords = localStorage.getItem(recordName); - if (existingGeneratorRecords && data.code) { - const generatorRecords = JSON.parse(existingGeneratorRecords); - const filteredRecords = generatorRecords.filter((item: ShareCodeResponse) => item.code !== data.code); - localStorage.setItem(recordName, JSON.stringify(filteredRecords)); - } - return; - } - const newRecord = { - id: `${Date.now()}`, // 生成唯一ID - code: data.code, - expiresAt: dayjs(data.expiresAt).format('YYYY-MM-DD HH:mm:ss'), - fileName: data.fileName || `文件_${data.code}`, - canUseTimes: data.canUseTimes, - resource: { - id: data.resource.id, - type: data.resource.type, - url: data.resource.url, - meta: { - size: data.resource.meta.size, - filename: data.resource.meta.filename, - filetype: data.resource.meta.filetype - } - }, - uploadIp: data.uploadIp, - createdAt: data.createdAt - }; - // 获取已有记录并添加新记录 - const existingGeneratorRecords = localStorage.getItem(recordName); - let generatorRecords = existingGeneratorRecords ? JSON.parse(existingGeneratorRecords) : []; - if (data.code) { - generatorRecords = generatorRecords.filter((item: ShareCodeResponse) => item.code !== data.code) - } - generatorRecords.unshift(newRecord); // 添加到最前面 - localStorage.setItem(recordName, JSON.stringify(generatorRecords)); - } - const handleValidSuccess = async (fileUrl: string, fileName: string) => { - setIsGetingFileId(true); - try { - const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${fileUrl}`; - const link = document.createElement('a'); - link.href = downloadUrl; - link.download = fileName; - link.target = '_blank'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - message.success('文件下载开始'); - } catch (error) { - console.error('下载失败:', error); - message.error('文件下载失败'); - } finally { - setIsGetingFileId(false); - } - }; - const { data: downloadResult, isLoading, refetch: refetchShareCodeWithResource } = api.shareCode.getFileByShareCode.useQuery( - { code: downloadCode?.trim() }, - { - enabled: false - } - ) - return <> - - {children} - - -}; - -export const useQuickFileContext = () => { - const context = useContext(QuickFileContext); - if (!context) { - throw new Error("useQuickFileContext must be used within a QuickFileProvider"); - } - return context; -}; \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/ShareCodeCenerator.module.css b/apps/web/src/app/admin/sharecode/ShareCodeCenerator.module.css deleted file mode 100755 index 9972735..0000000 --- a/apps/web/src/app/admin/sharecode/ShareCodeCenerator.module.css +++ /dev/null @@ -1,68 +0,0 @@ -.container { - padding: 20px; - border-radius: 8px; - background-color: #f8f9fa; - } - - .generateButton { - width: 100%; - padding: 12px; - border: none; - border-radius: 6px; - background-color: #1890ff; - color: white; - font-size: 16px; - cursor: pointer; - transition: background-color 0.3s; - } - - .generateButton:hover { - background-color: #40a9ff; - } - - .generateButton:disabled { - background-color: #d9d9d9; - cursor: not-allowed; - } - - .codeDisplay { - text-align: center; - } - - .codeWrapper { - display: flex; - align-items: center; - justify-content: center; - gap: 12px; - margin: 16px 0; - } - - .code { - font-size: 24px; - font-weight: bold; - letter-spacing: 2px; - color: #1890ff; - padding: 8px 16px; - background-color: #e6f7ff; - border-radius: 4px; - } - - .copyButton { - border: none; - background: none; - cursor: pointer; - color: #1890ff; - font-size: 18px; - padding: 4px; - } - - .expireInfo { - color: #666; - margin: 8px 0; - } - - .hint { - color: #ff4d4f; - margin: 8px 0; - font-size: 14px; - } \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/ShareCodeValidator.module.css b/apps/web/src/app/admin/sharecode/ShareCodeValidator.module.css deleted file mode 100755 index 440a145..0000000 --- a/apps/web/src/app/admin/sharecode/ShareCodeValidator.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.container { - display: flex; - gap: 12px; - padding: 20px; - border-radius: 8px; - background-color: #f8f9fa; - } - - .input { - font-size: 16px; - text-transform: uppercase; - } \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/components/CodeRecord.tsx b/apps/web/src/app/admin/sharecode/components/CodeRecord.tsx deleted file mode 100644 index 25483c3..0000000 --- a/apps/web/src/app/admin/sharecode/components/CodeRecord.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useState, useEffect } from "react"; -import { Button, Drawer, Empty } from "antd"; -import { HistoryOutlined } from "@ant-design/icons"; -import ShareCodeListCard from "../../code-manage/components/ShareCodeListCard"; -import { ShareCodeResponse, useQuickFileContext } from "../../quick-file/quickFileContext"; - -export default function CodeRecord({ title, btnContent, recordName ,styles,isDownload}: - { title: string, btnContent: string , recordName: string, styles?:string,isDownload?:boolean}) { - const [open, setOpen] = useState(false); - const [records, setRecords] = useState([]); - const {handleValidSuccess,saveCodeRecord,refetchShareCodeWithResource,setDownloadCode} = useQuickFileContext(); - - const loadRecordsFromStorage = () => { - const storedRecords = localStorage.getItem(recordName); - if (storedRecords) { - setRecords(JSON.parse(storedRecords)); - } - }; - - useEffect(() => { - loadRecordsFromStorage(); - }, [recordName]); - - const saveAndUpdateRecord = (data: ShareCodeResponse, name: string) => { - saveCodeRecord(data, name); - if (name === recordName) { - loadRecordsFromStorage(); - } - }; - - const handleOpen = () => { - loadRecordsFromStorage(); - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const handleDelete = (id: string) => { - const updatedRecords = records.filter(record => record.id !== id); - setRecords(updatedRecords); - localStorage.setItem(recordName, JSON.stringify(updatedRecords)); - }; - - const handleDownload = async (code:string,recordName:string) => { - await setDownloadCode(code) - const {data:result} = await refetchShareCodeWithResource() - console.log('下载', result); - handleValidSuccess(result.resource.url, result.fileName); - saveAndUpdateRecord(result as any as ShareCodeResponse, 'shareCodeUploadRecords'); - saveAndUpdateRecord(result as any as ShareCodeResponse, 'shareCodeDownloadRecords'); - }; - - return ( - <> - - - {records.length > 0 ? ( -
- {records.map(item => ( - handleDownload(item.code, recordName)} - /> - ))} -
- ) : ( - - )} -
- - ); -} \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx b/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx deleted file mode 100755 index 009ef89..0000000 --- a/apps/web/src/app/admin/sharecode/sharecodegenerator.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Button, DatePicker, Form, InputNumber, message } from 'antd'; -import { CopyOutlined } from '@ant-design/icons'; -import { useQueryClient } from '@tanstack/react-query'; -import { getQueryKey } from '@trpc/react-query'; -import { api } from '@nice/client'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; -import { ShareCodeResponse, useQuickFileContext } from '../quick-file/quickFileContext'; -dayjs.extend(utc); -dayjs.extend(timezone); -interface ShareCodeGeneratorProps { - fileId: string; - onSuccess?: (code: string) => void; - fileName?: string; - fileMsg?: File; -} - - -export function copyToClipboard(text) { - if (navigator.clipboard) { - return navigator.clipboard.writeText(text); - } else { - const textarea = document.createElement('textarea'); - textarea.value = text; - document.body.appendChild(textarea); - textarea.select(); - document.execCommand('copy'); - document.body.removeChild(textarea); - return Promise.resolve(); - } -} -export const ShareCodeGenerator: React.FC = ({ - fileId, - fileName, - fileMsg -}) => { - const [loading, setLoading] = useState(false); - const [shareCode, setShareCode] = useState(''); - const [expiresAt, setExpiresAt] = useState(null); - const [canUseTimes, setCanUseTimes] = useState(null); - const queryClient = useQueryClient(); - const queryKey = getQueryKey(api.shareCode); - const [isGenerate, setIsGenerate] = useState(false); - const [currentFileId, setCurrentFileId] = useState(''); - const [form] = Form.useForm(); - const { saveCodeRecord } = useQuickFileContext(); - const generateShareCode = api.shareCode.generateShareCodeByFileId.useMutation({ - onSuccess: (data) => { - queryClient.invalidateQueries({ queryKey }); - }, - }); - useEffect(() => { - if (fileId !== currentFileId || !fileId) { - setIsGenerate(false); - } - setCurrentFileId(fileId); - }, [fileId]) - const generateCode = async () => { - if (!fileId) { - message.error('请先上传文件'); - return; - } - setLoading(true); - console.log('开始生成分享码,fileId:', fileId, 'fileName:', fileName); - try { - let expiresAt = form.getFieldsValue()?.expiresAt ? form.getFieldsValue().expiresAt.tz('Asia/Shanghai').toDate() : dayjs().add(1, 'day').tz('Asia/Shanghai').toDate(); - if (fileMsg && fileMsg.size > 1024 * 1024 * 1024 * 5) { - message.info('文件大小超过5GB,系统将设置过期时间为1天', 6); - expiresAt = dayjs().add(1, 'day').tz('Asia/Shanghai').toDate(); - } - const data: ShareCodeResponse = await generateShareCode.mutateAsync({ - fileId, - expiresAt: expiresAt, - canUseTimes: form.getFieldsValue()?.canUseTimes ? form.getFieldsValue().canUseTimes : 10, - }); - console.log('data', data) - setShareCode(data.code); - setIsGenerate(true); - setExpiresAt(dayjs(data.expiresAt).format('YYYY-MM-DD HH:mm:ss')); - setCanUseTimes(data.canUseTimes); - saveCodeRecord(data, 'shareCodeGeneratorRecords'); - message.success('分享码生成成功' + data.code); - } catch (error) { - console.error('生成分享码错误:', error); - message.error('生成分享码失败: ' + (error instanceof Error ? error.message : '未知错误')); - } finally { - setLoading(false); - } - }; - - const handleCopy = (code) => { - copyToClipboard(code) - .then(() => console.log('复制成功')) - .catch(() => console.error('复制失败')); - }; - useEffect(() => { - const date = dayjs().add(1, 'day').tz('Asia/Shanghai'); - form.setFieldsValue({ - expiresAt: date, - canUseTimes: 5 - }); - }, [form]); - - useEffect(() => { - if (fileId) { - generateCode() - } - }, [fileId]) - return ( -
-
- 文件ID: {fileId ? fileId : '暂未上传文件'} -
- {!isGenerate ? ( - <> -
-
- - {"分享码的有效期"} - - - - (current && current < dayjs().startOf('day')) || - (current && current > dayjs().add(7, 'day').endOf('day')) - } - disabledTime={(current) => { - if (current && current.isSame(dayjs(), 'day')) { - return { - disabledHours: () => [...Array(dayjs().hour() + 1).keys()], - disabledMinutes: (selectedHour) => { - if (selectedHour === dayjs().hour()) { - return [...Array(dayjs().minute() + 1).keys()]; - } - return []; - } - }; - } - return {}; - }} - /> - - - {"分享码的使用次数"} - - - - -
-
- - - ) : ( -
-
- - {shareCode} - -
- {isGenerate && expiresAt ? ( -
- 有效期至: {expiresAt} 可使用次数: {canUseTimes} -
- ) : ( -
- 未获取到有效期信息 -
- )} -
- )} -
- ); -}; \ No newline at end of file diff --git a/apps/web/src/app/admin/sharecode/sharecodevalidator.tsx b/apps/web/src/app/admin/sharecode/sharecodevalidator.tsx deleted file mode 100755 index 704e5d8..0000000 --- a/apps/web/src/app/admin/sharecode/sharecodevalidator.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Input, Button, message, Spin } from 'antd'; -import styles from './ShareCodeValidator.module.css'; -import { api } from '@nice/client'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; -import timezone from 'dayjs/plugin/timezone'; -import { env } from '@web/src/env'; -import { copyToClipboard } from './sharecodegenerator'; -import { ShareCodeResponse, useQuickFileContext } from '../quick-file/quickFileContext'; -dayjs.extend(utc); -dayjs.extend(timezone); -export const ShareCodeValidator: React.FC = ({ }) => { - const { saveCodeRecord,handleValidSuccess,isGetingFileId,downloadCode,setDownloadCode,refetchShareCodeWithResource,isLoading,downloadResult } = useQuickFileContext(); - - const validateCode = useCallback(async () => { - - if (!downloadCode?.trim()) { - message.warning('请输入正确的分享码'); - return; - } - try { - const { data: latestResult } = await refetchShareCodeWithResource(); - console.log('验证分享码返回数据:', latestResult); - if (latestResult) { - saveCodeRecord(latestResult as any as ShareCodeResponse,'shareCodeDownloadRecords') - handleValidSuccess(latestResult.resource.url, latestResult.fileName); - } else { - message.error('分享码无效或已过期'); - } - } catch (error) { - console.error('验证分享码失败:', error); - message.error('分享码无效或已过期'); - } - }, [refetchShareCodeWithResource, downloadCode, handleValidSuccess]); - const getDownloadUrl = useCallback(async () => { - try { - const { data: latestResult } = await refetchShareCodeWithResource(); - saveCodeRecord(latestResult as any as ShareCodeResponse,'shareCodeDownloadRecords') - console.log('验证分享码返回数据:', latestResult); - const downloadUrl = `http://${env.SERVER_IP}:${env.FILE_PORT}/uploads/${latestResult.resource.url}`; - copyToClipboard(downloadUrl) - .then(() => { - message.success(`${latestResult.fileName}的下载链接已复制`, 6) - }) - .catch(() => { - message.error('复制失败') - }); - } catch (error) { - console.error('验证分享码失败:', error); - message.error('分享码无效或已过期'); - } - }, [refetchShareCodeWithResource, downloadCode, handleValidSuccess]); - return ( - <> - { - isGetingFileId ? - () : - ( - <> -
- setDownloadCode(e.target.value.toUpperCase())} - placeholder="请输入分享码" - maxLength={8} - onPressEnter={validateCode} - /> - - -
- { - !isLoading && downloadResult && ( -
- {`分享码:${downloadResult?.code ? downloadResult.code : ''}`} - {`文件名:${downloadResult?.fileName ? downloadResult.fileName : ''}`} - {`过期时间:${downloadResult?.expiresAt ? dayjs(downloadResult.expiresAt).format('YYYY-MM-DD HH:mm:ss') : ''}`} - {`剩余使用次数:${downloadResult?.canUseTimes ? downloadResult.canUseTimes : 0}`} -
- ) - } - - ) - } - - ); -}; \ No newline at end of file diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 52f865f..6a885df 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -1,21 +1,10 @@ import { createBrowserRouter, IndexRouteObject, - Link, - Navigate, NonIndexRouteObject, - useParams, } from "react-router-dom"; import ErrorPage from "../app/error"; import LoginPage from "../app/login"; -import WithAuth from "../components/utils/with-auth"; -import { CodeManageProvider } from "../app/admin/code-manage/CodeManageContext"; -import CodeManageLayout from "../app/admin/code-manage/CodeManageLayout"; -import QuickUploadPage from "../app/admin/quick-file/page"; -import { QuickFileProvider } from "../app/admin/quick-file/quickFileContext"; -import QuickFileManage from "../app/admin/quick-file/manage"; -import { DashboardProvider } from "../app/admin/dashboard/DashboardContext"; -import DashboardLayout from "../app/admin/dashboard/DashboardLayout"; interface CustomIndexRouteObject extends IndexRouteObject { name?: string; breadcrumb?: string; @@ -39,11 +28,7 @@ export type CustomRouteObject = export const routes: CustomRouteObject[] = [ { path: "/", - element: <> - - - - , + element: <>, errorElement: , }, { @@ -51,46 +36,7 @@ export const routes: CustomRouteObject[] = [ breadcrumb: "登录", element: , }, - { - path: "/code-manage", - element: <> - - - - - - - }, - { - path: "/manage", - element: <> - - - - , - children: [ - { - index: true, - element: - }, - { - path: "/manage/share-code", - element: <> - - - - - }, - { - path: "/manage/dashboard", - element: <> - - - - - } - ] - } + ]; export const router = createBrowserRouter(routes); diff --git a/config/nginx/conf.d/web.conf b/config/nginx/conf.d/web.conf index 8f9683a..2dd9d9e 100755 --- a/config/nginx/conf.d/web.conf +++ b/config/nginx/conf.d/web.conf @@ -2,7 +2,7 @@ server { # 监听80端口 listen 80; # 服务器域名/IP地址,使用环境变量 - server_name 192.168.43.206; + server_name 192.168.252.77; # 基础性能优化配置 # 启用tcp_nopush以优化数据发送 @@ -100,7 +100,7 @@ server { # 仅供内部使用 internal; # 代理到认证服务 - proxy_pass http://192.168.43.206:3006/auth/file; + proxy_pass http://192.168.252.77:3000/auth/file; # 请求优化:不传递请求体 proxy_pass_request_body off; diff --git a/packages/client/src/api/hooks/index.ts b/packages/client/src/api/hooks/index.ts index 73b516d..ffe1cf5 100755 --- a/packages/client/src/api/hooks/index.ts +++ b/packages/client/src/api/hooks/index.ts @@ -6,7 +6,6 @@ export * from "./useRole" export * from "./useRoleMap" export * from "./useTransform" export * from "./useTaxonomy" -export * from "./useVisitor" export * from "./useMessage" export * from "./usePost" export * from "./useEntity" diff --git a/packages/client/src/api/hooks/useVisitor.ts b/packages/client/src/api/hooks/useVisitor.ts deleted file mode 100755 index 30e4e5e..0000000 --- a/packages/client/src/api/hooks/useVisitor.ts +++ /dev/null @@ -1,178 +0,0 @@ -import { api } from "../trpc"; -import { PostParams } from "../../singleton/DataHolder"; - -export function useVisitor() { - const utils = api.useUtils(); - const postParams = PostParams.getInstance(); - - const create = api.visitor.create.useMutation({ - onSuccess() { - utils.visitor.invalidate(); - - // utils.post.invalidate(); - }, - }); - /** - * 通用的乐观更新mutation工厂函数 - * @param updateFn 更新数据的具体逻辑函数 - * @returns 封装后的mutation配置对象 - */ - const createOptimisticMutation = ( - updateFn: (item: any, variables: any) => any - ) => ({ - //在请求发送前执行本地数据预更新 - onMutate: async (variables: any) => { - const previousDataList: any[] = []; - const previousDetailDataList: any[] = []; - - // 处理列表数据 - const paramsList = postParams.getItems(); - for (const params of paramsList) { - await utils.post.findManyWithCursor.cancel(); - const previousData = - utils.post.findManyWithCursor.getInfiniteData({ - ...params, - }); - previousDataList.push(previousData); - utils.post.findManyWithCursor.setInfiniteData( - { - ...params, - }, - (oldData) => { - if (!oldData) return oldData; - return { - ...oldData, - pages: oldData.pages.map((page) => ({ - ...page, - items: (page.items as any).map((item) => - item.id === variables?.postId - ? updateFn(item, variables) - : item - ), - })), - }; - } - ); - } - - // 处理详情数据 - const detailParamsList = postParams.getDetailItems(); - for (const params of detailParamsList) { - await utils.post.findFirst.cancel(); - const previousDetailData = utils.post.findFirst.getData(params); - previousDetailDataList.push(previousDetailData); - utils.post.findFirst.setData(params, (oldData) => { - if (!oldData) return oldData; - return oldData.id === variables?.postId - ? updateFn(oldData, variables) - : oldData; - }); - } - - return { previousDataList, previousDetailDataList }; - }, - // 错误处理:数据回滚 - onError: (_err: any, _variables: any, context: any) => { - const paramsList = postParams.getItems(); - paramsList.forEach((params, index) => { - if (context?.previousDataList?.[index]) { - utils.post.findManyWithCursor.setInfiniteData( - { ...params }, - context.previousDataList[index] - ); - } - }); - }, - // 成功后的缓存失效 - onSuccess: async (_: any, variables: any) => { - await Promise.all([ - utils.visitor.invalidate(), - utils.post.findFirst.invalidate({ - where: { - id: (variables as any)?.postId, - }, - }), - utils.post.findManyWithCursor.invalidate(), - ]); - }, - }); - // 定义具体的mutation - const read = api.visitor.create.useMutation( - createOptimisticMutation((item) => ({ - ...item, - views: (item.views || 0) + 1, - readed: true, - })) - ); - const like = api.visitor.create.useMutation( - createOptimisticMutation((item) => ({ - ...item, - likes: (item.likes || 0) + 1, - liked: true, - })) - ); - const unLike = api.visitor.deleteMany.useMutation( - createOptimisticMutation((item) => ({ - ...item, - likes: item.likes - 1 || 0, - liked: false, - })) - ); - - const hate = api.visitor.create.useMutation( - createOptimisticMutation((item) => ({ - ...item, - hates: (item.hates || 0) + 1, - hated: true, - })) - ); - const unHate = api.visitor.deleteMany.useMutation( - createOptimisticMutation((item) => ({ - ...item, - hates: item.hates - 1 || 0, - hated: false, - })) - ); - - const addStar = api.visitor.create.useMutation( - createOptimisticMutation((item) => ({ - ...item, - star: true, - })) - ); - - const deleteStar = api.visitor.deleteMany.useMutation( - createOptimisticMutation((item) => ({ - ...item, - star: false, - })) - ); - - const deleteMany = api.visitor.deleteMany.useMutation({ - onSuccess() { - utils.visitor.invalidate(); - }, - }); - - const createMany = api.visitor.createMany.useMutation({ - onSuccess() { - utils.visitor.invalidate(); - utils.message.invalidate(); - utils.post.invalidate(); - }, - }); - - return { - postParams, - create, - createMany, - deleteMany, - read, - addStar, - deleteStar, - like, - unLike, - hate, - unHate, - }; -}