fjrcloud-community-ui/src/views/Home/Index.vue

406 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="home-container">
<!-- 欢迎区域 -->
<el-card shadow="never" class="welcome-card">
<el-skeleton :loading="loading" animated>
<el-row :gutter="16" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<el-avatar :src="avatar" :size="70" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div>
<div class="text-20px">
你好 {{ username }} 祝你开心每一天!
</div>
<div class="mt-10px text-14px text-gray-500">
今日晴20 - 32
</div>
</div>
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="h-70px flex items-center justify-end lt-sm:mt-10px">
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">项目数</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.project"
:duration="2600"
/>
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">待办</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.todo"
:duration="2600"
/>
</div>
<el-divider direction="vertical" border-style="dashed" />
<div class="px-8px text-right">
<div class="mb-16px text-14px text-gray-400">项目访问</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<!-- 主要内容区域 -->
<el-row class="mt-8px" :gutter="8" justify="space-between">
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
<!-- 小区项目列表 -->
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>项目数</span>
<el-link type="primary" :underline="false" @click="handleMoreClick">
</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="8"
:sm="24"
:xs="24"
>
<el-card
shadow="hover"
class="mr-5px mt-5px cursor-pointer"
@click="handleProjectClick(item)"
>
<div class="flex items-center">
<Icon
:icon="item.icon"
:size="25"
class="mr-8px"
:style="{ color: item.color }"
/>
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-12px text-12px text-gray-400">{{ item.description }}</div>
<div class="mt-12px flex justify-between text-12px text-gray-400">
<span>{{ item.location }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<!-- 图表区域 -->
<el-card shadow="never" class="mt-8px">
<el-skeleton :loading="loading" animated>
<el-row :gutter="20" justify="space-between">
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="pieOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-8px">
<el-skeleton :loading="loading" animated>
<Echart :options="barOptionsData" :height="280" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</el-col>
<!-- 右侧区域 -->
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
<!-- 快捷入口 -->
<el-card shadow="never">
<template #header>
<div class="h-3 flex justify-between">
<span>快捷入口</span>
</div>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-8px" :style="{ color: item.color }" />
<el-link type="default" :underline="false" @click="handleShortcutClick(item.url)">
{{ item.name }}
</el-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<!-- 通知公告 -->
<el-card shadow="never" class="mt-8px">
<template #header>
<div class="h-3 flex justify-between">
<span>通知公告</span>
<el-link type="primary" :underline="false" @click="handleNoticeMoreClick">更多</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-start">
<el-avatar :src="avatar" :size="35" class="mr-16px">
<img src="@/assets/imgs/avatar.gif" alt="" />
</el-avatar>
<div class="flex-1">
<div class="text-14px">
<span class="text-gray-800">{{ item.type }}</span>
<span class="text-gray-600">{{ item.title }}</span>
</div>
<div class="mt-8px text-12px text-gray-400">
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider v-if="index < notice.length - 1" />
</div>
</el-skeleton>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { formatTime } from '@/utils'
import { useUserStore } from '@/store/modules/user'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { pieOptions, barOptions } from './echarts-data'
import { useRouter } from 'vue-router'
import { CommunityApi, Community } from '@/api/community/community'
import { NoticeApi, Notice as NoticeInfo } from '@/api/community/notice'
defineOptions({ name: 'Index' })
const { t } = useI18n()
const router = useRouter()
const userStore = useUserStore()
const loading = ref(true)
const avatar = userStore.getUser.avatar
const username = userStore.getUser.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
// 获取统计数
let totalSate = reactive<WorkplaceTotal>({
project: 0,
access: 0,
todo: 0
})
const getCount = async () => {
try {
const data = await CommunityApi.getCommunitySimpleList()
totalSate.project = data.length
totalSate.access = 2340
totalSate.todo = 10
} catch (error) {
console.error('获取小区数量失败:', error)
}
}
// 获取小区列表
let projects = reactive<Project[]>([])
const getProject = async () => {
try {
const data = await CommunityApi.getCommunityPage({
pageNo: 1,
pageSize: 100
})
projects = data.list.map((item: Community) => ({
name: item.communityName || '',
icon: 'ep:house',
description: item.streetName || item.districtName || '智慧社区',
location: item.communityAddress || '',
time: new Date(),
color: '#409EFF'
}))
} catch (error) {
console.error('获取小区列表失败:', error)
}
}
// 获取通知公告
let notice = reactive<Notice[]>([])
const getNotice = async () => {
try {
const data = await NoticeApi.getNoticePage({
pageNo: 1,
pageSize: 4,
status: 1
})
notice = data.list.map((item: NoticeInfo) => ({
title: item.title || '',
type: item.publisher || '系统通知',
keys: [item.title || ''],
date: new Date(item.createTime || '')
}))
} catch (error) {
console.error('获取通知公告失败:', error)
}
}
// 获取快捷入口
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: '社区动态',
icon: 'ep:document',
url: '/operation/post',
color: '#1677ff'
},
{
name: '通知公告',
icon: 'ep:bell',
url: '/operation/notice',
color: '#ff6b6b'
},
{
name: '知识课堂',
icon: 'ep:reading',
url: '/operation/knowledge-class',
color: '#7c3aed'
},
{
name: '小区活动',
icon: 'ep:calendar',
url: '/operation/activity',
color: '#3fb27f'
},
{
name: '小区管理',
icon: 'ep:office-building',
url: '/system/community',
color: '#4daf1bc9'
},
{
name: 'Banner管理',
icon: 'ep:picture',
url: '/system/banner',
color: '#1a73e8'
}
]
shortcut = Object.assign(shortcut, data)
}
// 用户访问来源
const getUserAccessSource = async () => {
const data = [
{ value: 335, name: '直接访问' },
{ value: 310, name: '邮件营销' },
{ value: 234, name: '联盟广告' },
{ value: 135, name: '视频广告' },
{ value: 1548, name: '搜索引擎' }
]
set(
pieOptionsData,
'legend.data',
data.map((v) => v.name)
)
pieOptionsData!.series![0].data = data.map((v) => {
return {
name: v.name,
value: v.value
}
})
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
// 周活跃量
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: '周一' },
{ value: 34235, name: '周二' },
{ value: 26321, name: '周三' },
{ value: 12340, name: '周四' },
{ value: 24643, name: '周五' },
{ value: 1322, name: '周六' },
{ value: 1324, name: '周日' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => v.name)
)
set(barOptionsData, 'series', [
{
name: '活跃用户',
data: data.map((v) => v.value),
type: 'bar'
}
])
}
const getAllApi = async () => {
await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getUserAccessSource(),
getWeeklyUserActivity()
])
loading.value = false
}
const handleProjectClick = (item: Project) => {
// TODO: 跳转到小区详情页
console.log('点击小区:', item.name)
}
const handleShortcutClick = (url: string) => {
router.push(url)
}
const handleMoreClick = () => {
router.push('/system/community')
}
const handleNoticeMoreClick = () => {
router.push('/operation/notice')
}
getAllApi()
</script>
<style lang="scss" scoped>
.home-container {
padding: 0;
}
.welcome-card {
:deep(.el-card__body) {
padding: 20px;
}
}
</style>