ec-admin/src/pages/index.vue
liuyun c96151d2ae refactor: convert raw CSS to Tailwind @apply + search collapseCount=3
- ty-search-form 折叠默认数 2 → 3
- login/register/forgot/home/ty-header/ty-sidebar/ty-mixbar/ty-tabs/ty-footer/ty-setting-panel/icon/default 布局裸 CSS 转 @apply
- 修复背景色工具类误写(bg-base→bg-bg-base、bg-page→bg-bg-page)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 13:18:45 +08:00

172 lines
4.3 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>
<div class="home">
<div class="home__banner">
<div class="home__banner-decor" aria-hidden="true">
<span class="home__banner-decor-1"></span>
<span class="home__banner-decor-2"></span>
</div>
<div class="home__banner-inner">
<h1 class="home__greeting">{{ greeting }},{{ username }}</h1>
<p class="home__datetime">{{ datetime }}</p>
<p class="home__subtitle">欢迎使用 {{ title }}</p>
</div>
</div>
<div class="home__modules">
<el-card v-for="m in topMenus" :key="m.code" class="home__module" shadow="never">
<div class="home__module-head">
<icon type="iconify" :icon="m.icon ?? 'mdi:folder'" class="home__module-icon" />
<span class="home__module-title">{{ m.name }}</span>
</div>
<div class="home__module-links">
<el-link
v-for="c in m.children"
:key="c.code"
class="home__module-link"
:underline="false"
@click="router.push(c.path ?? c.code)"
>
{{ c.name }}
</el-link>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/modules/auth'
import { useMenuStore } from '@/stores/modules/menu'
import { defaultLoginConfig } from '@/config/login'
const router = useRouter()
const auth = useAuthStore()
const menuStore = useMenuStore()
const title = defaultLoginConfig.title
const username = computed(() => decodeUsername(auth.accessToken))
const greeting = computed(() => {
const h = new Date().getHours()
if (h < 6) return '凌晨好'
if (h < 12) return '早上好'
if (h < 18) return '下午好'
return '晚上好'
})
// 实时时间
const now = ref(new Date())
const timer = setInterval(() => {
now.value = new Date()
}, 1000)
onBeforeUnmount(() => clearInterval(timer))
const datetime = computed(() => {
const d = now.value
const pad = (n: number) => String(n).padStart(2, '0')
const week = ['日', '一', '二', '三', '四', '五', '六'][d.getDay()]
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} 星期${week} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
})
/** 顶级菜单(有子菜单的,作为快捷入口)。 */
const topMenus = computed(() => menuStore.menus.filter((m) => m.children?.length))
function decodeUsername(token: string | null): string {
if (!token) return ''
try {
const payload = token.split('.')[1]
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(payload.length / 4) * 4, '=')
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0))
const json = JSON.parse(new TextDecoder().decode(bytes))
return (json.username as string) ?? ''
} catch {
return ''
}
}
</script>
<style scoped lang="scss">
.home {
@apply flex flex-col gap-md;
}
.home__banner {
@apply relative overflow-hidden rounded-lg text-white;
background: linear-gradient(135deg, var(--wm-color-primary), var(--wm-color-primary-dark-2));
}
.home__banner-decor {
@apply absolute inset-0;
}
.home__banner-decor-1 {
@apply absolute rounded-full;
width: 260px;
height: 260px;
top: -100px;
right: -60px;
background: rgba(255, 255, 255, 0.08);
}
.home__banner-decor-2 {
@apply absolute rounded-full;
width: 160px;
height: 160px;
bottom: -60px;
right: 120px;
background: rgba(255, 255, 255, 0.05);
}
.home__banner-inner {
@apply relative px-xl py-2xl;
}
.home__greeting {
@apply text-2xl font-semibold;
}
.home__datetime {
@apply mt-sm text-base opacity-90;
}
.home__subtitle {
@apply mt-md text-sm opacity-75;
}
.home__modules {
@apply grid grid-cols-1 gap-md md:grid-cols-2 xl:grid-cols-3;
}
.home__module {
@apply transition-shadow hover:shadow-md;
:deep(.el-card__body) {
@apply p-lg;
}
}
.home__module-head {
@apply flex items-center gap-sm;
}
.home__module-icon {
@apply text-2xl;
color: var(--wm-color-primary);
}
.home__module-title {
@apply text-base font-medium text-text-primary;
}
.home__module-links {
@apply mt-md flex flex-wrap gap-x-lg gap-y-sm;
}
.home__module-link {
@apply text-sm text-text-regular;
}
</style>