uark_front/src/layouts/NavBar.vue

607 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<nav
class="fixed top-0 w-full h-14 lg:h-16 bg-white/20 backdrop-blur-sm border-b-2 grid grid-cols-[1fr_auto_1fr] items-center px-4 sm:px-6 lg:px-8 z-[80]"
>
<!-- 左側Logo + 機構切換 -->
<div
class="col-span-1 justify-self-start flex items-center gap-4 sm:gap-8 shrink-0 w-[240px] md:w-[280px] lg:w-[340px]"
>
<RouterLink to="/" class="h-9 md:h-11">
<img src="/img/common/logo.png" alt="Logo" class="h-full w-auto" />
</RouterLink>
<div class="relative" v-if="!isHome">
<div
ref="triggerRef"
class="btn bg-brand-green text-white hover:opacity-90 shadow border-none rounded-full px-6 lg:px-8 h-10 gap-2"
role="button"
tabindex="0"
aria-haspopup="true"
:aria-expanded="isFacilityOpen"
@click="toggleFacility"
@keydown.enter.prevent="toggleFacility"
@keydown.space.prevent="toggleFacility"
>
<span class="tracking-wider">{{ displayLabel }}</span>
<!-- caret icon -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="m98 190.06l139.78 163.12a24 24 0 0 0 36.44 0L414 190.06c13.34-15.57 2.28-39.62-18.22-39.62h-279.6c-20.5 0-31.56 24.05-18.18 39.62"
/>
</svg>
</div>
<!-- Dropdown -->
<div
v-show="isFacilityOpen"
ref="panelRef"
class="absolute top-12 left-0 z-50 w-56 md:w-64 rounded-md border border-brand-gray-light bg-white shadow-lg p-2"
@click.stop
>
<ul class="max-h-48 overflow-y-auto text-brand-black">
<li
v-for="item in facilities"
:key="item"
@click="selectItem(item)"
class="px-3 py-2 rounded-md cursor-pointer hover:bg-brand-gray-light tracking-wider"
:class="selectedItem === item ? 'bg-brand-green-light' : ''"
>
{{ item }}
</li>
</ul>
</div>
</div>
</div>
<!-- 中間:導覽(平板以上顯示;置中) -->
<div
class="col-span-1 justify-self-center hidden lg:flex items-center rounded-full min-w-[300px] w-[300px] bg-white/60 shadow"
>
<nav class="w-full grid grid-cols-3 divide-x divide-transparent">
<RouterLink to="/" v-slot="{ href, navigate, isExactActive }">
<a
:href="href"
@click="navigate"
:class="[
'w-full py-2 flex justify-center items-center rounded-full transition-colors',
isExactActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>首頁</a
>
</RouterLink>
<RouterLink to="/operation" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate"
:class="[
'w-full py-2 flex justify-center items-center rounded-full transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>營運</a
>
</RouterLink>
<RouterLink to="/nursing" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate"
:class="[
'w-full py-2 flex justify-center items-center rounded-full transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>照護</a
>
</RouterLink>
</nav>
</div>
<!-- 右側:通知 + 使用者(桌機顯示) -->
<div
class="col-span-1 justify-self-end hidden lg:flex items-center gap-3 sm:gap-6"
>
<button
class="relative btn bg-white text-brand-black hover:opacity-90 shadow border-none rounded-full p-2"
aria-label="通知"
@click="notif.open()"
>
<!-- 鈴鐺 -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 16 16"
>
<path
fill="currentColor"
d="M8 2a4.5 4.5 0 0 0-4.5 4.5v2.401l-.964 2.414A.5.5 0 0 0 3 12h3c0 1.108.892 2 2 2s2-.892 2-2h3a.5.5 0 0 0 .464-.685L12.5 8.9V6.5A4.5 4.5 0 0 0 8 2m1 10c0 .556-.444 1-1 1s-1-.444-1-1zM4.5 6.5a3.5 3.5 0 1 1 7 0v2.498a.5.5 0 0 0 .036.185L12.262 11H3.738l.726-1.817a.5.5 0 0 0 .036-.185z"
/>
</svg>
<!-- 紅色圓圈徽章 -->
<span
class="absolute top-0 -right-3 w-5 h-5 rounded-full bg-brand-red-dark text-white text-xs font-bold flex items-center justify-center leading-none shadow"
aria-label="未讀通知 3 則"
>3</span
>
</button>
<div
class="btn bg-white text-brand-black hover:opacity-90 shadow border-none rounded-full px-3 md:px-5 h-9 md:h-10 gap-2 md:gap-3"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 md:w-5 md:h-5"
viewBox="0 0 16 16"
>
<path
fill="currentColor"
d="M11 7c0 1.66-1.34 3-3 3S5 8.66 5 7s1.34-3 3-3s3 1.34 3 3"
/>
<path
fill="currentColor"
fill-rule="evenodd"
d="M16 8c0 4.42-3.58 8-8 8s-8-3.58-8-8s3.58-8 8-8s8 3.58 8 8M4 13.75C4.16 13.484 5.71 11 7.99 11c2.27 0 3.83 2.49 3.99 2.75A6.98 6.98 0 0 0 14.99 8c0-3.87-3.13-7-7-7s-7 3.13-7 7c0 2.38 1.19 4.49 3.01 5.75"
clip-rule="evenodd"
/>
</svg>
<p class="hidden md:inline">使用者</p>
<svg
xmlns="http://www.w3.org/2000/svg"
class="hidden md:block w-4 h-4"
viewBox="0 0 512 512"
>
<path
fill="currentColor"
d="m98 190.06l139.78 163.12a24 24 0 0 0 36.44 0L414 190.06c13.34-15.57 2.28-39.62-18.22-39.62h-279.6c-20.5 0-31.56 24.05-18.18 39.62"
/>
</svg>
</div>
</div>
<!-- 手機:漢堡按鈕-->
<button
class="lg:hidden btn bg-white text-brand-black hover:opacity-90 shadow border-none rounded-md p-2 justify-self-end"
@click="toggleMobileMenu"
:aria-expanded="isMobileMenuOpen"
aria-controls="mobile-menu"
:aria-label="isMobileMenuOpen ? '關閉主選單' : '開啟主選單'"
>
<!-- 關閉(叉叉) -->
<svg
v-if="isMobileMenuOpen"
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m12 10.586l4.95-4.95l1.414 1.414L13.414 12l4.95 4.95l-1.414 1.414L12 13.414l-4.95 4.95l-1.414-1.414L10.586 12l-4.95-4.95l1.414-1.414z"
/>
</svg>
<!-- 漢堡 -->
<svg
v-else
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z" />
</svg>
</button>
<!-- 手機:全螢幕 ham menu白底hover 灰active 綠) -->
<div
id="mobile-menu-overlay"
class="sm:hidden fixed inset-0 z-[1100] bg-white p-4 flex flex-col gap-4 transition-opacity duration-200"
:class="
isMobileMenuOpen
? 'opacity-100 pointer-events-auto'
: 'opacity-0 pointer-events-none'
"
role="dialog"
aria-modal="true"
aria-label="主選單"
@keydown.esc.prevent="closeMobileMenu"
>
<div
v-show="isMobileMenuOpen"
id="mobile-menu"
class="md:hidden fixed inset-0 z-[1100] bg-white p-4 flex flex-col gap-4"
role="dialog"
aria-modal="true"
aria-label="主選單"
@keydown.esc.prevent="closeMobileMenu"
>
<!-- 頂部:標題 + 叉叉(和右上按鈕行為一致) -->
<div class="flex items-center justify-between">
<p class="font-semibold text-brand-black text-base">選單</p>
<button
class="btn btn-sm bg-white border-none shadow text-brand-black"
@click="closeMobileMenu"
aria-label="關閉主選單"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-5 h-5"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m12 10.586l4.95-4.95l1.414 1.414L13.414 12l4.95 4.95l-1.414 1.414L12 13.414l-4.95 4.95l-1.414-1.414L10.586 12l-4.95-4.95l1.414-1.414z"
/>
</svg>
</button>
</div>
<!-- 導覽icon + 文字) -->
<nav class="space-y-2">
<RouterLink to="/" v-slot="{ href, navigate, isExactActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isExactActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M4 19v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-3q-.425 0-.712-.288T14 20v-5q0-.425-.288-.712T13 14h-2q-.425 0-.712.288T10 15v5q0 .425-.288.713T9 21H6q-.825 0-1.412-.587T4 19"
/>
</svg>
<span class="text-brand-black">首頁</span>
</a>
</RouterLink>
<RouterLink to="/operation" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 13.75a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75v6.75H14V4.25c0-.728-.002-1.2-.048-1.546c-.044-.325-.115-.427-.172-.484s-.159-.128-.484-.172C12.949 2.002 12.478 2 11.75 2s-1.2.002-1.546.048c-.325.044-.427.115-.484.172s-.128.159-.172.484c-.046.347-.048.818-.048 1.546V20.5H8V8.75A.75.75 0 0 0 7.25 8h-3a.75.75 0 0 0-.75.75V20.5H1.75a.75.75 0 0 0 0 1.5h20a.75.75 0 0 0 0-1.5H20z"
/>
</svg>
<span class="text-brand-black">營運</span>
</a>
</RouterLink>
<RouterLink to="/nursing" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M22 9.95v4.11a1.78 1.78 0 0 1-1.78 1.78h-4.39v4.39a1.73 1.73 0 0 1-.52 1.25a1.8 1.8 0 0 1-1.26.52H9.94a1.8 1.8 0 0 1-1.26-.52a1.8 1.8 0 0 1-.52-1.25v-4.39H3.78A1.78 1.78 0 0 1 2 14.06V9.95a1.78 1.78 0 0 1 1.78-1.78h4.38V3.78a1.8 1.8 0 0 1 1.103-1.646A1.8 1.8 0 0 1 9.94 2H14a1.8 1.8 0 0 1 1.26.52a1.77 1.77 0 0 1 .52 1.26v4.39h4.39c.472.003.924.19 1.26.52A1.78 1.78 0 0 1 22 9.95"
/>
</svg>
<span class="text-brand-black">照護</span>
</a>
</RouterLink>
</nav>
<hr class="border-gray-200" />
<!-- 通知 + 使用者(放在全螢幕 menu 內;同樣 hover 灰、active 綠) -->
<div class="space-y-2">
<button
class="w-full flex items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-gray-100 text-left"
@click="closeMobileMenu()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M12.45 16.002a2.5 2.5 0 0 1-4.9 0zM9.998 2c3.149 0 5.744 2.335 5.984 5.355l.013.223l.005.224l-.001 3.606l.954 2.587l.025.085l.016.086l.005.089c0 .315-.196.59-.522.707l-.114.033l-.114.01H3.751a.8.8 0 0 1-.259-.047c-.287-.105-.476-.372-.482-.716l.004-.117l.034-.13l.95-2.584L4 7.793l.004-.225C4.127 4.451 6.771 2 9.998 2"
/>
</svg>
<span class="text-brand-black">通知</span>
</button>
<button
class="w-full flex items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-gray-100 text-left"
@click="closeMobileMenu()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7 7a5 5 0 1 1 10 0A5 5 0 0 1 7 7M3.5 19a5 5 0 0 1 5-5h7a5 5 0 0 1 5 5v2h-17z"
/>
</svg>
<span class="text-brand-black">使用者</span>
</button>
</div>
<div class="mt-auto text-xs text-gray-500">© U-ARK</div>
</div>
</div>
<!-- 平板直式 modal在 640px1023px 顯示,位置在漢堡鈕下方 -->
<div
v-show="isMobileMenuOpen"
class="hidden sm:block lg:hidden absolute right-4 top-16 z-[1100] w-[88vw] sm:w-[240px] bg-white rounded-xl shadow-md border border-gray-100 p-4"
role="dialog"
aria-modal="true"
aria-label="主選單(平板)"
@click.stop
>
<!-- 頂部:標題 + 關閉 -->
<div class="flex items-center justify-between mb-2">
<p class="font-semibold text-brand-black text-base">選單</p>
</div>
<!-- 導覽icon + 文字) -->
<nav class="space-y-2">
<RouterLink to="/" v-slot="{ href, navigate, isExactActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isExactActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M4 19v-9q0-.475.213-.9t.587-.7l6-4.5q.525-.4 1.2-.4t1.2.4l6 4.5q.375.275.588.7T20 10v9q0 .825-.588 1.413T18 21h-3q-.425 0-.712-.288T14 20v-5q0-.425-.288-.712T13 14h-2q-.425 0-.712.288T10 15v5q0 .425-.288.713T9 21H6q-.825 0-1.412-.587T4 19"
/>
</svg>
<span class="text-brand-black">首頁</span>
</a>
</RouterLink>
<RouterLink to="/operation" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M20 13.75a.75.75 0 0 0-.75-.75h-3a.75.75 0 0 0-.75.75v6.75H14V4.25c0-.728-.002-1.2-.048-1.546c-.044-.325-.115-.427-.172-.484s-.159-.128-.484-.172C12.949 2.002 12.478 2 11.75 2s-1.2.002-1.546.048c-.325.044-.427.115-.484.172s-.128.159-.172.484c-.046.347-.048 1.818-.048 1.546V20.5H8V8.75A.75.75 0 0 0 7.25 8h-3a.75.75 0 0 0-.75.75V20.5H1.75a.75.75 0 0 0 0 1.5h20a.75.75 0 0 0 0-1.5H20z"
/>
</svg>
<span class="text-brand-black">營運</span>
</a>
</RouterLink>
<RouterLink to="/nursing" v-slot="{ href, navigate, isActive }">
<a
:href="href"
@click="navigate, closeMobileMenu()"
:class="[
'flex items-center gap-3 rounded-lg px-3 py-3 transition-colors',
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
]"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M22 9.95v4.11a1.78 1.78 0 0 1-1.78 1.78h-4.39v4.39a1.73 1.73 0 0 1-.52 1.25a1.8 1.8 0 0 1-1.26.52H9.94a1.8 1.8 0 0 1-1.26-.52a1.8 1.8 0 0 1-.52-1.25v-4.39H3.78A1.78 1.78 0 0 1 2 14.06V9.95a1.78 1.78 0 0 1 1.78-1.78h4.38V3.78a1.8 1.8 0 0 1 1.103-1.646A1.8 1.8 0 0 1 9.94 2H14a1.8 1.8 0 0 1 1.26.52a1.77 1.77 0 0 1 .52 1.26v4.39h4.39c.472.003.924.19 1.26.52A1.78 1.78 0 0 1 22 9.95"
/>
</svg>
<span class="text-brand-black">照護</span>
</a>
</RouterLink>
</nav>
<hr class="border-gray-200 my-3" />
<!-- 通知 + 使用者(放在全螢幕 menu 內;同樣 hover 灰、active 綠) -->
<div class="space-y-2">
<button
class="w-full flex items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-gray-100 text-left"
@click="closeMobileMenu()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M12.45 16.002a2.5 2.5 0 0 1-4.9 0zM9.998 2c3.149 0 5.744 2.335 5.984 5.355l.013.223l.005.224l-.001 3.606l.954 2.587l.025.085l.016.086l.005.089c0 .315-.196.59-.522.707l-.114.033l-.114.01H3.751a.8.8 0 0 1-.259-.047c-.287-.105-.476-.372-.482-.716l.004-.117l.034-.13l.95-2.584L4 7.793l.004-.225C4.127 4.451 6.771 2 9.998 2"
/>
</svg>
<span class="text-brand-black">通知</span>
</button>
<button
class="w-full flex items-center gap-3 rounded-lg px-3 py-3 transition-colors hover:bg-gray-100 text-left"
@click="closeMobileMenu()"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M7 7a5 5 0 1 1 10 0A5 5 0 0 1 7 7M3.5 19a5 5 0 0 1 5-5h7a5 5 0 0 1 5 5v2h-17z"
/>
</svg>
<span class="text-brand-black">使用者</span>
</button>
</div>
</div>
</nav>
</template>
<script setup>
import {
ref,
computed,
onMounted,
onUnmounted,
defineOptions,
nextTick,
watch,
} from "vue";
import { useRoute } from "vue-router";
import { useNotifStore } from "@/stores/notif";
const notif = useNotifStore();
// ====== 通知 Modal 狀態與行為 ======
const isNotifOpen = ref(false);
const notifBtnRef = ref(null);
const notifPanelRef = ref(null);
const notifCloseRef = ref(null);
const openNotif = async () => {
isNotifOpen.value = true;
await nextTick();
// 開啟後把焦點放到關閉鍵(無障礙友善)
notifCloseRef.value?.focus?.();
};
const closeNotif = async () => {
isNotifOpen.value = false;
await nextTick();
// 關閉後將焦點還給原本的通知按鈕
notifBtnRef.value?.focus?.();
};
// ====== 首頁隱藏(機構選單功能)======
const route = useRoute();
const isHome = computed(() => route.name === "home" || route.path === "/");
// Dropdown 狀態
const isOpen = ref(false);
watch(
() => route.fullPath,
(p) => {
if (p === "/" || route.name === "home") isOpen.value = false;
}
);
// ====== Dropdown機構清單======
const isFacilityOpen = ref(false);
const triggerRef = ref(null);
const panelRef = ref(null);
const facilities = ["A 機構", "B 機構", "C 機構", "D 機構", "E 機構", "F 機構"];
const selectedItem = ref(facilities[0]);
const selectItem = (item) => {
selectedItem.value = item;
isOpen.value = false;
};
const displayLabel = computed(() => {
const label = String(selectedItem.value ?? "護祐");
return label.length > 4 ? label.slice(0, 4) + "..." : label;
});
defineOptions({ name: "NavBar" });
const toggleFacility = () => {
isFacilityOpen.value = !isFacilityOpen.value;
};
const onClickOutside = (e) => {
const t = e.target;
if (!triggerRef.value || !panelRef.value) return;
const clickedInsideTrigger = triggerRef.value.contains(t);
const clickedInsidePanel = panelRef.value.contains(t);
if (!clickedInsideTrigger && !clickedInsidePanel) {
isFacilityOpen.value = false;
}
};
// ====== 手機版抽屜(漢堡選單)======
const isMobileMenuOpen = ref(false);
const toggleMobileMenu = async () => {
isMobileMenuOpen.value = !isMobileMenuOpen.value;
if (isMobileMenuOpen.value) {
await nextTick();
// 需要的話在這裡把焦點移到關閉鈕或第一個連結
}
};
const closeMobileMenu = () => {
isMobileMenuOpen.value = false;
};
// ====== 全域鍵盤Esc 同時關閉兩種面板)======
const handleKeydown = (e) => {
if (e.key === "Escape") {
isFacilityOpen.value = false; // 關 dropdown
isMobileMenuOpen.value = false; // 關 ham menu
isNotifOpen.value = false; // 關通知 Modal
}
};
onMounted(() => {
document.addEventListener("click", onClickOutside);
document.addEventListener("keydown", handleKeydown);
});
onUnmounted(() => {
document.removeEventListener("click", onClickOutside);
document.removeEventListener("keydown", handleKeydown);
});
</script>
<style scoped></style>