likes
comments
collection
share

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

作者站长头像
站长
· 阅读数 43

目标

写一个表格,支持添加、查询、删除、修改表格数据,同时也支持翻页

准备

  1. 数据库用mysql

  2. 数据可视化工具用vscode的插件

    【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

  3. 操作数据库用前端的 TypeOrm 。它是TypeScript中最成熟的对象关系映射器(ORM)。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成

  4. 将 Nest 与数据库连接起来。在app.module.ts中,使用 imports 引入 TypeOrmModule

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

  1. 准备实体文件,例如 user.entity.ts。它是 Nest 与数据库通信的桥梁,本质上是一个映射到数据库表的类。
  • 在 user.entity.ts 中用 typeorm 的一系列装饰器,定义数据库将来会有的数据,例如下图的 id、username、pwd、createTime 字段

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

  • 将实体文件 user.entity.ts 与数据库关联:在 user.module.ts 中,在imports中,导入 User 实体类和 TypeOrmModule

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

数据库生成如下:

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

后端

在 user.service.ts 中,引入实体、typeorm,把他们和服务 Service 关联起来后,就能利用typeorm的API向数据中存数据、读数据

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

控制器

在控制器中使用一些列装饰器分别对应请求,并将请求数据提取后,交给服务去处理;服务返回的结果,会交还给控制器,它再响应给浏览器

  • user.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete, Query } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

interface SearchParams {
  keyWord?: string
  page?: number
  pageSize?: number
}

@Controller('user')
export class UserController {
  constructor(private readonly userService: UserService) { }

  // 接收 POST
  @Post()
  create(@Body() createUserDto: CreateUserDto) {// 用 @Body() 提取请求体
    return this.userService.create(createUserDto);
  }

  // 接收 GET
  @Get()
  findAll(@Query() query: SearchParams) { // 用 @Query() 提取查询参数
    return this.userService.findAll(query);
  }

  // 接收 Delete
  @Delete(':id')
  remove(@Param('id') id: string) {// 用 @Query() 提取斜杠后的参数
    return this.userService.remove(Number(id));
  }

  // 接收 Patch
  @Patch(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    // 用 @Param('id') 提取斜杠后的参数; 用 @Body() 提取请求体
    return this.userService.update(Number(id), updateUserDto);
  }

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

服务

在服务中,接收数据,使用 typeorm 操作数据库

  • user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

import { Repository, Like } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm'; // 导入装饰器
import { User } from './entities/user.entity' // 导入实体

interface SearchParams {
  keyWord?: string
  page?: number
  pageSize?: number
}

@Injectable()
export class UserService {
  //! 用 InjectRepository 装饰 user
  constructor(@InjectRepository(User) private readonly user: Repository<User>) {
    // 
  }

  // A. 添加新用户
  create(createUserDto: CreateUserDto) {
    // 1. 实例化实体 User
    const obj = new User()

    // 2. 把前端传来的数据添加到实体对象上
    obj.username = createUserDto.username
    obj.pwd = createUserDto.pwd

    // 3. 保存到数据库,并作为响应返回
    return this.user.save(obj)
  }

  // B. 搜索用户(模糊查询)
  async findAll(query: SearchParams) { // 参数 query 就是用户的查询参数
    
    const searchObj = {
      // 根据关键词模糊查询
      where: {
        username: Like(`%${query.keyWord}%`)
      },
      // 根据分页信息查询
      skip: (query.page - 1) * query.pageSize, // 从第x页开始,需要x条数据
      take: query.pageSize
    }

    // 总数据条数
    const totalNum = {
      where: {
        username: Like(`%${query.keyWord}%`)
      }
    }

    const data = await this.user.find(searchObj)
    const total = await this.user.count(totalNum)

    // 从数据库中获取结果,并作为响应返回
    return {
      data,
      total
    }
  }

  // C. 删除用户
  remove(id: number) {
    // 从数据库中删除,并作为响应返回
    return this.user.delete(id)
  }

  // D. 更新用户
  update(id: number, updateUserDto: UpdateUserDto) {
    // 将id和新数据放入数据库,并作为响应返回
    return this.user.update(id, updateUserDto)
  }

  findOne(id: number) {
    return `This action returns a #${id} user`;
  }
}

前端

整体如下:表格 + 分页 + 搜索框 + 添加按钮

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

【NestJS实战】前端Vue+后端Nest,实现你的第一个CURD目标 写一个表格,支持添加、查询、删除、修改表格数据

  • 请求封装
import axios from 'axios'

axios.defaults.baseURL = 'http://localhost:3000'

export const addUser = (data) => {
    return axios.post('/user', data).then(res => res.data)
}

export const getList = (data) => {
    return axios.get('/user', { params: data }).then(res => res.data)
}

export const delUser = (data) => {
    return axios.delete(`/user/${data.id}`).then(res => res.data)
}

export const updateUser = (data) => {
    return axios.patch(`/user/${data.id}`, data).then(res => res.data)
} 
  • 结构
<template>
    <div class="wraps">
        <!-- 搜索栏 -->
        <div>
            <h2 class="title">用户管理:增、删、改、查</h2>
            <el-input v-model="search.keyWord" style="width:300px;" placeholder="请输入用户名"></el-input>
            <el-button @click="init" style="margin-left:10px;">搜索</el-button>
            <el-button @click="openDialog" type="primary" style="margin-left: 527px;">添加新用户</el-button>
        </div>

        <!-- 表格 -->
        <el-table border :data="tableData" style="width: 100%; margin-top: 30px;">
            <el-table-column prop="username" label="用户名" />
            <el-table-column prop="pwd" label="密码" />
            <el-table-column prop="id" label="id" />
            <el-table-column>
                <template #default="scope">
                    <el-button @click="edit(scope.row)">编辑</el-button>
                    <el-button @click="deleteRow(scope.row)">删除</el-button>
                </template>
            </el-table-column>
        </el-table>

        <!-- 分页 -->
        <el-pagination
            @current-change="changeSize"
            background
            layout="prev, pager, next"
            :total="total" 
            style="float:right; margin-top:10px;"
        />
    </div>

    <!-- 新增和编辑的弹框 -->
    <el-dialog v-model="dialogVisible" :title="title" width="30%">
        <el-form :model="form">
            <el-form-item prop="username" label="用户名">
                <el-input v-model="form.username" placeholder="用户名" />
            </el-form-item>
            <el-form-item prop="pwd" label="密码">
                <el-input v-model="form.pwd" placeholder="密码">
                </el-input>
            </el-form-item>
        </el-form>
        <template #footer>
            <span class="dialog-footer">
                <el-button @click="close">关闭</el-button>
                <el-button type="primary" @click="save">
                    保存
                </el-button>
            </span>
        </template>
    </el-dialog>
</template>

<style lang='less'>
* {
    padding: 0;
    margin: 0;
}

html,
body {
    background: #ccc;
}

.wraps {
    width: 1000px;
    height: 600px;
    padding: 30px;

    .title {
        margin-bottom: 30px;
    }
}
</style>
  • CURD的js
<script setup>
import { ref, reactive, computed } from 'vue'
import { addUser, updateUser, delUser, getList } from './http'
import { ElMessage, ElMessageBox } from 'element-plus'

// 表格数据
const tableData = ref([])
// 数据总条数
const total = ref(0)
// 搜索框
const search = reactive({
    keyWord: "",
    page: 1,
    pageSize: 10
})
// 表单
const form = reactive({
    username: "",
    pwd: "",
    id: 0
})
const title = computed(() => {
    return  form.id ? '编辑用户' : '新增用户'
})

// 获取数据填充表格数据
const init = async () => {
    const { result } = await getList(search)
    console.log('result', result)
    tableData.value = result.data
    total.value = result.total
}

init()


// 清空数据
const resetForm = reactive({ ...form })

// 弹框
const dialogVisible = ref(false)
const openDialog = () => {
    dialogVisible.value = true;
    Object.assign(form, resetForm)
}

const changeSize = (page) => {
    search.page = page
    init()
}

// 新增、编辑用户
const save = async () => {
    if (form.id) { // 编辑用户
        await updateUser(form)
        ElMessage.success('编辑成功')
    } else { // 新增用户
        await addUser(form)
        ElMessage.success('添加成功')
    }
    close() // 关闭弹窗
    init() // 重新获取数据更新表格
}

// 删除用户
const deleteRow = (row) => {
    const id = row.id
    ElMessageBox.confirm(
        '确认删除?',
        'Warning',
        {
            confirmButtonText: '确定',
            cancelButtonText: '取消',
            type: 'warning',
        }
    )
        .then(async () => {
            await delUser({ id })
            init()
            ElMessage({
                type: 'success',
                message: '删除成功',
            })
        })
}

// 编辑用户
const edit = (row) => {
    dialogVisible.value = true
    form.username = row.username
    form.pwd = row.pwd
    form.id = row.id
}

// 关闭弹框
const close = () => {
    dialogVisible.value = false;
}
</script>

可能遇到的问题

Nest 返回的数据不一致怎么办?难道每个接口的响应都要手动写成json对象吗? 使用全局响应拦截器,它一般用于格式化响应数据

  1. 创建响应拦截器 formatt.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { map, Observable } from 'rxjs';

@Injectable()
export class FormattInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // 格式化响应数据
    const doMap = map((data) => {
      return {
        code: 200,
        message:'成功',
        result: data
      }
    })
    return next.handle().pipe(doMap);
  }
}

  1. 在 main.ts中注册为全局。如果遇到跨域问题,可直接使用 NestJS 内置的.enableCors(),代码如下
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { FormattInterceptor } from './formatt/formatt.interceptor'

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 启用CORS
  app.enableCors({
    origin: 'http://localhost:5173'
  });

  // 注册全局的响应拦截器
  app.useGlobalInterceptors(new FormattInterceptor())

  await app.listen(3000);
}
bootstrap();
转载自:https://juejin.cn/post/7400584538626080806
评论
请登录