NestJS实战05-完成当日目标和长期目标模块

by 雪隐 from juejin.cn/user/143341...

本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

概要

上一章介绍了如何在NestJS中使用Moogoose,这一章继续完成当日任务和长期目标的2个模块。

当日任务和长期目标的功能介绍

  • 首页 首页我的想法是一个大的日历展示,然后每个日期里面每日任务的标题内容。绿色的点是已完成,黄色的点代买没有完成。

  • 当日任务页面 当日任务有增删改查,还有复制前一日的任务,批量删除等。

  • 长期目标页面 长期目标的内容主要是一些增删改查和批量删除等。

当日任务模块

  • 根据上面的内容展示可以知道,当日任务的主要功能有:
  1. 传统的增删改查。
  2. 批量删除
  3. 根据年月取得某个月的任务
  4. 根据某个日期取得某天的任务
  5. 根据日期复制任务到当天
  • 表的模型如下:

这里为 title添加设置了索引@Prop({ index: true }),是为了以后可能会扩展应用,发生检索标题的情况。另外,给isCompleted设置了默认初期值,默认状态是任务未完成的状态。

ts 复制代码
// task.entity.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type TaskDocument = Task & Document;

@Schema({ timestamps: true }) // 使用timestamps自动生成createdAt和updatedAt字段
export class Task {

    // 任务名字
    @Prop({ index: true }) // 为title字段添加索引,提高查询速度
    title: string;

    // 任务内容
    @Prop()
    content: string;

    // 是否完成
    @Prop({ default: false })
    isCompleted: boolean;
}

export const TaskSchema = SchemaFactory.createForClass(Task);
  • controller层如下:

基本上就是调用下,服务层代码就不多做介绍了。

ts 复制代码
// tasks.controller.ts
// 省略导入内容。。。。

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  @Post()
  create(@Body() createTaskDto: CreateTaskDto) {
    return this.tasksService.create(createTaskDto);
  }

  @Get()
  findAll() {
    return this.tasksService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.tasksService.findOne(id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateTaskDto: UpdateTaskDto) {
    return this.tasksService.update(id, updateTaskDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string): Promise<mongo.DeleteResult> {
    return this.tasksService.remove(id);
  }

  @Post('search')
  search(@Body() searchTaskDto: SearchTaskDto) {
    return this.tasksService.search(searchTaskDto);
  }

  @Delete('delete/batch')
  @HttpCode(204) // 无内容的响应状态码
  async removeMany(@Body('ids') ids: string[]): Promise<mongo.DeleteResult> {
    return this.tasksService.removeMany(ids);
  }

  @Get('by-month/:year/:month')
  async getTasksByMonth(@Param() monthYearTaskDto: MonthYearTaskDto) {
    const tasks = await this.tasksService.getTasksByYearAndMonth(
      monthYearTaskDto,
    );
    return tasks;
  }

  @Post('by-date')
  async getTasksByDate(@Body() dateTaskDto: DateTaskDto) {
    const tasks = await this.tasksService.getTasksByDate(dateTaskDto);
    return tasks;
  }

  @Post('copy-date')
  async copyTasksToDate(@Body() dateTaskDto: DateTaskDto) {
    const tasks = await this.tasksService.copyTasksToDate(dateTaskDto);
    return tasks;
  }
}
  • service层如下:
  1. 导入 Mongoose :在代码的开头,可以看到通过 import { InjectModel } from '@nestjs/mongoose'; 导入了 Mongoose 模块。这是用于与 MongoDB 数据库进行交互的关键。
  2. 模型创建 :在构造函数中,你使用了 @InjectModel(Task.name) 装饰器来注入了一个 Mongoose 模型(taskModel)。这个模型是基于 Task 实体的,它充当了任务数据在应用中的接口。
  3. 创建任务 :在 create 方法中,使用了 this.taskModel.create(createTaskDto) 来创建一个新的任务文档,并返回了一个 Promise。这是 Mongoose 提供的用于在数据库中插入新文档的方法。如果插入失败,抛出了自定义异常。
  4. 查询任务 :在 findAllfindOne 方法中,使用 this.taskModel.find()this.taskModel.findById() 来查询数据库中的任务。这些方法是 Mongoose 提供的查询工具,允许你按条件检索文档。
  5. 更新任务 :在 update 方法中,使用了 this.taskModel.findByIdAndUpdate() 方法来更新任务。这是 Mongoose 提供的方法,用于按照给定的条件更新文档。
  6. 删除任务 :在 remove 方法中,使用了 this.taskModel.deleteOne() 方法来删除任务文档。这也是 Mongoose 提供的一个用于删除文档的方法。
  7. 聚合操作 :在 getTasksByYearAndMonth 方法中,使用了 this.taskModel.aggregate() 方法来执行聚合操作,以按日期分组任务。这是 Mongoose 中用于执行聚合查询的功能,可以用于更复杂的数据处理需求。
  8. 自定义异常 :在各个方法中都使用了自定义异常类 BusinessException 来处理错误情况。确保应用程序能够更好地处理和报告错误。
ts 复制代码
// tasks.service.ts
// 省略导入内容。。。。

@Injectable()
export class TasksService {
  constructor(@InjectModel(Task.name) private taskModel: Model<Task>) {}

  async create(createTaskDto: CreateTaskDto): Promise<Task> {
    const task = await this.taskModel.create(createTaskDto);
    if (!task) {
      throw new BusinessException('当日任务创建失败');
    }
    return task;
  }

  async findAll(): Promise<Task[]> {
    const tasks = await this.taskModel.find().exec();
    return tasks;
  }

  async findOne(id: string): Promise<Task> {
    const task = await this.taskModel.findById(id).exec();
    if (!task) {
      throw new BusinessException('未找到指定的任务');
    }
    return task;
  }

  async update(id: string, updateTaskDto: UpdateTaskDto): Promise<Task> {
    const updatedTask = await this.taskModel
      .findByIdAndUpdate(id, updateTaskDto, { new: true })
      .exec();
    if (!updatedTask) {
      throw new BusinessException('任务更新失败');
    }
    return updatedTask;
  }

  async remove(id: string): Promise<mongo.DeleteResult> {
    const remove = await this.taskModel.deleteOne({ _id: id }).exec();
    if (remove.deletedCount === 0) {
      throw new BusinessException('未找到要删除的任务');
    }
    return remove;
  }

  async search(
    searchTaskDto: SearchTaskDto,
  ): Promise<{ data: Task[]; total: number }> {
    const { startDate, endDate, page = 1, limit = 20 } = searchTaskDto;

    const queryCondition = {};

    // 如果提供了startDate和endDate,才加入查询条件
    if (startDate && endDate) {
      queryCondition['createdAt'] = {
        $gte: new Date(startDate + 'T00:00:00Z'), // 开始于这一天的开始
        $lte: new Date(endDate + 'T23:59:59Z'), // 结束于这一天的结束
      };
    }

    // 查询特定时间范围内的任务数量,为前端提供总页数计算的数据
    const total = await this.taskModel.countDocuments(queryCondition);

    // 根据 createdAt 字段查询任务并实现分页
    const tasks = await this.taskModel
      .find(queryCondition)
      .skip((page - 1) * limit) // 跳过之前页的任务
      .limit(limit) // 限制返回的任务数量
      .exec();

    return {
      data: tasks,
      total,
    };
  }

  async removeMany(ids: string[]): Promise<mongo.DeleteResult> {
    const res = await this.taskModel.deleteMany({ _id: { $in: ids } }).exec();
    if (res.deletedCount === 0) {
      throw new BusinessException('没有找到要删除的任务');
    }
    return res;
  }

  /**
   * 根据年月取得任务
   * @param year
   * @returns
   */
  async getTasksByYearAndMonth(monthYearTaskDto: MonthYearTaskDto) {
    const { year, month } = monthYearTaskDto;
    // 计算查询的起始日期和结束日期
    const startDate = new Date(year, month - 1, 1); // 月份从0开始,所以要减去1
    const endDate = new Date(year, month, 0); // 月份从0开始,最后一天是当月0号

    // 使用聚合操作按日期分组数据
    const result = await this.taskModel.aggregate([
      {
        $match: {
          createdAt: {
            $gte: startDate,
            $lte: endDate,
          },
        },
      },
      {
        $group: {
          _id: { $dayOfMonth: '$createdAt' },
          tasks: {
            $push: {
              type: {
                $cond: {
                  if: { $eq: ['$isCompleted', true] }, // 如果 isCompleted 为 true,则为 success,否则为 warning
                  then: 'success',
                  else: 'warning',
                },
              },
              content: '$title', // 您可能需要根据实际存储的字段名进行更改
            },
          },
        },
      },
      {
        $sort: {
          _id: 1,
        },
      },
    ]);

    // 将结果转换为所需的格式
    const formattedResult = {};
    result.forEach((item) => {
      formattedResult[item._id] = item.tasks;
    });

    return formattedResult;
  }

  async getTasksByDate(dateTaskDto: DateTaskDto) {
    const { date } = dateTaskDto;
    // 使用聚合操作按日期分组数据
    const result = await this.taskModel.aggregate([
      {
        $match: {
          createdAt: {
            $gte: new Date(
              dayjs(date).year(),
              dayjs(date).month(),
              dayjs(date).date(),
              0,
              0,
              0,
            ), // 开始于指定日期的开始
            $lte: new Date(
              dayjs(date).year(),
              dayjs(date).month(),
              dayjs(date).date(),
              23,
              59,
              59,
            ), // 结束于指定日期的结束
          },
        },
      },
      {
        $project: {
          type: { $ifNull: ['$type', 'normal'] }, // 如果没有类型字段,默认为'normal'
          title: '$title', // 根据实际字段进行更改
          content: '$content', // 根据实际字段进行更改
        },
      },
    ]);

    return result;
  }

  async copyTasksToDate(dateTaskDto: DateTaskDto): Promise<void> {
    // 获取特定日期的任务列表
    const tasks = await this.getTasksByDate(dateTaskDto);

    // 遍历任务列表并复制到今天
    for (const task of tasks) {
      const createTaskDto: CreateTaskDto = {
        title: task.title,
        content: task.content,
        isCompleted: false, // 如果需要复制完成状态,请设置为 task.isCompleted
      };

      // 创建新任务并保存到数据库
      await this.create(createTaskDto);
    }
  }
}

长期任务模块

长期任务的主要功能就是,由于实现方法和当日任务差不多,除了表模型以外。大家直接看代码就行了,这里不多做介绍了。

  1. 传统增删改查。
  2. 批量删除。
  • 表的模型如下:
ts 复制代码
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type LongTermGoalDocument = LongTermGoal & Document;

@Schema({ timestamps: true }) // 使用timestamps自动生成createdAt和updatedAt字段
export class LongTermGoal {

    // 目标标题
    @Prop({ index: true }) // 为title字段添加索引,提高查询速度
    title: string;

    // 目标内容
    @Prop()
    content: string;

    // 目标截止日期
    @Prop()
    deadline: Date;

    // 是否完成
    @Prop({ default: false })
    isCompleted: boolean;

    // 是否已警告(例如,如果超过了截止日期)
    @Prop({ default: false })
    isWarned: boolean;
}

export const LongTermGoalSchema = SchemaFactory.createForClass(LongTermGoal);

前端页面做成

由于前端不是本章的重点,就不错做介绍了,随便做的,可以用就行了。前端页面的代码里面back-base请自提。

本地运行

  • 先运行Mongo的docker容器(前一章容器的yml已经提供给大家了)
shall 复制代码
docker-compose up -d
  • 打开back-base代码,在根目录运行命令:
shall 复制代码
npm run start:dev
  • 打开front-base代码,在根目录运行命令:
shall 复制代码
npm run dev

总结

这一章比较简单,把当日任务和长期目标2个模块做好了。如果这篇文章对大家有帮助请点赞和评论🙏!

相关推荐
fishmemory7sec4 分钟前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec7 分钟前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆1 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建
JUNAI_Strive_ving1 小时前
番茄小说逆向爬取
javascript·python
看到请催我学习1 小时前
如何实现两个标签页之间的通信
javascript·css·typescript·node.js·html5
twins35202 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky2 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~2 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
哪 吒2 小时前
华为OD机试 - 几何平均值最大子数(Python/JS/C/C++ 2024 E卷 200分)
javascript·python·华为od
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺