feat: add DingTalk integration config page

This commit is contained in:
2026-07-09 10:14:51 +08:00
parent 7df7695073
commit e8dec3b171
3 changed files with 151 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
import React, { useEffect, useState } from 'react';
import {
Card, Form, Input, Button, Space, message, Spin, Switch, Alert, Descriptions, Tag,
} from 'antd';
import {
SaveOutlined, ApiOutlined, CheckCircleOutlined, CloseCircleOutlined,
} from '@ant-design/icons';
import api from '../../api';
interface DingTalkConfig {
agentId: string;
appSecret: string;
corpId: string;
startEnable: boolean;
}
const IntegrationConfigPage: React.FC = () => {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [testing, setTesting] = useState(false);
const [config, setConfig] = useState<DingTalkConfig | null>(null);
const [verified, setVerified] = useState<boolean | null>(null);
const [form] = Form.useForm<DingTalkConfig>();
const fetchConfig = async () => {
setLoading(true);
try {
const res = await api.get<{ success: boolean; data: Array<{ type: string; verify: boolean; config: DingTalkConfig }> }>('/integration/config');
const dt = res.data?.find((c) => c.type === 'DINGTALK');
if (dt) {
setConfig(dt.config);
setVerified(dt.verify);
form.setFieldsValue(dt.config);
}
} catch {
// not configured
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchConfig();
}, []);
const handleSave = async () => {
const values = await form.validateFields();
setSaving(true);
try {
await api.post('/integration/config', { type: 'DINGTALK', config: values });
message.success('配置已保存');
await fetchConfig();
} catch (e: unknown) {
const err = e as { message?: string };
message.error(err?.message || '保存失败');
} finally {
setSaving(false);
}
};
const handleTest = async () => {
const values = await form.validateFields();
setTesting(true);
try {
const res = await api.post<{ success: boolean; message: string }>('/integration/config/test', {
type: 'DINGTALK',
config: values,
});
setVerified(res.success);
message.success(res.message);
} catch (e: unknown) {
const err = e as { message?: string };
setVerified(false);
message.error(err?.message || '连接失败');
} finally {
setTesting(false);
}
};
return (
<Card title="钉钉集成配置" extra={
<Space>
{verified === true && <Tag icon={<CheckCircleOutlined />} color="success"></Tag>}
{verified === false && <Tag icon={<CloseCircleOutlined />} color="error"></Tag>}
</Space>
}>
<Spin spinning={loading}>
{config && (
<Descriptions size="small" column={2} style={{ marginBottom: 24 }}>
<Descriptions.Item label="CorpId">{config.corpId || '-'}</Descriptions.Item>
<Descriptions.Item label="AppKey">{config.agentId || '-'}</Descriptions.Item>
<Descriptions.Item label="启用同步">
<Tag color={config.startEnable ? 'green' : 'default'}>
{config.startEnable ? '已启用' : '未启用'}
</Tag>
</Descriptions.Item>
</Descriptions>
)}
<Alert
type="info"
message="配置钉钉应用凭证后,可使用组织架构同步、考勤导入和排班同步功能。"
style={{ marginBottom: 24 }}
showIcon
/>
<Form form={form} layout="vertical" style={{ maxWidth: 480 }}>
<Form.Item name="corpId" label="CorpId企业ID" rules={[{ required: true, message: '请输入 CorpId' }]}>
<Input placeholder="dingxxxxxxxx" />
</Form.Item>
<Form.Item name="agentId" label="AppKey应用凭证" rules={[{ required: true, message: '请输入 AppKey' }]}>
<Input placeholder="从钉钉开放平台获取" />
</Form.Item>
<Form.Item
name="appSecret"
label="AppSecret应用密钥"
rules={[{ required: true, message: '请输入 AppSecret' }]}
extra="保存后仅返回脱敏信息,重新编辑时需再次输入完整密钥"
>
<Input.Password placeholder="从钉钉开放平台获取" />
</Form.Item>
<Form.Item name="startEnable" label="启用同步" valuePropName="checked">
<Switch />
</Form.Item>
<Space>
<Button type="primary" icon={<SaveOutlined />} loading={saving} onClick={handleSave}>
</Button>
<Button icon={<ApiOutlined />} loading={testing} onClick={handleTest}>
</Button>
</Space>
</Form>
</Spin>
</Card>
);
};
export default IntegrationConfigPage;