datai-vue/src/views/index.vue

607 lines
15 KiB
Vue
Raw Normal View History

2025-12-08 22:00:21 +08:00
<template>
<div class="app-container home">
<el-row :gutter="20">
<!-- 左侧个人信息 -->
<el-col :lg="6" :md="8" :sm="24" :xs="24">
<el-card class="user-info-card">
<div class="user-profile">
<el-avatar :size="80" :src="userInfo.avatar || profile" />
<h2 class="welcome-text">欢迎回来{{ userInfo.name }}</h2>
<p class="user-role">{{ userInfo.roleName }}</p>
</div>
<div class="user-stats">
<div class="stat-item">
<div class="stat-value text-ellipsis" :title="formatDate(userInfo.loginDate) || '暂无'">
{{ formatDate(userInfo.loginDate) || '暂无' }}
</div>
<p class="stat-label">上次登录</p>
</div>
<div class="stat-item">
<div class="stat-value text-ellipsis" :title="userInfo.deptName || '暂无'">
{{ userInfo.deptName || '暂无' }}
</div>
<p class="stat-label">所属部门</p>
</div>
</div>
</el-card>
<!-- 系统公告卡片 -->
<el-card class="notice-card hide-on-small" v-loading="noticeLoading">
<template #header>
<div class="card-header">
<span class="header-title">
<el-icon>
<Bell />
</el-icon>
系统公告
</span>
<el-button v-if="noticeList.length" link @click="viewMoreNotices">
查看更多<el-icon>
<ArrowRight />
</el-icon>
</el-button>
</div>
</template>
<div v-if="noticeList.length" class="notice-list">
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" @click="showNoticeDetail(item)">
<el-tag size="small" :type="item.noticeType === '1' ? 'danger' : 'success'">
{{ item.noticeType === '1' ? '通知' : '公告' }}
</el-tag>
<span class="notice-title">{{ item.noticeTitle }}</span>
<span class="notice-time">{{ parseTime(item.createTime) }}</span>
</div>
</div>
<el-empty v-else description="暂无公告" />
</el-card>
</el-col>
<!-- 右侧内容区 -->
<el-col :lg="18" :md="16" :sm="24" :xs="24">
<el-row :gutter="20">
<!-- 欢迎信息和特色功能 -->
<el-col :span="24">
<el-card class="welcome-card">
<div class="welcome-container">
<h1 class="welcome-title">欢迎使用 datai-Vue3</h1>
<p class="welcome-desc">基于 Vue3TypeScriptViteElement-Plus 的后台管理系统</p>
</div>
<el-row :gutter="20" class="feature-section">
<el-col :lg="8" :md="12" :sm="24" :xs="24" v-for="(feature, index) in features" :key="index">
<div class="feature-item">
<div class="feature-icon">
<el-icon>
<component :is="feature.icon" />
</el-icon>
</div>
<h3>{{ feature.title }}</h3>
<p>{{ feature.description }}</p>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
<!-- 移动端公告展示 -->
<el-col :span="24">
<el-card class="notice-card show-on-small" v-loading="noticeLoading">
<template #header>
<div class="card-header">
<span class="header-title">
<el-icon>
<Bell />
</el-icon>
系统公告
</span>
<el-button v-if="noticeList.length" link @click="viewMoreNotices">
查看更多<el-icon>
<ArrowRight />
</el-icon>
</el-button>
</div>
</template>
<div v-if="noticeList.length" class="notice-list">
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item"
@click="showNoticeDetail(item)">
<el-tag size="small" :type="item.noticeType === '1' ? 'danger' : 'success'">
{{ item.noticeType === '1' ? '通知' : '公告' }}
</el-tag>
<span class="notice-title">{{ item.noticeTitle }}</span>
<span class="notice-time">{{ parseTime(item.createTime) }}</span>
</div>
</div>
<el-empty v-else description="暂无公告" />
</el-card>
</el-col>
<!-- 内置功能说明 -->
<el-col :span="24">
<el-card class="features-card">
<template #header>
<div class="card-header">
<span>内置功能说明</span>
</div>
</template>
<el-row :gutter="20">
<el-col :span="12" v-for="(section, index) in [
{ header: '内置插件', items: plugins },
{ header: '内置指令', items: directives }
]" :key="index">
<div class="feature-group">
<h3 class="group-title">{{ section.header }}</h3>
<div class="feature-list">
<div v-for="(item, idx) in section.items" :key="idx" class="feature-list-item">
<h4>{{ item.name }}</h4>
<p>{{ item.description }}</p>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</el-col>
</el-row>
</el-col>
</el-row>
<!-- 公告详情对话框 -->
<el-dialog v-model="noticeDialogVisible" :title="currentNotice.noticeTitle" width="50%" destroy-on-close>
<div v-html="currentNotice.noticeContent"></div>
</el-dialog>
</div>
</template>
<script setup name="Index" lang="ts">
import { ref, onMounted } from 'vue'
import { listNotice } from '@/api/system/notice'
import { parseTime } from '@/utils/ruoyi'
import { Bell, ArrowRight } from '@element-plus/icons-vue'
import { formatDate } from '@/utils'
import profile from '@/assets/images/profile.jpg'
import useUserStore from '@/store/modules/user'
import { useRouter, type RouteLocationRaw } from 'vue-router'
import { GeekResponseForList } from '@/types/request'
const router = useRouter()
interface Feature {
icon: string;
title: string;
description: string;
}
interface Plugin {
name: string;
description: string;
}
interface Directive {
name: string;
description: string;
usage: string;
}
interface Notice {
noticeId: number;
noticeTitle: string;
noticeType: string;
noticeContent: string;
status: string;
createBy: string;
createTime: string;
}
// 状态定义
const features = ref<Feature[]>([
{
icon: 'Monitor',
title: '技术先进',
description: '采用Vue3、TypeScript等最新技术栈保持与时俱进'
},
{
icon: 'SetUp',
title: '简单易用',
description: '开箱即用的后台解决方案,内置完整的权限验证系统'
},
{
icon: 'Document',
title: '规范开发',
description: '遵循最佳实践,统一的编码规范,让项目更易维护'
}
])
const plugins = ref<Plugin[]>([
{
name: 'Auth 权限验证',
description: '提供了权限验证相关方法包括hasPermi、hasRole等功能用于控制按钮和功能的访问权限'
},
{
name: 'Cache 缓存',
description: '提供了浏览器缓存操作方法支持session和local存储的设置与获取'
},
{
name: 'Modal 弹窗',
description: '封装了Element Plus的弹窗组件提供了更便捷的调用方式'
}
])
const directives = ref<Directive[]>([
{
name: 'hasPermi',
description: '用于控制按钮级别的权限',
usage: 'v-hasPermi="[\'system:user:add\']"'
},
{
name: 'hasRole',
description: '用于控制角色级别的权限',
usage: 'v-hasRole="[\'admin\']"'
},
{
name: 'auth',
description: '通用权限验证指令',
usage: 'v-auth="\'system:user:edit\'"'
}
])
const noticeList = ref<Notice[]>([])
const noticeLoading = ref(false)
const noticeDialogVisible = ref(false)
const currentNotice = ref<Notice>({} as Notice)
// 用户信息store
const userInfo = useUserStore()
// 获取公告列表
const getNoticeList = async () => {
noticeLoading.value = true
try {
const res: GeekResponseForList<Notice> = await listNotice({ pageNum: 1, pageSize: 5 })
noticeList.value = res.rows
} catch (error) {
console.error('获取公告列表失败:', error)
} finally {
noticeLoading.value = false
}
}
// 显示公告详情
const showNoticeDetail = (notice: Notice) => {
currentNotice.value = notice
noticeDialogVisible.value = true
}
// 查看更多公告
const viewMoreNotices = () => {
const route: RouteLocationRaw = { path: '/system/notice' }
router.push(route)
}
onMounted(() => {
userInfo.getInfo()
getNoticeList()
})
</script>
<style scoped lang="scss">
.home {
padding: clamp(10px, 3vw, 20px);
.welcome-container {
text-align: center;
margin-bottom: 40px;
.welcome-title {
font-size: clamp(1.8em, 4vw, 2.5em);
color: var(--el-text-color-primary);
margin-bottom: 20px;
}
.welcome-desc {
font-size: clamp(1em, 2vw, 1.2em);
color: var(--el-text-color-secondary);
}
}
// 响应式间距调整
:deep(.el-row) {
margin-left: -10px !important;
margin-right: -10px !important;
.el-col {
padding-left: 10px !important;
padding-right: 10px !important;
}
}
.feature-section {
.feature-card {
margin-bottom: 20px;
height: 100%;
text-align: center;
transition: transform 0.3s;
&:hover {
transform: translateY(-5px);
}
.feature-icon {
font-size: 2.5em;
color: var(--el-color-primary);
margin-bottom: 20px;
}
h3 {
margin: 10px 0;
font-size: 1.2em;
color: var(--el-text-color-primary);
}
p {
color: var(--el-text-color-secondary);
line-height: 1.5;
}
}
}
.user-info-card {
margin-bottom: 20px;
.user-profile {
text-align: center;
padding: 20px 0;
.welcome-text {
margin: 15px 0 5px;
font-size: 1.2em;
color: var(--el-text-color-primary);
}
.user-role {
color: var(--el-text-color-secondary);
font-size: 0.9em;
}
}
.user-stats {
display: flex;
justify-content: space-around;
padding: 15px 0;
border-top: 1px solid var(--el-border-color-lighter);
.stat-item {
text-align: center;
.stat-value {
font-size: 1.1em;
color: var(--el-text-color-primary);
margin-bottom: 5px;
}
.stat-label {
color: var(--el-text-color-secondary);
font-size: 0.9em;
}
}
}
}
.notice-card {
&.show-on-small {
display: none;
margin: 20px 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-title {
display: flex;
align-items: center;
gap: 5px;
font-weight: bold;
color: var(--el-text-color-primary);
}
}
.notice-list {
.notice-item {
padding: 10px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
cursor: pointer;
transition: all 0.3s;
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--el-fill-color-lighter);
}
.notice-title {
margin: 0 10px;
color: var(--el-text-color-primary);
flex: 1;
}
.notice-time {
font-size: 0.9em;
color: var(--el-text-color-secondary);
}
}
}
}
// 响应式显示控制
@media (max-width: 768px) {
.hide-on-small {
display: none;
}
.show-on-small {
display: block !important;
}
.welcome-container {
margin-bottom: 20px;
}
.docs-section {
.doc-card {
margin-bottom: 20px;
}
}
}
@media (max-width: 576px) {
.user-info-card {
.user-stats {
flex-direction: column;
gap: 15px;
.stat-item {
width: 100%;
padding: 10px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
&:last-child {
border-bottom: none;
}
}
}
}
.feature-section {
.feature-card {
.feature-icon {
font-size: 2em;
}
}
}
}
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.welcome-card {
margin-bottom: 20px;
.welcome-container {
text-align: center;
margin-bottom: 30px;
// ...existing code...
}
.feature-section {
.feature-item {
text-align: center;
padding: 20px;
border-radius: 8px;
transition: all 0.3s;
background-color: var(--el-fill-color-light);
margin-bottom: 20px;
&:hover {
transform: translateY(-5px);
background-color: var(--el-fill-color);
}
.feature-icon {
font-size: 2.5em;
color: var(--el-color-primary);
margin-bottom: 15px;
}
h3 {
margin: 10px 0;
font-size: 1.2em;
color: var(--el-text-color-primary);
}
p {
color: var(--el-text-color-secondary);
line-height: 1.5;
margin: 0;
}
}
}
}
.features-card {
.feature-group {
margin-bottom: 30px;
.group-title {
font-size: 1.2em;
color: var(--el-text-color-primary);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
.feature-list {
.feature-list-item {
padding: 15px;
margin-bottom: 15px;
border-radius: 6px;
background-color: var(--el-fill-color-light);
h4 {
color: var(--el-color-primary);
margin: 0 0 10px;
}
p {
color: var(--el-text-color-secondary);
margin: 0 0 10px;
line-height: 1.5;
}
}
}
}
}
// 响应式调整
@media (max-width: 768px) {
.features-card {
.el-row {
.el-col {
width: 100%;
}
}
}
}
}
// 覆盖卡片样式
:deep(.el-card) {
--el-card-padding: 20px;
.el-card__header {
padding: 15px 20px;
border-bottom: 1px solid var(--el-border-color-lighter);
}
}
// 调整卡片间距
.el-card {
margin-bottom: clamp(15px, 3vw, 20px);
:deep(.el-card__body) {
padding: clamp(12px, 2vw, 20px);
}
}
// 调整对话框响应式
:deep(.el-dialog) {
@media (max-width: 768px) {
width: 90% !important;
margin: 0 auto;
}
}
</style>