feat: NavBart RWD 製作
This commit is contained in:
parent
d1b51dcc9d
commit
458cfc7eb0
@ -13,7 +13,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>諾亞克 U-ARK 戰情中心</title>
|
||||
</head>
|
||||
<body class="w-screen bg-brand-gray-light">
|
||||
<body class="w-screen bg-brand-gray-light font-noto text-brand-black">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
6
jsconfig.json
Normal file
6
jsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": false
|
||||
},
|
||||
"include": ["src/**/*.js", "src/**/*.vue", "src/types/**/*.d.ts"]
|
||||
}
|
7
package-lock.json
generated
7
package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"echarts": "^6.0.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"pinia": "^3.0.3",
|
||||
"three": "^0.179.1",
|
||||
"vue": "^3.5.18",
|
||||
@ -2077,6 +2078,12 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/leaflet": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
|
||||
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"echarts": "^6.0.0",
|
||||
"leaflet": "^1.9.4",
|
||||
"pinia": "^3.0.3",
|
||||
"three": "^0.179.1",
|
||||
"vue": "^3.5.18",
|
||||
|
Before Width: | Height: | Size: 541 KiB After Width: | Height: | Size: 541 KiB |
BIN
public/img/leaflet/marker-icon-2x.png
Normal file
BIN
public/img/leaflet/marker-icon-2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
public/img/leaflet/marker-icon.png
Normal file
BIN
public/img/leaflet/marker-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
public/img/leaflet/marker-shadow.png
Normal file
BIN
public/img/leaflet/marker-shadow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 618 B |
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<section id="app" class="flex flex-col min-h-screen ">
|
||||
<NavBar class="fixed" />
|
||||
<main class="w-full p-4 overflow-x-hidden pt-[72px] pb-4">
|
||||
<main class="w-full p-4 overflow-x-hidden pb-4">
|
||||
<RouterView />
|
||||
</main>
|
||||
</section>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<section id="app" class="flex flex-col min-h-screen">
|
||||
<NavBar class="fixed" />
|
||||
|
||||
<main class="w-full p-4 mt-[72px]">
|
||||
<main class="w-full p-4">
|
||||
<section class="grid grid-cols-7 gap-4 overflow-hidden">
|
||||
<!-- 左側:地圖 -->
|
||||
<section class="col-span-3 bg-white/50 rounded-md shadow p-3 h-full overflow-hidden">
|
||||
|
@ -1,46 +1,48 @@
|
||||
<template>
|
||||
<nav
|
||||
class="w-full h-[64px] bg-white bg-opacity-50 shadow-md flex justify-between items-center px-8"
|
||||
class="sticky top-0 w-full h-14 md:h-16 bg-white/70 bg-opacity-80 shadow-md flex justify-between items-center px-4 sm:px-6 lg:px-8 z-[1000]"
|
||||
>
|
||||
<!-- 左側 logo、選單區 -->
|
||||
<div class="flex justify-start items-center gap-12">
|
||||
<RouterLink to="/" class="h-[45px]">
|
||||
<img src="/img/logo.png" alt="Logo" class="w-full h-full" />
|
||||
<!-- 左側:Logo + 機構切換 -->
|
||||
<div
|
||||
class="flex items-center gap-4 sm:gap-8 shrink-0 w-[240px] md:w-[300px] lg:w-[340px]"
|
||||
>
|
||||
<RouterLink to="/" class="h-9 md:h-11">
|
||||
<img src="/img/logo.png" alt="Logo" class="h-full w-auto" />
|
||||
</RouterLink>
|
||||
|
||||
<div class="relative">
|
||||
<!-- 手機到桌機都顯示機構名稱,維持桌機尺寸 -->
|
||||
<div
|
||||
ref="triggerRef"
|
||||
class="btn text-white bg-brand-green hover:opacity-90 shadow-md border-none rounded-full px-8 tracking-widest"
|
||||
class="btn bg-brand-green text-white hover:opacity-90 shadow-md border-none rounded-full px-6 lg:px-8 h-10 gap-2"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-haspopup="true"
|
||||
:aria-expanded="isOpen ? 'true' : 'false'"
|
||||
:aria-expanded="isOpen"
|
||||
@click="toggle"
|
||||
@keydown.enter.prevent="toggle"
|
||||
@keydown.space.prevent="toggle"
|
||||
>
|
||||
{{ displayLabel }}
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
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>
|
||||
</span>
|
||||
<!-- 直接顯示,不再隱藏 -->
|
||||
<span>{{ 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>
|
||||
|
||||
<!-- Modal:定位在按鈕右下方 -->
|
||||
<!-- Dropdown(小螢幕靠左;桌機可改靠右) -->
|
||||
<div
|
||||
v-show="isOpen"
|
||||
ref="panelRef"
|
||||
class="absolute top-16 left-0 z-50 w-64 rounded-md border border-gray-100 bg-white shadow-lg p-2"
|
||||
class="absolute top-12 left-0 md:top-12 md:left-0 lg:left-0 z-50 w-56 md:w-64 rounded-md border border-gray-100 bg-white shadow-lg p-2"
|
||||
@click.stop
|
||||
>
|
||||
<ul class="max-h-48 overflow-y-auto text-brand-black">
|
||||
@ -58,22 +60,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中間 導覽按鈕區 -->
|
||||
|
||||
<!-- 中間:導覽(手機隱藏;平板以上顯示;桌機放大間距) -->
|
||||
<div
|
||||
class="min-w-[300px] text-brand-black bg-white shadow-md rounded-full grid grid-cols-3 items-center"
|
||||
class="hidden md:grid min-w-[300px] grid-cols-3 items-center bg-white shadow-md rounded-full md:px-0 lg:min-w-[420px]"
|
||||
>
|
||||
<RouterLink to="/" v-slot="{ href, navigate, isExactActive }">
|
||||
<a
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="[
|
||||
'px-5 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
'px-4 lg:px-6 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
isExactActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
|
||||
]"
|
||||
>首頁</a
|
||||
>
|
||||
首頁
|
||||
</a>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/operation" v-slot="{ href, navigate, isActive }">
|
||||
@ -81,12 +81,11 @@
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="[
|
||||
'px-5 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
'px-4 lg:px-6 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
|
||||
]"
|
||||
>營運</a
|
||||
>
|
||||
營運
|
||||
</a>
|
||||
</RouterLink>
|
||||
|
||||
<RouterLink to="/nursing" v-slot="{ href, navigate, isActive }">
|
||||
@ -94,20 +93,21 @@
|
||||
:href="href"
|
||||
@click="navigate"
|
||||
:class="[
|
||||
'px-5 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
'px-4 lg:px-6 py-2 rounded-full flex justify-center items-center transition-colors',
|
||||
isActive ? 'bg-brand-green-light' : 'hover:bg-gray-100',
|
||||
]"
|
||||
>照護</a
|
||||
>
|
||||
照護
|
||||
</a>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- 右側 登入區 -->
|
||||
<div class="flex justify-end items-center gap-8">
|
||||
<div
|
||||
class="btn text-brand-black bg-white hover:opacity-90 shadow-md border-none rounded-full p-2 flex justify-center items-center"
|
||||
<!-- 右側:通知 + 使用者(手機只顯示圖示;平板顯示文字) -->
|
||||
<div class="hidden md:flex items-center gap-3 sm:gap-6">
|
||||
<button
|
||||
class="btn bg-white text-brand-black hover:opacity-90 shadow-md border-none rounded-full p-2"
|
||||
aria-label="通知"
|
||||
>
|
||||
<!-- 鈴鐺 -->
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
@ -119,71 +119,262 @@
|
||||
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>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="btn text-brand-black bg-white hover:opacity-90 shadow-md border-none rounded-full px-6 flex justify-center items-center gap-3"
|
||||
class="btn bg-white text-brand-black hover:opacity-90 shadow-md border-none rounded-full px-3 md:px-5 h-9 md:h-10 gap-2 md:gap-3"
|
||||
>
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
<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="md:hidden btn bg-white text-brand-black hover:opacity-90 shadow-md border-none rounded-md p-2"
|
||||
@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"
|
||||
class="md: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="關閉主選單"
|
||||
>
|
||||
<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>
|
||||
</span>
|
||||
<p>使用者</p>
|
||||
<span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 512 512"
|
||||
<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()"
|
||||
>
|
||||
<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>
|
||||
</span>
|
||||
<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>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, defineOptions } from "vue";
|
||||
|
||||
// modal 操作
|
||||
const selectItem = (item) => {
|
||||
selectedItem.value = item;
|
||||
isOpen.value = false;
|
||||
};
|
||||
|
||||
// 按鈕顯示文字(>4 字就加 …)
|
||||
const displayLabel = computed(() => {
|
||||
const label = String(selectedItem.value ?? "護祐");
|
||||
return label.length > 4 ? label.slice(0, 4) + "..." : label;
|
||||
});
|
||||
|
||||
defineOptions({ name: "NavBar" });
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
defineOptions,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
|
||||
// ====== Dropdown(機構清單)======
|
||||
const isOpen = ref(false);
|
||||
const triggerRef = ref(null);
|
||||
const panelRef = ref(null);
|
||||
|
||||
// 機構名稱清單
|
||||
const facilities = [
|
||||
"總部",
|
||||
"護祐護理之家",
|
||||
@ -200,16 +391,22 @@ const facilities = [
|
||||
"崇智護理之家",
|
||||
"慈祐長照中心(養護型)",
|
||||
];
|
||||
|
||||
// 預設選中第一筆(同時讓按鈕顯示第一筆、li 也保持選中底色)
|
||||
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 toggle = () => {
|
||||
isOpen.value = !isOpen.value;
|
||||
};
|
||||
|
||||
// 點空白處關閉
|
||||
const onClickOutside = (e) => {
|
||||
const t = e.target;
|
||||
if (!triggerRef.value || !panelRef.value) return;
|
||||
@ -220,19 +417,35 @@ const onClickOutside = (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Esc 關閉
|
||||
const onKeydown = (e) => {
|
||||
if (e.key === "Escape") isOpen.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") {
|
||||
isOpen.value = false; // 關 dropdown
|
||||
isMobileMenuOpen.value = false; // 關手機抽屜
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener("click", onClickOutside);
|
||||
document.addEventListener("keydown", onKeydown);
|
||||
document.addEventListener("keydown", handleKeydown); // ✅ 只綁一次
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", onClickOutside);
|
||||
document.removeEventListener("keydown", onKeydown);
|
||||
document.removeEventListener("keydown", handleKeydown); // ✅ 只解一次
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -2,7 +2,8 @@ import { createApp } from 'vue'
|
||||
import { createPinia } from "pinia";
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import router from './router'
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
></progress>
|
||||
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 flex items-center text-[20px] font-nats text-brand-black/80"
|
||||
class="pointer-events-none absolute bottom-0 flex items-center text-[20px] font-nats text-brand-black/80"
|
||||
:class="textPosClass"
|
||||
>
|
||||
{{ currentLocale }} / {{ totalLocale }}
|
||||
|
@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<section
|
||||
class="grid grid-cols-3 gap-2 h-[calc(100vh-72px-32px)] justify-center text-brand-black"
|
||||
class="grid grid-cols-3 gap-2 h-[calc(100vh-72px-32px)] justify-center"
|
||||
>
|
||||
<!-- 左側 -->
|
||||
<section class="grid grid-rows-12 gap-2">
|
||||
<!-- 機構照片 + 機構說明 + 核心資料 bars + chart -->
|
||||
<section
|
||||
class="row-span-10 bg-white/50 rounded-md shadow py-6 px-4 flex flex-col items-start gap-4"
|
||||
class="h-[740px] row-span-9 bg-white/50 rounded-md shadow py-6 px-4 flex flex-col items-start gap-4"
|
||||
>
|
||||
<div
|
||||
class="grid grid-cols-12 justify-center items-start gap-8 "
|
||||
class="w-full h-[400px] grid grid-cols-2 justify-center items-start gap-6 px-2"
|
||||
>
|
||||
<div
|
||||
class="col-span-5 grid grid-rows-12 justify-start items-start gap-6"
|
||||
class="w-full col-span-1 grid grid-rows-12 justify-start items-start gap-6"
|
||||
>
|
||||
<div class="h-[240px] row-span-6">
|
||||
<img
|
||||
src="/img/building_photo_headquarter.png"
|
||||
src="/img/building/headquarter.png"
|
||||
alt="機構照片"
|
||||
class="w-full h-full rounded-md object-cover"
|
||||
/>
|
||||
@ -28,61 +28,688 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- Progress bars -->
|
||||
<div class="col-span-6 flex flex-col gap-4">
|
||||
<div class="w-full col-span-1 flex flex-col gap-6">
|
||||
<ProgressBar
|
||||
label="現在住民/全立案床數"
|
||||
:current="240"
|
||||
:total="360"
|
||||
|
||||
/>
|
||||
<ProgressBar
|
||||
label="目前空床數/全立案床數"
|
||||
:current="120"
|
||||
:total="360"
|
||||
|
||||
|
||||
/>
|
||||
<ProgressBar
|
||||
label="今日住院/當月累積住院"
|
||||
:current="8"
|
||||
:total="50"
|
||||
|
||||
|
||||
/>
|
||||
<ProgressBar
|
||||
label="今日新人入住/當月累積入住"
|
||||
:current="12"
|
||||
:total="50"
|
||||
|
||||
/>
|
||||
<ProgressBar
|
||||
label="今日離院/累積離院"
|
||||
:current="8"
|
||||
:total="50"
|
||||
|
||||
/>
|
||||
<ProgressBar label="今日離院/累積離院" :current="8" :total="50" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-black">
|
||||
<div class="w-full h-[240px] flex justify-center items-center">
|
||||
<!-- chart -->
|
||||
<div ref="chartEl" class="w-full h-full"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 葷/素資料 -->
|
||||
<section class="row-span-2 grid grid-cols-2 gap-2">
|
||||
<div class="col-span-1 bg-white/50 rounded-md shadow p-3"></div>
|
||||
<div class="col-span-1 bg-white/50 rounded-md shadow p-3"></div>
|
||||
<section class="row-span-3 grid grid-cols-2 gap-2">
|
||||
<div class="col-span-1 bg-white/50 rounded-md shadow p-6">
|
||||
<div
|
||||
class="border border-brand-yellow rounded-md w-full h-full flex flex-col justify-start items-center gap-3 p-3"
|
||||
>
|
||||
<div class="relative text-brand-yellow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M13 2.05v3.03c3.39.49 6 3.39 6 6.92c0 .9-.18 1.75-.5 2.54l2.62 1.53c.56-1.24.88-2.62.88-4.07c0-5.18-3.95-9.45-9-9.95M12 19a7 7 0 0 1-7-7c0-3.53 2.61-6.43 6-6.92V2.05c-5.06.5-9 4.76-9 9.95a10 10 0 0 0 10 10c3.3 0 6.23-1.61 8.05-4.09l-2.6-1.53A6.89 6.89 0 0 1 12 19"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="w-9 h-9 flex justify-center items-center p-2 bg-brand-yellow text-brand-black absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full"
|
||||
>
|
||||
葷
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-6">
|
||||
<div class="flex flex-col justify-center items-start gap-3">
|
||||
<p>總數:100</p>
|
||||
<p>一般:90</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-start gap-3">
|
||||
<p>碎食:5</p>
|
||||
<p>管灌:5</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-1 bg-white/50 rounded-md shadow p-6">
|
||||
<div
|
||||
class="border border-brand-yellow rounded-md w-full h-full flex flex-col justify-start items-center gap-3 p-3"
|
||||
>
|
||||
<div class="relative text-brand-yellow">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M13 2.05v3.03c3.39.49 6 3.39 6 6.92c0 .9-.18 1.75-.5 2.54l2.62 1.53c.56-1.24.88-2.62.88-4.07c0-5.18-3.95-9.45-9-9.95M12 19a7 7 0 0 1-7-7c0-3.53 2.61-6.43 6-6.92V2.05c-5.06.5-9 4.76-9 9.95a10 10 0 0 0 10 10c3.3 0 6.23-1.61 8.05-4.09l-2.6-1.53A6.89 6.89 0 0 1 12 19"
|
||||
/>
|
||||
</svg>
|
||||
<p
|
||||
class="w-9 h-9 flex justify-center items-center p-2 bg-brand-yellow text-brand-black absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full"
|
||||
>
|
||||
素
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-6">
|
||||
<div class="flex flex-col justify-center items-start gap-3">
|
||||
<p>總數:100</p>
|
||||
<p>一般:90</p>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center items-start gap-3">
|
||||
<p>碎食:5</p>
|
||||
<p>管灌:5</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<!-- 中間 -->
|
||||
<section class="bg-white/50 rounded-md shadow p-3"></section>
|
||||
<section class="bg-white/50 rounded-md shadow p-3">
|
||||
<div ref="mapEl" class="w-full h-full rounded-md overflow-hidden"></div>
|
||||
</section>
|
||||
<!-- 右側 -->
|
||||
<section class="bg-white/50 rounded-md shadow p-3"></section>
|
||||
<section class="bg-white/50 rounded-md shadow p-3 flex flex-col gap-2">
|
||||
<!-- 表格 今日活動 -->
|
||||
<section class="rounded-md shadow p-3 flex flex-col min-h-0 gap-3">
|
||||
<h3 class="text-xl font-bold">今日活動</h3>
|
||||
<!-- 可捲動內容區(水平) -->
|
||||
<div class="flex flex-col gap-4 mb-6">
|
||||
<div class="w-full overflow-x-auto overflow-y-auto">
|
||||
<table class="table whitespace-nowrap">
|
||||
<thead
|
||||
class="bg-brand-gray-light text-brand-black sticky top-0 z-[1]"
|
||||
>
|
||||
<tr>
|
||||
<th class="w-[120px]">時間</th>
|
||||
<th class="w-[160px]">機構</th>
|
||||
<th class="w-[160px]">活動名稱</th>
|
||||
<th class="w-[120px] text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in pagedRows"
|
||||
:key="row.id"
|
||||
class="transition-colors duration-150 hover:bg-brand-gray-light focus-visible:bg-gray-100/70"
|
||||
>
|
||||
<td>{{ row.time }}</td>
|
||||
<td>{{ row.org }}</td>
|
||||
<td class="truncate">{{ row.title }}</td>
|
||||
<td class="text-center">
|
||||
<button
|
||||
class="btn btn-link btn-xs px-0 text-brand-purple-dark !no-underline hover:!no-underline hover:opacity-80 focus-visible:!no-underline"
|
||||
@click="viewDetail(row)"
|
||||
>
|
||||
查看詳情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分頁 -->
|
||||
<div class="mt-3 flex items-center justify-between px-3">
|
||||
<span class="text-sm text-gray-500">
|
||||
共 {{ total }} 筆,每頁 {{ pageSize }} 筆
|
||||
</span>
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一頁
|
||||
</button>
|
||||
|
||||
<span class="px-2 text-sm tabular-nums text-brand-purple-dark">
|
||||
{{ currentPage }} / {{ totalPages }}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一頁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 表格 今日異常事件 -->
|
||||
<section class="rounded-md shadow p-3 flex flex-col min-h-0 gap-3">
|
||||
<h3 class="text-xl font-bold">今日異常事件</h3>
|
||||
<!-- 可捲動內容區(水平) -->
|
||||
<div class="flex flex-col gap-4 mb-6">
|
||||
<div class="w-full overflow-x-auto overflow-y-auto">
|
||||
<table class="table whitespace-nowrap">
|
||||
<thead
|
||||
class="bg-brand-gray-light text-brand-black sticky top-0 z-[1]"
|
||||
>
|
||||
<tr>
|
||||
<th class="w-[100px]">時間</th>
|
||||
<th class="w-[160px]">機構</th>
|
||||
<th class="w-[180px]">事件</th>
|
||||
|
||||
<th class="w-[120px] text-center">查看詳情</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in pagedIncidentRows"
|
||||
:key="row.id"
|
||||
class="transition-colors duration-150 hover:bg-brand-gray-light focus-visible:bg-gray-100/70"
|
||||
>
|
||||
<td>{{ row.time }}</td>
|
||||
<td class="truncate" :title="row.org">{{ row.org }}</td>
|
||||
<td class="truncate" :title="row.event">{{ row.event }}</td>
|
||||
|
||||
<td class="text-center">
|
||||
<button
|
||||
class="btn btn-link btn-xs px-0 text-brand-purple-dark !no-underline hover:!no-underline hover:opacity-80 focus-visible:!no-underline"
|
||||
@click="viewDetailIncident(row)"
|
||||
>
|
||||
查看詳情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分頁(固定在底) -->
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<span class="ml-3 text-sm text-gray-500">
|
||||
共 {{ incidentTotal }} 筆,每頁 {{ incidentPageSize }} 筆
|
||||
</span>
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一頁
|
||||
</button>
|
||||
|
||||
<span class="px-2 text-sm tabular-nums text-brand-purple-dark">
|
||||
{{ currentPage }} / {{ totalPages }}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一頁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 表格 今日派車總表 -->
|
||||
<section class="rounded-md shadow p-3 flex flex-col min-h-0 gap-3">
|
||||
<h3 class="text-xl font-bold">今日派車總表</h3>
|
||||
|
||||
<!-- 可捲動內容區(水平) -->
|
||||
<div class="flex flex-col gap-4 mb-6">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<table class="table whitespace-nowrap">
|
||||
<thead
|
||||
class="bg-brand-gray-light text-brand-black sticky top-0 z-[1]"
|
||||
>
|
||||
<tr>
|
||||
<th class="w-[120px]">時間</th>
|
||||
<th class="w-[160px]">機構</th>
|
||||
<th class="w-[120px]">聯絡人</th>
|
||||
<th class="w-[120px] text-center">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in pagedDispatchRows"
|
||||
:key="row.id"
|
||||
class="transition-colors duration-150 hover:bg-brand-gray-light focus-visible:bg-gray-100/70"
|
||||
>
|
||||
<td>{{ row.time }}</td>
|
||||
<td class="truncate" :title="row.org">{{ row.org }}</td>
|
||||
<td class="truncate" :title="row.contact">
|
||||
{{ row.contact }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button
|
||||
class="btn btn-link btn-xs px-0 text-brand-purple-dark !no-underline hover:!no-underline hover:opacity-80 focus-visible:!no-underline"
|
||||
@click="viewDispatchDetail(row)"
|
||||
>
|
||||
查看詳情
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分頁(固定在底) -->
|
||||
<div class="mt-3 flex items-center justify-between">
|
||||
<span class="ml-3 text-sm text-gray-500">
|
||||
共 {{ dispatchTotal }} 筆,每頁 {{ dispatchPageSize }} 筆
|
||||
</span>
|
||||
<div class="inline-flex items-center">
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === 1"
|
||||
@click="currentPage--"
|
||||
>
|
||||
上一頁
|
||||
</button>
|
||||
|
||||
<span class="px-2 text-sm tabular-nums text-brand-purple-dark">
|
||||
{{ currentPage }} / {{ totalPages }}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline disabled:!opacity-100 disabled:!text-gray-500 disabled:!border-gray-300 disabled:cursor-not-allowed"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="currentPage++"
|
||||
>
|
||||
下一頁
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ProgressBar from "./ProgressBar.vue";
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { brand } from "@/styles/palette";
|
||||
|
||||
// 產生最近 7 天的標籤(M/D)
|
||||
function last7DaysLabels() {
|
||||
const labels = [];
|
||||
const now = new Date();
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const d = new Date(now);
|
||||
d.setDate(now.getDate() - i);
|
||||
labels.push(`${d.getMonth() + 1}/${d.getDate()}`);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
// Demo 資料(0~100,可換成你的實際資料)
|
||||
const dataA = [15, 58, 25, 75, 90, 38, 76];
|
||||
const dataB = [26, 78, 85, 32, 30, 52, 72];
|
||||
|
||||
const chartEl = ref(null);
|
||||
let chart;
|
||||
|
||||
onMounted(() => {
|
||||
if (!chartEl.value) return;
|
||||
chart = echarts.init(chartEl.value);
|
||||
|
||||
const labels = last7DaysLabels();
|
||||
|
||||
chart.setOption({
|
||||
color: [brand.green, brand.purple], // 藍、綠(可換你的品牌色)
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: { type: "line" },
|
||||
confine: true,
|
||||
},
|
||||
legend: {
|
||||
bottom: 8,
|
||||
icon: "circle",
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 24,
|
||||
textStyle: { color: brand.gray },
|
||||
data: ["現在住民", "全立案人數"],
|
||||
},
|
||||
grid: { left: 36, right: 16, top: 16, bottom: 48, containLabel: true },
|
||||
xAxis: {
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: labels,
|
||||
axisLine: { lineStyle: { color: brand.gray } },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: brand.gray },
|
||||
splitLine: { show: false },
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
min: 0,
|
||||
max: 100,
|
||||
interval: 20,
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
axisLabel: { color: brand.gray },
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: { color: brand.grayLight },
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "現在住民",
|
||||
type: "line",
|
||||
data: dataA,
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
lineStyle: { width: 2 },
|
||||
itemStyle: { opacity: 1 },
|
||||
},
|
||||
{
|
||||
name: "全立案人數",
|
||||
type: "line",
|
||||
data: dataB,
|
||||
symbol: "circle",
|
||||
symbolSize: 6,
|
||||
lineStyle: { width: 2 },
|
||||
itemStyle: { opacity: 1 },
|
||||
},
|
||||
],
|
||||
animationDuration: 500,
|
||||
});
|
||||
|
||||
// RWD
|
||||
const onResize = () => chart?.resize();
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
chart.__onResize = onResize;
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (chart) {
|
||||
window.removeEventListener("resize", chart.__onResize);
|
||||
chart.dispose();
|
||||
chart = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 以 BASE_URL 為前綴,避免根目錄/子目錄路徑差異
|
||||
const base = import.meta.env.BASE_URL;
|
||||
const iconBase = `${base}img/leaflet/`;
|
||||
|
||||
const defaultIcon = L.icon({
|
||||
iconUrl: `${iconBase}marker-icon.png`,
|
||||
iconRetinaUrl: `${iconBase}marker-icon-2x.png`,
|
||||
shadowUrl: `${iconBase}marker-shadow.png`,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41],
|
||||
});
|
||||
|
||||
L.Marker.prototype.options.icon = defaultIcon;
|
||||
|
||||
const mapEl = ref(null);
|
||||
let map = null;
|
||||
|
||||
// 2) 高雄「緊」與「鬆」的範圍
|
||||
const KAOHSIUNG_BOUNDS_TIGHT = L.latLngBounds(
|
||||
[22.45, 120.15], // SW
|
||||
[22.95, 120.55] // NE
|
||||
);
|
||||
const KAOHSIUNG_BOUNDS_LOOSE = L.latLngBounds(
|
||||
[22.35, 120.0], // SW
|
||||
[23.05, 120.75] // NE
|
||||
);
|
||||
|
||||
// 3) 六個據點(固定座標,無需再發 fetch)
|
||||
const LOCATIONS = [
|
||||
{
|
||||
name: "崇恩護理之家",
|
||||
addr: "高雄市楠梓區立仁街131、133號",
|
||||
lat: 22.7409648895,
|
||||
lng: 120.3354644775,
|
||||
},
|
||||
{
|
||||
name: "育祐護理之家",
|
||||
addr: "高雄市楠梓區常德路317巷9弄27號",
|
||||
lat: 22.7366924286,
|
||||
lng: 120.3363342285,
|
||||
},
|
||||
{
|
||||
name: "崇祐護理之家",
|
||||
addr: "高雄市楠梓區宏昌街135、137號",
|
||||
lat: 22.717588,
|
||||
lng: 120.29406,
|
||||
},
|
||||
{
|
||||
name: "崇智護理之家",
|
||||
addr: "高雄市左營區民族一路980號",
|
||||
lat: 22.678054,
|
||||
lng: 120.3192,
|
||||
},
|
||||
{
|
||||
name: "護祐護理之家",
|
||||
addr: "高雄市三民區黃興路336號",
|
||||
lat: 22.651488,
|
||||
lng: 120.33731,
|
||||
},
|
||||
{
|
||||
name: "傳祐長照中心",
|
||||
addr: "高雄市小港區沿海一路377號",
|
||||
lat: 22.5644512177,
|
||||
lng: 120.3544540405,
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
if (!mapEl.value) return;
|
||||
|
||||
map = L.map(mapEl.value, {
|
||||
center: [22.6273, 120.3014],
|
||||
zoom: 13,
|
||||
minZoom: 12,
|
||||
maxZoom: 20,
|
||||
zoomSnap: 0.25,
|
||||
zoomDelta: 0.25,
|
||||
maxBounds: KAOHSIUNG_BOUNDS_LOOSE,
|
||||
maxBoundsViscosity: 0.8,
|
||||
preferCanvas: true,
|
||||
});
|
||||
|
||||
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
maxZoom: 20,
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
}).addTo(map);
|
||||
|
||||
const ICON_H = defaultIcon.options.iconSize?.[1] ?? 41; // marker 高度
|
||||
const TIP_GAP = 8; // pin 與 tooltip 的縫隙
|
||||
|
||||
// 建立 featureGroup 以便一次調整視野
|
||||
const group = L.featureGroup();
|
||||
LOCATIONS.forEach((p) => {
|
||||
L.marker([p.lat, p.lng])
|
||||
.addTo(group)
|
||||
.bindTooltip(
|
||||
`<div class="tip p-2">
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<!-- 第一列:icon + 名稱-->
|
||||
<div class="inline-flex justify-start items-center text-brand-purple-dark font-noto gap-1">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M5 19v-8.692q0-.384.172-.727t.474-.565l5.385-4.078q.423-.323.966-.323t.972.323l5.385 4.077q.303.222.474.566q.172.343.172.727V19q0 .402-.299.701T18 20h-3.384q-.344 0-.576-.232q-.232-.233-.232-.576v-4.769q0-.343-.232-.575q-.233-.233-.576-.233h-2q-.343 0-.575.233q-.233.232-.233.575v4.77q0 .343-.232.575T9.385 20H6q-.402 0-.701-.299T5 19"/>
|
||||
</svg>
|
||||
</span>
|
||||
<strong class="text-[14px]">${p.name}</strong>
|
||||
</div>
|
||||
|
||||
<!-- 第二列:統計卡群 -->
|
||||
<div class="flex justify-center items-center gap-1">
|
||||
<div class="flex flex-col justify-center items-center gap-1 border p-2 rounded-md">
|
||||
<div class="text-[16px]"><strong>住民</strong></div>
|
||||
<div class="font-nats text-xl text-brand-purple-dark"><p>36 / 49</p></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center items-center gap-1 border p-2 rounded-md">
|
||||
<div class="text-[16px]"><strong>空床</strong></div>
|
||||
<div class="font-nats text-xl text-brand-purple-dark"><p>12 / 49</p></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center items-center gap-1 border p-2 rounded-md">
|
||||
<div class="text-[16px]"><strong>住院</strong></div>
|
||||
<div class="font-nats text-xl text-brand-purple-dark"><p>1 / 20</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
{
|
||||
permanent: true, // 常駐顯示
|
||||
direction: "top", // 顯示在 pin 上方
|
||||
offset: [0, -(ICON_H + TIP_GAP)],
|
||||
opacity: 0.9,
|
||||
}
|
||||
);
|
||||
});
|
||||
group.addTo(map);
|
||||
|
||||
const bounds = group.getBounds().pad(0.06);
|
||||
map.fitBounds(bounds, {
|
||||
paddingTopLeft: [24, 140],
|
||||
paddingBottomRight: [24, 24],
|
||||
maxZoom: 15,
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (map) {
|
||||
map.remove();
|
||||
map = null;
|
||||
}
|
||||
});
|
||||
|
||||
const rows = ref([
|
||||
{
|
||||
id: 1,
|
||||
time: "10:00",
|
||||
org: "崇祐護理之家",
|
||||
title: "全人評估相關課程",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
time: "14:00",
|
||||
org: "崇祐護理之家",
|
||||
title: "CPR教育訓練",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
time: "20:00",
|
||||
org: "傳祐長照中心",
|
||||
title: "消防夜間演練",
|
||||
},
|
||||
]);
|
||||
|
||||
// 分頁參數
|
||||
const pageSize = 10;
|
||||
const currentPage = ref(1);
|
||||
const total = computed(() => rows.value.length);
|
||||
const totalPages = computed(() =>
|
||||
Math.max(1, Math.ceil(total.value / pageSize))
|
||||
);
|
||||
|
||||
const pagedRows = computed(() => {
|
||||
const start = (currentPage.value - 1) * pageSize;
|
||||
return rows.value.slice(start, start + pageSize);
|
||||
});
|
||||
|
||||
// 操作:查看詳情
|
||||
function viewDetail(row) {
|
||||
console.log("查看詳情:", row);
|
||||
}
|
||||
|
||||
const incidentRows = ref([
|
||||
{
|
||||
id: 1,
|
||||
time: "10:00",
|
||||
org: "崇恩護理之家",
|
||||
event: "跌倒事件",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
time: "12:00",
|
||||
event: "延遲給藥",
|
||||
org: "育祐護理之家",
|
||||
},
|
||||
]);
|
||||
|
||||
// 分頁
|
||||
const incidentPageSize = 10;
|
||||
const incidentPage = ref(1);
|
||||
const incidentTotal = computed(() => incidentRows.value.length);
|
||||
const incidentTotalPages = computed(() =>
|
||||
Math.max(1, Math.ceil(incidentTotal.value / incidentPageSize))
|
||||
);
|
||||
const pagedIncidentRows = computed(() => {
|
||||
const start = (incidentPage.value - 1) * incidentPageSize;
|
||||
return incidentRows.value.slice(start, start + incidentPageSize);
|
||||
});
|
||||
|
||||
function viewDetailIncident(row) {
|
||||
console.log("事件詳情:", row);
|
||||
}
|
||||
|
||||
// 今日派車總表
|
||||
const dispatchRows = ref([
|
||||
{ id: 1, time: "09:30", org: "崇恩護理之家", contact: "黃國毅" },
|
||||
{ id: 2, time: "10:30", org: "育祐護理之家", contact: "李佩怡" },
|
||||
{ id: 3, time: "11:30", org: "崇智護理之家", contact: "陳筱安" },
|
||||
]);
|
||||
|
||||
// 分頁參數
|
||||
const dispatchPageSize = 10;
|
||||
const dispatchPage = ref(1);
|
||||
const dispatchTotal = computed(() => dispatchRows.value.length);
|
||||
const dispatchTotalPages = computed(() =>
|
||||
Math.max(1, Math.ceil(dispatchTotal.value / dispatchPageSize))
|
||||
);
|
||||
const pagedDispatchRows = computed(() => {
|
||||
const start = (dispatchPage.value - 1) * dispatchPageSize;
|
||||
return dispatchRows.value.slice(start, start + dispatchPageSize);
|
||||
});
|
||||
|
||||
// 操作:查看詳情
|
||||
function viewDispatchDetail(row) {
|
||||
console.log("派車詳情:", row);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style scoped></style>
|
||||
|
@ -157,3 +157,10 @@ body {
|
||||
overflow-x: hidden; /* 防止偶發橫向卷軸 */
|
||||
font-family: "Noto Sans TC", sans-serif;
|
||||
}
|
||||
|
||||
/* 降低 Leaflet 控制層級:預設是 1000 */
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
z-index: 400 !important; /* 低於你的 Nav,但仍高於地圖瓦片 */
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
export const brand = {
|
||||
green: "#34D5C8",
|
||||
greenLight: "#C4FBE5",
|
||||
greenDark:"#0CA99C",
|
||||
red: "#FF8678",
|
||||
purple: "#A5BEFF",
|
||||
purpleLight: "#D5E1FF",
|
||||
@ -10,5 +11,6 @@ export const brand = {
|
||||
yellow: "#E1F391",
|
||||
black: "#424242",
|
||||
gray: "#828282",
|
||||
grayLight: "#F0F0F0",
|
||||
grayLight: "#E9E9E9",
|
||||
grayDark: "#D2D2D2",
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ module.exports = {
|
||||
},
|
||||
colors: {
|
||||
brand: {
|
||||
green: { DEFAULT: "#34D5C8", light: "#C4FBE5" },
|
||||
green: { DEFAULT: "#34D5C8", light: "#C4FBE5", dark:"#0CA99C" },
|
||||
red: "#FF8678",
|
||||
purple: {
|
||||
DEFAULT: "#A5BEFF",
|
||||
@ -53,9 +53,9 @@ module.exports = {
|
||||
},
|
||||
black: "#424242",
|
||||
gray: {
|
||||
light: "#F0F0F0",
|
||||
deepLight:"#D2D2D2",
|
||||
DEFAULT: "#828282",
|
||||
light: "#E9E9E9",
|
||||
dark: "#D2D2D2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user