import { useState } from 'react'; import { Alert, Button, Checkbox, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { WECOM_PLUGIN_API_KEY_HEADER, WECOM_PLUGIN_PERMISSION_GROUPS, WECOM_PLUGIN_PERMISSION_LABELS, resolveWecomPluginPublicUrl, type WecomApiPluginDto, type WecomApiPluginSecretDto, type WecomPluginPermission, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; import { useAdminListColumns } from '../lib/useAdminListColumns'; import { AdminListHeader } from '../components/AdminListHeader'; import { AdminPrimaryLink } from '../components/AdminPrimaryLink'; type FormValues = { name: string; apiKey?: string; permissions: WecomPluginPermission[]; remark?: string; enabled: boolean; sortOrder: number; }; export default function WecomApiPluginsPage() { const [filterForm] = Form.useForm(); const [form] = Form.useForm(); const [filters, setFilters] = useState>({}); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const [rotatingId, setRotatingId] = useState(null); const [revealed, setRevealed] = useState(null); const pluginUrl = resolveWecomPluginPublicUrl(window.location.hostname); const permissionsWatch = Form.useWatch('permissions', form) as WecomPluginPermission[] | undefined; const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/wecom-api-plugins', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.enabled) qs.set('enabled', filters.enabled); return qs; }, [filters], ); function openCreate() { setEditing(null); form.resetFields(); form.setFieldsValue({ enabled: true, permissions: [], sortOrder: 0, }); setModalOpen(true); } function openEdit(row: WecomApiPluginDto) { setEditing(row); form.setFieldsValue({ name: row.name, apiKey: '', permissions: [...row.permissions], remark: row.remark ?? undefined, enabled: row.enabled, sortOrder: row.sortOrder, }); setModalOpen(true); } async function save() { const values = await form.validateFields(); setSaving(true); try { const body = { name: values.name, permissions: values.permissions, remark: values.remark?.trim() || null, enabled: values.enabled, sortOrder: values.sortOrder ?? 0, apiKey: values.apiKey?.trim() || undefined, }; if (editing) { await request(`/admin/wecom-api-plugins/${editing.id}`, { method: 'PUT', body: JSON.stringify(body), }); message.success('已更新'); } else { const created = await request('/admin/wecom-api-plugins', { method: 'POST', body: JSON.stringify(body), }); setRevealed(created); message.success('已创建,请立即复制密钥'); } setModalOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function remove(id: string) { try { await request(`/admin/wecom-api-plugins/${id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } async function rotateKey(id: string) { setRotatingId(id); try { const row = await request(`/admin/wecom-api-plugins/${id}/rotate-key`, { method: 'POST', body: '{}', }); setRevealed(row); message.success('已轮换,请立即复制新密钥'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '轮换失败'); } finally { setRotatingId(null); } } const baseColumns: ColumnsType = [ { title: '名称', dataIndex: 'name', render: (name: string, row) => ( openEdit(row)}>{name} ), }, { title: '密钥', dataIndex: 'apiKeyMasked', width: 160 }, { title: '权限', dataIndex: 'permissions', render: (perms: WecomPluginPermission[]) => perms.map((p) => ( {WECOM_PLUGIN_PERMISSION_LABELS[p]} )), }, { title: '备注', dataIndex: 'remark', render: (v: string | null) => v || '—' }, { title: '启用', dataIndex: 'enabled', width: 72, render: (v: boolean) => (v ? : ), }, { title: '排序', dataIndex: 'sortOrder', width: 64 }, { title: '更新时间', dataIndex: 'updatedAt', width: 170, render: (v: string) => fmtTime(v), }, { title: '操作', key: 'actions', width: 220, render: (_, row) => ( void rotateKey(row.id)} > void remove(row.id)}> ), }, ]; const { columns, settingsButton, settingsModal } = useAdminListColumns( 'wecom-api-plugins', baseColumns, { page, pageSize }, ); return ( <> {settingsModal} 新建插件 } />
插件 URL: {pluginUrl}
OpenAPI(带该实例 Key): {`${pluginUrl}/openapi.json`}
授权方式 Service token / API key,Header 名 {WECOM_PLUGIN_API_KEY_HEADER} ;第 2 步只添加本实例已勾选的工具(GET + Query)。 } />
{ setFilters(v as Record); setPage(1); }} > {!editing ? ( ) : ( {editing.apiKeyMasked}(列表不回显明文,需要时请轮换) )} v?.length ? Promise.resolve() : Promise.reject(new Error('请至少勾选一项')), }, ]} extra={ permissionsWatch?.length ? ( 已选 {permissionsWatch.length} 项 ) : null } > {WECOM_PLUGIN_PERMISSION_GROUPS.map((group) => (
{group.label}
{group.permissions.map((p) => ( {WECOM_PLUGIN_PERMISSION_LABELS[p]} ))}
))}
setRevealed(null)} footer={[ , ]} > {revealed ? (

实例「{revealed.name}」的 {WECOM_PLUGIN_API_KEY_HEADER},关闭后不再展示。

{revealed.apiKey}
) : null}
); }