From 459e19cbb8860d8a06d3a29be31deda6b206fc27 Mon Sep 17 00:00:00 2001 From: gitadmin Date: Mon, 13 Jul 2026 16:41:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20wechat=E9=A1=B5per-webhook=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E4=BF=9D=E5=AD=98=20+=20monitor=E9=A1=B5=E7=94=A8useD?= =?UTF-8?q?irtyTracker=E7=BB=9F=E4=B8=80section=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wechat页: 每推送组底部独立保存按钮(仅修改后显示),移除全局保存按钮 - monitor页: 用 useDirtyTracker 替代 originalConfig+hasSectionChanged 模式 --- src/app/(app)/settings/monitor/page.tsx | 17 +++++----- src/app/(app)/settings/wechat/page.tsx | 44 ++++++++++++++----------- 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/app/(app)/settings/monitor/page.tsx b/src/app/(app)/settings/monitor/page.tsx index 4ca152d..671b35e 100644 --- a/src/app/(app)/settings/monitor/page.tsx +++ b/src/app/(app)/settings/monitor/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { Card } from '@/components/ui' import { Search, RefreshCw } from 'lucide-react' +import { useDirtyTracker } from '@shared/ui/hooks/useDirtyTracker' interface MonitorStatus { enabled: boolean @@ -53,8 +54,8 @@ type SectionKey = 'mail' | 'filter' | 'monitor' export default function MonitorSettingsPage() { const [status, setStatus] = useState(null) const [config, setConfig] = useState(null) - const [originalConfig, setOriginalConfig] = useState(null) const [loading, setLoading] = useState(true) + const { isDirty, markClean } = useDirtyTracker() const [saving, setSaving] = useState(null) const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const [newKeyword, setNewKeyword] = useState('') @@ -212,7 +213,9 @@ export default function MonitorSettingsPage() { ]).then(([s, c, scan, history]) => { setStatus(s) setConfig(c) - setOriginalConfig(JSON.parse(JSON.stringify(c))) + markClean('mail', { mail: c.mail }) + markClean('filter', { filter: c.filter }) + markClean('monitor', { monitor: c.monitor }) setScanState(scan) setScanHistory(history) setLoading(false) @@ -361,7 +364,7 @@ export default function MonitorSettingsPage() { const data = await res.json() setSaving(null) if (data.success) { - setOriginalConfig(prev => prev ? { ...prev, [section]: JSON.parse(JSON.stringify(config[section])) } : prev) + markClean(section, { [section]: config[section] }) setSectionMessage(prev => ({ ...prev, [section]: { type: 'success', text: '已保存' } })) } else { setSectionMessage(prev => ({ ...prev, [section]: { type: 'error', text: data.error } })) @@ -380,16 +383,12 @@ export default function MonitorSettingsPage() { setConfig({ ...config, filter: { ...config.filter, subject_keywords: config.filter.subject_keywords.filter((_, i) => i !== index) } }) } - function hasSectionChanged(original: MonitorConfig | null, current: MonitorConfig | null, section: SectionKey): boolean { - if (!original || !current) return false - return JSON.stringify(original[section]) !== JSON.stringify(current[section]) - } - const inputClass = (path: string, changed = false) => `w-full px-3 py-2 border rounded-lg text-sm transition-colors ${changed ? 'border-amber-400 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-600' : 'border-slate-300 dark:border-slate-600'}` const SectionSaveButton = ({ section }: { section: SectionKey }) => { - const changed = hasSectionChanged(originalConfig, config, section) + if (!config) return null + const changed = isDirty(section, { [section]: config[section] }) if (!changed) return null return (
diff --git a/src/app/(app)/settings/wechat/page.tsx b/src/app/(app)/settings/wechat/page.tsx index 8414548..4699e97 100644 --- a/src/app/(app)/settings/wechat/page.tsx +++ b/src/app/(app)/settings/wechat/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react' import { Card } from '@/components/ui' import { Plus, Trash2, ChevronDown, ChevronUp } from 'lucide-react' +import { useDirtyTracker } from '@shared/ui/hooks/useDirtyTracker' interface WebhookConfig { id: string @@ -23,7 +24,8 @@ function generateId() { export default function WechatSettingsPage() { const [config, setConfig] = useState<{ wechat: { webhooks: WebhookConfig[] } } | null>(null) const [loading, setLoading] = useState(true) - const [saving, setSaving] = useState(false) + const { isDirty, markClean, removeItem } = useDirtyTracker() + const [savingId, setSavingId] = useState(null) const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const [expandedWebhooks, setExpandedWebhooks] = useState>(new Set()) const [testingWebhook, setTestingWebhook] = useState(null) @@ -40,25 +42,28 @@ export default function WechatSettingsPage() { .finally(() => setLoading(false)) }, []) - // 保存 - const handleSave = async () => { + // 保存单个推送组 + const handleSaveSingle = async (id: string) => { if (!config) return - setSaving(true) + const wh = config.wechat.webhooks.find(w => w.id === id) + if (!wh) return + setSavingId(id) setMessage(null) try { const res = await fetch('/api/monitor/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ wechat: config.wechat }), + body: JSON.stringify({ wechat: { webhooks: [wh] } }), }) const data = await res.json() if (!res.ok) throw new Error(data.error || '保存失败') - setMessage({ type: 'success', text: '保存成功' }) + markClean(id, wh) + setMessage({ type: 'success', text: `「${wh.name}」保存成功` }) setTimeout(() => setMessage(null), 3000) } catch (err) { - setMessage({ type: 'error', text: '保存失败: ' + (err instanceof Error ? err.message : String(err)) }) + setMessage({ type: 'error', text: `「${wh.name}」保存失败: ${err instanceof Error ? err.message : String(err)}` }) } finally { - setSaving(false) + setSavingId(null) } } @@ -99,6 +104,7 @@ export default function WechatSettingsPage() { if (!config) return if (config.wechat.webhooks.length <= 1) return setConfig({ wechat: { ...config.wechat, webhooks: config.wechat.webhooks.filter(w => w.id !== id) } }) + removeItem(id) } // 更新 webhook 基本字段 @@ -265,22 +271,22 @@ export default function WechatSettingsPage() {
+ {isDirty(wh.id, wh) && ( +
+ +
+ )} )} ))} - - {/* 保存按钮 */} -
- -
)