refactor: replace common confirm dialogs with maintenance CRUD dialogs and streamline form handling in MasterDetailMntC.vue and SingleRecordMnt.vue

This commit is contained in:
skytek_xinliang
2026-03-26 16:01:20 +08:00
parent ec3fbace1a
commit 389ec56480
32 changed files with 2549 additions and 2763 deletions
@@ -0,0 +1,211 @@
import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from 'vue'
import type { AdminLayoutMenuItem } from '@/components/layouts/sk-admin-layout/types'
type ToggleSidebarPayload = {
drawer: boolean
rail: boolean
}
type UseAdminLayoutStateOptions = {
appBarRef: Ref<unknown>
breadcrumbBarVisible: boolean | null
emitUpdateBreadcrumbBarVisible: (value: boolean) => void
emitUpdateFavoritesBarVisible: (value: boolean) => void
emitUpdateIsRail: (value: boolean) => void
favoritesBarVisible: boolean | null
isMobile: Ref<boolean>
isRail: boolean | null
menuItems: AdminLayoutMenuItem[]
onToggleSidebar: (payload: ToggleSidebarPayload) => void
}
export function useAdminLayoutState (options: UseAdminLayoutStateOptions) {
const drawer = ref(true)
const mobileFavoritesPanel = ref(false)
const mobileMenuPath = ref<AdminLayoutMenuItem[]>([])
const localBreadcrumbBarVisible = ref(true)
const localFavoritesBarVisible = ref(true)
const localIsRail = ref(false)
const opened = ref<string[]>([])
const appBarHeight = ref(0)
const isRail = computed({
get: () => (options.isRail ?? localIsRail.value),
set: (value: boolean) => {
if (options.isRail === null) {
localIsRail.value = value
return
}
options.emitUpdateIsRail(value)
},
})
const showFavoritesBar = computed({
get: () => (options.favoritesBarVisible ?? localFavoritesBarVisible.value),
set: (value: boolean) => {
if (options.favoritesBarVisible === null) {
localFavoritesBarVisible.value = value
return
}
options.emitUpdateFavoritesBarVisible(value)
},
})
const showBreadcrumbBar = computed({
get: () => (options.breadcrumbBarVisible ?? localBreadcrumbBarVisible.value),
set: (value: boolean) => {
if (options.breadcrumbBarVisible === null) {
localBreadcrumbBarVisible.value = value
return
}
options.emitUpdateBreadcrumbBarVisible(value)
},
})
const mobileCurrentItems = computed(() =>
mobileMenuPath.value.reduce(
(items, currentItem) => currentItem?.subItems ?? [],
options.menuItems || []
)
)
const mobileCurrentLevel = computed(() => mobileMenuPath.value.length + 1)
const mobileMenuLevels = computed(() =>
Array.from({ length: mobileCurrentLevel.value }, (_, index) => ({
level: index + 1,
title: index === 0 ? '主選單' : (mobileMenuPath.value[index - 1]?.title ?? `${index + 1}`),
}))
)
function resetMobilePanels () {
mobileFavoritesPanel.value = false
mobileMenuPath.value = []
}
function toggleSidebar () {
if (options.isMobile.value) {
drawer.value = !drawer.value
} else {
isRail.value = !isRail.value
}
options.onToggleSidebar({
drawer: drawer.value,
rail: isRail.value,
})
}
function goToMobileLevel (level: number) {
mobileFavoritesPanel.value = false
mobileMenuPath.value = mobileMenuPath.value.slice(0, Math.max(0, level - 1))
}
function openMobileFavoritesPanel () {
mobileMenuPath.value = []
mobileFavoritesPanel.value = true
}
function handleMobileMenuClick (
item: AdminLayoutMenuItem,
onSelect: (selectedItem: AdminLayoutMenuItem) => void
) {
if (item?.subItems?.length) {
mobileMenuPath.value = [...mobileMenuPath.value, item]
return
}
onSelect(item)
}
function handleSelectFavorite (
item: AdminLayoutMenuItem,
onSelect: (selectedItem: AdminLayoutMenuItem) => void
) {
onSelect(item)
mobileFavoritesPanel.value = false
}
function toggleFavoritesBar (nextValue?: boolean) {
showFavoritesBar.value =
typeof nextValue === 'boolean' ? nextValue : !showFavoritesBar.value
}
function handleUnshrink () {
isRail.value = false
}
let appBarObserver: ResizeObserver | null = null
function resolveObservedElement () {
const target = options.appBarRef.value as HTMLElement | { $el?: HTMLElement } | null
if (!target) return null
if (target instanceof HTMLElement) return target
return target.$el ?? null
}
function updateAppBarHeight () {
const el = resolveObservedElement()
if (!el) return
appBarHeight.value = Math.round(el.getBoundingClientRect().height || 0)
}
onMounted(() => {
updateAppBarHeight()
if (typeof ResizeObserver === 'undefined') return
const el = resolveObservedElement()
if (!el) return
appBarObserver = new ResizeObserver(() => updateAppBarHeight())
appBarObserver.observe(el)
})
onBeforeUnmount(() => {
if (!appBarObserver) return
appBarObserver.disconnect()
appBarObserver = null
})
watch(options.isMobile, (value) => {
if (!value) {
resetMobilePanels()
}
})
watch(drawer, (value) => {
if (!value) {
resetMobilePanels()
}
})
const mainStyle = computed(() => ({
paddingTop: appBarHeight.value ? `${appBarHeight.value}px` : undefined,
height: '100vh',
minHeight: 0,
flex: '1 1 0',
}))
return {
drawer,
goToMobileLevel,
handleMobileMenuClick,
handleSelectFavorite,
handleUnshrink,
isRail,
mainStyle,
mobileCurrentItems,
mobileCurrentLevel,
mobileFavoritesPanel,
mobileMenuLevels,
openMobileFavoritesPanel,
opened,
showBreadcrumbBar,
showFavoritesBar,
toggleFavoritesBar,
toggleSidebar,
}
}