diff --git a/.gitignore b/.gitignore index ee2ff20..1c45866 100644 --- a/.gitignore +++ b/.gitignore @@ -238,15 +238,12 @@ backend-fastapi/ai_platform/nodes/builtin/system_summary_node.py !backend-fastapi/ai_platform/nodes/builtin/system_summary_node.py web/apps/web-ele/src/api/core/database-monitor.ts web/apps/web-ele/src/api/core/demo.ts -web/apps/web-ele/src/api/core/dingtalk-sync.ts -web/apps/web-ele/src/api/core/feishu-sync.ts web/apps/web-ele/src/api/core/link-preview.ts web/apps/web-ele/src/api/core/redis-manager.ts web/apps/web-ele/src/api/core/redis-monitor.ts web/apps/web-ele/src/api/core/region.ts web/apps/web-ele/src/api/core/scheduler.ts web/apps/web-ele/src/api/core/server-monitor.ts -web/apps/web-ele/src/api/core/wecom-sync.ts web/apps/web-ele/src/router/routes/modules/wiki.ts web/apps/web-ele/src/views/_core/approval-center/ web/apps/web-ele/src/views/_core/data-source/ @@ -260,9 +257,6 @@ web/apps/web-ele/src/views/_core/redis-monitor/ web/apps/web-ele/src/views/_core/region-manager/ web/apps/web-ele/src/views/_core/scheduler/ web/apps/web-ele/src/views/_core/server-monitor/ -web/apps/web-ele/src/views/_core/system-config/modules/dingtalk-sync-form.vue -web/apps/web-ele/src/views/_core/system-config/modules/feishu-sync-form.vue -web/apps/web-ele/src/views/_core/system-config/modules/wecom-sync-form.vue web/apps/web-ele/src/views/ai-platform/workflow/editor/nodes/SystemSummaryNode.vue web/apps/web-ele/src/views/ai-platform/workflow/editor/panels/SystemSummaryPanel.vue !web/apps/web-ele/src/views/ai-platform/workflow/editor/nodes/SystemSummaryNode.vue diff --git a/web/apps/web-ele/src/api/core/dingtalk-sync.ts b/web/apps/web-ele/src/api/core/dingtalk-sync.ts new file mode 100644 index 0000000..e46e68e --- /dev/null +++ b/web/apps/web-ele/src/api/core/dingtalk-sync.ts @@ -0,0 +1,233 @@ +import { requestClient } from '#/api/request'; + +/** + * 连接测试请求 + */ +export interface TestConnectionParams { + app_key?: string; + app_secret?: string; +} + +/** + * 同步配置 + */ +export interface DingtalkSyncConfig { + callback_aes_key?: string; + callback_token?: string; + callback_url?: string; + corp_id?: string; + app_key?: string; + app_secret?: string; + sync_dept_id?: string; + sync_root_dept_id?: string; + enable_dept_event?: string; + enable_user_event?: string; +} + +/** + * 回调状态 + */ +export interface CallbackStatus { + registered: boolean; + callback_url?: string; + subscribed_events?: string[]; +} + +/** + * 同步统计项 + */ +export interface SyncTypeStats { + total_count: number; + success_count: number; + fail_count: number; + not_synced: number; + status: null | string; + sync_time: null | string; +} + +/** + * 同步统计 + */ +export interface SyncStats { + dept: SyncTypeStats; + user: SyncTypeStats; +} + +/** + * 钉钉部门树节点 + */ +export interface DingtalkDeptTreeNode { + dept_id: number; + name: string; + children?: DingtalkDeptTreeNode[]; +} + +/** + * 异步同步任务返回 + */ +export interface SyncTaskResult { + log_id: string; +} + +/** + * 同步任务状态查询结果 + */ +export interface SyncStatusResult { + id: string; + sync_type: string; + total_count: number; + success_count: number; + fail_count: number; + status: string; + started_at: null | string; + finished_at: null | string; +} + +/** + * Stream 增量事件日志 + */ +export interface StreamEventLog { + id: string; + event_type: string; + target_type: string; + target_name: null | string; + dingtalk_dept_id: null | string; + dingtalk_userid: null | string; + status: string; + error_detail: null | string; + event_time: null | string; +} + +/** + * Stream 事件日志分页结果 + */ +export interface StreamEventsResult { + items: StreamEventLog[]; + total: number; + page: number; + page_size: number; +} + +/** + * 连接测试 + */ +export async function testConnectionApi(data: TestConnectionParams) { + return requestClient.post('/api/core/dingtalk-sync/test-connection', data); +} + +/** + * 获取同步配置 + */ +export async function getSyncConfigApi() { + return requestClient.get>( + '/api/core/dingtalk-sync/config', + ); +} + +/** + * 保存同步配置 + */ +export async function updateSyncConfigApi(data: DingtalkSyncConfig) { + return requestClient.put('/api/core/dingtalk-sync/config', data); +} + +/** + * 同步组织架构(异步任务) + */ +export async function syncDeptApi() { + return requestClient.post('/api/core/dingtalk-sync/sync/dept'); +} + +/** + * 同步用户(异步任务) + */ +export async function syncUserApi() { + return requestClient.post('/api/core/dingtalk-sync/sync/user'); +} + +/** + * 查询同步任务状态 + */ +export async function getSyncStatusApi(logId: string) { + return requestClient.get( + `/api/core/dingtalk-sync/sync/status/${logId}`, + ); +} + +/** + * 获取 Stream 增量事件日志 + */ +export async function getStreamEventsApi(page: number = 1, pageSize: number = 20) { + return requestClient.get('/api/core/dingtalk-sync/stream/events', { + params: { page, page_size: pageSize }, + }); +} + +/** + * 获取同步统计 + */ +export async function getSyncStatsApi() { + return requestClient.get('/api/core/dingtalk-sync/stats'); +} + +/** + * 获取同步日志 + */ +export async function getSyncLogsApi(page: number = 1, pageSize: number = 20) { + return requestClient.get('/api/core/dingtalk-sync/logs', { + params: { page, page_size: pageSize }, + }); +} + +/** + * 获取钉钉部门树(选择同步范围) + */ +export async function getDeptTreeApi(data?: TestConnectionParams) { + return requestClient.post( + '/api/core/dingtalk-sync/dept-tree', + data || {}, + ); +} + +/** + * 注册事件回调 + */ +export async function registerCallbackApi() { + return requestClient.post('/api/core/dingtalk-sync/callback/register'); +} + +/** + * 删除事件回调 + */ +export async function deleteCallbackApi() { + return requestClient.delete('/api/core/dingtalk-sync/callback/register'); +} + +/** + * 查询回调注册状态 + */ +export async function getCallbackStatusApi() { + return requestClient.get( + '/api/core/dingtalk-sync/callback/status', + ); +} + +/** + * Stream 模式连接状态 + */ +export interface StreamStatus { + stream_mode: boolean; + running: boolean; + total_events: number; + last_event_type: string | null; + last_event_time: string | null; +} + +/** + * 查询 Stream 模式状态 + */ +export async function getStreamStatusApi() { + return requestClient.get( + '/api/core/dingtalk-sync/stream/status', + ); +} diff --git a/web/apps/web-ele/src/api/core/feishu-sync.ts b/web/apps/web-ele/src/api/core/feishu-sync.ts new file mode 100644 index 0000000..68f4159 --- /dev/null +++ b/web/apps/web-ele/src/api/core/feishu-sync.ts @@ -0,0 +1,134 @@ +import { requestClient } from '#/api/request'; + +/** + * 连接测试请求 + */ +export interface TestConnectionParams { + app_id?: string; + app_secret?: string; +} + +/** + * 同步配置 + */ +export interface FeishuSyncConfig { + app_id?: string; + app_secret?: string; + sync_dept_id?: string; + sync_root_dept_id?: string; + enable_dept_event?: string; + enable_user_event?: string; + encrypt_key?: string; + verification_token?: string; + callback_url?: string; +} + +/** + * 回调状态 + */ +export interface CallbackStatus { + registered: boolean; + callback_url?: string; + subscribed_events?: string[]; +} + +/** + * 同步统计项 + */ +export interface SyncTypeStats { + total_count: number; + success_count: number; + fail_count: number; + not_synced: number; + status: null | string; + sync_time: null | string; +} + +/** + * 同步统计 + */ +export interface SyncStats { + dept: SyncTypeStats; + user: SyncTypeStats; +} + +/** + * 飞书部门树节点 + */ +export interface FeishuDeptTreeNode { + dept_id: string; + name: string; + children?: FeishuDeptTreeNode[]; +} + +/** + * 连接测试 + */ +export async function testConnectionApi(data: TestConnectionParams) { + return requestClient.post('/api/core/feishu-sync/test-connection', data); +} + +/** + * 获取同步配置 + */ +export async function getSyncConfigApi() { + return requestClient.get>( + '/api/core/feishu-sync/config', + ); +} + +/** + * 保存同步配置 + */ +export async function updateSyncConfigApi(data: FeishuSyncConfig) { + return requestClient.put('/api/core/feishu-sync/config', data); +} + +/** + * 同步组织架构 + */ +export async function syncDeptApi() { + return requestClient.post('/api/core/feishu-sync/sync/dept'); +} + +/** + * 同步用户 + */ +export async function syncUserApi() { + return requestClient.post('/api/core/feishu-sync/sync/user'); +} + +/** + * 获取同步统计 + */ +export async function getSyncStatsApi() { + return requestClient.get('/api/core/feishu-sync/stats'); +} + +/** + * 获取同步日志 + */ +export async function getSyncLogsApi(page: number = 1, pageSize: number = 20) { + return requestClient.get('/api/core/feishu-sync/logs', { + params: { page, page_size: pageSize }, + }); +} + +/** + * 获取飞书部门树(选择同步范围) + */ +export async function getDeptTreeApi(data?: TestConnectionParams) { + return requestClient.post( + '/api/core/feishu-sync/dept-tree', + data || {}, + ); +} + +/** + * 查询回调注册状态 + */ +export async function getCallbackStatusApi() { + return requestClient.get( + '/api/core/feishu-sync/callback/status', + ); +} diff --git a/web/apps/web-ele/src/api/core/wecom-sync.ts b/web/apps/web-ele/src/api/core/wecom-sync.ts new file mode 100644 index 0000000..f17e1f6 --- /dev/null +++ b/web/apps/web-ele/src/api/core/wecom-sync.ts @@ -0,0 +1,83 @@ +import { requestClient } from '#/api/request'; + +export interface TestConnectionParams { + corp_id?: string; + corp_secret?: string; +} + +export interface WecomSyncConfig { + callback_aes_key?: string; + callback_token?: string; + callback_url?: string; + corp_id?: string; + corp_secret?: string; + sync_dept_id?: string; + sync_root_dept_id?: string; + enable_dept_event?: string; + enable_user_event?: string; +} + +export interface CallbackStatus { + registered: boolean; + callback_url?: string; + subscribed_events?: string[]; +} + +export interface SyncTypeStats { + total_count: number; + success_count: number; + fail_count: number; + not_synced: number; + status: null | string; + sync_time: null | string; +} + +export interface SyncStats { + dept: SyncTypeStats; + user: SyncTypeStats; +} + +export interface WecomDeptTreeNode { + dept_id: number; + name: string; + children?: WecomDeptTreeNode[]; +} + +export async function testConnectionApi(data: TestConnectionParams) { + return requestClient.post('/api/core/wecom-sync/test-connection', data); +} + +export async function getSyncConfigApi() { + return requestClient.get>( + '/api/core/wecom-sync/config', + ); +} + +export async function updateSyncConfigApi(data: WecomSyncConfig) { + return requestClient.put('/api/core/wecom-sync/config', data); +} + +export async function syncDeptApi() { + return requestClient.post('/api/core/wecom-sync/sync/dept'); +} + +export async function syncUserApi() { + return requestClient.post('/api/core/wecom-sync/sync/user'); +} + +export async function getSyncStatsApi() { + return requestClient.get('/api/core/wecom-sync/stats'); +} + +export async function getDeptTreeApi(data?: TestConnectionParams) { + return requestClient.post( + '/api/core/wecom-sync/dept-tree', + data || {}, + ); +} + +export async function getCallbackStatusApi() { + return requestClient.get( + '/api/core/wecom-sync/callback/status', + ); +} diff --git a/web/apps/web-ele/src/locales/index.ts b/web/apps/web-ele/src/locales/index.ts index aeea48b..58372ec 100644 --- a/web/apps/web-ele/src/locales/index.ts +++ b/web/apps/web-ele/src/locales/index.ts @@ -30,7 +30,9 @@ const modules = import.meta.glob([ './langs/*/chat.json', './langs/*/common.json', './langs/*/dept.json', + './langs/*/dingtalk-sync.json', './langs/*/dict.json', + './langs/*/feishu-sync.json', './langs/*/file-manager.json', './langs/*/form-manager.json', './langs/*/loginLog.json', @@ -49,6 +51,7 @@ const modules = import.meta.glob([ './langs/*/ui-config.json', './langs/*/user-avatar.json', './langs/*/user.json', + './langs/*/wecom-sync.json', ]); const localesMap = loadLocalesMapFromDir( diff --git a/web/apps/web-ele/src/views/_core/system-config/index.vue b/web/apps/web-ele/src/views/_core/system-config/index.vue index c814848..c145616 100644 --- a/web/apps/web-ele/src/views/_core/system-config/index.vue +++ b/web/apps/web-ele/src/views/_core/system-config/index.vue @@ -8,7 +8,9 @@ import { Page } from '@vben/common-ui'; import { BellRing, Bot, + CircleHelp, IconifyIcon, + RefreshCw, Shield, } from '@vben/icons'; import { $t } from '@vben/locales'; @@ -26,10 +28,14 @@ import { getAllConfigsApi, } from '#/api/core/system-config'; import { CardList } from '#/components/card-list'; +import { ZqDialog } from '#/components/zq-dialog'; import { ZqTabs } from '#/components/zq-tabs'; import ConfigForm from './modules/config-form.vue'; +import DingtalkSyncForm from './modules/dingtalk-sync-form.vue'; import ModelConfigForm from './modules/model-config-form.vue'; +import FeishuSyncForm from './modules/feishu-sync-form.vue'; +import WecomSyncForm from './modules/wecom-sync-form.vue'; defineOptions({ name: 'SystemConfigManager' }); @@ -38,7 +44,7 @@ interface ConfigMenuItem extends CardListItem { name: string; group: string; icon: string; - category: 'notify' | 'oauth'; + category: 'notify' | 'oauth' | 'sync'; } const SSO_GROUPS = [ @@ -48,14 +54,26 @@ const SSO_GROUPS = [ 'oauth_google', 'oauth_wechat', 'oauth_microsoft', + 'oauth_dingtalk', + 'oauth_feishu', + 'oauth_wecom', ]; const NOTIFY_GROUPS = [ 'notify_email', 'notify_sms', + 'notify_dingtalk', + 'notify_feishu', + 'notify_wecom', 'notify_wechat_mp', ]; +const SYNC_GROUPS = [ + 'sync_dingtalk', + 'sync_wecom', + 'sync_feishu', +]; + const GROUP_ICONS: Record = { oauth_gitee: 'simple-icons:gitee', oauth_github: 'simple-icons:github', @@ -63,9 +81,18 @@ const GROUP_ICONS: Record = { oauth_google: 'simple-icons:google', oauth_wechat: 'simple-icons:wechat', oauth_microsoft: 'simple-icons:microsoft', + oauth_dingtalk: 'ri:dingding-line', + oauth_feishu: 'simple-icons:bytedance', + oauth_wecom: 'simple-icons:wechat', notify_email: 'mdi:email-outline', notify_sms: 'mdi:message-text-outline', + notify_dingtalk: 'ri:dingding-line', + notify_feishu: 'simple-icons:bytedance', + notify_wecom: 'simple-icons:wechat', notify_wechat_mp: 'simple-icons:wechat', + sync_dingtalk: 'ri:dingding-line', + sync_wecom: 'simple-icons:wechat', + sync_feishu: 'simple-icons:bytedance', }; const activeTab = ref('oauth'); @@ -74,6 +101,11 @@ const selectedMenuId = ref('oauth_gitee'); const loading = ref(false); const saving = ref(false); const configFormRef = ref>(); +const syncFormRef = ref>(); +const wecomSyncFormRef = ref>(); +const feishuSyncFormRef = ref>(); +const syncSaving = ref(false); +const showGuideDialog = ref(false); const menuItems = computed(() => { const items: ConfigMenuItem[] = []; @@ -98,6 +130,16 @@ const menuItems = computed(() => { }); } + for (const group of SYNC_GROUPS) { + items.push({ + id: group, + name: $t(`system-config.groups.${group}`), + group, + icon: GROUP_ICONS[group] || 'mdi:sync', + category: 'sync', + }); + } + return items; }); @@ -115,6 +157,7 @@ const tabItems = computed(() => [ { key: 'oauth', label: $t('system-config.ssoConfig'), icon: Shield }, { key: 'notify', label: $t('system-config.notifyConfig'), icon: BellRing }, { key: 'model', label: $t('system-config.modelConfig'), icon: Bot }, + { key: 'sync', label: $t('system-config.syncConfig'), icon: RefreshCw }, ]); const cardListOptions: CardListOptions = { @@ -176,6 +219,21 @@ function handleSaved() { loadAllConfigs(); } +async function handleSyncSave() { + syncSaving.value = true; + try { + if (selectedMenuId.value === 'sync_wecom') { + await wecomSyncFormRef.value?.save(); + } else if (selectedMenuId.value === 'sync_feishu') { + await feishuSyncFormRef.value?.save(); + } else { + await syncFormRef.value?.save(); + } + } finally { + syncSaving.value = false; + } +} + onMounted(() => { loadAllConfigs(); }); @@ -194,7 +252,7 @@ onMounted(() => { /> - + diff --git a/web/apps/web-ele/src/views/_core/system-config/modules/dingtalk-sync-form.vue b/web/apps/web-ele/src/views/_core/system-config/modules/dingtalk-sync-form.vue new file mode 100644 index 0000000..624b56d --- /dev/null +++ b/web/apps/web-ele/src/views/_core/system-config/modules/dingtalk-sync-form.vue @@ -0,0 +1,838 @@ + + + diff --git a/web/apps/web-ele/src/views/_core/system-config/modules/feishu-sync-form.vue b/web/apps/web-ele/src/views/_core/system-config/modules/feishu-sync-form.vue new file mode 100644 index 0000000..8ffc66f --- /dev/null +++ b/web/apps/web-ele/src/views/_core/system-config/modules/feishu-sync-form.vue @@ -0,0 +1,575 @@ + + + diff --git a/web/apps/web-ele/src/views/_core/system-config/modules/wecom-sync-form.vue b/web/apps/web-ele/src/views/_core/system-config/modules/wecom-sync-form.vue new file mode 100644 index 0000000..d3530a1 --- /dev/null +++ b/web/apps/web-ele/src/views/_core/system-config/modules/wecom-sync-form.vue @@ -0,0 +1,562 @@ + + +