mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
Refine offline status toasts and theme styling
This commit is contained in:
@@ -467,7 +467,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- 👉 Offline Page -->
|
<!-- 👉 Offline Page -->
|
||||||
<OfflinePage :navbar-extra-height="navbarExtraHeight" />
|
<OfflinePage />
|
||||||
|
|
||||||
<!-- 👉 Pull Down Indicator -->
|
<!-- 👉 Pull Down Indicator -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,22 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGlobalOfflineStatus } from '@/composables/useOfflineStatus'
|
import { useGlobalOfflineStatus } from '@/composables/useOfflineStatus'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
navbarExtraHeight?: string
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
navbarExtraHeight: '0rem',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { connectionStatus, connectionReason, requestConnectionCheck } = useGlobalOfflineStatus()
|
const toast = useToast()
|
||||||
const dismissed = ref(false)
|
const { connectionStatus, connectionReason } = useGlobalOfflineStatus()
|
||||||
|
const lastConnectionPromptKey = ref('')
|
||||||
|
|
||||||
const shouldShow = computed(() => connectionStatus.value !== 'online' && !dismissed.value)
|
|
||||||
const isChecking = computed(() => connectionStatus.value === 'checking')
|
const isChecking = computed(() => connectionStatus.value === 'checking')
|
||||||
const alertType = computed(() => (isChecking.value ? 'warning' : 'error'))
|
|
||||||
const statusTitle = computed(() => (isChecking.value ? t('app.connectionChecking') : t('app.serviceUnavailable')))
|
const statusTitle = computed(() => (isChecking.value ? t('app.connectionChecking') : t('app.serviceUnavailable')))
|
||||||
const statusMessage = computed(() => {
|
const statusMessage = computed(() => {
|
||||||
if (connectionReason.value === 'browser-offline') return t('app.browserOfflineMessage')
|
if (connectionReason.value === 'browser-offline') return t('app.browserOfflineMessage')
|
||||||
@@ -25,133 +16,45 @@ const statusMessage = computed(() => {
|
|||||||
return t('app.serviceUnavailableMessage')
|
return t('app.serviceUnavailableMessage')
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 立即请求重新探测 MoviePilot 服务。 */
|
/** 拼接离线状态提示文案,供 Toast 或 Agent 助手气泡展示。 */
|
||||||
function handleRetry() {
|
function buildConnectionPromptMessage() {
|
||||||
requestConnectionCheck()
|
return `${statusTitle.value}:${statusMessage.value}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 隐藏本次连接提示并允许用户继续浏览。 */
|
/** 根据当前连接状态选择 Toast 级别,Agent 助手可用时会由全局 Toast 路由接管。 */
|
||||||
function handleContinueBrowsing() {
|
function showConnectionPrompt() {
|
||||||
dismissed.value = true
|
const message = buildConnectionPromptMessage()
|
||||||
}
|
const options = {
|
||||||
|
timeout: isChecking.value ? 5000 : 7000,
|
||||||
watch(connectionStatus, (status, previousStatus) => {
|
|
||||||
if (status === 'online' || (status === 'offline' && previousStatus === 'checking')) {
|
|
||||||
dismissed.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isChecking.value) {
|
||||||
|
toast.warning(message, options)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(message, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在连接状态变化时发出一次离线提示,并在恢复在线后允许下一轮提示重新出现。 */
|
||||||
|
function handleConnectionStatusChange() {
|
||||||
|
if (connectionStatus.value === 'online') {
|
||||||
|
lastConnectionPromptKey.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const promptKey = `${connectionStatus.value}:${connectionReason.value || 'unknown'}`
|
||||||
|
if (promptKey === lastConnectionPromptKey.value) return
|
||||||
|
|
||||||
|
lastConnectionPromptKey.value = promptKey
|
||||||
|
showConnectionPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([connectionStatus, connectionReason], handleConnectionStatusChange, {
|
||||||
|
flush: 'post',
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Transition name="connection-status">
|
<span class="d-none" aria-hidden="true" />
|
||||||
<div
|
|
||||||
v-if="shouldShow"
|
|
||||||
class="connection-status-host"
|
|
||||||
:style="{ '--connection-status-navbar-extra-height': props.navbarExtraHeight }"
|
|
||||||
role="status"
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
<VAlert :type="alertType" variant="elevated" density="comfortable" class="connection-status-alert">
|
|
||||||
<div class="connection-status-content">
|
|
||||||
<div class="connection-status-copy">
|
|
||||||
<div class="text-subtitle-2 font-weight-bold">
|
|
||||||
{{ statusTitle }}
|
|
||||||
</div>
|
|
||||||
<div class="text-body-2 mt-1">
|
|
||||||
{{ statusMessage }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="connection-status-actions">
|
|
||||||
<VBtn
|
|
||||||
size="small"
|
|
||||||
variant="text"
|
|
||||||
:loading="isChecking"
|
|
||||||
:disabled="isChecking"
|
|
||||||
@click="handleRetry"
|
|
||||||
>
|
|
||||||
{{ isChecking ? t('common.checking') : t('common.retry') }}
|
|
||||||
</VBtn>
|
|
||||||
<VBtn size="small" variant="text" @click="handleContinueBrowsing">
|
|
||||||
{{ t('app.continueBrowsing') }}
|
|
||||||
</VBtn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</VAlert>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.connection-status-host {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 30;
|
|
||||||
inline-size: min(44rem, calc(100vw - 2rem));
|
|
||||||
inset-block-start: calc(
|
|
||||||
env(safe-area-inset-top, 0px) + 4rem + var(--connection-status-navbar-extra-height, 0rem) + 0.75rem
|
|
||||||
);
|
|
||||||
inset-inline-start: 50%;
|
|
||||||
pointer-events: none;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-alert {
|
|
||||||
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
|
||||||
border-radius: var(--app-overlay-radius);
|
|
||||||
box-shadow: var(--app-overlay-shadow);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-copy {
|
|
||||||
flex: 1;
|
|
||||||
min-inline-size: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-enter-active,
|
|
||||||
.connection-status-leave-active {
|
|
||||||
transition:
|
|
||||||
opacity 0.2s ease,
|
|
||||||
transform 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-enter-from,
|
|
||||||
.connection-status-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, -0.75rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 600px) {
|
|
||||||
.connection-status-host {
|
|
||||||
inline-size: calc(100vw - 1rem);
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-content {
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-status-actions {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.connection-status-enter-active,
|
|
||||||
.connection-status-leave-active {
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -847,8 +847,52 @@ html[data-theme="transparent"].transparent-glass-realtime .v-theme--transparent
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Vue-Toastification__toast {
|
.Vue-Toastification__toast {
|
||||||
|
--mp-toast-accent-rgb: var(--v-theme-primary);
|
||||||
|
|
||||||
|
border: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
|
||||||
|
border-inline-start: 0.25rem solid rgba(var(--mp-toast-accent-rgb), 0.88);
|
||||||
border-radius: var(--app-overlay-radius);
|
border-radius: var(--app-overlay-radius);
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--mp-toast-accent-rgb), 0.14),
|
||||||
|
rgba(var(--mp-toast-accent-rgb), 0.05) 42%,
|
||||||
|
rgba(var(--v-theme-surface), 0.96)
|
||||||
|
),
|
||||||
|
rgb(var(--v-theme-surface)) !important;
|
||||||
box-shadow: var(--app-overlay-shadow);
|
box-shadow: var(--app-overlay-shadow);
|
||||||
|
color: rgba(var(--v-theme-on-surface), var(--v-high-emphasis-opacity)) !important;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast--default,
|
||||||
|
.Vue-Toastification__toast--info {
|
||||||
|
--mp-toast-accent-rgb: var(--v-theme-info);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast--success {
|
||||||
|
--mp-toast-accent-rgb: var(--v-theme-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast--error {
|
||||||
|
--mp-toast-accent-rgb: var(--v-theme-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast--warning {
|
||||||
|
--mp-toast-accent-rgb: var(--v-theme-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__icon,
|
||||||
|
.Vue-Toastification__close-button {
|
||||||
|
color: rgba(var(--mp-toast-accent-rgb), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__close-button {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__progress-bar {
|
||||||
|
background-color: rgba(var(--mp-toast-accent-rgb), 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对话框样式
|
// 对话框样式
|
||||||
|
|||||||
@@ -221,6 +221,18 @@ html[data-theme="transparent"] {
|
|||||||
backdrop-filter: blur(var(--transparent-blur));
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy));
|
background-color: rgba(var(--v-theme-surface), var(--transparent-opacity-heavy));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Vue-Toastification__toast {
|
||||||
|
backdrop-filter: blur(var(--transparent-blur));
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--mp-toast-accent-rgb), 0.16),
|
||||||
|
rgba(var(--mp-toast-accent-rgb), 0.06) 44%,
|
||||||
|
rgba(var(--v-theme-surface), var(--transparent-opacity-heavy))
|
||||||
|
),
|
||||||
|
rgba(var(--v-theme-surface), var(--transparent-opacity-heavy)) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-theme="transparent"].transparent-background-blur-disabled {
|
html[data-theme="transparent"].transparent-background-blur-disabled {
|
||||||
|
|||||||
Reference in New Issue
Block a user