效果展示
APIFox原图
正常展示状态
选择状态
组件模仿结果
正常展示状态
选择状态
过程
类型、tsx、less是在编写组件的过程中一起写出来的,没有先写后写的区分,写好组件以后,在
components目录下创建types文件统一导出
目录结构
│
├─ApiOperator
│ index.less
│ index.tsx
│ type.ts
│
└─types
type.d.ts
components统一导出类型(type.d.ts)
// 通用组件类型统一导出
export * from '@/components/types/type'
1. 类型ts
export type Method =
| 'GET'
| 'POST'
| 'PUT'
| 'DELETE'
| 'OPTIONS'
| 'HEAD'
| 'PATCH'
export interface ApiOptReqOptType {
label: string
value: Method
colorClassName?: string
}
// 组件属性描述
export type ApiOptProps = {
// 子元素
children?: React.ReactNode
// 自定义右侧内容宽度(默认为225px)
rightWidth?: string
// 输入框placeholder
placeholder?: string
// 接口选项信息
methodOptions?: ApiOptReqOptType[]
// 默认展示的接口信息
defaultMethod?: ApiOptReqOptType
// 接口信息值
methodValue?: ApiOptReqOptType
// 输入框值
inputValue?: string
// 下拉菜单和选择器同宽。默认将设置 min-width,当值小于选择框宽度时会被忽略。
popupMatchSelectWidth?: number
// 左侧选项改变事件
onOptionChange?: (value: ApiOptReqOptType) => void
// 输入框内容改变事件
onInputChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
}
组件tsx
import React, { memo, useState } from 'react'
import { Input, Select, Space } from 'antd'
import type { ApiOptReqOptType, ApiOptProps } from './type'
import './index.less'
// 默认请求类型信息
const methodOptions: ApiOptReqOptType[] = [
{ label: 'GET', value: 'GET', colorClassName: 'color-get' },
{
label: 'POST',
value: 'POST',
colorClassName: 'color-post'
},
{ label: 'PUT', value: 'PUT', colorClassName: 'color-put' },
{ label: 'DELETE', value: 'DELETE', colorClassName: 'color-delete' },
{ label: 'OPTIONS', value: 'OPTIONS', colorClassName: 'color-options' },
{ label: 'HEAD', value: 'HEAD', colorClassName: 'color-head' },
{ label: 'PATCH', value: 'PATCH', colorClassName: 'color-patch' }
]
const ApiOperator: React.FunctionComponent<ApiOptProps> = memo((props) => {
const [selectVisible, setSelectVisible] = useState(false)
// 手动渲染请求方式下拉列表
function getDropDownEle(): React.ReactElement {
return (
<ul className="method-select">
{methodOptions.map((item, index) => (
<li
className={['method-item', item.colorClassName].join(' ')}
onClick={() => onReqMethodChange(item)}
key={index}
>
{item.label}
</li>
))}
</ul>
)
}
// 选择请求方式点击事件
function onReqMethodChange(req: ApiOptReqOptType): void {
setSelectVisible(false)
if (props.onOptionChange) props.onOptionChange(req)
}
// 输入框改变事件
function onInputChange(e: React.ChangeEvent<HTMLInputElement>): void {
if (props.onInputChange) props.onInputChange(e)
}
return (
<div className="api-operator">
<div
className={['left-info', props.methodValue?.colorClassName].join(' ')}
>
<Space.Compact block>
<Select
open={selectVisible}
onDropdownVisibleChange={(visible) => setSelectVisible(visible)}
defaultValue={props.defaultMethod}
value={props.methodValue}
options={props.methodOptions}
popupMatchSelectWidth={props.popupMatchSelectWidth}
dropdownRender={getDropDownEle}
onSelect={(e) => console.log(e)}
/>
<Input
value={props.inputValue}
onChange={(e) => onInputChange(e)}
placeholder={props.placeholder}
/>
</Space.Compact>
</div>
<div className="right-warp" style={{ width: props.rightWidth }}>
{props.children}
</div>
</div>
)
})
ApiOperator.defaultProps = {
rightWidth: '225px',
placeholder: '接口地址',
methodOptions: methodOptions,
defaultMethod: methodOptions[0],
methodValue: methodOptions[0],
popupMatchSelectWidth: 100,
inputValue: ''
}
export default ApiOperator
样式less
@aop-color-get: #49aa19;
@aop-color-post: #d87a16;
@aop-color-put: #176ddc;
@aop-color-delete: #d84a1e;
@aop-color-options: #176ddc;
@aop-color-head: #176ddc;
@aop-color-patch: #cf2f86;
.api-operator {
display: flex;
.left-info {
flex: 1;
// 选中的样式
.ant-select-selection-item {
font-weight: 700;
}
}
.color-get {
.ant-select-selection-item {
color: @aop-color-get;
}
}
.color-post {
.ant-select-selection-item {
color: @aop-color-post;
}
}
.color-put {
.ant-select-selection-item {
color: @aop-color-put;
}
}
.color-delete {
.ant-select-selection-item {
color: @aop-color-delete;
}
}
.color-options {
.ant-select-selection-item {
color: @aop-color-options;
}
}
.color-head {
.ant-select-selection-item {
color: @aop-color-head;
}
}
.color-patch {
.ant-select-selection-item {
color: @aop-color-patch;
}
}
.right-warp {
display: flex;
.btn {
margin-left: 10px;
}
}
}
.method-select {
padding: 3px;
.method-item {
padding-left: 5px;
font-weight: 700;
height: 30px;
line-height: 30px;
font-size: 16px;
border-radius: 5px;
transition: all 250ms ease;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
cursor: pointer;
}
}
.color-get {
color: @aop-color-get;
}
.color-post {
color: @aop-color-post;
}
.color-put {
color: @aop-color-put;
}
.color-delete {
color: @aop-color-delete;
}
.color-options {
color: @aop-color-options;
}
.color-head {
color: @aop-color-head;
}
.color-patch {
color: @aop-color-patch;
}
}