From bfd2cb8f63f5b3f0d7329cc49e40536dc7ec5d60 Mon Sep 17 00:00:00 2001 From: minorcell Date: Sat, 17 May 2025 18:25:26 +0800 Subject: [PATCH] feat: add time range and active status to articles and user name to recruitment --- src/article/article.service.ts | 27 +++++++++++++++++++ src/article/dto/query-article.dto.ts | 10 +++++++ src/article/dto/update-article.dto.ts | 25 ++++++++++++++++- src/article/entities/article.entity.ts | 25 +++++++++++++++++ src/recruitment/dto/create-recruitment.dto.ts | 7 +++++ src/recruitment/dto/query-recruitment.dto.ts | 7 +++++ .../dto/recruitment-response.dto.ts | 6 +++++ src/recruitment/entities/recruiment.entity.ts | 8 ++++++ src/recruitment/recruitment.service.ts | 8 ++++++ 9 files changed, 122 insertions(+), 1 deletion(-) diff --git a/src/article/article.service.ts b/src/article/article.service.ts index 7137084..ed1f280 100644 --- a/src/article/article.service.ts +++ b/src/article/article.service.ts @@ -43,6 +43,7 @@ export class ArticleService { const article = this.articleRepository.create({ ...createArticleDto, userId, + isActive: false, // 创建时默认为未激活状态 }); const savedArticle = await this.articleRepository.save(article); return new ArticleResponseDto(savedArticle); @@ -64,6 +65,7 @@ export class ArticleService { articleAuthor, startTime, endTime, + showAll = false, // 默认只显示激活的文章 } = query; const skip = (page - 1) * limit; @@ -95,6 +97,14 @@ export class ArticleService { ); } + // 如果不是显示所有,只显示激活的文章 + if (!showAll) { + queryBuilder.andWhere('article.isActive = :isActive', { isActive: true }) + .andWhere('(article.startTime IS NULL OR article.startTime <= :now)') + .andWhere('(article.endTime IS NULL OR article.endTime >= :now)') + .setParameter('now', new Date()); + } + // 执行查询并获取结果 const [items, total] = await queryBuilder .orderBy('article.createTime', 'DESC') @@ -170,6 +180,23 @@ export class ArticleService { throw new ForbiddenException('您没有权限修改这篇文章'); } + // 如果要修改文章状态、开始时间或结束时间,必须是管理员 + if ((updateArticleDto.isActive !== undefined || + updateArticleDto.startTime !== undefined || + updateArticleDto.endTime !== undefined) && + !isAdmin) { + throw new ForbiddenException('只有管理员可以修改文章状态和时间设置'); + } + + // 如果设置了开始和结束时间,验证时间范围 + if (updateArticleDto.startTime && updateArticleDto.endTime) { + const startTime = new Date(updateArticleDto.startTime); + const endTime = new Date(updateArticleDto.endTime); + if (startTime >= endTime) { + throw new ForbiddenException('结束时间必须晚于开始时间'); + } + } + const updatedArticle = await this.articleRepository.save({ ...article, ...updateArticleDto, diff --git a/src/article/dto/query-article.dto.ts b/src/article/dto/query-article.dto.ts index dcab062..768696c 100644 --- a/src/article/dto/query-article.dto.ts +++ b/src/article/dto/query-article.dto.ts @@ -3,6 +3,7 @@ import { IsInt, IsString, IsDateString, + IsBoolean, Min, Max, } from 'class-validator'; @@ -37,4 +38,13 @@ export class QueryArticleDto { @IsOptional() @IsDateString({}, { message: '结束时间格式不正确,应为ISO格式的日期字符串' }) endTime?: string; + + /** + * 是否显示所有文章,包括未激活的 + * 只有管理员可以设置为true + */ + @IsOptional() + @IsBoolean({ message: '显示所有文章必须是布尔值' }) + @Type(() => Boolean) + showAll?: boolean = false; } diff --git a/src/article/dto/update-article.dto.ts b/src/article/dto/update-article.dto.ts index 64ab108..f0f91d8 100644 --- a/src/article/dto/update-article.dto.ts +++ b/src/article/dto/update-article.dto.ts @@ -1,6 +1,7 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateArticleDto } from './create-article.dto'; -import { IsOptional, IsString, Length, MaxLength } from 'class-validator'; +import { IsOptional, IsString, Length, MaxLength, IsBoolean, IsDateString } from 'class-validator'; +import { Type } from 'class-transformer'; export class UpdateArticleDto extends PartialType(CreateArticleDto) { @IsOptional() @@ -29,4 +30,26 @@ export class UpdateArticleDto extends PartialType(CreateArticleDto) { @IsOptional() @IsString({ message: '文章内容必须是字符串' }) articleContent?: string; + + /** + * 文章状态 + */ + @IsOptional() + @IsBoolean({ message: '文章状态必须是布尔值' }) + @Type(() => Boolean) + isActive?: boolean; + + /** + * 开始时间 + */ + @IsOptional() + @IsDateString({}, { message: '开始时间格式不正确,应为ISO格式的日期字符串' }) + startTime?: Date; + + /** + * 结束时间 + */ + @IsOptional() + @IsDateString({}, { message: '结束时间格式不正确,应为ISO格式的日期字符串' }) + endTime?: Date; } diff --git a/src/article/entities/article.entity.ts b/src/article/entities/article.entity.ts index d9b0d69..6159545 100644 --- a/src/article/entities/article.entity.ts +++ b/src/article/entities/article.entity.ts @@ -88,6 +88,31 @@ export class ArticleEntity { }) articleContent: string; + // 开始时间 + @Column({ + type: 'datetime', + nullable: true, + name: 'start_time' + }) + startTime: Date; + + // 结束时间 + @Column({ + type: 'datetime', + nullable: true, + name: 'end_time' + }) + endTime: Date; + + // 状态。是否开始 + @Column({ + type: 'bool', + nullable: false, + name: 'is_active', + default: false + }) + isActive: boolean + // 创建日期 @CreateDateColumn({ type: 'datetime', diff --git a/src/recruitment/dto/create-recruitment.dto.ts b/src/recruitment/dto/create-recruitment.dto.ts index 55ad9ee..7877796 100644 --- a/src/recruitment/dto/create-recruitment.dto.ts +++ b/src/recruitment/dto/create-recruitment.dto.ts @@ -45,4 +45,11 @@ export class CreateRecruitmentDto { @IsNotEmpty({ message: '简历文件地址不能为空' }) @IsOptional() resumeFilePath?: string; + + /** + * 投递者姓名 + */ + @IsString({ message: '姓名必须是字符串' }) + @IsNotEmpty({ message: '姓名不能为空' }) + userName: string; } diff --git a/src/recruitment/dto/query-recruitment.dto.ts b/src/recruitment/dto/query-recruitment.dto.ts index 20b0dae..d791c5d 100644 --- a/src/recruitment/dto/query-recruitment.dto.ts +++ b/src/recruitment/dto/query-recruitment.dto.ts @@ -82,4 +82,11 @@ export class QueryRecruitmentDto { @IsOptional() @IsString({ message: '电话号码必须是字符串' }) phone?: string; + + /** + * 投递者姓名(支持模糊查询) + */ + @IsOptional() + @IsString({ message: '姓名必须是字符串' }) + userName?: string; } diff --git a/src/recruitment/dto/recruitment-response.dto.ts b/src/recruitment/dto/recruitment-response.dto.ts index db96a17..8824abd 100644 --- a/src/recruitment/dto/recruitment-response.dto.ts +++ b/src/recruitment/dto/recruitment-response.dto.ts @@ -66,6 +66,11 @@ export class RecruitmentResponseDto { */ phone: string; + /** + * 投递者姓名 + */ + userName: string; + constructor(recruitment: RecruitmentEntity) { this.officialResumeId = recruitment.officialResumeId; this.userId = recruitment.userId; @@ -81,6 +86,7 @@ export class RecruitmentResponseDto { this.updateTime = recruitment.updateTime; this.email = recruitment.email; this.phone = recruitment.phone; + this.userName = recruitment.userName; } /** diff --git a/src/recruitment/entities/recruiment.entity.ts b/src/recruitment/entities/recruiment.entity.ts index 948a4e8..35c9ce6 100644 --- a/src/recruitment/entities/recruiment.entity.ts +++ b/src/recruitment/entities/recruiment.entity.ts @@ -23,6 +23,14 @@ export class RecruitmentEntity { }) userId: number; + // 投递者名字 + @Column({ + name: 'user_name', + nullable: false, + type: 'string' + }) + userName: string; + // 求职类型 @Column({ name: 'recr_type', diff --git a/src/recruitment/recruitment.service.ts b/src/recruitment/recruitment.service.ts index e33bc03..7d4d6e0 100644 --- a/src/recruitment/recruitment.service.ts +++ b/src/recruitment/recruitment.service.ts @@ -61,6 +61,7 @@ export class RecruitmentService { endTime, email, phone, + userName, } = query; const skip = (page - 1) * limit; @@ -94,6 +95,13 @@ export class RecruitmentService { }); } + // 根据投递者姓名过滤 + if (userName) { + queryBuilder.andWhere('recruitment.userName LIKE :userName', { + userName: `%${userName}%`, + }); + } + // 根据时间范围过滤 if (startTime && endTime) { queryBuilder.andWhere(