一个 Vue 的前端接口调用示例

官网菜单结构

在中医在线官网中,菜单的结构是有父级菜单和子级菜单。

根据api接口文档,可以看到后端是父级菜单和子级菜单分开查询的。

接口调用

这里是一个直接封装好的 http 请求

import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from "axios";
import { message } from "ant-design-vue";
import { useUserStore } from "../store/user";
const config = {
  baseURL: "http://localhost:8089/medicine-online-client-backend",
  timeout: 10000,
};

// 定义返回值类型
export interface Result<T = unknown> {
  code: number;
  msg: string;
  data: T;
}

class Http {
  // axios 实例
  private instance: AxiosInstance;

  // 构造函数里初始化
  constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config);
    // 定义拦截器
    this.interceptors();
  }

  // 拦截器
  private interceptors() {
    // axios 发送请求之前的处理
    this.instance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        const store = useUserStore();
        const token = store.token;
        if (token) {
          config.headers!["Authorization"] = token;
        }
        return config;
      },
      (error) => {
        error.data = {};
        error.data.msg = "服务器异常,请联系管理员!";
        return Promise.reject(error);
      }
    );

    // axios 请求返回之后的处理
    this.instance.interceptors.response.use(
      (res: AxiosResponse) => {
        if (res.data.code == 0) {
          return res.data;
        } else {
          message.error(res.data.msg || "服务器出错!");
          return Promise.reject(res.data.msg || "服务器出错");
        }
      },
      (error) => {
        error.data = {};
        if (error && error.response) {
          switch (error.response.status) {
            case 400:
              error.data.msg = "错误请求";
              break;
            case 401:
              error.data.msg = "未授权,请重新登录";
              break;
            case 403:
              error.data.msg = "拒绝访问";
              break;
            case 404:
              error.data.msg = "请求错误, 未找到接口";
              break;
            case 405:
              error.data.msg = "请求方法未允许";
              break;
            case 408:
              error.data.msg = "请求超时";
              break;
            case 500:
              error.data.msg = "服务器端出错";
              break;
            case 501:
              error.data.msg = "网络未实现";
              break;
            case 502:
              error.data.msg = "网络错误";
              break;
            case 503:
              error.data.msg = "服务不可用";
              break;
            case 504:
              error.data.msg = "网络超时";
              break;
            case 505:
              error.data.msg = "HTTP版本不支持该请求";
              break;
            default:
              error.data.msg = `连接错误 ${error.response.status}`;
          }
          message.error(error.data.msg);
        } else {
          error.data.msg = "连接到服务器失败";
          message.error(error.data.msg);
        }
        return Promise.reject(error);
      }
    );
  }

  /* GET 方法 */
  get<T = Result>(url: string, params?: object): Promise<T> {
    return this.instance.get(url, { params });
  }

  /* POST 方法 */
  post<T = Result>(url: string, data?: object): Promise<T> {
    return this.instance.post(url, data);
  }

  /* PUT 方法 */
  put<T = Result>(url: string, data?: object): Promise<T> {
    return this.instance.put(url, data);
  }

  /* DELETE 方法 */
  delete<T = Result>(url: string): Promise<T> {
    return this.instance.delete(url);
  }

  // 图片上传
  upload<T = Result>(url: string, params?: object): Promise<T> {
    return this.instance.post(url, params, {
      headers: {
        "Content-Type": "multipart/form-data",
      },
    });
  }
}

export default new Http(config);

因为已经封装好了 http ,可以直接在 api 下新建你的模块的文件夹,在文件夹中新建文件名为 index.ts ,并在该文件中调用接口。我这里页面的路径是 src/api/video/index.ts

import http from "../../http";

// 获取视频父级菜单
export const fetchCategoryData = () => {
  return http.get("/index/item/category").then((response) => {
    return response.data;
  });
};

// 获取视频子级菜单
export const fetchCategoryListData = () => {
  return http.get("/index/item/categorylist").then((response) => {
    return response.data;
  });
};

使用

引用接口

首先,要在你的页面中引用刚刚写的两个接口

import { fetchCategoryData,fetchCategoryListData } from "@/api/video/index.ts";

初始化空数组

我原本在页面中是用了父子结构的数组模拟的。每个菜单项都是一个键值对。

// 菜单数据
const menuData = reactive([
  {
    key: "sub1",
    title: "中医方剂",
    items: [
      { key: "1", label: "泻下剂" },
      { key: "2", label: "解和剂" },
      { key: "3", label: "补益剂" },
      { key: "4", label: "安神剂" },
      { key: "5", label: "理血剂" },
      { key: "6", label: "开窍剂" },
      { key: "7", label: "方剂实战" },
    ],
  },
  {
    key: "sub2",
    title: "中医基础",
    items: [
      { key: "8", label: "绪论" },
      { key: "9", label: "阴阳五行" },
      { key: "10", label: "藏象" },
      { key: "11", label: "经络" },
      { key: "12", label: "病因病机" },
      { key: "13", label: "养生防病" },
      { key: "14", label: "治则治法" },
      { key: "15", label: "气血津液" },
    ],
  },
  // 其他省略

现在可以把这个数组换成动态数组

// 菜单数据
const menuData = reactive([]);

调用接口

调用接口的逻辑就是用接口中的数据替换我们的数组中的数据。

合并菜单

这里写了一个函数叫 fetchAndMergeMenuData ,用于合并菜单。

  1. 分别获取父级菜单和子级菜单的数据。
  2. 把父级菜单初始化成一个键值对,键是 pkId,值是一个对象,包含了 key 、 title 、 items 。其中 items 里面存放的是子菜单项。
  3. 遍历子级菜单数据 , 通过 parentId 找到对应的父菜单 , 如果找到父菜单,则将当前子菜单项(包含 keylabel)添加到父菜单的 items 数组中。 (看上面的 API 接口可知,查询子菜单的接口有个 parentId 参数)
  4. 生成最终菜单, 遍历 menuMap 的值,将每个父菜单对象添加到 menuData 中 。
// 合并菜单数据
async function fetchAndMergeMenuData() {
  try {
    // 获取父级菜单数据
    const categories = await fetchCategoryData();
    // 获取子级菜单数据
    const categoryList = await fetchCategoryListData();
    // 将父级菜单数据进行初始化
    const menuMap = new Map();
    categories.forEach((category) => {
      menuMap.set(category.pkId, {
        key: `sub${category.pkId}`, // 唯一键值
        title: category.title, // 父菜单标题
        items: [],// 存放子菜单项
      });
    });
    // 根据 parentId 将子菜单项添加到父级菜单
    categoryList.forEach((item) => {
      const parentMenu = menuMap.get(item.parentId);// 找到父菜单
      if (parentMenu) {
        parentMenu.items.push({
          key: item.pkId.toString(), // 子菜单的唯一标识
          label: item.title,// 子菜单标题
        });
      }
    });
    // 将合并后的菜单数据赋值给 menuData
    menuData.length = 0;// 清空原数据
    menuMap.forEach((menu) => {
      menuData.push(menu);// 添加合并后的菜单
    });
  } catch (error) {
    console.error(error);
  }
}

到这一步,接口调用完成。

也就是说当你的父级菜单数据是

[
  { "pkId": 1, "title": "菜单一" },
  { "pkId": 2, "title": "菜单二" }
]

子级菜单数据是

[
  { "pkId": 101, "title": "子菜单1-1", "parentId": 1 },
  { "pkId": 102, "title": "子菜单1-2", "parentId": 1 },
  { "pkId": 201, "title": "子菜单2-1", "parentId": 2 }
]

的时候,合并出的数组数据为

[
  {
    "key": "sub1",
    "title": "菜单一",
    "items": [
      { "key": "101", "label": "子菜单1-1" },
      { "key": "102", "label": "子菜单1-2" }
    ]
  },
  {
    "key": "sub2",
    "title": "菜单二",
    "items": [
      { "key": "201", "label": "子菜单2-1" }
    ]
  }
]
直接使用

如果不需要改变接口的结构,可以直接调用。

这里有两个处理方法,假设调用接口获取的数据就是数组,直接使用 menuData.splice(0, menuData.length) 清空原数组 ,然后...responseresponse 数据逐项添加到 menuData 中 。

假设接口的返回体包含 code 和 msg 字段,获取的值就是response.data

// 从接口获取数据并赋值给 menuData
async function loadMenuData() {
  try {
    const response = await fetchAudioMenu();
    // 如果返回的是数组,直接赋值给 menuData
    if (Array.isArray(response)) {
      menuData.splice(0, menuData.length, ...response);
    } else {
      // 如果是对象且包含 code 和 msg 字段
      if (response.code === 0) {
        menuData.splice(0, menuData.length, ...response.data);
      } else {
        console.error(response.msg);
      }
    }
  } catch (error) {
    console.error(error);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值