mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: pagination and search api
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
|
||||
import { DragSortTable, ProTable } from '@ant-design/pro-components'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { withMinimumDelay } from '@common/utils/ux'
|
||||
import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd'
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
|
||||
import { debounce } from 'lodash-es'
|
||||
import {
|
||||
ChangeEvent,
|
||||
RefAttributes,
|
||||
@@ -9,16 +17,8 @@ import {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
|
||||
import { DragSortTable, ProTable } from '@ant-design/pro-components'
|
||||
import './PageList.module.css'
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import { debounce } from 'lodash-es'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { withMinimumDelay } from '@common/utils/ux'
|
||||
import './PageList.module.css'
|
||||
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }
|
||||
|
||||
@@ -104,7 +104,6 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
|
||||
// 使用useImperativeHandle来自定义暴露给父组件的实例值
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -209,6 +208,32 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
)
|
||||
}
|
||||
|
||||
const getTableActions = () => {
|
||||
return [
|
||||
...(beforeSearchNode ? [beforeSearchNode] : []),
|
||||
...(searchPlaceholder
|
||||
? [
|
||||
<Input
|
||||
key="search-input"
|
||||
className="my-btnbase ml-btnbase"
|
||||
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
|
||||
onPressEnter={() => (manualReloadTable ? manualReloadTable() : actionRef.current?.reload?.())}
|
||||
allowClear
|
||||
placeholder={searchPlaceholder}
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
actionRef.current?.reload?.()
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
|
||||
const requestWithDelay = (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sort: unknown,
|
||||
@@ -229,7 +254,15 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
columns={newColumns}
|
||||
rowKey={primaryKey}
|
||||
search={false}
|
||||
pagination={false}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
request={request}
|
||||
dragSortKey={dragSortKey}
|
||||
onDragSortEnd={onDragSortEnd}
|
||||
@@ -239,6 +272,9 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
density: false,
|
||||
setting: false
|
||||
}}
|
||||
toolbar={{
|
||||
actions: getTableActions()
|
||||
}}
|
||||
headerTitle={headerTitle()}
|
||||
/>
|
||||
) : (
|
||||
@@ -260,28 +296,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
) : null
|
||||
]}
|
||||
toolbar={{
|
||||
actions: [
|
||||
...[beforeSearchNode],
|
||||
...[
|
||||
searchPlaceholder ? (
|
||||
<Input
|
||||
className="my-btnbase ml-btnbase"
|
||||
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
|
||||
onPressEnter={() => (manualReloadTable ? manualReloadTable() : actionRef.current?.reload?.())}
|
||||
allowClear
|
||||
placeholder={searchPlaceholder}
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
actionRef.current?.reload?.()
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
]
|
||||
]
|
||||
actions: getTableActions()
|
||||
}}
|
||||
options={{
|
||||
reload: false,
|
||||
@@ -304,7 +319,6 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
}
|
||||
: false
|
||||
}
|
||||
rowKey={primaryKey}
|
||||
onChange={(
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
@@ -319,6 +333,7 @@ const PageList = <T extends Record<string, unknown>>(
|
||||
)
|
||||
onChange?.(pagination, filters, sorter, extra)
|
||||
}}
|
||||
rowKey={primaryKey}
|
||||
dataSource={dataSource}
|
||||
search={false}
|
||||
headerTitle={headerTitle()}
|
||||
|
||||
@@ -76,7 +76,7 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={mode === 'add' ? $t('Add {{vendorName}} name', { vendorName }) : $t('Edit API Key')}
|
||||
title={mode === 'add' ? $t('添加 {{vendorName}} APIKey', { vendorName }) : $t('编辑 APIKey')}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={handleOk}
|
||||
@@ -84,11 +84,7 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
|
||||
width={600}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="id"
|
||||
label={$t('* KEY Name')}
|
||||
rules={[{ required: true, message: $t('Please input the KEY name') }]}
|
||||
>
|
||||
<Form.Item name="id" label={$t('APIKey 名称')} rules={[{ required: true, message: $t('请输入 APIKey') }]}>
|
||||
<Input disabled={mode === 'edit'} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -96,15 +92,15 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
|
||||
name="name"
|
||||
label={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
|
||||
<span>{$t('* API KEY')}</span>
|
||||
<span>{$t('API Key')}</span>
|
||||
{mode === 'add' && (
|
||||
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer">
|
||||
{$t('Get API KEY from OpenAI')}
|
||||
{$t('从 OpenAI 获取 API Key')}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
rules={[{ required: true, message: $t('Please input the API key') }]}
|
||||
rules={[{ required: true, message: $t('请输入 API Key') }]}
|
||||
>
|
||||
{mode === 'add' ? (
|
||||
<TextArea
|
||||
@@ -125,8 +121,8 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
|
||||
|
||||
<Form.Item name="neverExpire" valuePropName="checked">
|
||||
<Switch
|
||||
checkedChildren={$t('Never Expire')}
|
||||
unCheckedChildren={$t('Set Expiration')}
|
||||
checkedChildren={$t('永不过期')}
|
||||
unCheckedChildren={$t('设置过期时间')}
|
||||
onChange={handleNeverExpireChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -134,16 +130,16 @@ const ApiKeyModal: React.FC<ApiKeyModalProps> = ({
|
||||
{!neverExpire && (
|
||||
<Form.Item
|
||||
name="expire_time"
|
||||
label={$t('Expiration Date')}
|
||||
rules={[{ required: true, message: $t('Please select expiration date') }]}
|
||||
label={$t('过期时间')}
|
||||
rules={[{ required: true, message: $t('请选择过期时间') }]}
|
||||
>
|
||||
<DatePicker style={{ width: '100%' }} showTime />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{mode === 'edit' && (
|
||||
<Form.Item name="enabled" label={$t('Enabled')} valuePropName="checked">
|
||||
<Switch checkedChildren={$t('Yes')} unCheckedChildren={$t('No')} />
|
||||
<Form.Item name="enabled" label={$t('启用状态')} valuePropName="checked">
|
||||
<Switch checkedChildren={$t('是')} unCheckedChildren={$t('否')} />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -29,20 +29,9 @@ const KeySettings: React.FC = () => {
|
||||
const [editingKey, setEditingKey] = useState<APIKey | null>(null)
|
||||
const [apiKeys, setApiKeys] = useState<APIKey[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
fetchData<BasicResponse<{ data: APIKey[] }>>('ai/resource/keys', {
|
||||
method: 'GET',
|
||||
eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
|
||||
}).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setApiKeys(data.keys)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
useEffect(() => {}, [])
|
||||
|
||||
const handleEdit = (record: APIKey) => {
|
||||
setEditingKey(record)
|
||||
@@ -104,6 +93,41 @@ const KeySettings: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const requestApiKeys = async (params: { pageSize: number; current: number }) => {
|
||||
try {
|
||||
const response = await fetchData<BasicResponse<{ data: APIKey[] }>>('ai/resource/keys', {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
provider: selectedProvider,
|
||||
page_size: params.pageSize,
|
||||
keyword: searchWord,
|
||||
page: params.current
|
||||
},
|
||||
eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
return {
|
||||
data: response.data.keys,
|
||||
success: true,
|
||||
total: response.data.total
|
||||
}
|
||||
} else {
|
||||
message.error(response.msg || $t(RESPONSE_TIPS.error))
|
||||
return {
|
||||
data: [],
|
||||
success: false,
|
||||
total: response.data.total
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
data: [],
|
||||
success: false,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
const statusEnum = {
|
||||
normal: { text: <Typography.Text type="success">{$t('正常')}</Typography.Text> },
|
||||
exceeded: { text: <Typography.Text type="warning">{$t('超额')}</Typography.Text> },
|
||||
@@ -140,6 +164,11 @@ const KeySettings: React.FC = () => {
|
||||
]
|
||||
|
||||
const columns: PageProColumns<APIKey>[] = [
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'drag',
|
||||
width: '40px'
|
||||
},
|
||||
{
|
||||
title: $t('调用优先级'),
|
||||
dataIndex: 'priority',
|
||||
@@ -197,45 +226,18 @@ const KeySettings: React.FC = () => {
|
||||
showBorder={false}
|
||||
scrollPage={false}
|
||||
>
|
||||
<div className="h-[calc(100%-1rem)]">
|
||||
<div className="h-[calc(100%-1rem-36px)]">
|
||||
<PageList
|
||||
actionRef={actionRef}
|
||||
rowKey="id"
|
||||
request={async (params) => {
|
||||
try {
|
||||
const response = await fetchData<BasicResponse<{ data: APIKey[] }>>('ai/resource/keys', {
|
||||
method: 'GET',
|
||||
eoParams: {
|
||||
provider: selectedProvider,
|
||||
...params
|
||||
},
|
||||
eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/'
|
||||
})
|
||||
|
||||
if (response.code === STATUS_CODE.SUCCESS) {
|
||||
return {
|
||||
data: response.data.keys,
|
||||
success: true,
|
||||
total: response.data.keys.length
|
||||
}
|
||||
} else {
|
||||
message.error(response.msg || $t(RESPONSE_TIPS.error))
|
||||
return {
|
||||
data: [],
|
||||
success: false,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
data: [],
|
||||
success: false,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
request={requestApiKeys}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
}}
|
||||
showPagination={true}
|
||||
searchPlaceholder={$t('请输入名称搜索')}
|
||||
columns={columns}
|
||||
dragSortKey="sort"
|
||||
dragSortKey="drag"
|
||||
// onDragSortEnd={handleDragSortEnd}
|
||||
addNewBtnTitle={$t('添加 APIKey')}
|
||||
onAddNewBtnClick={handleAdd}
|
||||
|
||||
@@ -52,7 +52,7 @@ const SystemList: FC = () => {
|
||||
eoTransformKeys: ['api_num', 'service_num', 'create_time']
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setTableListDataSource(data.services)
|
||||
@@ -78,7 +78,7 @@ const SystemList: FC = () => {
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(
|
||||
!checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams',
|
||||
{ method: 'GET', eoTransformKeys: [] }
|
||||
).then(response => {
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
setTeamList(data.teams)
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
@@ -101,10 +101,9 @@ const SystemList: FC = () => {
|
||||
|
||||
const getMemberList = async () => {
|
||||
setMemberValueEnum({})
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ members: SimpleMemberItem[] }>>(
|
||||
'simple/member',
|
||||
{ method: 'GET' }
|
||||
)
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ members: SimpleMemberItem[] }>>('simple/member', {
|
||||
method: 'GET'
|
||||
})
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
const tmpValueEnum: { [k: string]: { text: string } } = {}
|
||||
data.members?.forEach((x: SimpleMemberItem) => {
|
||||
@@ -131,7 +130,7 @@ const SystemList: FC = () => {
|
||||
}
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const res = SYSTEM_TABLE_COLUMNS.map(x => {
|
||||
const res = SYSTEM_TABLE_COLUMNS.map((x) => {
|
||||
const dataIndex = x.dataIndex as string[]
|
||||
|
||||
if (x.filters && dataIndex?.indexOf('master') !== -1) {
|
||||
@@ -142,8 +141,8 @@ const SystemList: FC = () => {
|
||||
}
|
||||
if ((x.dataIndex as string) === 'service_kind') {
|
||||
x.valueEnum = {}
|
||||
SERVICE_KIND_OPTIONS.forEach(option => {
|
||||
(x.valueEnum as any)[option.value] = { text: $t(option.label) }
|
||||
SERVICE_KIND_OPTIONS.forEach((option) => {
|
||||
;(x.valueEnum as any)[option.value] = { text: $t(option.label) }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -188,13 +187,11 @@ const SystemList: FC = () => {
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
onSearchWordChange={e => {
|
||||
onSearchWordChange={(e) => {
|
||||
setTableSearchWord(e.target.value)
|
||||
}}
|
||||
onRowClick={(row: SystemTableListItem) =>
|
||||
navigate(
|
||||
`/service/${row.team.id}/${row.service_kind === 'ai' ? 'aiInside' : 'inside'}/${row.id}`
|
||||
)
|
||||
navigate(`/service/${row.team.id}/${row.service_kind === 'ai' ? 'aiInside' : 'inside'}/${row.id}`)
|
||||
}
|
||||
/>
|
||||
<DrawerWithFooter
|
||||
@@ -202,7 +199,7 @@ const SystemList: FC = () => {
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onSubmit={() =>
|
||||
drawerFormRef.current?.save()?.then(res => {
|
||||
drawerFormRef.current?.save()?.then((res) => {
|
||||
res && manualReloadTable()
|
||||
return res
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user