mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 03:13:43 +08:00
feat: add WeCom notification settings
This commit is contained in:
@@ -6,7 +6,7 @@ import { Select } from "../ui/Select";
|
||||
import { Switch } from "../ui/Switch";
|
||||
import { ChannelHeader, EmptyLine, Field, UrlListEditor } from "./controls";
|
||||
import { HEADER_NAME_SUGGESTIONS, nextHeaderRowId } from "./model";
|
||||
import type { BarkForm, EmailForm, HeaderRow, WebhookForm } from "./model";
|
||||
import type { BarkForm, EmailForm, HeaderRow, WebhookForm, WecomForm } from "./model";
|
||||
|
||||
const HEADER_LIST_ID = "vocat-webhook-header-names";
|
||||
|
||||
@@ -267,3 +267,53 @@ export function WebhookTab({ value, onChange, testing, onTest }: PushChannelProp
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function WecomTab({ value, onChange, testing, onTest }: PushChannelProps<WecomForm>) {
|
||||
const { t } = useI18n();
|
||||
const off = !value.enabled;
|
||||
const complete = hasAnyUrl(value.urls) && !!value.payloadTemplate.trim();
|
||||
return (
|
||||
<div className="pt-2">
|
||||
<ChannelHeader
|
||||
title={t("启用企业微信推送")}
|
||||
enabled={value.enabled}
|
||||
onToggle={(enabled) => onChange({ enabled })}
|
||||
actions={
|
||||
<Button size="small" variant="primary" plain loading={testing} disabled={off || !complete} onClick={onTest}>
|
||||
{t("测试通知")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<SMSOnlyHint />
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg bg-gray-50 px-3 py-2 text-xs leading-5 text-gray-500 dark:bg-gray-800/60 dark:text-gray-400">
|
||||
{t("每个企业微信消息推送 Webhook URL 单独占一行,点击添加 URL 新增一行;不使用逗号、空格或换行分隔多个 URL。")}
|
||||
</div>
|
||||
<UrlListEditor
|
||||
urls={value.urls}
|
||||
onChange={(urls) => onChange({ urls })}
|
||||
enabled={value.enabled}
|
||||
placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
|
||||
emptyText={t("尚未配置任何企业微信 Webhook URL,点击右侧添加按钮。")}
|
||||
/>
|
||||
<Field
|
||||
label={t("JSON 请求体模板")}
|
||||
hint={
|
||||
<>
|
||||
{t("支持完整企业微信消息 JSON。变量必须作为 JSON 值使用,例如")} <code>{"{{message}}"}</code>。
|
||||
{t("可用变量:{{event}}、{{title}}、{{message}}、{{timestamp}}、{{content}}、{{number}}、{{device_id}}、{{device_name}}、{{device_label}}、{{time}}。")}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Textarea
|
||||
value={value.payloadTemplate}
|
||||
onChange={(event) => onChange({ payloadTemplate: event.target.value })}
|
||||
disabled={off}
|
||||
rows={12}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,18 +47,32 @@ export interface EmailForm {
|
||||
}
|
||||
|
||||
export interface PushplusForm {
|
||||
enabled: boolean;
|
||||
token: string;
|
||||
topic: string;
|
||||
channel: string;
|
||||
enabled: boolean;
|
||||
token: string;
|
||||
topic: string;
|
||||
channel: string;
|
||||
}
|
||||
|
||||
export interface WecomForm {
|
||||
enabled: boolean;
|
||||
urls: string[];
|
||||
payloadTemplate: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_WECOM_PAYLOAD_TEMPLATE = `{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": {{message}}
|
||||
}
|
||||
}`;
|
||||
|
||||
export interface NotifyForms {
|
||||
telegram: TelegramForm;
|
||||
webhook: WebhookForm;
|
||||
bark: BarkForm;
|
||||
email: EmailForm;
|
||||
pushplus: PushplusForm;
|
||||
email: EmailForm;
|
||||
pushplus: PushplusForm;
|
||||
wecom: WecomForm;
|
||||
}
|
||||
|
||||
// 系统保留头,自定义同名头会被忽略(品牌 vocat)
|
||||
@@ -131,8 +145,9 @@ export function formsFromNotifications(data: Partial<NotificationSettings>): Not
|
||||
const telegram = asRecord(data.telegram);
|
||||
const webhook = asRecord(data.webhook);
|
||||
const bark = asRecord(data.bark);
|
||||
const email = asRecord(data.email);
|
||||
const pushplus = asRecord(data.pushplus);
|
||||
const email = asRecord(data.email);
|
||||
const pushplus = asRecord(data.pushplus);
|
||||
const wecom = asRecord(data.wecom);
|
||||
return {
|
||||
telegram: {
|
||||
enabled: !!telegram.enabled,
|
||||
@@ -171,13 +186,18 @@ export function formsFromNotifications(data: Partial<NotificationSettings>): Not
|
||||
fromAddress: str(email.fromAddress),
|
||||
toAddresses: joinList(email.toAddresses),
|
||||
},
|
||||
pushplus: {
|
||||
enabled: !!pushplus.enabled,
|
||||
token: str(pushplus.token),
|
||||
topic: str(pushplus.topic),
|
||||
channel: str(pushplus.channel) || "wechat",
|
||||
},
|
||||
};
|
||||
pushplus: {
|
||||
enabled: !!pushplus.enabled,
|
||||
token: str(pushplus.token),
|
||||
topic: str(pushplus.topic),
|
||||
channel: str(pushplus.channel) || "wechat",
|
||||
},
|
||||
wecom: {
|
||||
enabled: !!wecom.enabled,
|
||||
urls: strList(wecom.urls),
|
||||
payloadTemplate: str(wecom.payloadTemplate ?? wecom.payload_template) || DEFAULT_WECOM_PAYLOAD_TEMPLATE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function splitList(value: string): string[] {
|
||||
@@ -226,6 +246,15 @@ export function buildEmailPayload(form: EmailForm, forTest = false) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildWecomPayload(form: WecomForm, forTest = false) {
|
||||
const urls = Array.isArray(form.urls) ? form.urls : [];
|
||||
return {
|
||||
enabled: !!form.enabled,
|
||||
urls: forTest ? urls.map((url) => String(url || "").trim()).filter(Boolean) : urls,
|
||||
payload_template: String(form.payloadTemplate || ""),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildNotificationsPayload(forms: NotifyForms) {
|
||||
return {
|
||||
telegram: {
|
||||
@@ -245,6 +274,7 @@ export function buildNotificationsPayload(forms: NotifyForms) {
|
||||
channel: forms.pushplus.channel || "",
|
||||
},
|
||||
webhook: buildWebhookPayload(forms.webhook),
|
||||
bark: buildBarkPayload(forms.bark),
|
||||
};
|
||||
bark: buildBarkPayload(forms.bark),
|
||||
wecom: buildWecomPayload(forms.wecom),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ import {
|
||||
buildBarkPayload,
|
||||
buildEmailPayload,
|
||||
buildNotificationsPayload,
|
||||
buildWecomPayload,
|
||||
buildWebhookPayload,
|
||||
defaultNotifyForms,
|
||||
formsFromNotifications,
|
||||
type NotifyForms,
|
||||
} from "../components/settings/model";
|
||||
import { PushplusTab, TelegramTab } from "../components/settings/BotTabs";
|
||||
import { BarkTab, EmailTab, WebhookTab } from "../components/settings/PushTabs";
|
||||
import { BarkTab, EmailTab, WebhookTab, WecomTab } from "../components/settings/PushTabs";
|
||||
import { PluginsCard } from "../components/settings/PluginsCard";
|
||||
import { HTTPSCard } from "../components/settings/HTTPSCard";
|
||||
import { DeviceQuotaCard } from "../components/settings/DeviceQuotaCard";
|
||||
@@ -34,6 +35,7 @@ const NOTIFY_TABS = [
|
||||
{ key: "email", label: "Email" },
|
||||
{ key: "pushplus", label: "Pushplus" },
|
||||
{ key: "webhook", label: "Webhook" },
|
||||
{ key: "wecom", label: "企业微信" },
|
||||
];
|
||||
|
||||
const EMPTY_SYSTEM_INFO: SystemInfo = { version: "", buildTime: "", config: "" };
|
||||
@@ -51,6 +53,7 @@ export default function SettingsPage() {
|
||||
const [testingWebhook, setTestingWebhook] = useState(false);
|
||||
const [testingBark, setTestingBark] = useState(false);
|
||||
const [testingEmail, setTestingEmail] = useState(false);
|
||||
const [testingWecom, setTestingWecom] = useState(false);
|
||||
const [changingPassword, setChangingPassword] = useState(false);
|
||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||
const [applyingUpdate, setApplyingUpdate] = useState(false);
|
||||
@@ -320,6 +323,21 @@ export default function SettingsPage() {
|
||||
}
|
||||
}, [forms.email]);
|
||||
|
||||
const onTestWecom = useCallback(async () => {
|
||||
setTestingWecom(true);
|
||||
try {
|
||||
await api("/settings/notifications/wecom/test", {
|
||||
method: "POST",
|
||||
body: buildWecomPayload(forms.wecom, true),
|
||||
});
|
||||
message.success(t("测试通知已发送"));
|
||||
} catch (error) {
|
||||
message.error(apiMessage(error) || t("企业微信测试失败"));
|
||||
} finally {
|
||||
setTestingWecom(false);
|
||||
}
|
||||
}, [forms.wecom]);
|
||||
|
||||
const onCheckUpdate = useCallback(async () => {
|
||||
setCheckingUpdate(true);
|
||||
try {
|
||||
@@ -448,7 +466,7 @@ export default function SettingsPage() {
|
||||
<CardIcon>
|
||||
<AlertRegular className="text-[24px]" />
|
||||
</CardIcon>
|
||||
<CardTitle title={t("通知")} subtitle={t("Telegram / Bark / Email / Pushplus / Webhook")} />
|
||||
<CardTitle title={t("通知")} subtitle={t("Telegram / Bark / Email / Pushplus / Webhook / 企业微信")} />
|
||||
</div>
|
||||
<Button variant="primary" loading={savingNotif} disabled={loadingNotif} onClick={onSaveNotifications} className="!border-0" icon={<CheckmarkRegular />}>
|
||||
{t("保存通知配置")}
|
||||
@@ -479,6 +497,9 @@ export default function SettingsPage() {
|
||||
onTest={onTestWebhook}
|
||||
/>
|
||||
) : null}
|
||||
{activeTab === "wecom" ? (
|
||||
<WecomTab value={forms.wecom} onChange={(p) => updateChannel("wecom", p)} testing={testingWecom} onTest={onTestWecom} />
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -371,6 +371,7 @@ export interface NotificationSettings {
|
||||
bark: Record<string, unknown>;
|
||||
email: Record<string, unknown>;
|
||||
pushplus: Record<string, unknown>;
|
||||
wecom: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// 网络访问控制策略:默认仅放行内网网段,可切换到对公网开放。
|
||||
|
||||
Reference in New Issue
Block a user