mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-03-20 03:46:40 +08:00
Replace non-existent ab-dialog with ab-popup for proper modal behavior, use ab-button/ab-label components and consistent spacing/styles matching other settings panels (search-provider, proxy). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
555 lines
15 KiB
Vue
555 lines
15 KiB
Vue
<script lang="ts" setup>
|
|
import type { NotificationProviderConfig, NotificationType } from '#/config';
|
|
import type { TupleToUnion } from '#/utils';
|
|
import { apiNotification } from '@/api/notification';
|
|
|
|
const { t } = useMyI18n();
|
|
const { getSettingGroup } = useConfigStore();
|
|
|
|
const notificationRef = getSettingGroup('notification');
|
|
|
|
// Provider types with display names
|
|
const providerTypes: { value: TupleToUnion<NotificationType>; label: string }[] = [
|
|
{ value: 'telegram', label: 'Telegram' },
|
|
{ value: 'discord', label: 'Discord' },
|
|
{ value: 'bark', label: 'Bark' },
|
|
{ value: 'server-chan', label: 'Server Chan' },
|
|
{ value: 'wecom', label: 'WeChat Work' },
|
|
{ value: 'gotify', label: 'Gotify' },
|
|
{ value: 'pushover', label: 'Pushover' },
|
|
{ value: 'webhook', label: 'Webhook' },
|
|
];
|
|
|
|
// Provider field configurations
|
|
const providerFields: Record<
|
|
string,
|
|
{ key: keyof NotificationProviderConfig; label: string; placeholder: string }[]
|
|
> = {
|
|
telegram: [
|
|
{ key: 'token', label: 'Bot Token', placeholder: 'bot token' },
|
|
{ key: 'chat_id', label: 'Chat ID', placeholder: 'chat id' },
|
|
],
|
|
discord: [
|
|
{
|
|
key: 'webhook_url',
|
|
label: 'Webhook URL',
|
|
placeholder: 'https://discord.com/api/webhooks/...',
|
|
},
|
|
],
|
|
bark: [
|
|
{ key: 'device_key', label: 'Device Key', placeholder: 'device key' },
|
|
{
|
|
key: 'server_url',
|
|
label: 'Server URL (optional)',
|
|
placeholder: 'https://api.day.app',
|
|
},
|
|
],
|
|
'server-chan': [{ key: 'token', label: 'SendKey', placeholder: 'sendkey' }],
|
|
wecom: [
|
|
{ key: 'webhook_url', label: 'Webhook URL', placeholder: 'webhook url' },
|
|
{ key: 'token', label: 'Key', placeholder: 'key' },
|
|
],
|
|
gotify: [
|
|
{
|
|
key: 'server_url',
|
|
label: 'Server URL',
|
|
placeholder: 'https://gotify.example.com',
|
|
},
|
|
{ key: 'token', label: 'App Token', placeholder: 'app token' },
|
|
],
|
|
pushover: [
|
|
{ key: 'user_key', label: 'User Key', placeholder: 'user key' },
|
|
{ key: 'api_token', label: 'API Token', placeholder: 'api token' },
|
|
],
|
|
webhook: [
|
|
{
|
|
key: 'url',
|
|
label: 'Webhook URL',
|
|
placeholder: 'https://example.com/webhook',
|
|
},
|
|
{
|
|
key: 'template',
|
|
label: 'Template (JSON)',
|
|
placeholder: '{"title": "{{title}}", "episode": {{episode}}}',
|
|
},
|
|
],
|
|
};
|
|
|
|
// Dialog state
|
|
const showAddDialog = ref(false);
|
|
const showEditDialog = ref(false);
|
|
const editingIndex = ref(-1);
|
|
const newProvider = ref<NotificationProviderConfig>({
|
|
type: 'telegram',
|
|
enabled: true,
|
|
});
|
|
|
|
// Testing state
|
|
const testingIndex = ref(-1);
|
|
const testResult = ref<{ success: boolean; message: string } | null>(null);
|
|
|
|
// Computed properties to access notification settings
|
|
const notificationEnabled = computed({
|
|
get: () => notificationRef.value.enable,
|
|
set: (val) => {
|
|
notificationRef.value.enable = val;
|
|
},
|
|
});
|
|
|
|
const providers = computed({
|
|
get: () => notificationRef.value.providers || [],
|
|
set: (val) => {
|
|
notificationRef.value.providers = val;
|
|
},
|
|
});
|
|
|
|
// Initialize providers array if not exists
|
|
if (!notificationRef.value.providers) {
|
|
notificationRef.value.providers = [];
|
|
}
|
|
|
|
function getProviderLabel(type: string): string {
|
|
return providerTypes.find((p) => p.value === type)?.label || type;
|
|
}
|
|
|
|
function getProviderIcon(type: string): string {
|
|
const icons: Record<string, string> = {
|
|
telegram: 'i-simple-icons-telegram',
|
|
discord: 'i-simple-icons-discord',
|
|
bark: 'i-carbon-notification',
|
|
'server-chan': 'i-simple-icons-wechat',
|
|
wecom: 'i-simple-icons-wechat',
|
|
gotify: 'i-carbon-notification-filled',
|
|
pushover: 'i-carbon-mobile',
|
|
webhook: 'i-carbon-webhook',
|
|
};
|
|
return icons[type] || 'i-carbon-notification';
|
|
}
|
|
|
|
function openAddDialog() {
|
|
newProvider.value = {
|
|
type: 'telegram',
|
|
enabled: true,
|
|
};
|
|
testResult.value = null;
|
|
showAddDialog.value = true;
|
|
}
|
|
|
|
function openEditDialog(index: number) {
|
|
editingIndex.value = index;
|
|
newProvider.value = { ...providers.value[index] };
|
|
testResult.value = null;
|
|
showEditDialog.value = true;
|
|
}
|
|
|
|
function addProvider() {
|
|
const newProviders = [...providers.value, { ...newProvider.value }];
|
|
providers.value = newProviders;
|
|
showAddDialog.value = false;
|
|
}
|
|
|
|
function saveProvider() {
|
|
if (editingIndex.value >= 0) {
|
|
const newProviders = [...providers.value];
|
|
newProviders[editingIndex.value] = { ...newProvider.value };
|
|
providers.value = newProviders;
|
|
}
|
|
showEditDialog.value = false;
|
|
editingIndex.value = -1;
|
|
}
|
|
|
|
function removeProvider(index: number) {
|
|
const newProviders = providers.value.filter((_, i) => i !== index);
|
|
providers.value = newProviders;
|
|
}
|
|
|
|
function toggleProvider(index: number) {
|
|
const newProviders = [...providers.value];
|
|
newProviders[index] = {
|
|
...newProviders[index],
|
|
enabled: !newProviders[index].enabled,
|
|
};
|
|
providers.value = newProviders;
|
|
}
|
|
|
|
async function testProvider(index: number) {
|
|
testingIndex.value = index;
|
|
testResult.value = null;
|
|
try {
|
|
const response = await apiNotification.testProvider({ provider_index: index });
|
|
testResult.value = {
|
|
success: response.data.success,
|
|
message: response.data.message_zh || response.data.message,
|
|
};
|
|
} catch (error: any) {
|
|
testResult.value = {
|
|
success: false,
|
|
message: error.message || 'Test failed',
|
|
};
|
|
} finally {
|
|
testingIndex.value = -1;
|
|
}
|
|
}
|
|
|
|
async function testNewProvider() {
|
|
testingIndex.value = -999; // Special index for new provider
|
|
testResult.value = null;
|
|
try {
|
|
const response = await apiNotification.testProviderConfig(
|
|
newProvider.value as any
|
|
);
|
|
testResult.value = {
|
|
success: response.data.success,
|
|
message: response.data.message_zh || response.data.message,
|
|
};
|
|
} catch (error: any) {
|
|
testResult.value = {
|
|
success: false,
|
|
message: error.message || 'Test failed',
|
|
};
|
|
} finally {
|
|
testingIndex.value = -1;
|
|
}
|
|
}
|
|
|
|
function getFieldsForType(type: string) {
|
|
return providerFields[type] || [];
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ab-fold-panel :title="$t('config.notification_set.title')">
|
|
<div space-y-8>
|
|
<!-- Global enable switch -->
|
|
<ab-setting
|
|
config-key="enable"
|
|
:label="() => t('config.notification_set.enable')"
|
|
type="switch"
|
|
v-model:data="notificationEnabled"
|
|
bottom-line
|
|
/>
|
|
|
|
<!-- Provider list -->
|
|
<div v-if="notificationEnabled" space-y-8>
|
|
<div
|
|
v-for="(provider, index) in providers"
|
|
:key="index"
|
|
class="provider-item"
|
|
:class="{ 'provider-disabled': !provider.enabled }"
|
|
>
|
|
<div class="provider-info">
|
|
<div class="provider-name">
|
|
<div :class="getProviderIcon(provider.type)" />
|
|
{{ getProviderLabel(provider.type) }}
|
|
<span v-if="!provider.enabled" class="disabled-badge">
|
|
{{ $t('config.notification_set.disabled') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="provider-actions">
|
|
<ab-button
|
|
size="small"
|
|
type="secondary"
|
|
:disabled="testingIndex === index"
|
|
:title="$t('config.notification_set.test')"
|
|
@click="testProvider(index)"
|
|
>
|
|
<div
|
|
v-if="testingIndex === index"
|
|
i-carbon-circle-dash
|
|
animate-spin
|
|
/>
|
|
<div v-else i-carbon-play />
|
|
</ab-button>
|
|
<ab-button
|
|
size="small"
|
|
type="secondary"
|
|
:title="$t('config.notification_set.edit')"
|
|
@click="openEditDialog(index)"
|
|
>
|
|
<div i-carbon-edit />
|
|
</ab-button>
|
|
<ab-button
|
|
size="small"
|
|
type="secondary"
|
|
:title="
|
|
provider.enabled
|
|
? $t('config.notification_set.disable')
|
|
: $t('config.notification_set.enable_provider')
|
|
"
|
|
@click="toggleProvider(index)"
|
|
>
|
|
<div
|
|
:class="provider.enabled ? 'i-carbon-view' : 'i-carbon-view-off'"
|
|
/>
|
|
</ab-button>
|
|
<ab-button
|
|
size="small"
|
|
type="warn"
|
|
:title="$t('config.notification_set.remove')"
|
|
@click="removeProvider(index)"
|
|
>
|
|
<div i-carbon-trash-can />
|
|
</ab-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test result message -->
|
|
<div
|
|
v-if="testResult"
|
|
class="test-result"
|
|
:class="testResult.success ? 'test-success' : 'test-error'"
|
|
>
|
|
{{ testResult.message }}
|
|
</div>
|
|
|
|
<div line></div>
|
|
|
|
<!-- Add provider button -->
|
|
<div flex="~ justify-end">
|
|
<ab-button size="small" type="primary" @click="openAddDialog">
|
|
<div i-carbon-add />
|
|
{{ $t('config.notification_set.add_provider') }}
|
|
</ab-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Dialog -->
|
|
<ab-popup
|
|
v-model:show="showAddDialog"
|
|
:title="$t('config.notification_set.add_provider')"
|
|
css="w-400"
|
|
>
|
|
<div space-y-16>
|
|
<ab-label :label="$t('config.notification_set.type')">
|
|
<select v-model="newProvider.type" ab-input>
|
|
<option v-for="pt in providerTypes" :key="pt.value" :value="pt.value">
|
|
{{ pt.label }}
|
|
</option>
|
|
</select>
|
|
</ab-label>
|
|
|
|
<ab-label
|
|
v-for="field in getFieldsForType(newProvider.type)"
|
|
:key="field.key"
|
|
:label="field.label"
|
|
>
|
|
<input
|
|
v-if="field.key !== 'template'"
|
|
v-model="(newProvider as any)[field.key]"
|
|
:placeholder="field.placeholder"
|
|
ab-input
|
|
/>
|
|
<textarea
|
|
v-else
|
|
v-model="(newProvider as any)[field.key]"
|
|
:placeholder="field.placeholder"
|
|
ab-input
|
|
class="field-textarea"
|
|
rows="3"
|
|
/>
|
|
</ab-label>
|
|
|
|
<div
|
|
v-if="testResult"
|
|
class="test-result"
|
|
:class="testResult.success ? 'test-success' : 'test-error'"
|
|
>
|
|
{{ testResult.message }}
|
|
</div>
|
|
|
|
<div line></div>
|
|
|
|
<div flex="~ justify-between items-center">
|
|
<ab-button
|
|
size="small"
|
|
type="secondary"
|
|
:disabled="testingIndex === -999"
|
|
@click="testNewProvider"
|
|
>
|
|
<div
|
|
v-if="testingIndex === -999"
|
|
i-carbon-circle-dash
|
|
animate-spin
|
|
/>
|
|
<div v-else i-carbon-play />
|
|
{{ $t('config.notification_set.test') }}
|
|
</ab-button>
|
|
<div flex="~ gap-8">
|
|
<ab-button size="small" type="warn" @click="showAddDialog = false">
|
|
{{ $t('config.cancel') }}
|
|
</ab-button>
|
|
<ab-button size="small" type="primary" @click="addProvider">
|
|
{{ $t('config.apply') }}
|
|
</ab-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ab-popup>
|
|
|
|
<!-- Edit Dialog -->
|
|
<ab-popup
|
|
v-model:show="showEditDialog"
|
|
:title="$t('config.notification_set.edit_provider')"
|
|
css="w-400"
|
|
>
|
|
<div space-y-16>
|
|
<ab-label :label="$t('config.notification_set.type')">
|
|
<select v-model="newProvider.type" ab-input disabled>
|
|
<option v-for="pt in providerTypes" :key="pt.value" :value="pt.value">
|
|
{{ pt.label }}
|
|
</option>
|
|
</select>
|
|
</ab-label>
|
|
|
|
<ab-label
|
|
v-for="field in getFieldsForType(newProvider.type)"
|
|
:key="field.key"
|
|
:label="field.label"
|
|
>
|
|
<input
|
|
v-if="field.key !== 'template'"
|
|
v-model="(newProvider as any)[field.key]"
|
|
:placeholder="field.placeholder"
|
|
ab-input
|
|
/>
|
|
<textarea
|
|
v-else
|
|
v-model="(newProvider as any)[field.key]"
|
|
:placeholder="field.placeholder"
|
|
ab-input
|
|
class="field-textarea"
|
|
rows="3"
|
|
/>
|
|
</ab-label>
|
|
|
|
<div
|
|
v-if="testResult"
|
|
class="test-result"
|
|
:class="testResult.success ? 'test-success' : 'test-error'"
|
|
>
|
|
{{ testResult.message }}
|
|
</div>
|
|
|
|
<div line></div>
|
|
|
|
<div flex="~ justify-between items-center">
|
|
<ab-button
|
|
size="small"
|
|
type="secondary"
|
|
:disabled="testingIndex === -999"
|
|
@click="testNewProvider"
|
|
>
|
|
<div
|
|
v-if="testingIndex === -999"
|
|
i-carbon-circle-dash
|
|
animate-spin
|
|
/>
|
|
<div v-else i-carbon-play />
|
|
{{ $t('config.notification_set.test') }}
|
|
</ab-button>
|
|
<div flex="~ gap-8">
|
|
<ab-button size="small" type="warn" @click="showEditDialog = false">
|
|
{{ $t('config.cancel') }}
|
|
</ab-button>
|
|
<ab-button size="small" type="primary" @click="saveProvider">
|
|
{{ $t('config.apply') }}
|
|
</ab-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</ab-popup>
|
|
</ab-fold-panel>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.provider-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 12px;
|
|
background: var(--color-surface-elevated, #f9fafb);
|
|
border-radius: 8px;
|
|
transition: background-color var(--transition-normal), opacity var(--transition-normal);
|
|
|
|
:root.dark & {
|
|
background: var(--color-surface-elevated, #1f2937);
|
|
}
|
|
}
|
|
|
|
.provider-disabled {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.provider-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.provider-name {
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
color: var(--color-text);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.disabled-badge {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
background: var(--color-danger);
|
|
color: white;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.provider-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-shrink: 0;
|
|
|
|
:deep(.btn--small) {
|
|
min-width: 32px;
|
|
width: 32px;
|
|
height: 32px;
|
|
padding: 0;
|
|
}
|
|
|
|
:deep(.n-spin-container),
|
|
:deep(.n-spin-content) {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.field-textarea {
|
|
resize: none;
|
|
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.test-result {
|
|
font-size: 12px;
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.test-success {
|
|
color: var(--color-success, #22c55e);
|
|
background: color-mix(in srgb, var(--color-success, #22c55e) 10%, transparent);
|
|
}
|
|
|
|
.test-error {
|
|
color: var(--color-danger, #ef4444);
|
|
background: color-mix(in srgb, var(--color-danger, #ef4444) 10%, transparent);
|
|
}
|
|
</style>
|