写在前面
本次章节,由于要完成postman页面,因此篇幅预计在3万字上下。且因为文档最后会放上开源连接,因此在此只在文章中展示核心代码。
目的
需求文档(仿)
前端需求文档 - 基于 Postman 的原型开发
-
概述
本文档描述了基于 Postman 原型的前端开发需求,旨在实现一个简化的 API 请求工具,提供类似 Postman 的功能和界面,支持用户进行 GET 和 POST 请求的测试。
-
功能模块
2.1. 请求类型支持
- 支持用户选择请求类型,包括 GET 和 POST。
- 用户可以通过单选按钮选择请求类型。
2.2. URL 输入
- 提供一个输入框,用于用户输入请求的 URL 地址。
2.3. 请求参数和请求体
- 支持用户输入请求参数,以键值对的形式。
- 对于 POST 请求,支持用户输入请求体,可以选择 JSON 或 x-www-form-urlencoded 格式。
2.4. 发送请求
- 提供一个 "Send" 按钮,用户点击后会发送请求。
- 提供一个 "Save" 按钮,用户点击后会保存请求(留给后期自动化使用)。
2.5. 多个请求类型
- 对于 POST 请求,用户可以选择请求体的格式,包括 JSON 和 x-www-form-urlencoded。
- 如果选择 x-www-form-urlencoded,用户可以输入多个键值对。
2.7. 响应展示
- 在页面上展示请求的响应结果,包括响应状态码、响应体和响应头。
- 响应体可以根据类型(JSON 或文本)进行展示。
设计前端结构
讲解
- JSONAceEditor.tsx文件中,主要存放一个JSON的样式文件
- MaterialOneDark.js文件中同理,也是一个样式文件
- Table文件夹中,主要存放于一系列的表单样式
- EditableTable.tsx存放需求中的请求参数的表单样式
- FormDataTable.tsx主要存放form-data样式
- xFormTable.tsx主要存放xform的样式
- services目录中是咱们存放的一系列请求信息
- 新增的request.ts文件,用来存放咱们的发送请求功能
开始编写样式
- 请求类型+url输入框+send按钮
return (
<PageContainer title="在线HTTP测试工具">
<Card>
<Row gutter={[8, 8]}>
<Col span={18}>
<Input
size="large"
value={url}
addonBefore={selectBefore}
placeholder="请输入要请求的url"
onChange={(e) => {
setUrl(e.target.value);
splitUrl(e.target.value);
}}
/>
</Col>
<Col span={6}>
<Button
onClick={onRequest}
loading={loading}
type="primary"
size="large"
style={{marginRight: 16, float: 'right'}}
>
Save{' '}
</Button>
<Button
onClick={onRequest}
loading={loading}
type="primary"
size="large"
style={{marginRight: 16, float: 'right'}}
>
Send{' '}
</Button>
</Col>
</Row>
</Card>
</PageContainer>
);
此样式中可以看到有几个变量:
- url:是我们后期输入的url,因为要作为传参,因此初始化一个url变量
- selectBefore:请求方法下拉框函数,此函数中编写了我们的请求方式,函数如下:
// 请求方法下拉选择框
const selectBefore = (
<Select
value={method}
onChange={(data) => setMethod(data)}
style={{width: 120, fontSize: 16, textAlign: 'left'}}
>
<Option key="GET" value="GET">GET</Option>
<Option key="POST" value="POST">POST</Option>
{/*<Option key="PUT" value="PUT">PUT</Option>*/}
{/*<Option key="DELETE" value="DELETE">DELETE</Option>*/}
</Select>
);
- setUrl是我们自定义的一个变量
- splitUrl是我们编写的一个自动根据body输入内容更新url内容的一个函数,函数如下:
// 拆分 URL 并更新 paramsData 和 editableKeys 状态
const splitUrl = (nowUrl: string) => {
const split = nowUrl.split('?');
if (split.length < 2) {
setParamsData([]);
} else {
const params = split[1].split('&');
const newParams: { key: string; value: string; id: number; description: string }[] = [];
const keys: number[] = [];
params.forEach((item, idx) => {
const [key, value] = item.split('=');
const now = Date.now();
keys.push(now + idx + 10);
newParams.push({key, value, id: now + idx + 10, description: ''});
});
setParamsData(newParams);
setEditableRowKeys(keys);
}
};
函数中的一些重设置,主要是一些自定义变量
- 编写Body部分
咱们先来尝试编写Body部分,先看看postman的页面:
这次主要针对红框部分来编写,且此部分比较统一,因此我们可以把它拆出来作为一个表单样式。
- 新增
src/components/Table/EditableTable.tsx
文件,编写以下内容作为我们这一栏的样式:
import React, { useEffect } from 'react';
import { EditableProTable } from '@ant-design/pro-table';
// 定义表格列配置的接口
interface TableColumn {
title: string;
key: string;
dataIndex: string;
// 可以根据实际情况添加其他属性
}
// 定义表格数据项的接口
interface TableDataItem {
id: number;
key: string;
value: string;
description: string;
// 可以根据实际情况添加其他属性
}
// 定义可编辑表格的属性接口
interface EditableTableProps {
columns: TableColumn[];
dataSource: TableDataItem[];
title: string;
setDataSource: (data: TableDataItem[]) => void;
editableKeys: React.Key[];
setEditableRowKeys: (keys: React.Key[]) => void;
extra?: (recordList: TableDataItem[]) => void;
}
// 可编辑表格组件
const EditableTable: React.FC<EditableTableProps> = ({
columns,
dataSource,
title,
setDataSource,
editableKeys,
setEditableRowKeys,
extra,
}) => {
// 当数据源变化时,更新可编辑行的键值列表
useEffect(() => {
setEditableRowKeys(dataSource.map((v) => v.id));
}, [dataSource]);
return (
<EditableProTable
headerTitle={title}
columns={columns}
rowKey="id"
value={dataSource}
onChange={setDataSource}
recordCreatorProps={{
newRecordType: 'dataSource',
record: () => ({
id: Date.now(),
}),
}}
editable={{
type: 'multiple',
editableKeys,
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onValuesChange: (record, recordList) => {
if (extra) {
extra(recordList);
}
setDataSource(recordList);
},
onChange: setEditableRowKeys,
}}
/>
);
};
export default EditableTable;
- 新增我们的index文件中的主样式
<Row style={{marginTop: 8}}>
<Tabs defaultActiveKey="1" style={{width: '100%'}}>
<TabPane tab="Params" key="1">
<EditableTable
columns={columns('params')}
title="Query Params"
dataSource={paramsData}
setDataSource={setParamsData}
extra={joinUrl}
editableKeys={editableKeys}
setEditableRowKeys={setEditableRowKeys}>
</EditableTable>
</TabPane>
<TabPane tab="Headers" key="2">
<EditableTable
columns={columns('headers')}
title="Headers"
dataSource={headers}
setDataSource={setHeaders}
editableKeys={headersKeys}
setEditableRowKeys={setHeadersKeys}>
</EditableTable>
</TabPane>
<TabPane tab="Body" key="3">
//较为复杂,在这进行逻辑占位,详细部分下面讲解
</TabPane>
</Tabs>
</Row>
由此就可以见到我们的Parames和Headers两种样式了,并且可以设置相应的表单。
- 下一个目标,我们要在选择body时,出现json,raw等选项,也就是说,在刚才较为复杂的占位中,进行补充。
目标如图:
老规矩,先上代码,在进行讲解:
<TabPane tab="Body" key="3">
<Row>
<Radio.Group
defaultValue={'none'}
value={bodyType}
onChange={(e) => {
setBodyType(e.target.value)
}}
>
<Radio value={'none'}>none</Radio>
<Radio value={'form-data'}>form-data</Radio>
<Radio value={'x-www-form-urlencoded'}>x-www-form-urlencoded</Radio>
<Radio value={'raw'}>raw</Radio>
<Radio value={'binary'}>binary</Radio>
<Radio value={'GraphQL'}>GraphQL</Radio>
</Radio.Group>
{/*如果 bodyType 等于 1,则会渲染一个下拉菜单,菜单内有一个链接,显示 rawType 的值和一个向下的箭头图标。
当用户点击链接时,会显示下拉菜单的内容,而不会触发页面跳转。*/}
{bodyType === 'raw' ? (
<Dropdown style={{marginLeft: 8}} overlay={menu} trigger={['click']}>
<a onClick={(e) => e.preventDefault()}>
{rawType} <DownOutlined/>
</a>
</Dropdown>
) : null}
</Row>
{getBody(bodyType)}
</TabPane>
由此可见,当我们点到Body时,是会有一些Radio按钮的,如none,form-data等按钮。此为我们body部分的第一部份功能。
- 第二部分:当我们点几raw的时候,出现一个下拉选择框,来选择JSON,TEXT等
在代码中,也有相应注释,由此可见我们是有一个menu函数来展示的,代码如下:
const menu = (
<Menu>
<Menu.Item key="Text">
<a onClick={() => onClickMenu('Text')}>Text</a>
</Menu.Item>
<Menu.Item key="JavaScript">
<a onClick={() => onClickMenu('JavaScript')}>JavaScript</a>
</Menu.Item>
<Menu.Item key="JSON">
<a onClick={() => onClickMenu('JSON')}>JSON</a>
</Menu.Item>
<Menu.Item key="HTML">
<a onClick={() => onClickMenu('HTML')}>HTML</a>
</Menu.Item>
<Menu.Item key="XML">
<a onClick={() => onClickMenu('XML')}>XML</a>
</Menu.Item>
</Menu>
);
- 编写body文本部分
老规矩,上postman页面,红框就是我们的任务!
在此举例展示,不一一展示了。大概意思就是:
- none:展示body主体为一个文字描述
- form-data:展示主体为一个可选类型的表单
- x-www-form-urlencoded:展示主体为一个表单
- raw:展示主体为一个编辑框
- binary,GraphQL。暂时只作为一个占位展示
开始编写body文本部分!
大家应该可以看到,我们在新增body主体样式的时候,有一个{getBody(bodyType)}
函数,我们的body文本部分,就写在此函数中。老规矩,先上代码!
// 根据 Body 类型生成不同的显示内容
const getBody = bd => {
if (bd === 'none') {
return <div style={{height: '20vh', lineHeight: '20vh', textAlign: 'center'}}>
This request does not have a body
</div>
}
if (bd === 'form-data') {
return <FormData
columns={columns('FormData')}
dataSource={formData}
setDataSource={setFormData}
editableKeys = {formDataKeys}
setEditableRowKeys = {setFormDataKeys}>
</FormData>
}
if (bd === 'binary') {
return <div style={{height: '20vh', lineHeight: '20vh', textAlign: 'center'}}>
binary占位!,先给常用功能做出
</div>
}
if (bd === 'GraphQL') {
return <div style={{height: '20vh', lineHeight: '20vh', textAlign: 'center'}}>
GraphQL占位!,先给常用功能做出
</div>
}
if (bd === 'x-www-form-urlencoded') {
return <XForm
columns={columns('XForm')}
dataSource={xform}
setDataSource={setxForm}
editableKeys = {xformKeys}
setEditableRowKeys = {setxFormKeys}>
</XForm>
}
return <Row style={{marginTop: 12}}>
<Col span={24}>
<Card bodyStyle={{padding: 0}}>
<JSONAceEditor value={body} onChange={e => setBody(e)} height="20vh" setEditor={setEditor}/>
</Card>
</Col>
</Row>
}
代码讲解:
- 如果类型为none,则显示一个文本
- binary和GraphQL同理,暂时进行占位
- 若类型为form-data,则展示一个formData表单,具体代码后面贴出
- 若类型为x-www-form-urlencoded,则展示xForm表单,具体代码后面贴出
- 若都不匹配,则显示一个编辑窗口,即raw类型
formData
表单代码:
import React, { useState } from 'react';
import { Form, Select } from 'antd';
import { EditableProTable } from '@ant-design/pro-table';
const { Option } = Select;
// 定义表格列配置的接口
interface TableColumn {
key: string;
dataIndex: string;
// 可以根据实际情况添加其他属性
}
// 定义表格数据项的接口,新增了 'type' 属性
interface TableDataItem {
id: number;
key: string;
value: string;
description: string;
type: string; // 添加 'type' 属性
// 可以根据实际情况添加其他属性
}
// 定义 FormDataComponent 组件的属性接口
interface FormDataProps {
dataSource: TableDataItem[];
setDataSource: (data: TableDataItem[]) => void;
setEditableRowKeys: (keys: React.Key[]) => void;
extra?: (recordList: TableDataItem[]) => void;
editableKeys: React.Key[];
}
// FormDataComponent 可编辑表格组件
const FormDataComponent: React.FC<FormDataProps> = ({
dataSource,
setDataSource,
setEditableRowKeys,
extra,
editableKeys,
}) => {
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState<string>('');
// 表格列配置
const columns: TableColumn[] = [
{
title: 'KEY',
key: 'key',
dataIndex: 'key',
},
{
title: 'TYPE',
key: 'type',
dataIndex: 'type',
width: '10%',
valueType: 'select',
fieldProps: {
defaultValue: 'TEXT', // 设置默认值为 'TEXT'
},
renderFormItem: (_, { value, onChange }) => (
<Select value={value} onChange={onChange}>
<Option value="TEXT">TEXT</Option>
{/*<Option value="FILE">FILE</Option>*/}
</Select>
),
},
{
title: 'VALUE',
key: 'value',
dataIndex: 'value',
},
{
title: 'DESCRIPTION',
key: 'description',
dataIndex: 'description',
},
{
title: 'OPTION',
valueType: 'option',
},
];
return (
<EditableProTable
columns={columns}
rowKey="id"
value={dataSource}
onChange={setDataSource}
recordCreatorProps={{
newRecordType: 'dataSource',
record: () => ({
id: Date.now(),
type: 'TEXT', // 设置默认类型为 'TEXT'
}),
}}
editable={{
type: 'multiple',
editableKeys,
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onValuesChange: (record, recordList) => {
if (extra) {
extra(recordList);
}
setDataSource(recordList);
},
onChange: setEditableRowKeys,
}}
/>
);
};
export default FormDataComponent;
xForm表单样式代码:
import React from 'react';
import { Props } from "@floating-ui/react-dom-interactions";
import { EditableProTable } from "@ant-design/pro-table";
// 定义表格列配置的接口
interface TableColumn {
key: string;
dataIndex: string;
// 可以根据实际情况添加其他属性
}
// 定义表格数据项的接口
interface TableDataItem {
id: number;
key: string;
value: string;
description: string;
// 可以根据实际情况添加其他属性
}
// 定义 xFormComponent 组件的属性接口
interface xFormProps {
dataSource: TableDataItem[];
setDataSource: (data: TableDataItem[]) => void;
setEditableRowKeys: (keys: React.Key[]) => void;
extra?: (recordList: TableDataItem[]) => void;
editableKeys: React.Key[];
}
// xFormComponent 组件,可编辑表格
const xFormComponent: React.FC<xFormProps> = ({
dataSource,
setDataSource,
setEditableRowKeys,
extra,
editableKeys
}) => {
// 表格列配置
const columns = ({ columnType, onDelete }: Props): TableColumn[] => {
return [
{
title: 'KEY',
key: 'key',
dataIndex: 'key',
},
{
title: 'VALUE',
key: 'value',
dataIndex: 'value',
},
{
title: 'DESCRIPTION',
key: 'description',
dataIndex: 'description',
},
{
title: 'OPTION',
valueType: 'option',
},
];
};
return (
<EditableProTable
columns={columns('prod')}
rowKey="id"
value={dataSource}
onChange={setDataSource}
recordCreatorProps={{
newRecordType: 'dataSource',
record: () => ({
id: Date.now(),
}),
}}
editable={{
type: 'multiple',
editableKeys,
actionRender: (row, config, defaultDoms) => {
return [defaultDoms.delete];
},
onValuesChange: (record, recordList) => {
if (extra) {
extra(recordList);
}
setDataSource(recordList);
},
onChange: setEditableRowKeys,
}}
/>
);
};
export default xFormComponent;
这两部分代码,分别在Table中管理。如图:
- 编写Response部分
老规矩,原型图如下:
由此可见,其实我们编写返回值部分,需要完成的有几个部分:
- Body,Cookies,Headers等选项
- 接口响应信息
- 接口数据信息
相应样式的核心代码:
<Row gutter={[8, 8]}>
{Object.keys(response).length === 0 ? null : (
<Tabs style={{width: '100%'}} tabBarExtraContent={tabExtra(response)}>
<TabPane tab="Body" key="1">
<JSONAceEditor
readOnly={true}
setEditor={setEditor}
language={response.response_data && response.response_headers.indexOf("json") > -1 ? 'json' : 'text'}
value={response.response_data && typeof response.response_data === 'object' ? JSON.stringify(response.response_data, null, 2) : response.response_data || ''}
height="30vh"
/>
</TabPane>
<TabPane tab="Cookie" key="2">
<Table
columns={resColumns}
dataSource={toTable('cookies')}
size="small"
pagination={false}
/>
</TabPane>
<TabPane tab="Headers" key="3">
<Table
columns={resColumns}
dataSource={toTable('response_headers')}
size="small"
pagination={false}
/>
</TabPane>
</Tabs>
)}
</Row>
TODO:因为编写代码时忘记了接口耗时等信息,所以有关接口耗时等信息会暂时跳过,留在下一篇。
实际上很简单,大致意思无非就是,tab匹配到body时,展示body的样式,以此类推。在此就不做过多解释,稍后贴出个人信息,先把项目开源出来,大家感兴趣的可以一起加群交流。
- 开源相关
加群一起讨论相关问题呀!如果群二维码过期了,可以加我个人微信: yyi11yy
我拉你进群~