refactor: remove unused dashboard components and views

This commit is contained in:
skytek_xinliang
2026-03-30 10:08:24 +08:00
parent 8ef6873abc
commit 742d5cafcd
33 changed files with 257 additions and 3207 deletions
-88
View File
@@ -1,88 +0,0 @@
<template>
<SKAnalysis
:bar-data="barData"
:chart1-title="chart1Title"
:chart2-title="chart2Title"
:chart3-title="chart3Title"
:pie1-data="pie1Data"
:pie2-data="pie2Data"
:stats="stats"
:trend-data="trendData"
:trend-title="trendTitle"
/>
</template>
<script setup lang="ts">
import {
mdiAccountSchool,
mdiBookOpenPageVariant,
mdiChartPie,
mdiCheckDecagram,
mdiCloudDownload,
} from '@mdi/js'
import { ref } from 'vue'
import SKAnalysis from '@/components/SKAnalysis.vue'
// Mock Data
const stats = ref([
{
title: '學生總數',
value: '2,580',
icon: mdiAccountSchool,
color: 'primary',
label: '總學籍人數',
total: '120,000',
},
{
title: '平台訪問',
value: '20,000',
icon: mdiChartPie,
color: 'error',
label: '今日訪問量',
total: '500,000',
},
{
title: '教材下載',
value: '8,000',
icon: mdiCloudDownload,
color: 'warning',
label: '本月下載次數',
total: '120,000',
},
{
title: '圖書館借閱',
value: '5,000',
icon: mdiBookOpenPageVariant,
color: 'success',
label: '總借閱量',
total: '50,000',
},
])
const trendTitle = ref('數位學習平台流量')
const trendData = ref([0, 2, 5, 9, 5, 10, 3, 5, 0, 0, 1, 8, 2, 9, 0])
const chart1Title = ref('學生核心素養指標 (平均)')
const barData = ref([
{ label: '道德實踐 (Moral)', value: 85, color: 'info' },
{ label: '智力發展 (Intellectual)', value: 72, color: 'success' },
{ label: '體育健康 (Physical)', value: 90, color: 'warning' },
{ label: '群育合作 (Social)', value: 65, color: 'error' },
{ label: '美感教育 (Aesthetic)', value: 80, color: 'primary' },
])
const chart2Title = ref('訪問裝置來源')
const pie1Data = ref({
value: 75,
label: '行動裝置',
color: 'purple-accent-2',
})
const chart3Title = ref('本學期及格率')
const pie2Data = ref({
value: 92,
label: '全校平均',
color: 'teal-lighten-1',
icon: mdiCheckDecagram,
})
</script>
-132
View File
@@ -1,132 +0,0 @@
<template>
<SKDashboard
:announcements="announcements"
:applications="applications"
:greeting-title="greetingTitle"
:quick-navs="quickNavs"
:todos="todos"
:user-avatar="userAvatar"
/>
</template>
<script setup lang="ts">
import {
mdiAccountGroup,
mdiBookOpenVariant,
mdiCalendarCheck,
mdiChartBar,
mdiCog,
mdiHammerWrench,
mdiHome,
mdiLayers,
mdiLock,
mdiMonitorShimmer,
mdiSchool,
mdiViewDashboard,
} from '@mdi/js'
import { ref } from 'vue'
import SKDashboard from '@/components/SKDashboard.vue'
const userAvatar = ref(
'https://avataaars.io/?avatarStyle=Circle&topType=ShortHairShortFlat&accessoriesType=Sunglasses&hairColor=Black&facialHairType=Blank&clotheType=BlazerShirt&clotheColor=Blue01&eyeType=Happy&eyebrowType=Default&mouthType=Smile&skinColor=Light'
)
const greetingTitle = ref('早安,王校長,開始您一天的工作吧!')
const applications = ref([
{
name: '校務行政系統',
icon: mdiSchool,
desc: '全校教職員工生學籍資料、人事資料、財產管理等核心系統入口。',
group: '行政組',
date: '2025-01-05',
color: 'primary',
},
{
name: '數位學習平台',
icon: mdiMonitorShimmer,
desc: '提供線上課程、作業繳交、測驗評量與師生互動討論功能。',
group: '教學組',
date: '2025-01-02',
color: 'success',
},
{
name: '圖書館系統',
icon: mdiBookOpenVariant,
desc: '館藏查詢、圖書借閱、還書預約與電子書資源整合平台。',
group: '圖書館',
date: '2024-12-28',
color: 'warning',
},
{
name: '學生請假系統',
icon: mdiCalendarCheck,
desc: '學生線上請假申請、導師審核、生輔組備查流程電子化。',
group: '學務處',
date: '2024-12-25',
color: 'error',
},
{
name: '報修系統',
icon: mdiHammerWrench,
desc: '校園設施設備故障通報、維修進度查詢與滿意度調查。',
group: '總務處',
date: '2024-12-20',
color: 'purple',
},
{
name: '會議室預約',
icon: mdiAccountGroup,
desc: '校內各大型會議室、視聽教室場地查詢與線上預約登記。',
group: '總務處',
date: '2024-12-15',
color: 'teal',
},
])
const announcements = ref([
{
title: '發布全校停課通知 (凱米颱風)',
author: '教務處',
time: '1 小時前',
avatarColor: 'error',
avatarSrc: null,
},
{
title: '發布 113 學年度行事曆',
author: '王校長',
time: '2 天前',
avatarColor: 'primary',
avatarSrc: userAvatar.value,
},
{
title: '回覆關於營養午餐的建議',
author: '總務主任',
time: '3 天前',
avatarColor: 'warning',
avatarSrc: null,
},
{
title: '更新校園防疫規定',
author: '衛生組長',
time: '1 週前',
avatarColor: 'success',
avatarSrc: null,
},
])
const quickNavs = ref([
{ title: '首頁', icon: mdiHome, color: 'primary' },
{ title: '控制台', icon: mdiViewDashboard, color: 'error' },
{ title: '組件', icon: mdiLayers, color: 'warning' },
{ title: '系統管理', icon: mdiCog, color: 'success' },
{ title: '權限', icon: mdiLock, color: 'purple' },
{ title: '圖表', icon: mdiChartBar, color: 'info' },
])
const todos = ref([
{ title: '審查期末考題', due: '今天 11:00', done: false },
{ title: '簽核採購申請單', due: '今天 14:00', done: false },
{ title: '校務會議', due: '明天 09:00', done: false },
{ title: '教學巡堂', due: '週五 10:00', done: true },
])
</script>
-188
View File
@@ -1,188 +0,0 @@
<template>
<SKDeptManagement
:items="deptItems"
:loading="loading"
:status-options="statusOptions"
@add-sub="onAddSub"
@create="onCreate"
@delete="onDelete"
@edit="onEdit"
/>
</template>
<script setup lang="ts">
import type { DeptItem } from '@/components/SKDeptManagement.vue'
import { ref } from 'vue'
import SKDeptManagement from '@/components/SKDeptManagement.vue'
const loading = ref(false)
const statusOptions = [
{ title: '已啟用', value: 1 },
{ title: '已禁用', value: 0 },
]
const deptItems = ref([
{
id: 1,
name: '校長室',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '負責統籌全校校務發展',
children: [],
},
{
id: 2,
name: '教務處',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '負責課程教學與學籍管理',
children: [
{
id: 21,
name: '教學組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '課程編排、教師授課與教學評鑑',
children: [],
},
{
id: 22,
name: '註冊組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '學生學籍管理、成績處理與升學輔導',
children: [],
},
{
id: 23,
name: '設備組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '教學設備採購與維護',
children: [],
},
],
},
{
id: 3,
name: '學務處',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '負責學生生活輔導與活動規劃',
children: [
{
id: 31,
name: '訓育組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '學生自治活動、社團活動與校慶規劃',
children: [],
},
{
id: 32,
name: '生輔組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '學生生活常規、出缺席管理與校園安全',
children: [],
},
{
id: 33,
name: '衛生組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '校園環境衛生、傳染病防治與健康促進',
children: [],
},
],
},
{
id: 4,
name: '總務處',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '負責校園營繕、財產管理與經費出納',
children: [
{
id: 41,
name: '庶務組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '校舍修繕、物品採購與工友管理',
children: [],
},
{
id: 42,
name: '出納組',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '現金出納、薪津發放與學費收繳',
children: [],
},
],
},
{
id: 5,
name: '輔導室',
status: 1,
createTime: '2023/08/01 09:00:00',
note: '負責學生心理輔導與生涯規劃',
children: [],
},
])
function addChildById(items: DeptItem[], parentId: string | number, child: DeptItem): boolean {
for (const current of items) {
if (!current) continue
if (current.id === parentId) {
if (!current.children) current.children = []
current.children.push(child)
return true
}
if (current.children && current.children.length > 0) {
const found = addChildById(current.children, parentId, child)
if (found) return true
}
}
return false
}
function updateTreeItemById(items: DeptItem[], updated: DeptItem): boolean {
for (let i = 0; i < items.length; i += 1) {
const current = items[i]
if (!current) continue
if (current.id === updated.id) {
items[i] = {
...current,
name: updated.name,
note: updated.note,
status: updated.status,
}
return true
}
if (current.children && current.children.length > 0) {
const found = updateTreeItemById(current.children, updated)
if (found) return true
}
}
return false
}
const onCreate = () => alert('Create Dept')
function onAddSub(parent: DeptItem, newItem: DeptItem) {
addChildById(deptItems.value, parent.id, newItem)
}
function onEdit(item: DeptItem) {
updateTreeItemById(deptItems.value, item)
}
function onDelete(item: DeptItem) {
if (confirm(`Delete ${item.name}?`)) {
alert('Deleted')
}
}
</script>
+11 -193
View File
@@ -1,141 +1,19 @@
<template>
<v-container class="pa-0" fluid>
<div class="d-flex flex-column ga-5 py-4 pr-2 pl-0">
<v-sheet
class="d-flex flex-column flex-sm-row align-start align-sm-center ga-4 pa-5 elevation-1"
color="surface"
>
<v-avatar color="primary" size="52" variant="tonal">
<span class="text-h5">👋</span>
</v-avatar>
<div>
<div class="text-h5 font-weight-bold">歡迎使用校務資訊系統</div>
<div class="text-body-2 text-medium-emphasis mt-1">
使用頂部搜尋框快速找到功能或從左側選單瀏覽所有系統模組
</div>
</div>
</v-sheet>
<section class="d-flex flex-column">
<div class="d-flex align-center ga-2 text-h6 font-weight-bold">📰 最新消息</div>
<!--
使用 v-data-iterator 讓消息列表具備一致的資料迭代結構
也方便未來需要加上排序 / 分頁時直接擴充
-->
<v-data-iterator class="mt-2" item-key="id" :items="newsItems" :items-per-page="-1">
<!--
v-data-iterator default slot 會提供包裝後的 items
這裡透過 resolveNewsItem 抽出原始資料再沿用原本的卡片排版
-->
<template #default="{ items }">
<v-row density="compact">
<v-col v-for="wrapped in items" :key="resolveNewsItem(wrapped).id" cols="12">
<v-card
class="news-item d-flex flex-column flex-sm-row ga-4 pa-4 bg-surface"
variant="outlined"
@click="handleNews(resolveNewsItem(wrapped))"
>
<v-sheet class="news-badge">
<div class="news-badge-date">{{ resolveNewsItem(wrapped).date }}</div>
<div class="news-badge-month">{{ resolveNewsItem(wrapped).month }}</div>
</v-sheet>
<div class="flex-grow-1">
<div class="d-flex flex-wrap align-center font-weight-bold">
{{ resolveNewsItem(wrapped).title }}
<v-chip
v-if="resolveNewsItem(wrapped).isNew"
class="ml-2"
color="primary"
size="x-small"
variant="flat"
>
NEW
</v-chip>
</div>
<div class="text-body-2 text-medium-emphasis mt-2">
{{ resolveNewsItem(wrapped).desc }}
</div>
<div class="d-flex ga-4 mt-3 text-caption text-medium-emphasis">
<div class="d-flex align-center ga-1">
<v-icon size="14" :icon="mdiFolderOutline" />
<span>{{ resolveNewsItem(wrapped).dept }}</span>
</div>
<div class="d-flex align-center ga-1">
<v-icon size="14" :icon="mdiEyeOutline" />
<span>{{ resolveNewsItem(wrapped).views }} 次瀏覽</span>
</div>
</div>
</div>
</v-card>
</v-col>
</v-row>
</template>
</v-data-iterator>
</section>
<v-card
class="d-flex align-center justify-space-between ga-3 px-5 py-4"
color="secondary"
rounded="xl"
variant="tonal"
@click="handleMessageCenter"
>
<div class="d-flex align-center ga-4">
<v-avatar color="secondary" size="44" variant="flat">
<span class="text-h6"></span>
</v-avatar>
<div>
<div class="text-subtitle-1 font-weight-bold">訊息中心</div>
<div class="text-body-2 font-weight-bold text-on-secondary">12 筆未讀</div>
</div>
</div>
<div class="text-body-2 font-weight-medium">查看全部 </div>
</v-card>
<section class="d-flex flex-column pb-4">
<div class="d-flex align-center ga-2 text-h6 font-weight-bold">🚀 快速存取</div>
<v-row class="mt-2" density="compact">
<v-col v-for="item in quickItems" :key="item.title" cols="6" md="2" sm="4">
<v-card
class="d-flex flex-column align-center ga-2 text-center py-4 px-2 quick-item"
variant="outlined"
@click="handleQuick(item)"
>
<div class="text-h5">{{ item.icon }}</div>
<div class="text-body-2 font-weight-medium">{{ item.title }}</div>
</v-card>
</v-col>
</v-row>
</section>
</div>
<!--
點擊消息後顯示的對話框
僅負責呈現內容不做任何延伸操作確保互動行為單純可預期
-->
<v-dialog v-model="isNewsDialogOpen" max-width="640">
<v-card v-if="selectedNews">
<v-card-title class="text-h6 font-weight-bold bg-primary-variant pa-4">
{{ selectedNews.title }}
</v-card-title>
<v-card-subtitle class="text-body-2 pt-4 text-medium-emphasis">
{{ selectedNews.month }} {{ selectedNews.date }} · {{ selectedNews.dept }} ·
{{ selectedNews.views }} 次瀏覽
</v-card-subtitle>
<v-card-text class="pt-4">
{{ selectedNews.desc }}
</v-card-text>
<v-card-actions class="justify-end">
<v-btn color="primary" variant="text" @click="isNewsDialogOpen = false">關閉</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
<PageIndex
:is-news-dialog-open="isNewsDialogOpen"
:news-items="newsItems"
:quick-items="quickItems"
:selected-news="selectedNews"
@message-center="handleMessageCenter"
@news="handleNews"
@quick="handleQuick"
@update:is-news-dialog-open="isNewsDialogOpen = $event"
/>
</template>
<script setup lang="ts">
import { mdiEyeOutline, mdiFolderOutline } from '@mdi/js'
import { ref } from 'vue'
import PageIndex from '@/components/PageIndex.vue'
import { useMessageStore } from '@/stores/messages'
import { useSnackbarStore } from '@/stores/snackbar'
@@ -189,15 +67,6 @@ const quickItems = [
const selectedNews = ref<NewsItem | null>(null)
const isNewsDialogOpen = ref(false)
// v-data-iterator 會包裝 items,這個方法用來安全地取回原始資料結構。
function resolveNewsItem(wrapped: unknown): NewsItem {
if (wrapped && typeof wrapped === 'object' && 'raw' in wrapped) {
return (wrapped as { raw: NewsItem }).raw
}
return wrapped as NewsItem
}
function handleNews(item: NewsItem) {
selectedNews.value = item
isNewsDialogOpen.value = true
@@ -212,54 +81,3 @@ function handleQuick(item: (typeof quickItems)[number]) {
snackbar.show({ message: `前往:${item.title}`, color: 'info' })
}
</script>
<style scoped>
.news-item {
cursor: pointer;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.news-item:hover {
transform: translateY(-2px);
box-shadow: 0 10px 24px rgba(var(--v-theme-on-surface), 0.12);
}
.news-badge {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgb(var(--v-theme-primary));
color: rgb(var(--v-theme-on-primary));
border-radius: 12px;
padding: 10px 6px;
min-height: 64px;
min-width: 64px;
}
.news-badge-date {
font-size: 20px;
font-weight: 700;
line-height: 1;
}
.news-badge-month {
font-size: 12px;
margin-top: 4px;
}
.quick-item {
display: flex;
cursor: pointer;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}
.quick-item:hover {
transform: translateY(-2px);
box-shadow: 0 10px 24px rgba(var(--v-theme-on-surface), 0.12);
}
</style>
-144
View File
@@ -1,144 +0,0 @@
<template>
<SKMenuManagement
:items="menuItems"
:loading="loading"
:permission-options="permissionOptions"
:status-options="statusOptions"
@edit="onEdit"
/>
</template>
<script setup lang="ts">
import type { MenuItem } from '@/components/SKMenuManagement.vue'
import { ref } from 'vue'
import SKMenuManagement from '@/components/SKMenuManagement.vue'
const loading = ref(false)
const statusOptions = [
{ title: '已啟用', value: 1 },
{ title: '已禁用', value: 0 },
]
const permissionOptions = ['管理員', '一級主管', '二級主管', '使用者']
const menuItems = ref([
{
id: 1,
title: '工作台',
permission: '使用者',
path: '/workspace',
component: '/dashboard/workspace/index',
status: 1,
children: [],
},
{
id: 2,
title: '系統管理',
permission: '管理員',
path: '/system',
component: 'LAYOUT',
status: 1,
isNew: true,
children: [
{
id: 21,
title: '菜單管理',
permission: '管理員',
path: '/system/menu',
component: '/system/menu/list',
status: 1,
children: [
{
id: 211,
title: '新增',
permission: '管理員',
status: 1,
},
{
id: 212,
title: '修改',
permission: '管理員',
status: 1,
},
{
id: 213,
title: '刪除',
permission: '管理員',
status: 1,
},
],
},
{
id: 22,
title: '部門管理',
permission: '一級主管',
path: '/system/dept',
component: '/system/dept/list',
status: 1,
children: [],
},
],
},
{
id: 3,
title: '項目',
permission: '二級主管',
path: '/vben-admin',
component: 'LAYOUT',
status: 1,
children: [
{
id: 31,
title: '文檔',
permission: '使用者',
path: '/vben-admin/document',
component: 'https://doc.vben.pro',
status: 1,
},
{
id: 32,
title: 'Ant Design Vue 版本',
permission: '使用者',
path: '/vben-admin/antdv',
component: 'https://ant.vben.pro',
status: 0,
},
],
},
{
id: 4,
title: '關於',
permission: '使用者',
path: '/about',
component: '_core/about/index',
status: 1,
children: [],
},
])
function updateTreeItemById(items: MenuItem[], updated: MenuItem): boolean {
for (let i = 0; i < items.length; i += 1) {
const current = items[i]
if (!current) continue
if (current.id === updated.id) {
items[i] = {
...current,
status: updated.status,
permission: updated.permission,
}
return true
}
if (current.children && current.children.length > 0) {
const found = updateTreeItemById(current.children, updated)
if (found) return true
}
}
return false
}
function onEdit(item: MenuItem) {
updateTreeItemById(menuItems.value, item)
}
</script>
-122
View File
@@ -1,122 +0,0 @@
<template>
<SKRoleManagement
:loading="loading"
:roles="roles"
@create="onCreate"
@delete="onDelete"
@edit="onEdit"
@reset="onReset"
@search="onSearch"
@update:status="onStatusUpdate"
/>
</template>
<script setup lang="ts">
import type { RoleItem } from '@/components/SKRoleManagement.vue'
import { ref } from 'vue'
import SKRoleManagement from '@/components/SKRoleManagement.vue'
const loading = ref(false)
const roles = ref([
{
name: 'Car',
id: '2216c0ab-9d0e-4f6e-a7d6-12...',
status: false,
note: 'Somnus careo ultio caste vix adversus textilis vaco defero coniuratio.',
createTime: '2022/05/13 01:03:31',
},
{
name: 'Chair',
id: '2625810b-639f-4213-944e-ae...',
status: false,
note: 'Ulterius tristis voluptatum demergo.',
createTime: '2024/05/30 21:04:49',
},
{
name: 'Shoes',
id: 'c19c2b27-f9c9-46b8-93db-11f...',
status: true,
note: 'Illum delinquo texo tumultus perferendis debilito.',
createTime: '2024/07/18 06:21:55',
},
{
name: 'Cheese',
id: '7d1053ac-acc1-405f-8c66-235...',
status: true,
note: 'Acidus vobis coruscus.',
createTime: '2024/07/03 07:44:10',
},
{
name: 'Gloves',
id: '9bb9ed44-dfcd-11b8-bcb1-d0...',
status: true,
note: 'Vivo caelum quo caveo valetudo.',
createTime: '2024/10/05 12:00:17',
},
{
name: 'Shoes',
id: 'cdb0c47a-284e-4ca3-b434-e8...',
status: true,
note: 'Veritas adflicto temperantia.',
createTime: '2024/11/20 16:35:09',
},
{
name: 'Computer',
id: 'db9d1509-7247-4a38-9bd9-b...',
status: true,
note: 'Viridis titulus somnus voluptate voluptate aspicio sophismata ansor th...',
createTime: '2023/02/21 06:16:25',
},
{
name: 'Cheese',
id: '1beb5ce6-11f2-490f-9549-101...',
status: true,
note: 'Eveniet amicitia bestia supra.',
createTime: '2022/02/19 12:51:41',
},
{
name: 'Towels',
id: 'bd9c74bb-fc3a-4a7a-8b3f-55f...',
status: true,
note: 'Dapifer aliquam amoveo vitium ubi repellendus tactus bardus crepusc...',
createTime: '2023/02/16 20:09:27',
},
])
function onSearch(params: Record<string, unknown>) {
console.log('Search:', params)
loading.value = true
setTimeout(() => {
loading.value = false
alert('Search Triggered! Check Console.')
}, 500)
}
function onReset() {
console.log('Reset')
}
function onCreate() {
console.log('Create Role')
alert('Create Role Clicked')
}
function onEdit(item: RoleItem) {
console.log('Edit:', item)
alert(`Edit ${item.name}`)
}
function onDelete(item: RoleItem) {
console.log('Delete:', item)
if (confirm(`Are you sure you want to delete ${item.name}?`)) {
roles.value = roles.value.filter((r) => r.id !== item.id)
}
}
function onStatusUpdate(item: RoleItem, newVal: boolean) {
// In a real app, API call here
item.status = newVal
console.log('Status Update:', item.name, newVal)
}
</script>