feat: 添加了浏览器通知和页面内通知

暂包含建造完成和科研完成
This commit is contained in:
StarsEnd
2025-12-18 01:34:29 +08:00
parent 2e3ac1231f
commit e8590d54c7
10 changed files with 327 additions and 11 deletions

View File

@@ -89,6 +89,85 @@
</CardContent>
</Card>
<!-- 通知设置 -->
<Card>
<CardHeader>
<CardTitle>{{ t('settings.notifications') }}</CardTitle>
<CardDescription>{{ t('settings.notificationsDesc') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<!-- 浏览器通知 -->
<div class="flex flex-col gap-4 p-4 border rounded-lg">
<div class="flex items-center justify-between">
<div class="space-y-1">
<h3 class="font-medium">{{ t('settings.browserNotifications') }}</h3>
<p class="text-sm text-muted-foreground">{{ t('settings.browserPermission') }}</p>
</div>
<Switch :checked="gameStore.player.notificationSettings?.browser" @update:checked="handleBrowserSwitch" />
</div>
<!-- 页面聚焦时不发送 -->
<div class="flex items-center justify-between pl-4 border-l-2" :class="{ 'opacity-50 pointer-events-none': !gameStore.player.notificationSettings?.browser }">
<Label class="font-normal">{{ t('settings.suppressInFocus') }}</Label>
<Switch
:checked="gameStore.player.notificationSettings?.suppressInFocus"
@update:checked="updateSuppressSetting"
:disabled="!gameStore.player.notificationSettings?.browser"
/>
</div>
</div>
<!-- 页面内通知 -->
<div class="flex items-center justify-between p-4 border rounded-lg">
<div class="space-y-1">
<h3 class="font-medium">{{ t('settings.inAppNotifications') }}</h3>
<p class="text-sm text-muted-foreground">{{ t('settings.inAppNotificationsDesc') || t('settings.inAppNotifications') }}</p>
</div>
<Switch
:checked="gameStore.player.notificationSettings?.inApp"
@update:checked="(val: boolean) => updateInAppSetting(val)"
/>
</div>
<!-- 具体通知类型 -->
<div class="border rounded-lg overflow-hidden" :class="{ 'opacity-50 pointer-events-none': areMainSwitchesOff }">
<div
class="flex items-center justify-between p-4 bg-muted/50 cursor-pointer select-none"
@click="!areMainSwitchesOff && (isTypesExpanded = !isTypesExpanded)"
>
<div class="space-y-1">
<h3 class="font-medium">{{ t('settings.notificationTypes') }}</h3>
<p class="text-sm text-muted-foreground">
{{ areMainSwitchesOff ? t('settings.notificationsDisabled') : (isTypesExpanded ? t('settings.collapseTypes') : t('settings.expandTypes')) }}
</p>
</div>
<Button variant="ghost" size="sm" class="h-8 w-8 p-0">
<component :is="isTypesExpanded ? ChevronUp : ChevronDown" class="h-4 w-4" />
</Button>
</div>
<div v-if="isTypesExpanded && !areMainSwitchesOff" class="p-4 space-y-4 border-t bg-card">
<!-- 建造完成 -->
<div class="flex items-center justify-between">
<Label class="font-normal cursor-pointer" @click="toggleType('construction')">{{ t('settings.constructionComplete') }}</Label>
<Switch
:checked="gameStore.player.notificationSettings?.types.construction"
@update:checked="(val: boolean) => updateTypeSetting('construction', val)"
/>
</div>
<!-- 研究完成 -->
<div class="flex items-center justify-between">
<Label class="font-normal cursor-pointer" @click="toggleType('research')">{{ t('settings.researchComplete') }}</Label>
<Switch
:checked="gameStore.player.notificationSettings?.types.research"
@update:checked="(val: boolean) => updateTypeSetting('research', val)"
/>
</div>
</div>
</div>
</CardContent>
</Card>
<!-- 关于 -->
<Card>
<CardHeader>
@@ -164,6 +243,8 @@
import { useI18n } from '@/composables/useI18n'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Switch } from '@/components/ui/switch'
import { Label } from '@/components/ui/label'
import {
AlertDialog,
AlertDialogAction,
@@ -174,7 +255,7 @@
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import { Download, Upload, Trash2, ExternalLink, MessagesSquare, Play, Pause, RefreshCw } from 'lucide-vue-next'
import { Download, Upload, Trash2, ExternalLink, MessagesSquare, Play, Pause, RefreshCw, ChevronDown, ChevronUp } from 'lucide-vue-next'
import { saveAs } from 'file-saver'
import { toast } from 'vue-sonner'
import pkg from '../../package.json'
@@ -195,6 +276,70 @@
const confirmMessage = ref('')
let confirmCallback: (() => void) | null = null
const isTypesExpanded = ref(false)
// Ensure notification settings exist
if (!gameStore.player.notificationSettings) {
gameStore.player.notificationSettings = {
browser: false,
inApp: true,
suppressInFocus: false,
types: { construction: true, research: true }
}
}
const areMainSwitchesOff = computed(() => {
const s = gameStore.player.notificationSettings
return !s?.browser && !s?.inApp
})
// Auto-collapse if main switches are off
// watch(areMainSwitchesOff, (val) => {
// if (val) isTypesExpanded.value = false
// })
const updateInAppSetting = (val: boolean) => {
if (gameStore.player.notificationSettings) {
gameStore.player.notificationSettings.inApp = val
}
}
const updateSuppressSetting = (val: boolean) => {
if (gameStore.player.notificationSettings) {
gameStore.player.notificationSettings.suppressInFocus = val
}
}
const updateTypeSetting = (key: string, val: boolean) => {
if (gameStore.player.notificationSettings) {
gameStore.player.notificationSettings.types[key] = val
}
}
const toggleType = (key: string) => {
if (gameStore.player.notificationSettings) {
const current = gameStore.player.notificationSettings.types[key]
gameStore.player.notificationSettings.types[key] = !current
}
}
const handleBrowserSwitch = async (checked: boolean) => {
if (!gameStore.player.notificationSettings) return
if (checked) {
const granted = await gameStore.requestBrowserPermission()
if (granted) {
gameStore.player.notificationSettings.browser = true
toast.success(t('settings.permissionGranted'))
} else {
gameStore.player.notificationSettings.browser = false
toast.error(t('settings.permissionDenied'))
}
} else {
gameStore.player.notificationSettings.browser = false
}
}
// 计算是否可以检查版本主动检测5分钟内不能重复检查
const canCheck = computed(() => canCheckVersion(gameStore.player.lastManualUpdateCheck || 0))