mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-22 08:52:21 +08:00
refactor: migrate all dialogs to a centralized shared dialog management system using useSharedDialog composable
This commit is contained in:
96
src/composables/useSharedDialog.ts
Normal file
96
src/composables/useSharedDialog.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { markRaw, shallowRef, type Component } from 'vue'
|
||||
|
||||
export type SharedDialogEventHandler = (...args: any[]) => unknown
|
||||
|
||||
export interface SharedDialogOpenOptions {
|
||||
closeOn?: string[] | false
|
||||
events?: Record<string, SharedDialogEventHandler>
|
||||
props?: Record<string, unknown>
|
||||
replace?: boolean
|
||||
}
|
||||
|
||||
export interface SharedDialogEntry {
|
||||
closeOn: string[]
|
||||
component: Component
|
||||
events: Record<string, SharedDialogEventHandler>
|
||||
id: number
|
||||
props: Record<string, unknown>
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_CLOSE_EVENTS = ['close']
|
||||
const dialogStack = shallowRef<SharedDialogEntry[]>([])
|
||||
let dialogSeed = 0
|
||||
|
||||
// 规范化弹窗关闭事件,避免每个调用方重复处理关闭约定。
|
||||
function normalizeCloseEvents(closeOn: SharedDialogOpenOptions['closeOn']) {
|
||||
if (closeOn === false) return []
|
||||
return closeOn ?? DEFAULT_CLOSE_EVENTS
|
||||
}
|
||||
|
||||
// 更新弹窗栈引用,确保 Host 能响应数组内容变化。
|
||||
function setDialogStack(entries: SharedDialogEntry[]) {
|
||||
dialogStack.value = entries
|
||||
}
|
||||
|
||||
// 打开一个共享弹窗,并返回当前弹窗的控制器。
|
||||
export function openSharedDialog(
|
||||
component: Component,
|
||||
props: Record<string, unknown> = {},
|
||||
events: Record<string, SharedDialogEventHandler> = {},
|
||||
options: Omit<SharedDialogOpenOptions, 'props' | 'events'> = {},
|
||||
) {
|
||||
const id = ++dialogSeed
|
||||
const entry: SharedDialogEntry = {
|
||||
closeOn: normalizeCloseEvents(options.closeOn),
|
||||
component: markRaw(component),
|
||||
events,
|
||||
id,
|
||||
props,
|
||||
visible: true,
|
||||
}
|
||||
|
||||
setDialogStack(options.replace ? [entry] : [...dialogStack.value, entry])
|
||||
|
||||
return {
|
||||
id,
|
||||
close: () => closeSharedDialog(id),
|
||||
updateProps: (nextProps: Record<string, unknown>) => updateSharedDialogProps(id, nextProps),
|
||||
}
|
||||
}
|
||||
|
||||
// 使用对象参数打开共享弹窗,适合调用方需要传入更多选项的场景。
|
||||
export function openSharedDialogWithOptions(component: Component, options: SharedDialogOpenOptions = {}) {
|
||||
return openSharedDialog(component, options.props ?? {}, options.events ?? {}, {
|
||||
closeOn: options.closeOn,
|
||||
replace: options.replace,
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭指定弹窗;未传 id 时关闭最上层弹窗。
|
||||
export function closeSharedDialog(id?: number) {
|
||||
if (id === undefined) {
|
||||
setDialogStack(dialogStack.value.slice(0, -1))
|
||||
return
|
||||
}
|
||||
|
||||
setDialogStack(dialogStack.value.filter(entry => entry.id !== id))
|
||||
}
|
||||
|
||||
// 合并更新指定弹窗的 props,供进度弹窗等需要刷新内容的场景使用。
|
||||
export function updateSharedDialogProps(id: number, props: Record<string, unknown>) {
|
||||
setDialogStack(
|
||||
dialogStack.value.map(entry => (entry.id === id ? { ...entry, props: { ...entry.props, ...props } } : entry)),
|
||||
)
|
||||
}
|
||||
|
||||
// 提供共享弹窗的响应式状态和命令式操作方法。
|
||||
export function useSharedDialog() {
|
||||
return {
|
||||
dialogs: dialogStack,
|
||||
openDialog: openSharedDialog,
|
||||
openDialogWithOptions: openSharedDialogWithOptions,
|
||||
closeDialog: closeSharedDialog,
|
||||
updateDialogProps: updateSharedDialogProps,
|
||||
}
|
||||
}
|
||||
177
src/composables/useTransparencySettings.ts
Normal file
177
src/composables/useTransparencySettings.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export interface TransparencySettings {
|
||||
backgroundBlur: number
|
||||
backgroundPosterOpacity: number
|
||||
blur: number
|
||||
level: string
|
||||
opacity: number
|
||||
}
|
||||
|
||||
export const transparencyPresets = {
|
||||
low: { opacity: 0.1, blur: 5 },
|
||||
medium: { opacity: 0.3, blur: 10 },
|
||||
high: { opacity: 0.6, blur: 15 },
|
||||
}
|
||||
|
||||
/** 将数值限制在指定范围内。 */
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.min(max, Math.max(min, value))
|
||||
}
|
||||
|
||||
/** 从本地存储读取透明主题设置。 */
|
||||
export function readTransparencySettings(): TransparencySettings {
|
||||
return {
|
||||
opacity: parseFloat(localStorage.getItem('transparency-opacity') || '0.3'),
|
||||
blur: parseFloat(localStorage.getItem('transparency-blur') || '10'),
|
||||
backgroundPosterOpacity: parseFloat(localStorage.getItem('transparency-background-poster-opacity') || '0'),
|
||||
backgroundBlur: parseFloat(localStorage.getItem('transparency-background-blur') || '16'),
|
||||
level: localStorage.getItem('transparency-level') || 'medium',
|
||||
}
|
||||
}
|
||||
|
||||
/** 应用透明主题设置并写入本地存储。 */
|
||||
export function applyTransparencySettings(settings: TransparencySettings) {
|
||||
const normalized: TransparencySettings = {
|
||||
opacity: Number.isFinite(settings.opacity) ? clamp(settings.opacity, 0, 1) : 0.3,
|
||||
blur: Number.isFinite(settings.blur) ? clamp(settings.blur, 0, 30) : 10,
|
||||
backgroundPosterOpacity: Number.isFinite(settings.backgroundPosterOpacity)
|
||||
? clamp(settings.backgroundPosterOpacity, 0, 1)
|
||||
: 0,
|
||||
backgroundBlur: Number.isFinite(settings.backgroundBlur) ? clamp(settings.backgroundBlur, 0, 30) : 16,
|
||||
level: settings.level,
|
||||
}
|
||||
|
||||
const root = document.documentElement
|
||||
root.style.setProperty('--transparent-opacity', normalized.opacity.toString())
|
||||
root.style.setProperty('--transparent-opacity-light', (normalized.opacity * 0.67).toString())
|
||||
root.style.setProperty('--transparent-opacity-heavy', (normalized.opacity * 1.67).toString())
|
||||
root.style.setProperty('--transparent-blur', `${normalized.blur}px`)
|
||||
root.style.setProperty('--transparent-blur-light', `${normalized.blur * 0.6}px`)
|
||||
root.style.setProperty('--transparent-blur-heavy', `${normalized.blur * 1.6}px`)
|
||||
root.style.setProperty('--transparent-background-poster-opacity', (1 - normalized.backgroundPosterOpacity).toString())
|
||||
root.style.setProperty('--transparent-background-blur', `${normalized.backgroundBlur}px`)
|
||||
|
||||
localStorage.setItem('transparency-opacity', normalized.opacity.toString())
|
||||
localStorage.setItem('transparency-blur', normalized.blur.toString())
|
||||
localStorage.setItem('transparency-background-poster-opacity', normalized.backgroundPosterOpacity.toString())
|
||||
localStorage.setItem('transparency-background-blur', normalized.backgroundBlur.toString())
|
||||
localStorage.setItem('transparency-level', normalized.level)
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
/** 按本地存储中的最新值应用透明主题设置。 */
|
||||
export function applyStoredTransparencySettings() {
|
||||
return applyTransparencySettings(readTransparencySettings())
|
||||
}
|
||||
|
||||
/** 提供透明主题设置的响应式状态和操作方法。 */
|
||||
export function useTransparencySettings() {
|
||||
const storedSettings = readTransparencySettings()
|
||||
const transparencyOpacity = ref(storedSettings.opacity)
|
||||
const transparencyBlur = ref(storedSettings.blur)
|
||||
const backgroundPosterOpacity = ref(storedSettings.backgroundPosterOpacity)
|
||||
const backgroundBlur = ref(storedSettings.backgroundBlur)
|
||||
const transparencyLevel = ref(storedSettings.level)
|
||||
|
||||
const currentPresetLevel = computed(() => {
|
||||
for (const [level, preset] of Object.entries(transparencyPresets)) {
|
||||
if (
|
||||
Math.abs(transparencyOpacity.value - preset.opacity) < 0.01 &&
|
||||
Math.abs(transparencyBlur.value - preset.blur) < 0.1
|
||||
) {
|
||||
return level
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
/** 同步当前响应式状态到 CSS 变量和本地存储。 */
|
||||
function syncTransparencySettings() {
|
||||
const normalized = applyTransparencySettings({
|
||||
opacity: transparencyOpacity.value,
|
||||
blur: transparencyBlur.value,
|
||||
backgroundPosterOpacity: backgroundPosterOpacity.value,
|
||||
backgroundBlur: backgroundBlur.value,
|
||||
level: transparencyLevel.value,
|
||||
})
|
||||
|
||||
transparencyOpacity.value = normalized.opacity
|
||||
transparencyBlur.value = normalized.blur
|
||||
backgroundPosterOpacity.value = normalized.backgroundPosterOpacity
|
||||
backgroundBlur.value = normalized.backgroundBlur
|
||||
transparencyLevel.value = normalized.level
|
||||
}
|
||||
|
||||
/** 按预设级别调整透明度和模糊度。 */
|
||||
function adjustTransparency(level: string) {
|
||||
transparencyLevel.value = level
|
||||
|
||||
switch (level) {
|
||||
case 'low':
|
||||
transparencyOpacity.value = transparencyPresets.low.opacity
|
||||
transparencyBlur.value = transparencyPresets.low.blur
|
||||
break
|
||||
case 'medium':
|
||||
transparencyOpacity.value = transparencyPresets.medium.opacity
|
||||
transparencyBlur.value = transparencyPresets.medium.blur
|
||||
break
|
||||
case 'high':
|
||||
transparencyOpacity.value = transparencyPresets.high.opacity
|
||||
transparencyBlur.value = transparencyPresets.high.blur
|
||||
break
|
||||
}
|
||||
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
/** 处理手动调整面板透明度。 */
|
||||
function onOpacityChange() {
|
||||
transparencyLevel.value = ''
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
/** 处理手动调整面板模糊度。 */
|
||||
function onBlurChange() {
|
||||
transparencyLevel.value = ''
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
/** 处理背景海报透明度变化。 */
|
||||
function onBackgroundPosterOpacityChange() {
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
/** 处理背景磨砂变化。 */
|
||||
function onBackgroundBlurChange() {
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
/** 重置透明主题设置为默认值。 */
|
||||
function resetTransparencySettings() {
|
||||
transparencyOpacity.value = transparencyPresets.medium.opacity
|
||||
transparencyBlur.value = transparencyPresets.medium.blur
|
||||
backgroundPosterOpacity.value = 0
|
||||
backgroundBlur.value = 16
|
||||
transparencyLevel.value = 'medium'
|
||||
syncTransparencySettings()
|
||||
}
|
||||
|
||||
return {
|
||||
adjustTransparency,
|
||||
backgroundBlur,
|
||||
backgroundPosterOpacity,
|
||||
currentPresetLevel,
|
||||
onBackgroundBlurChange,
|
||||
onBackgroundPosterOpacityChange,
|
||||
onBlurChange,
|
||||
onOpacityChange,
|
||||
resetTransparencySettings,
|
||||
syncTransparencySettings,
|
||||
transparencyBlur,
|
||||
transparencyOpacity,
|
||||
transparencyLevel,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user