!225 refactor: vue3 index

Merge pull request !225 from xingyu/master
This commit is contained in:
芋道源码 2022-07-22 10:55:21 +00:00 committed by Gitee
commit 9b267746ab
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
7 changed files with 655 additions and 549 deletions

View File

@ -154,14 +154,14 @@ ps核心功能已经实现正在对接微信小程序中...
### 后端 ### 后端
| 框架 | 说明 | 版本 | 学习指南 | | 框架 | 说明 | 版本 | 学习指南 |
|---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------| |---------------------------------------------------------------------------------------------|-----------------------|-----------|----------------------------------------------------------------|
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.8 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | | [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.9 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | | | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | | | [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.3 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | | [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
@ -187,18 +187,15 @@ ps核心功能已经实现正在对接微信小程序中...
### vue3 前端 ### vue3 前端
| 框架 | 说明 | 版本 | | 框架 | 说明 | 版本 |
|------------------------------------------------------------------------------|---------------|--------| |----------------------------------------------------------------------|---------------------|--------|
| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 | | [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.1 | | [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.2 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.9 | | [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.9 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 | | [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.16 | | [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.16 |
| [vueuse](https://vueuse.org//) | 常用工具集 | 8.9.4 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 | | [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 |
| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.2 | | [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架| 3.5.6 |
| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 | | [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 |
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.11 |
## 🐷 演示图 ## 🐷 演示图

View File

@ -6,7 +6,7 @@
"private": false, "private": false,
"scripts": { "scripts": {
"i": "pnpm install", "i": "pnpm install",
"dev": "vite --mode base", "dev": "vite --mode base --open",
"ts:check": "vue-tsc --noEmit", "ts:check": "vue-tsc --noEmit",
"build:pro": "vite build --mode pro", "build:pro": "vite build --mode pro",
"build:dev": "vite build --mode dev", "build:dev": "vite build --mode dev",

View File

@ -171,15 +171,16 @@ export default {
sunday: 'Sunday' sunday: 'Sunday'
}, },
workplace: { workplace: {
goodMorning: 'Good morning', welcome: 'Hello',
happyDay: 'Wish you happy every day!', happyDay: 'Wish you happy every day!',
toady: `It's sunny today`, toady: `It's sunny today`,
notice: 'Announcement',
project: 'Project', project: 'Project',
access: 'Project access', access: 'Project access',
toDo: 'To do', toDo: 'To do',
introduction: 'A serious introduction', introduction: 'A serious introduction',
more: 'More', more: 'More',
shortcutOperation: 'Shortcut operation', shortcutOperation: 'Quick entry',
operation: 'Operation', operation: 'Operation',
index: 'Index', index: 'Index',
personal: 'Personal', personal: 'Personal',

View File

@ -171,15 +171,16 @@ export default {
sunday: '周日' sunday: '周日'
}, },
workplace: { workplace: {
goodMorning: '早安', welcome: '你好',
happyDay: '祝你开心每一天!', happyDay: '祝你开心每一天!',
toady: '今日晴', toady: '今日晴',
notice: '通知公告',
project: '项目数', project: '项目数',
access: '项目访问', access: '项目访问',
toDo: '待办', toDo: '待办',
introduction: '一个正经的简介', introduction: '一个正经的简介',
more: '更多', more: '更多',
shortcutOperation: '快捷操作', shortcutOperation: '快捷入口',
operation: '操作', operation: '操作',
index: '指数', index: '指数',
personal: '个人', personal: '个人',

View File

@ -1,38 +1,199 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive } from 'vue' import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
import { set } from 'lodash-es'
import { EChartsOption } from 'echarts'
import { Echart } from '@/components/Echart'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive } from 'vue'
import { CountTo } from '@/components/CountTo' import { CountTo } from '@/components/CountTo'
import type { AnalysisTotalTypes } from './types' import { formatTime } from '@/utils'
import { useDesign } from '@/hooks/web/useDesign' import { Echart } from '@/components/Echart'
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus' import { EChartsOption } from 'echarts'
import { radarOption } from './echarts-data'
import { Highlight } from '@/components/Highlight'
import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
import { set } from 'lodash-es'
import { useCache } from '@/hooks/web/useCache'
import { pieOptions, barOptions, lineOptions } from './echarts-data' import { pieOptions, barOptions, lineOptions } from './echarts-data'
const { t } = useI18n() const { t } = useI18n()
const { wsCache } = useCache()
const loading = ref(true) const loading = ref(true)
const { getPrefixCls } = useDesign() const avatar = wsCache.get('user').user.avatar
const prefixCls = getPrefixCls('panel') const username = wsCache.get('user').user.nickname
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
//
let totalState = reactive<AnalysisTotalTypes>({ let totalSate = reactive<WorkplaceTotal>({
users: 0, project: 0,
messages: 0, access: 0,
moneys: 0, todo: 0
shoppings: 0
}) })
const getCount = async () => { const getCount = async () => {
const data = { const data = {
users: 102400, project: 40,
messages: 81212, access: 2340,
moneys: 9280, todo: 10
shoppings: 13600
} }
totalState = Object.assign(totalState, data) totalSate = Object.assign(totalSate, data)
} }
//
let projects = reactive<Project[]>([])
const getProject = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
//
let notice = reactive<Notice[]>([])
const getNotice = async () => {
const data = [
{
title: '系统升级版本',
type: '通知',
keys: ['通知', '升级'],
date: new Date()
},
{
title: '系统凌晨维护',
type: '公告',
keys: ['公告', '维护'],
date: new Date()
},
{
title: '系统升级版本',
type: '通知',
keys: ['通知', '升级'],
date: new Date()
},
{
title: '系统凌晨维护',
type: '公告',
keys: ['公告', '维护'],
date: new Date()
}
]
notice = Object.assign(notice, data)
}
//
let shortcut = reactive<Shortcut[]>([])
const getShortcut = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill',
url: 'github.io'
},
{
name: 'Vue',
icon: 'logos:vue',
url: 'vuejs.org'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
url: 'https://vitejs.dev/'
},
{
name: 'Angular',
icon: 'logos:angular-icon',
url: 'github.io'
},
{
name: 'React',
icon: 'logos:react',
url: 'github.io'
},
{
name: 'Webpack',
icon: 'logos:webpack',
url: 'github.io'
}
]
shortcut = Object.assign(shortcut, data)
}
//
let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
const getRadar = async () => {
const data = [
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
]
set(
radarOptionData,
'radar.indicator',
data.map((v) => {
return {
name: t(v.name),
max: v.max
}
})
)
set(radarOptionData, 'series', [
{
name: '指数',
type: 'radar',
data: [
{
value: data.map((v) => v.personal),
name: t('workplace.personal')
},
{
value: data.map((v) => v.team),
name: t('workplace.team')
}
]
}
])
}
// //
const getUserAccessSource = async () => { const getUserAccessSource = async () => {
const data = [ const data = [
@ -121,7 +282,16 @@ const getMonthlySales = async () => {
} }
const getAllApi = async () => { const getAllApi = async () => {
await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()]) await Promise.all([
getCount(),
getProject(),
getNotice(),
getShortcut(),
getRadar(),
getUserAccessSource(),
getWeeklyUserActivity(),
getMonthlySales()
])
loading.value = false loading.value = false
} }
@ -129,123 +299,99 @@ getAllApi()
</script> </script>
<template> <template>
<el-row :gutter="20" justify="space-between" :class="prefixCls">
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div> <div>
<div <el-card shadow="never">
:class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`" <el-skeleton :loading="loading" animated>
> <el-row :gutter="20" justify="space-between">
<Icon icon="svg-icon:peoples" :size="40" /> <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
<div>
<div class="text-20px text-700">
{{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div> </div>
</div> </div>
<div class="flex flex-col justify-between"> </div>
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{ </el-col>
t('analysis.newUser') <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
}}</div> <div class="flex h-70px items-center justify-end <sm:mt-10px">
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
<CountTo <CountTo
class="text-20px font-700 text-right" class="text-20px"
:start-val="0" :start-val="0"
:end-val="102400" :end-val="totalSate.project"
:duration="2600"
/>
</div>
<el-divider direction="vertical" />
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</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="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600" :duration="2600"
/> />
</div> </div>
</div> </div>
</template> </el-col>
</el-row>
</el-skeleton> </el-skeleton>
</el-card> </el-card>
</el-col> </div>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24"> <el-row class="mt-10px" :gutter="20" justify="space-between">
<el-card shadow="hover" class="mb-20px"> <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2"> <el-card shadow="never">
<template #default> <template #header>
<div :class="`${prefixCls}__item flex justify-between`"> <div class="flex justify-between">
<div> <span>{{ t('workplace.project') }}</span>
<div <el-link type="primary" :underline="false">{{ t('workplace.more') }}</el-link>
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:message" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.unreadInformation')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="81212"
:duration="2600"
/>
</div>
</div> </div>
</template> </template>
</el-skeleton> <el-skeleton :loading="loading" animated>
</el-card> <el-row>
</el-col> <el-col
v-for="(item, index) in projects"
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24"> :key="`card-${index}`"
<el-card shadow="hover" class="mb-20px"> :xl="8"
<el-skeleton :loading="loading" animated :rows="2"> :lg="8"
<template #default> :md="12"
<div :class="`${prefixCls}__item flex justify-between`"> :sm="24"
<div> :xs="24"
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
> >
<Icon icon="svg-icon:money" :size="40" /> <el-card shadow="hover">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-10px" />
<span class="text-16px">{{ item.name }}</span>
</div> </div>
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-20px text-12px text-gray-400 flex justify-between">
<span>{{ item.personal }}</span>
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
</div> </div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.transactionAmount')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="9280"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:shopping" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.totalShopping')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="13600"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-10px">
<el-skeleton :loading="loading" animated>
<el-row :gutter="20" justify="space-between"> <el-row :gutter="20" justify="space-between">
<el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24"> <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<el-card shadow="hover" class="mb-20px"> <el-card shadow="hover" class="mb-20px">
@ -269,48 +415,61 @@ getAllApi()
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
<el-card shadow="never">
<template #header>
<span>{{ t('workplace.shortcutOperation') }}</span>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-20px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-10px" />
<el-link type="default" :underline="false" :href="item.url">
{{ item.name }}
</el-link>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-10px">
<template #header>
<div class="flex justify-between">
<span>{{ t('workplace.notice') }}</span>
<el-link type="primary" :underline="false">{{ t('workplace.more') }}</el-link>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in notice" :key="`dynamics-${index}`">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">
{{ item.type }} : {{ item.title }}
</Highlight>
</div>
<div class="mt-15px text-12px text-gray-400">
{{ formatTime(item.date, 'yyyy-MM-dd') }}
</div>
</div>
</div>
<el-divider />
</div>
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-10px">
<template #header>
<span>{{ t('workplace.index') }}</span>
</template>
<el-skeleton :loading="loading" animated>
<Echart :options="radarOptionData" :height="400" />
</el-skeleton>
</el-card>
</el-col>
</el-row>
</template> </template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-panel';
.@{prefix-cls} {
&__item {
&--peoples {
color: #40c9c6;
}
&--message {
color: #36a3f7;
}
&--money {
color: #f4516c;
}
&--shopping {
color: #34bfa3;
}
&:hover {
:deep(.@{namespace}-icon) {
color: #fff !important;
}
.@{prefix-cls}__item--icon {
transition: all 0.38s ease-out;
}
.@{prefix-cls}__item--peoples {
background: #40c9c6;
}
.@{prefix-cls}__item--message {
background: #36a3f7;
}
.@{prefix-cls}__item--money {
background: #f4516c;
}
.@{prefix-cls}__item--shopping {
background: #34bfa3;
}
}
}
}
</style>

View File

@ -1,197 +1,127 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTimeAgo } from '@/hooks/web/useTimeAgo'
import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { CountTo } from '@/components/CountTo'
import { formatTime } from '@/utils'
import { Echart } from '@/components/Echart'
import { EChartsOption } from 'echarts'
import { radarOption } from './echarts-data'
import { Highlight } from '@/components/Highlight'
import type { WorkplaceTotal, Project, Dynamic, Team } from './types'
import { set } from 'lodash-es' import { set } from 'lodash-es'
import { useCache } from '@/hooks/web/useCache' import { EChartsOption } from 'echarts'
import { Echart } from '@/components/Echart'
import { useI18n } from '@/hooks/web/useI18n'
import { CountTo } from '@/components/CountTo'
import type { AnalysisTotalTypes } from './types'
import { useDesign } from '@/hooks/web/useDesign'
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
import { pieOptions, barOptions, lineOptions } from './echarts-data'
const { t } = useI18n() const { t } = useI18n()
const { wsCache } = useCache()
const loading = ref(true) const loading = ref(true)
const avatar = wsCache.get('user').user.avatar const { getPrefixCls } = useDesign()
const username = wsCache.get('user').user.nickname const prefixCls = getPrefixCls('panel')
// const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
let totalSate = reactive<WorkplaceTotal>({
project: 0, let totalState = reactive<AnalysisTotalTypes>({
access: 0, users: 0,
todo: 0 messages: 0,
moneys: 0,
shoppings: 0
}) })
const getCount = async () => { const getCount = async () => {
const data = { const data = {
project: 40, users: 102400,
access: 2340, messages: 81212,
todo: 10 moneys: 9280,
shoppings: 13600
} }
totalSate = Object.assign(totalSate, data) totalState = Object.assign(totalState, data)
} }
let projects = reactive<Project[]>([]) //
const getUserAccessSource = async () => {
//
const getProject = async () => {
const data = [ const data = [
{ { value: 335, name: 'analysis.directAccess' },
name: 'Github', { value: 310, name: 'analysis.mailMarketing' },
icon: 'akar-icons:github-fill', { value: 234, name: 'analysis.allianceAdvertising' },
message: 'workplace.introduction', { value: 135, name: 'analysis.videoAdvertising' },
personal: 'Archer', { value: 1548, name: 'analysis.searchEngines' }
time: new Date()
},
{
name: 'Vue',
icon: 'logos:vue',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Angular',
icon: 'logos:angular-icon',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'React',
icon: 'logos:react',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Webpack',
icon: 'logos:webpack',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite',
message: 'workplace.introduction',
personal: 'Archer',
time: new Date()
}
]
projects = Object.assign(projects, data)
}
//
let dynamics = reactive<Dynamic[]>([])
const getDynamic = async () => {
const data = [
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
},
{
keys: ['workplace.push', 'Github'],
time: new Date()
}
]
dynamics = Object.assign(dynamics, data)
}
//
let team = reactive<Team[]>([])
const getTeam = async () => {
const data = [
{
name: 'Github',
icon: 'akar-icons:github-fill'
},
{
name: 'Vue',
icon: 'logos:vue'
},
{
name: 'Angular',
icon: 'logos:angular-icon'
},
{
name: 'React',
icon: 'logos:react'
},
{
name: 'Webpack',
icon: 'logos:webpack'
},
{
name: 'Vite',
icon: 'vscode-icons:file-type-vite'
}
]
team = Object.assign(team, data)
}
//
let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
const getRadar = async () => {
const data = [
{ name: 'workplace.quote', max: 65, personal: 42, team: 50 },
{ name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
{ name: 'workplace.hot', max: 300, personal: 20, team: 28 },
{ name: 'workplace.yield', max: 130, personal: 35, team: 35 },
{ name: 'workplace.follow', max: 100, personal: 80, team: 90 }
] ]
set( set(
radarOptionData, pieOptionsData,
'radar.indicator', 'legend.data',
data.map((v) => { data.map((v) => t(v.name))
return {
name: t(v.name),
max: v.max
}
})
) )
set(radarOptionData, 'series', [ set(pieOptionsData, 'series.data', data)
}
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
//
const getWeeklyUserActivity = async () => {
const data = [
{ value: 13253, name: 'analysis.monday' },
{ value: 34235, name: 'analysis.tuesday' },
{ value: 26321, name: 'analysis.wednesday' },
{ value: 12340, name: 'analysis.thursday' },
{ value: 24643, name: 'analysis.friday' },
{ value: 1322, name: 'analysis.saturday' },
{ value: 1324, name: 'analysis.sunday' }
]
set(
barOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(barOptionsData, 'series', [
{ {
name: '指数', name: t('analysis.activeQuantity'),
type: 'radar', data: data.map((v) => v.value),
data: [ type: 'bar'
}
])
}
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
//
const getMonthlySales = async () => {
const data = [
{ estimate: 100, actual: 120, name: 'analysis.january' },
{ estimate: 120, actual: 82, name: 'analysis.february' },
{ estimate: 161, actual: 91, name: 'analysis.march' },
{ estimate: 134, actual: 154, name: 'analysis.april' },
{ estimate: 105, actual: 162, name: 'analysis.may' },
{ estimate: 160, actual: 140, name: 'analysis.june' },
{ estimate: 165, actual: 145, name: 'analysis.july' },
{ estimate: 114, actual: 250, name: 'analysis.august' },
{ estimate: 163, actual: 134, name: 'analysis.september' },
{ estimate: 185, actual: 56, name: 'analysis.october' },
{ estimate: 118, actual: 99, name: 'analysis.november' },
{ estimate: 123, actual: 123, name: 'analysis.december' }
]
set(
lineOptionsData,
'xAxis.data',
data.map((v) => t(v.name))
)
set(lineOptionsData, 'series', [
{ {
value: data.map((v) => v.personal), name: t('analysis.estimate'),
name: t('workplace.personal') smooth: true,
type: 'line',
data: data.map((v) => v.estimate),
animationDuration: 2800,
animationEasing: 'cubicInOut'
}, },
{ {
value: data.map((v) => v.team), name: t('analysis.actual'),
name: t('workplace.team') smooth: true,
} type: 'line',
] itemStyle: {},
data: data.map((v) => v.actual),
animationDuration: 2800,
animationEasing: 'quadraticOut'
} }
]) ])
} }
const getAllApi = async () => { const getAllApi = async () => {
await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()]) await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
loading.value = false loading.value = false
} }
@ -199,173 +129,188 @@ getAllApi()
</script> </script>
<template> <template>
<el-row :gutter="20" justify="space-between" :class="prefixCls">
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div> <div>
<el-card shadow="never"> <div
<el-skeleton :loading="loading" animated> :class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:peoples" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.newUser')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="102400"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:message" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.unreadInformation')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="81212"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:money" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.transactionAmount')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="9280"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
<el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="2">
<template #default>
<div :class="`${prefixCls}__item flex justify-between`">
<div>
<div
:class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
>
<Icon icon="svg-icon:shopping" :size="40" />
</div>
</div>
<div class="flex flex-col justify-between">
<div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
t('analysis.totalShopping')
}}</div>
<CountTo
class="text-20px font-700 text-right"
:start-val="0"
:end-val="13600"
:duration="2600"
/>
</div>
</div>
</template>
</el-skeleton>
</el-card>
</el-col>
</el-row>
<el-row :gutter="20" justify="space-between"> <el-row :gutter="20" justify="space-between">
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24"> <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
<div class="flex items-center"> <el-card shadow="hover" class="mb-20px">
<img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
<div>
<div class="text-20px text-700">
{{ t('workplace.goodMorning') }} {{ username }} {{ t('workplace.happyDay') }}
</div>
<div class="mt-10px text-14px text-gray-500">
{{ t('workplace.toady') }}20 - 32
</div>
</div>
</div>
</el-col>
<el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
<div class="flex h-70px items-center justify-end <sm:mt-20px">
<div class="px-8px text-right">
<div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</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="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</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="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
<CountTo
class="text-20px"
:start-val="0"
:end-val="totalSate.access"
:duration="2600"
/>
</div>
</div>
</el-col>
</el-row>
</el-skeleton>
</el-card>
</div>
<el-row class="mt-20px" :gutter="20" justify="space-between">
<el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
<el-card shadow="never">
<template #header>
<div class="flex justify-between">
<span>{{ t('workplace.project') }}</span>
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
</div>
</template>
<el-skeleton :loading="loading" animated> <el-skeleton :loading="loading" animated>
<el-row> <Echart :options="pieOptionsData" :height="300" />
<el-col
v-for="(item, index) in projects"
:key="`card-${index}`"
:xl="8"
:lg="8"
:md="12"
:sm="24"
:xs="24"
>
<el-card shadow="hover">
<div class="flex items-center">
<Icon :icon="item.icon" :size="25" class="mr-10px" />
<span class="text-16px">{{ item.name }}</span>
</div>
<div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
<div class="mt-20px text-12px text-gray-400 flex justify-between">
<span>{{ item.personal }}</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-20px">
<template #header>
<div class="flex justify-between">
<span>{{ t('workplace.dynamic') }}</span>
<ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>
</div>
</template>
<el-skeleton :loading="loading" animated>
<div v-for="(item, index) in dynamics" :key="`dynamics-${index}`">
<div class="flex items-center">
<img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
<div>
<div class="text-14px">
<Highlight :keys="item.keys.map((v) => t(v))">
{{ username }} {{ t('workplace.pushCode') }}
</Highlight>
</div>
<div class="mt-15px text-12px text-gray-400">
{{ useTimeAgo(item.time) }}
</div>
</div>
</div>
<el-divider />
</div>
</el-skeleton> </el-skeleton>
</el-card> </el-card>
</el-col> </el-col>
<el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px"> <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
<el-card shadow="never"> <el-card shadow="hover" class="mb-20px">
<template #header>
<span>{{ t('workplace.shortcutOperation') }}</span>
</template>
<el-skeleton :loading="loading" animated> <el-skeleton :loading="loading" animated>
<el-col <Echart :options="barOptionsData" :height="300" />
v-for="item in 9"
:key="`card-${item}`"
:xl="12"
:lg="12"
:md="12"
:sm="24"
:xs="24"
class="mb-10px"
>
<ElLink type="default" :underline="false">
{{ t('workplace.operation') }}{{ item }}
</ElLink>
</el-col>
</el-skeleton> </el-skeleton>
</el-card> </el-card>
<el-card shadow="never" class="mt-20px">
<template #header>
<span>{{ t('workplace.index') }}</span>
</template>
<el-skeleton :loading="loading" animated>
<Echart :options="radarOptionData" :height="400" />
</el-skeleton>
</el-card>
<el-card shadow="never" class="mt-20px">
<template #header>
<span>{{ t('workplace.team') }}</span>
</template>
<el-skeleton :loading="loading" animated>
<el-row>
<el-col v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">
<div class="flex items-center">
<Icon :icon="item.icon" class="mr-10px" />
<ElLink type="default" :underline="false">
{{ item.name }}
</ElLink>
</div>
</el-col> </el-col>
</el-row> <el-col :span="24">
<el-card shadow="hover" class="mb-20px">
<el-skeleton :loading="loading" animated :rows="4">
<Echart :options="lineOptionsData" :height="350" />
</el-skeleton> </el-skeleton>
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
</template> </template>
<style lang="less" scoped>
@prefix-cls: ~'@{namespace}-panel';
.@{prefix-cls} {
&__item {
&--peoples {
color: #40c9c6;
}
&--message {
color: #36a3f7;
}
&--money {
color: #f4516c;
}
&--shopping {
color: #34bfa3;
}
&:hover {
:deep(.@{namespace}-icon) {
color: #fff !important;
}
.@{prefix-cls}__item--icon {
transition: all 0.38s ease-out;
}
.@{prefix-cls}__item--peoples {
background: #40c9c6;
}
.@{prefix-cls}__item--message {
background: #36a3f7;
}
.@{prefix-cls}__item--money {
background: #f4516c;
}
.@{prefix-cls}__item--shopping {
background: #34bfa3;
}
}
}
}
</style>

View File

@ -12,14 +12,17 @@ export type Project = {
time: Date | number | string time: Date | number | string
} }
export type Dynamic = { export type Notice = {
title: string
type: string
keys: string[] keys: string[]
time: Date | number | string date: Date | number | string
} }
export type Team = { export type Shortcut = {
name: string name: string
icon: string icon: string
url: string
} }
export type RadarData = { export type RadarData = {