diff --git a/src/api/types.ts b/src/api/types.ts index 776d7bba..21b49ade 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1839,6 +1839,8 @@ export interface DownloaderConf { // 通知配置 export interface NotificationConf { + // 稳定通知渠道身份;显示名称变化时保持不变,用于配置保存和运行态引用 + id: string // 名称 name: string // 类型 telegram/wechat/vocechat/synologychat diff --git a/src/components/dialog/NotificationChannelInfoDialog.vue b/src/components/dialog/NotificationChannelInfoDialog.vue index 1b9f16ef..c26d338f 100644 --- a/src/components/dialog/NotificationChannelInfoDialog.vue +++ b/src/components/dialog/NotificationChannelInfoDialog.vue @@ -47,6 +47,7 @@ const notificationInfoDialog = computed({ // 通知详情 const notificationInfo = ref({ + id: '', name: '', type: '', enabled: false, @@ -183,9 +184,12 @@ async function updateWechatClawBotQrImage(status?: WechatClawBotStatus | null) { /** 组装微信客服状态接口所需的请求参数。 */ function getWechatClawBotRequestParams(extraParams: Record = {}) { const config = notificationInfo.value.config || {} + // 配置身份优先于显示名称;名称仅作为旧配置尚未归一化时的明确读取回退。 + const source = notificationInfo.value.id || notificationInfo.value.name + const fallbackSource = props.notification.id || props.notification.name return { - source: notificationInfo.value.name, - fallback_source: props.notification.name, + source, + fallback_source: fallbackSource, WECHATCLAWBOT_BASE_URL: config.WECHATCLAWBOT_BASE_URL, WECHATCLAWBOT_DEFAULT_TARGET: config.WECHATCLAWBOT_DEFAULT_TARGET, WECHATCLAWBOT_ADMINS: config.WECHATCLAWBOT_ADMINS, @@ -225,19 +229,24 @@ function openNotificationInfoDialog() { /** 保存通知渠道编辑结果并通知父级刷新。 */ function saveNotificationInfo() { // 为空不保存,跳出警告框 - if (!notificationInfo.value.name) { + const normalizedName = notificationInfo.value.name.trim() + if (!normalizedName) { $toast.error(t('notification.name') + t('common.required')) return } // 重名判断 - if (props.notifications.some(item => item.name === notificationInfo.value.name && item !== props.notification)) { - $toast.error(t('notification.channel') + `【${notificationInfo.value.name}】` + t('common.exists')) + const duplicate = props.notifications.some( + item => item.id !== props.notification.id && item.name.trim().toLowerCase() === normalizedName.toLowerCase(), + ) + if (duplicate) { + $toast.error(t('notification.channel') + `【${normalizedName}】` + t('common.exists')) return } + notificationInfo.value.name = normalizedName ensureWechatConfigDefaults(notificationInfo.value) ensureWechatClawBotConfigDefaults(notificationInfo.value) notificationInfoDialog.value = false - emit('change', notificationInfo.value, props.notification.name) + emit('change', notificationInfo.value, props.notification.id) emit('done') } diff --git a/src/components/workflow/__tests__/SendMessageAction.spec.ts b/src/components/workflow/__tests__/SendMessageAction.spec.ts index ca482de5..f815562a 100644 --- a/src/components/workflow/__tests__/SendMessageAction.spec.ts +++ b/src/components/workflow/__tests__/SendMessageAction.spec.ts @@ -37,14 +37,14 @@ describe('SendMessageAction', () => { expect(getSelectItems(container, '渠道')).toEqual([]) }) - it('maps unwrapped admin notification channels to name options', async () => { + it('maps admin notification channels to name options while preserving workflow values', async () => { mocks.apiGet.mockResolvedValue({ success: true, message: '', data: { value: [ - { name: 'Telegram', type: 'telegram', enabled: true }, - { name: '企业微信', type: 'wechat', enabled: false }, + { id: 'telegram-channel', name: 'Telegram', type: 'telegram', enabled: true }, + { id: 'wechat-channel', name: '企业微信', type: 'wechat', enabled: false }, ], }, }) diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts index f525a6d4..ed512cc0 100644 --- a/src/locales/en-US.ts +++ b/src/locales/en-US.ts @@ -13,6 +13,7 @@ export default { loading: 'Loading', success: 'Success', error: 'Error', + exists: 'Exists', openInNewWindow: 'Open in new window', download: 'Download', uploadSpeed: 'Upload speed', @@ -2522,6 +2523,7 @@ export default { notification: { channels: 'Notification Channels', channelsDesc: 'Set message sending channel parameters', + channelsLoadFailed: 'Failed to load notification channels. Refresh and try again.', organizeSuccess: 'Media Import', downloadAdded: 'Download Added', subscribeAdded: 'Subscribe Added', diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts index f1551694..866a3d01 100644 --- a/src/locales/zh-CN.ts +++ b/src/locales/zh-CN.ts @@ -13,6 +13,7 @@ export default { loading: '加载中', success: '成功', error: '错误', + exists: '已存在', openInNewWindow: '在新窗口中打开', download: '下载', inputMessage: '输入消息或命令', @@ -2475,6 +2476,7 @@ export default { notification: { channels: '通知渠道', channelsDesc: '设置消息发送渠道参数', + channelsLoadFailed: '加载通知渠道失败,请刷新后重试', organizeSuccess: '资源入库', downloadAdded: '资源下载', subscribeAdded: '添加订阅', diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts index 99afebcb..bc5c51e1 100644 --- a/src/locales/zh-TW.ts +++ b/src/locales/zh-TW.ts @@ -13,6 +13,7 @@ export default { loading: '加載中', success: '成功', error: '錯誤', + exists: '已存在', openInNewWindow: '在新窗口中打開', download: '下載', inputMessage: '輸入消息或命令', @@ -2474,6 +2475,7 @@ export default { notification: { channels: '通知渠道', channelsDesc: '設置消息發送渠道參數', + channelsLoadFailed: '載入通知渠道失敗,請刷新後重試', organizeSuccess: '資源入庫', downloadAdded: '資源下載', subscribeAdded: '添加訂閱', diff --git a/src/views/setting/AccountSettingNotification.vue b/src/views/setting/AccountSettingNotification.vue index 8acde490..e2f2acdd 100644 --- a/src/views/setting/AccountSettingNotification.vue +++ b/src/views/setting/AccountSettingNotification.vue @@ -1,7 +1,6 @@