Files
skt-vuetify-templates/src/components/PageIndex.vue
T
skytek_xinliang 3b1ac6df92 fix: 樣式修正
2026-04-29 15:27:13 +08:00

235 lines
7.5 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
<v-container class="pa-0" fluid>
<div class="d-flex flex-column ga-5 pt-1 pb-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="props.newsItems" :items-per-page="-1">
<!--
Vuetify 會把原始資料包進 wrapper
這裡統一解包可避免模板層散落型別判斷
-->
<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="emit('news', 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="emit('message-center')"
>
<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 props.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="emit('quick', 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>
<!--
這個 dialog 只做消息內容呈現
開關狀態仍交給 view 管理避免頁面元件自行持有流程狀態
-->
<v-dialog
:model-value="props.isNewsDialogOpen"
max-width="640"
@update:model-value="emit('update:isNewsDialogOpen', $event)"
>
<v-card v-if="props.selectedNews">
<v-card-title class="text-h6 font-weight-bold bg-primary-variant pa-4">
{{ props.selectedNews.title }}
</v-card-title>
<v-card-subtitle class="text-body-2 pt-4 text-medium-emphasis">
{{ props.selectedNews.month }} {{ props.selectedNews.date }} ·
{{ props.selectedNews.dept }} · {{ props.selectedNews.views }} 次瀏覽
</v-card-subtitle>
<v-card-text class="pt-4">
{{ props.selectedNews.desc }}
</v-card-text>
<v-card-actions class="justify-end">
<v-btn color="primary" variant="text" @click="emit('update:isNewsDialogOpen', false)">
關閉
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup lang="ts">
import { mdiEyeOutline, mdiFolderOutline } from '@mdi/js'
interface NewsItem {
id: number
date: string
month: string
title: string
desc: string
dept: string
views: string
isNew: boolean
}
interface QuickItem {
icon: string
title: string
}
const props = defineProps<{
newsItems: NewsItem[]
quickItems: QuickItem[]
selectedNews: NewsItem | null
isNewsDialogOpen: boolean
}>()
const emit = defineEmits<{
news: [item: NewsItem]
'message-center': []
quick: [item: QuickItem]
'update:isNewsDialogOpen': [value: boolean]
}>()
// Vuetify 的 iterator 會回傳包裝後的資料,這裡集中解包可維持模板單純。
function resolveNewsItem(wrapped: unknown): NewsItem {
if (wrapped && typeof wrapped === 'object' && 'raw' in wrapped) {
return (wrapped as { raw: NewsItem }).raw
}
return wrapped as NewsItem
}
</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>