学习typeScript写服务(nestjs)【四,掉接口将数据展示前端页面(vue3+ts)】


前言

这篇文章将用之前写的增删改查接口渲染到前端页面,这里借助elementui完成页面,效果如下

在这里插入图片描述

一、安装elementUI

文档:https://element-plus.gitee.io/zh-CN/guide/installation.html#%E7%8E%AF%E5%A2%83%E6%94%AF%E6%8C%81

# 选择一个你喜欢的包管理器

# NPM
$ npm install element-plus --save

# Yarn
$ yarn add element-plus

# pnpm
$ pnpm install element-plus

二、搭建路由及静态页面

1. 路由搭建

在这里插入图片描述

2. 静态页面

我这里直接上代码了,在Table.vue中直接粘贴即可

<template>
  <el-button type="primary" plain @click="dialogFormVisible = true"
    >新增</el-button
  >
  <!-- 表格 -->
  <el-table :data="tableData">
    <el-table-column prop="name" label="Name" />
    <el-table-column prop="age" label="Age" />
    <el-table-column fixed="right" label="Operations" width="120">
      <template #default>
        <el-button type="text" size="small" @click="handleClick"
          >Delete</el-button
        >
        <el-button type="text" size="small">Edit</el-button>
      </template>
    </el-table-column>
  </el-table>
  <!-- 弹窗 -->
  <el-dialog v-model="dialogFormVisible" title="Shipping address">
    <el-form :model="form">
      <el-form-item label="Name" :label-width="formLabelWidth">
        <el-input v-model="form.name" autocomplete="off" />
      </el-form-item>
      <el-form-item label="Age" :label-width="formLabelWidth">
        <el-input v-model="form.age" autocomplete="off" />
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogFormVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogFormVisible = false"
          >Confirm</el-button
        >
      </span>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { reactive, ref, onMounted } from "vue";
import { getList } from "@/components/axiosTable";
const dialogTableVisible = ref(false);
const dialogFormVisible = ref(false);
const formLabelWidth = "140px";

const form = reactive({
  name: "",
  age: "",
});

const tableData = ref([
  {
    name:'TOM',
    agr:10
  }, {
    name:'TOM1',
    agr:11
  }
]);

</script>

<style lang="less">
</style>

效果是这样的
在这里插入图片描述
弹窗效果
在这里插入图片描述

三、封装axios请求

1. 首先安装axios

$ yarn add axios 
or
$ npm i --save axios 

2. 请求封装

  • Request类封装
//axios/request.js
import axios from "axios";
import type {
  RequestConfig,
  RequestInterceptors,
  CancelRequestSource,
} from "./type";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
class Request {
  // axios的实例
  instance: AxiosInstance;
  // 拦截器对象
  interceptorsObj?: RequestInterceptors;

  /**
   * 存放取消方法的集合
   * 在创建请求后将取消请求方法 push 到该集合中
   * 封装一个方法,可以取消请求,传入 url: string|string[]
   * 在请求之前判断同一URL是否存在,如果存在就取消请求
   */
  cancelRequestSourceList?: CancelRequestSource[];
  /**
   * 存放所有请求URL的集合
   * 请求之前需要将url push到该集合中
   * 请求完毕后将url从集合中删除
   * 添加在发送请求之前完成,删除在响应之后删除
   */
  requestUrlList?: string[];

  constructor(config: RequestConfig) {
    // 数据初始化
    this.requestUrlList = [];
    this.cancelRequestSourceList = [];

    this.instance = axios.create(config);
    this.interceptorsObj = config.interceptors;
    // 拦截器  拦截器的执行顺序为实例请求→类请求→实例响应→类响应
    this.instance.interceptors.request.use(
      (res: AxiosRequestConfig) => {
        console.log("全局请求拦截器");
        return res;
      },
      (err: any) => err
    );
    // 实例拦截器
    this.instance.interceptors.request.use(
      this.interceptorsObj?.requestInterceptors,
      this.interceptorsObj?.requestInterceptorsCatch
    );
    this.instance.interceptors.response.use(
      this.interceptorsObj?.responseInterceptors,
      this.interceptorsObj?.responseInterceptorsCatch
    );
    // 全局响应拦截器最后执行
    this.instance.interceptors.response.use(
      // 因为接口的数据都在res.data下,直接返回res.data即可
      (res: AxiosResponse) => {
        console.log("全局响应拦截器");
        return res.data;
      },
      (err: any) => err
    );
  }
  // 接口拦截
  request<T>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      //单个请求的拦截器
      if (config.interceptors?.requestInterceptors) {
        config = config.interceptors.requestInterceptors(config);
      }
      const url = config.url;
      // url存在保存取消请求方法和当前请求url
      if (url) {
        this.requestUrlList?.push(url);
        config.cancelToken = new axios.CancelToken((c) => {
          this.cancelRequestSourceList?.push({
            [url]: c,
          });
        });
      }
      this.instance
        .request<any, T>(config)
        .then((res) => {
          //单个相应拦截器
          if (config.interceptors?.responseInterceptors) {
            res = config.interceptors.responseInterceptors<T>(res);
          }
          resolve(res);
        })
        .catch((err: any) => {
          reject(err);
        })
        .finally(() => {
          url && this.delUrl(url);
        });
    });
  }
  /**
   * @description: 获取指定 url 在 cancelRequestSourceList 中的索引
   * @param {string} url
   * @returns {number} 索引位置
   */
  private getSourceIndex(url: string): number {
    return this.cancelRequestSourceList?.findIndex(
      (item: CancelRequestSource) => {
        return Object.keys(item)[0] === url;
      }
    ) as number;
  }
  /**
   * @description: 删除 requestUrlList 和 cancelRequestSourceList
   * @param {string} url
   * @returns {*}
   */
  private delUrl(url: string) {
    const urlIndex = this.requestUrlList?.findIndex((u) => u === url);
    const sourceIndex = this.getSourceIndex(url);
    // 删除url和cancel方法
    urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);
    sourceIndex !== -1 &&
      this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
  }
  /**
   * 取消全部请求
   */
  cancelAllRequest() {
    this.cancelRequestSourceList?.forEach((source) => {
      const key = Object.keys(source)[0];
      source[key]();
    });
  }
  /** 取消请求 */
  cancelRequest(url: string | string[]) {
    if (typeof url === "string") {
      // 取消单个请求
      const sourceIndex = this.getSourceIndex(url);
      sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url]();
    } else {
      // 存在多个需要取消请求的地址
      url.forEach((u) => {
        const sourceIndex = this.getSourceIndex(u);
        sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]();
      });
    }
  }
}

export default Request;

  • type类型/接口定义文件
//axios/type.ts
// *实例拦截器  为保证封装的灵活性(因为拦截后传参可能不一样)
import type { AxiosRequestConfig, AxiosResponse } from "axios";
//基础拦截器
export interface RequestInterceptors {
  // 请求拦截
  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;
  requestInterceptorsCatch?: (err: any) => any;
  // 响应拦截
  responseInterceptors?: <T = AxiosResponse>(config: T) => T;
  responseInterceptorsCatch?: (err: any) => any;
}

//自定义传入的参数 因为axios提供的AxiosRequestConfig不允许传入拦截器
export interface RequestConfig extends AxiosRequestConfig {
  interceptors?: RequestInterceptors;
}
//取消请求方法的接口定义
export interface CancelRequestSource {
  [index: string]: () => void;
}
  • index.ts
//axios/index.ts
import Request from "./request";
import type { RequestConfig } from "./type";
interface MyRequestConfig<T> extends RequestConfig {
  data?: T;
}
interface MyResponse<T> {
  code: number;
  message: string;
  data?: T;
}

// 封装请求方法
const request = new Request({
  //   baseURL: "import.meta.url",
  baseURL: "http://localhost:3000/",
  timeout: 1000 * 60 * 5,
  interceptors: {
    // 请求拦截器
    requestInterceptors: (config) => {
      console.log("实例请求拦截器");
      return config;
    },
    // 响应拦截器
    responseInterceptors: (result) => {
      console.log("实例响应拦截器");
      return result;
    },
  },
});
/**
 * *发送网络请求
 * @description: 函数的描述
 * @interface D 请求参数的interface
 * @interface T 响应结构的intercept
 * @param {MyRequestConfig} config 不管是GET还是POST请求都使用data
 * @returns {Promise}
 */
const myRequest = <D, T = any>(config: MyRequestConfig<D>) => {
  const { method = "GET" } = config;
  if (method === "get" || method === "GET") {
    config.params = config.data;
  }
  return request.request<MyResponse<T>>(config);
};
export default myRequest;

// 取消请求
export const cancelRequest = (url: string | string[]) => {
  return request.cancelRequest(url);
};
// 取消全部请求
export const cancelAllRequest = () => {
  return request.cancelAllRequest();
};

四、页面掉接口实现增删改查

1. 先在创建一个文件用于存放接口的

当前了名字自定义,取的时候路径别错了就行
在这里插入图片描述
代码

import request from "@/components/axios/index";
interface Req {
    name: string;
    age: string;
    id: string;
}

// 查询
export const getList = (data: Partial<Req>) => {
    return request<any, any>({
        url: "users/list",
        method: "get",
        data,
    });
};
// 增加
export const addPeople = (data: Pick<Req, 'name' | 'age'>) => {
    return request<any, any>({
        url: "users/add",
        method: "post",
        data,
    });
};
// 修改
export const updatePeople = (data: Req) => {
    return request<any, any>({
        url: "users/update",
        method: "post",
        data,
    });
};
//删除
export const delPeople = (id: string) => {
    return request<any, any>({
        url: "users/del",
        method: "get",
        data: { id },
    });
};
  • url 为之前写的接口的名称
  • method为请求方法
  • data为请求参数
    一定要确保后端服务启动了,不然一定会调取失败

2. 页面实现增删改查

我将代码需要注意的地方都打上了注释,感觉这块就是调取上面文件里的方法,这块我就直接贴代码,不细说了,有问题欢迎评论区留言哈

  • Table.vue最终代码
<template>
  <el-button type="primary" plain @click="addClick">新增</el-button>
  <!-- 表格 -->
  <el-table :data="tableData">
    <el-table-column prop="name" label="Name" />
    <el-table-column prop="age" label="Age" />
    <el-table-column fixed="right" label="Operations" width="120">
      <template #default="scope">
        <el-button type="text" size="small" @click="delClick(scope)"
          >Delete</el-button
        >
        <el-button type="text" size="small" @click="editClick(scope)"
          >Edit</el-button
        >
      </template>
    </el-table-column>
  </el-table>
  <!-- 弹窗 -->
  <el-dialog v-model="dialogFormVisible" title="Shipping address">
    <el-form :model="form">
      <el-form-item label="Name" :label-width="formLabelWidth">
        <el-input v-model="form.name" autocomplete="off" />
      </el-form-item>
      <el-form-item label="Age" :label-width="formLabelWidth">
        <el-input v-model="form.age" autocomplete="off" />
      </el-form-item>
    </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogFormVisible = false">Cancel</el-button>
        <el-button type="primary" @click="handleSubmit">Confirm</el-button>
      </span>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
import { reactive, ref, onMounted } from "vue";
import {
  getList,
  updatePeople,
  addPeople,
  delPeople,
} from "@/components/axiosTable";
const dialogFormVisible = ref(false);
const formLabelWidth = "140px";
const tableData = ref();
let form = reactive({
  id: null,
  name: "",
  age: "",
});
/**
 * 重置form
 */
function resetForm() {
  form.id = null;
  form.name = "";
  form.age = "";
}
/**  打开弹窗  添加 */
function addClick() {
  dialogFormVisible.value = true;
  //   一定要记得把form重置,不然会有上次修改赋值的数据
  resetForm();
}
/**
 * 弹窗保存
 */
async function handleSubmit() {
  dialogFormVisible.value = false;
  //   修改
  if (form.id) {
    await updatePeople(form);
  } else {
    //新增
    let { id, ...data } = form;
    await addPeople(data);
  }
  let { data } = await getList();
  tableData.value = data;
}
// 删除
async function delClick({ row }) {
  await delPeople(row._id);
  let { data } = await getList();
  tableData.value = data;
}
/**  打开弹窗  修改 */
function editClick({ row }) {
  form.id = row._id;
  form.name = row.name;
  form.age = row.age;
  dialogFormVisible.value = true;
}

onMounted(async () => {
  let { data } = await getList();
  tableData.value = data;
});
</script>

<style lang="less"></style>

最后

ok,效果已经实现了,可以自己点点试试,我把项目传在了gitee上:https://gitee.com/zdp970719/v3demo,也可以直接拷我的项目

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值