Files
workspace/code/fms/fms-vue-old2/src/layouts/DefaultLayout.vue
T
2026-08-13 22:26:56 +08:00

116 lines
3.8 KiB
Vue

<script setup lang="ts">
import { useEventListener, usePreferredReducedMotion } from "@vueuse/core";
import { computed, ref } from "vue";
import { useRoute } from "vue-router";
import { useAppStore } from "@/stores/app";
import AppSidebar from "@/layouts/components/AppSidebar.vue";
import AppTabs from "@/layouts/components/AppTabs.vue";
import AppTopbar from "@/layouts/components/AppTopbar.vue";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
const route = useRoute();
const appStore = useAppStore();
const preferredReducedMotion = usePreferredReducedMotion();
const routeInstanceKey = computed(
() => `${route.fullPath}:${appStore.getRouteRefreshKey(route.fullPath)}`,
);
// -- drag rail --
const MIN_WIDTH = 180;
const MAX_WIDTH = 400;
const dragging = ref(false);
let startX = 0,
startWidth = 0;
useEventListener("mousemove", (e: MouseEvent) => {
if (!dragging.value) return;
const newWidth = Math.max(
MIN_WIDTH,
Math.min(MAX_WIDTH, Math.round(startWidth + e.clientX - startX)),
);
const wrapper = document.querySelector("[data-slot='sidebar-wrapper']") as HTMLElement | null;
wrapper?.style.setProperty("--sidebar-width", `${newWidth / 16}rem`);
});
useEventListener("mouseup", () => {
if (!dragging.value) return;
dragging.value = false;
const wrapper = document.querySelector("[data-slot='sidebar-wrapper']") as HTMLElement | null;
if (wrapper) {
wrapper.style.userSelect = "";
const group = wrapper.querySelector(".group.peer") as HTMLElement | null;
group?.querySelectorAll("*").forEach((el) => {
(el as HTMLElement).style.removeProperty("transition");
});
}
});
function onRailMouseDown(e: MouseEvent) {
const sidebar = document.querySelector("[data-slot='sidebar']") as HTMLElement | null;
if (sidebar?.dataset.state !== "expanded") return;
e.preventDefault();
dragging.value = true;
startX = e.clientX;
const wrapper = document.querySelector("[data-slot='sidebar-wrapper']") as HTMLElement | null;
if (wrapper) {
const w = getComputedStyle(wrapper).getPropertyValue("--sidebar-width").trim();
startWidth = parseFloat(w || "14") * 16;
wrapper.style.userSelect = "none";
const group = wrapper.querySelector(".group.peer") as HTMLElement | null;
group?.querySelectorAll("*").forEach((el) => {
(el as HTMLElement).style.setProperty("transition", "none", "important");
});
}
}
</script>
<template>
<SidebarProvider
class="group/sidebar h-svh min-w-0 overflow-hidden bg-sidebar"
:style="{ '--sidebar-width': '14rem' }"
>
<AppSidebar />
<!-- Drag rail: hidden when collapsed, follows sidebar width when expanded -->
<div
data-sidebar="rail"
class="fixed top-0 z-50 hidden w-4 h-screen cursor-col-resize sm:block group-has-[[data-state=collapsed]]/sidebar:hidden after:absolute after:left-1/2 after:-translate-x-px after:inset-y-0 after:w-0.5 hover:after:bg-sidebar-border"
:style="{ left: 'calc(var(--sidebar-width) - 0.5rem)' }"
@mousedown="onRailMouseDown"
/>
<SidebarInset class="m-2 ml-0 min-w-0 overflow-hidden rounded-xl shadow z-10">
<AppTopbar />
<AppTabs />
<div class="flex min-h-0 min-w-0 flex-1 flex-col gap-4 overflow-auto p-4 pt-2">
<router-view v-slot="{ Component }">
<transition name="page" mode="out-in" :css="preferredReducedMotion !== 'reduce'">
<keep-alive :max="8">
<component :is="Component" :key="routeInstanceKey" />
</keep-alive>
</transition>
</router-view>
</div>
</SidebarInset>
</SidebarProvider>
</template>
<style>
.page-enter-active {
transition:
opacity 160ms ease-out,
transform 160ms ease-out;
}
.page-leave-active {
transition: opacity 80ms ease-in;
}
.page-enter-from {
opacity: 0;
transform: translateY(4px);
}
.page-leave-to {
opacity: 0;
}
</style>