Files
pay-bridge/frontend/src/pages/app/index.tsx
2026-03-13 15:51:59 +08:00

174 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import {
Table, Tag, Button, Space, Card, Modal, Form, Input,
message, Popconfirm, Alert, Typography,
} from 'antd'
import { KeyOutlined, PlusOutlined } from '@ant-design/icons'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import type { ColumnsType } from 'antd/es/table'
import dayjs from 'dayjs'
import { appApi } from '../../api/app'
import type { App } from '../../types'
const { Text } = Typography
export default function AppPage() {
const [createOpen, setCreateOpen] = useState(false)
const [secretModal, setSecretModal] = useState<{ appID: string; secret: string; tip: string } | null>(null)
const [form] = Form.useForm()
const qc = useQueryClient()
const { data, isLoading } = useQuery({
queryKey: ['apps'],
queryFn: () => appApi.list({ limit: 50, offset: 0 }),
})
const createMutation = useMutation({
mutationFn: appApi.create,
onSuccess: (res) => {
setCreateOpen(false)
form.resetFields()
qc.invalidateQueries({ queryKey: ['apps'] })
const d = res.data.data
setSecretModal({ appID: d.app_id, secret: d.app_secret, tip: d.secret_tip })
},
})
const disableMutation = useMutation({
mutationFn: appApi.disable,
onSuccess: () => {
message.success('已禁用')
qc.invalidateQueries({ queryKey: ['apps'] })
},
})
const enableMutation = useMutation({
mutationFn: appApi.enable,
onSuccess: () => {
message.success('已启用')
qc.invalidateQueries({ queryKey: ['apps'] })
},
})
const resetMutation = useMutation({
mutationFn: appApi.resetSecret,
onSuccess: (res) => {
const d = res.data.data
setSecretModal({ appID: d.app_id, secret: d.app_secret, tip: d.secret_tip })
},
})
const columns: ColumnsType<App> = [
{ title: 'App ID', dataIndex: 'app_id', width: 200 },
{ title: '应用名称', dataIndex: 'app_name', width: 160 },
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (v: number) =>
v === 1 ? <Tag color="success"></Tag> : <Tag color="default"></Tag>,
},
{
title: '创建时间',
dataIndex: 'created_at',
width: 180,
render: (v) => dayjs(v).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: '操作',
width: 220,
render: (_, record) => (
<Space>
<Popconfirm
title="重置后旧密钥立即失效,确认重置?"
onConfirm={() => resetMutation.mutate(record.app_id)}
>
<Button size="small" icon={<KeyOutlined />}></Button>
</Popconfirm>
{record.status === 1 ? (
<Popconfirm title="禁用后该应用将无法访问,确认?" onConfirm={() => disableMutation.mutate(record.app_id)}>
<Button size="small" danger></Button>
</Popconfirm>
) : (
<Popconfirm title="确认启用该应用?" onConfirm={() => enableMutation.mutate(record.app_id)}>
<Button size="small"></Button>
</Popconfirm>
)}
</Space>
),
},
]
const list: App[] = data?.data?.data?.list ?? []
return (
<>
<Card
extra={
<Button type="primary" icon={<PlusOutlined />} onClick={() => setCreateOpen(true)}>
</Button>
}
>
<Table
rowKey="app_id"
columns={columns}
dataSource={list}
loading={isLoading}
scroll={{ x: 800 }}
/>
</Card>
{/* 新建应用弹窗 */}
<Modal
title="新建接入应用"
open={createOpen}
onOk={() => form.submit()}
onCancel={() => { setCreateOpen(false); form.resetFields() }}
confirmLoading={createMutation.isPending}
>
<Form form={form} layout="vertical" onFinish={(v) => createMutation.mutate(v)}>
<Form.Item name="app_name" label="应用名称" rules={[{ required: true, message: '请输入应用名称' }]}>
<Input placeholder="例如商城系统、ERP系统" />
</Form.Item>
</Form>
<Alert
type="info"
showIcon
message="App ID 和 App Secret 将在创建后自动生成Secret 仅展示一次,请妥善保存。"
style={{ marginTop: 8 }}
/>
</Modal>
{/* Secret 展示弹窗(创建/重置后) */}
<Modal
title="请妥善保存以下凭证"
open={!!secretModal}
footer={<Button type="primary" onClick={() => setSecretModal(null)}></Button>}
onCancel={() => setSecretModal(null)}
closable={false}
maskClosable={false}
>
{secretModal && (
<>
<Alert
type="warning"
showIcon
message={secretModal.tip}
style={{ marginBottom: 16 }}
/>
<Form layout="vertical">
<Form.Item label="App ID">
<Text copyable code>{secretModal.appID}</Text>
</Form.Item>
<Form.Item label="App Secret">
<Text copyable code>{secretModal.secret}</Text>
</Form.Item>
</Form>
</>
)}
</Modal>
</>
)
}