draft
This commit is contained in:
173
frontend/src/pages/app/index.tsx
Normal file
173
frontend/src/pages/app/index.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user