feat: home welcome page
- 登录后首页改为欢迎页:问候语 + 用户名 + 实时时间 + 菜单驱动的快捷入口卡片 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7d0088dd10
commit
8742932f21
@ -1,61 +1,174 @@
|
||||
<!--
|
||||
@name: index.vue
|
||||
@description: 首页
|
||||
@author: 程序员优雅哥 youyacoder
|
||||
@time: 2026/2/11 15:58
|
||||
-->
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="lang in languages"
|
||||
:key="lang.value"
|
||||
:class="{ active: currentLocale === lang.value }"
|
||||
@click="onChangeLanguage(lang.value)"
|
||||
>
|
||||
{{ lang.label }}
|
||||
</button>
|
||||
<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>{{ $t('common.welcome', { name: '优雅哥' }) }}</div>
|
||||
<div>{{ $t('home.title') }}</div>
|
||||
<hr />
|
||||
|
||||
<h1 class="index">index page</h1>
|
||||
<div>{{ appName }}</div>
|
||||
<div>{{ msg }}</div>
|
||||
<div>isDark: {{ isDark }}</div>
|
||||
<button @click="toggleDark()">Toggle</button>
|
||||
<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 { useI18n } from 'vue-i18n'
|
||||
import { loadLanguage } from '@/i18n'
|
||||
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 { t, locale } = useI18n()
|
||||
console.log(t('common.welcome', { name: '优雅哥' }))
|
||||
const currentLocale = locale
|
||||
// 支持的语言
|
||||
const languages = [
|
||||
{ label: '中文', value: 'zh-CN' },
|
||||
{ label: 'English', value: 'en-US' },
|
||||
]
|
||||
const onChangeLanguage = async (lang: string) => {
|
||||
// 加载语言包
|
||||
await loadLanguage(lang)
|
||||
// 设置当前语言
|
||||
locale.value = lang
|
||||
}
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
const menuStore = useMenuStore()
|
||||
|
||||
const msg = ref('Hello world')
|
||||
const title = defaultLoginConfig.title
|
||||
|
||||
const isDark = useDark({
|
||||
selector: 'html',
|
||||
attribute: 'data-theme',
|
||||
valueDark: 'dark',
|
||||
valueLight: 'light',
|
||||
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 toggleDark = useToggle(isDark)
|
||||
// 实时时间
|
||||
const now = ref(new Date())
|
||||
const timer = setInterval(() => {
|
||||
now.value = new Date()
|
||||
}, 1000)
|
||||
onBeforeUnmount(() => clearInterval(timer))
|
||||
|
||||
const appName = import.meta.env?.VITE_APP_NAME ?? ''
|
||||
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"></style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home {
|
||||
@apply flex flex-col gap-md;
|
||||
}
|
||||
|
||||
.home__banner {
|
||||
@apply relative overflow-hidden rounded-lg;
|
||||
background: linear-gradient(135deg, var(--wm-color-primary), var(--wm-color-primary-dark-2));
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.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: 0.9;
|
||||
}
|
||||
|
||||
.home__subtitle {
|
||||
@apply mt-md text-sm;
|
||||
opacity: 0.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>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user