import { useState } from 'react'; import { Avatar, Button, Checkbox, Descriptions, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { WECOM_PUSH_CONDITION_GROUPS, WECOM_PUSH_CONDITION_LABELS, type WecomMessagePushDto, type WecomPushCondition, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; import OssUpload from '../components/OssUpload'; type FormValues = { name: string; avatarUrl?: string; webhookUrl: string; enabled: boolean; mentionWecomUserId?: string; pushConditions: WecomPushCondition[]; sortOrder: number; }; function WecomPushConditionPicker({ value, onChange, }: { value?: WecomPushCondition[]; onChange?: (next: WecomPushCondition[]) => void; }) { const selected = new Set(value ?? []); function toggle(cond: WecomPushCondition, checked: boolean) { const next = new Set(selected); if (checked) next.add(cond); else next.delete(cond); onChange?.([...next]); } return (
{WECOM_PUSH_CONDITION_GROUPS.map((group) => (
{group.label}
{group.conditions.map((c) => (
toggle(c, e.target.checked)} > {WECOM_PUSH_CONDITION_LABELS[c]}
))}
))}
); } export default function WecomMessagePushesPage() { 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 [detail, setDetail] = useState(null); const [testingId, setTestingId] = useState(null); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/wecom-message-pushes', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.enabled) qs.set('enabled', filters.enabled); return qs; }, [filters], ); const conditionsWatch = Form.useWatch('pushConditions', form) as WecomPushCondition[] | undefined; function openCreate() { setEditing(null); form.resetFields(); form.setFieldsValue({ enabled: true, pushConditions: ['alert.ops'], sortOrder: 0, }); setModalOpen(true); } function openEdit(row: WecomMessagePushDto) { setEditing(row); form.setFieldsValue({ name: row.name, avatarUrl: row.avatarUrl ?? undefined, webhookUrl: row.webhookUrl, enabled: row.enabled, mentionWecomUserId: row.mentionWecomUserId ?? undefined, pushConditions: row.pushConditions, sortOrder: row.sortOrder, }); setModalOpen(true); } async function save() { const values = await form.validateFields(); setSaving(true); try { const body = { name: values.name, avatarUrl: values.avatarUrl || null, webhookUrl: values.webhookUrl, enabled: values.enabled, mentionWecomUserId: values.mentionWecomUserId?.trim() || null, pushConditions: values.pushConditions, sortOrder: values.sortOrder ?? 0, }; if (editing) { await request(`/admin/wecom-message-pushes/${editing.id}`, { method: 'PUT', body: JSON.stringify(body), }); message.success('已更新'); } else { await request('/admin/wecom-message-pushes', { method: 'POST', body: JSON.stringify(body), }); 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-message-pushes/${id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } async function testPush(id: string) { setTestingId(id); try { const res = await request<{ ok: boolean; message: string }>( `/admin/wecom-message-pushes/${id}/test`, { method: 'POST', body: '{}' }, ); message.success(res.message || '已发送测试消息'); } catch (e) { message.error(e instanceof Error ? e.message : '测试失败'); } finally { setTestingId(null); } } const columns: ColumnsType = [ { title: '名称', dataIndex: 'name', render: (name: string, row) => ( {name.slice(0, 1)} {name} ), }, { title: 'Webhook', dataIndex: 'webhookUrlMasked', ellipsis: true, }, { title: '推送条件', dataIndex: 'pushConditions', render: (conds: WecomPushCondition[]) => conds.map((c) => ( {WECOM_PUSH_CONDITION_LABELS[c]} )), }, { title: '启用', dataIndex: 'enabled', width: 72, render: (v: boolean) => (v ? : ), }, { title: '排序', dataIndex: 'sortOrder', width: 64 }, { title: '操作', key: 'actions', width: 220, render: (_, row) => ( void remove(row.id)}> ), }, ]; return (
企微机器人 · 消息推送 配置群机器人 Webhook 多实例,按推送条件分发运营告警、技术支持工单、开发任务派发等消息。运行时不再读取 .env 中的 Webhook URL。
setFilters(v as Record)} > v?.length ? Promise.resolve() : Promise.reject(new Error('请至少勾选一项推送条件')), }, ]} > {conditionsWatch?.length ? ( 已选 {conditionsWatch.length} 项: {conditionsWatch.map((c) => WECOM_PUSH_CONDITION_LABELS[c]).join('、')} ) : null} setDetail(null)} footer={null} width={560} > {detail ? ( {detail.name} {detail.webhookUrlMasked} {detail.enabled ? '是' : '否'} {detail.mentionWecomUserId || '—'} {detail.pushConditions.map((c) => WECOM_PUSH_CONDITION_LABELS[c]).join('、')} {detail.sortOrder} {fmtTime(detail.createdAt)} {fmtTime(detail.updatedAt)} ) : null}
); }