MeteorCat / AntD的前端设计(二)

Created Tue, 16 Apr 2024 14:27:54 +0800 Modified Wed, 29 Oct 2025 23:24:45 +0800

AntD的前端设计(二)

之前只讲如何配置 RequestConfigResponseStructure, 这里面内部其实就是为了保持和服务端消息同步, 日常消息协议格式我都采取以下方式:

// 常规消息结构体
{
  "error": 0,
  "message": "success",
  "data": {},
}

// 这里提供登录授权完成和数据分页完成格式的返回示例
// 1. 授权完成
{
  "error": 0,
  "message": "success",
  "data": {
    "uid": 1,
    "username": "meteorcat",
    "nickname": "MeteorCat",
    "token": "97ba4dd71592c7b26bc90308806d42f3"
  },
}
// 2. 分页返回, 分页返回有两种: 
//  2.1 前端推送页数(page)和数据量(total), 服务端返回当前页和总页数(list,page,total)
//  2.2 前端推送偏移两(offset)和数据量(total), 服务端返回当前页和总页数(list,page,total)
{
  "error": 0,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 1,
        "name": "MeteorCat"
      },
      // 其他数据
    ],
    "page": 1,
    // 数据分页页码
    "total": 10,
    // 总共数据页数
  }
}

大部分业务都是围绕数据流展示( 像是支付订单|人员流失的查询 )和图表分析( 后台返回数据JSON格式 ), 基本上面那些方式够用了.

这里先编写 mock 测试数据用于之前编写的授权接口( mock/authAPI.ts ):

// mock 首先定义用户登录数据, 需要创建优先的 authAPI 接口
// 路径: mock/authAPI.ts
export default {

    // 登录接口
    // 定义数据格式 { error:int, message:string, data:any }
    'POST /api/auth/login': (req: any, res: any) => {
        // 这里模拟登录接口指定账号
        const data = req.body || {};
        const username = data.username || "";
        const password = data.password || "";

        // 测试账号
        if (username !== "meteorcat") {
            res.json({
                error: 1,
                message: "找不到账号",
                data: {},
            })
            return;
        }

        // 测试密码
        if (password !== "meteorcat") {
            res.json({
                error: 1,
                message: "密码错误",
                data: {},
            })
            return;
        }

        // 通过验证直接返回
        res.json({
            error: 0,
            message: "success",
            data: {
                uid: 1,
                username: "MeteorCat",
                nickname: "meteorcat",
                token: "97ba4dd71592c7b26bc90308806d42f3"
            }
        });
    },

    // 登出接口
    '/api/auth/logout': (req: any, res: any) => {
        // 需要识别 JWT 授权是否存在
        const headers = req.headers || {}
        const auth = headers.authorization || "";

        // 确认授权
        if (auth.length <= 0) {
            res.json({
                error: 1,
                message: "未授权操作",
                data: {}
            });
            return;
        }

        // 对比 JWT 授权
        if (auth !== 'Bearer 97ba4dd71592c7b26bc90308806d42f3') {
            res.json({
                error: 1,
                message: "未授权操作",
                data: {}
            });
            return;
        }

        res.json({
            error: 0,
            message: "success",
            data: {}
        });
    }
}

这里启动项目之后, 默认就会挂起个 Web 服务监听服务( 默认访问 http://localhost:8000 ), 按照上面编写的接口可以请求到:

  • http://localhost:8000/api/auth/login : 需要 POST + RAW 提交 JSON 参数(username,password)
  • http://localhost:8000/api/auth/logout : 需要 Header 内部带有 JWT 请求参数

可以试着推送测试数据确定是否能够跑通, 如果跑通之后就可以准备去测试联调之前的页面功能.

网络请求

umi 内部已经集成了网络请求接口, 内部已经集成好对象:

//import {request, useRequest} from 'umi';
import {request} from "@umijs/max"; // max 框架

request;
// 样例: request<ResponseAuthLogin>('url',{})

useRequest;
// 样例: const { data, error, loading } = useRequest(service);

所以基本上只需要调用请求即可, 一般网络请求都是在 src/services 内部文档处理; 官方内部放置了 demo 样例引入 OneAPI 自动生成, 这里只需要定义响应工具原型就能编写:

// 文件路径: src/utils/request.ts
// 后续请求工具类在此编写

// 响应格式化对象
export interface ResponseBody<T> {
    error: number,
    message: string,
    data: T
}

之后可以编写具体的请求业务对象服务:

// 文件路径: src/services/auth.ts
import {request} from "@umijs/max";
import {ResponseBody} from "@/utils/request";

// 响应格式化 - 具体数据
interface ResponseAuthLoginData {
    uid: number,
    username: string,
    nickname: string,
    token: string,
}


// 登录接口
export async function login(
    username: string,
    password: string,
    options?: { [key: string]: any },
) {
    return request<ResponseBody<ResponseAuthLoginData>>('/api/auth/login', {
        method: 'POST',
        data: {
            username: username,
            password: password,
        },
        ...(options || {}),
    });
}

// 登出接口
export async function logout(
    options?: { [key: string]: any },
) {
    return request<ResponseBody<any>>('/api/auth/logout', {
        method: 'GET',
        ...(options || {}),
    });
}

最后跑下页面请求代码, 我试用过官方提供的 useRequest, 后续感觉功能实际有点冗余不如直接编写请求高效快捷点, 原来的页面文件编写如下

// 文件路径: src/pages/Auth/Login/index.tsx
export default () => {
    const {token} = theme.useToken();
    const {setUsername, setNickname, setUid, setToken} = useModel('global');
    return (
        <ProConfigProvider hashed={false}>
            <div style={{backgroundColor: token.colorBgContainer}}>
                <LoginForm
                    title="Github"
                    subTitle="全球最大的代码托管平台"
                    onFinish={async (values) => {
                        const result = await login(values.username || '', values.password || '');
                        // 确定是否登录完成
                        if (result.error === 0) {
                            // 写入全局授权
                            const data = result.data;
                            setUid(data.uid);
                            setNickname(data.nickname);
                            setUsername(data.username);
                            setToken(data.token);

                            // 跳转首页
                            history.push("/home");
                        }
                    }}
                >
                </LoginForm>
            </div>
        </ProConfigProvider>
    )
}

这里测试之后能够正式跳转, 后续就是准备授权拦截的功能编写了.