Commit 9b267746ab7bf8dfce5ec799893e40c9973a51ae

Authored by 芋道源码
Committed by Gitee
2 parents 57cdb017 4f0b3cef

!225 refactor: vue3 index

Merge pull request !225 from xingyu/master
README.md
@@ -153,29 +153,29 @@ ps:核心功能已经实现,正在对接微信小程序中... @@ -153,29 +153,29 @@ ps:核心功能已经实现,正在对接微信小程序中...
153 153
154 ### 后端 154 ### 后端
155 155
156 -| 框架 | 说明 | 版本 | 学习指南 |  
157 -|---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|  
158 -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.8 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |  
159 -| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |  
160 -| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |  
161 -| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |  
162 -| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |  
163 -| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |  
164 -| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.3 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |  
165 -| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |  
166 -| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |  
167 -| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |  
168 -| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.0 | [文档](https://doc.iocoder.cn/bpm/) |  
169 -| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |  
170 -| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |  
171 -| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) | 156 +| 框架 | 说明 | 版本 | 学习指南 |
  157 +|---------------------------------------------------------------------------------------------|-----------------------|-----------|----------------------------------------------------------------|
  158 +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.6.9 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
  159 +| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
  160 +| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  161 +| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
  162 +| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
  163 +| [Redis](https://redis.io/) | key-value 数据库 | 5.0 | |
  164 +| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.17.4 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
  165 +| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.20 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
  166 +| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.6.5 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
  167 +| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.3 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
  168 +| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.7.0 | [文档](https://doc.iocoder.cn/bpm/) |
  169 +| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
  170 +| [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
  171 +| [Resilience4j](https://github.com/resilience4j/resilience4j) | 服务保障组件 | 1.7.1 | [文档](http://www.iocoder.cn/Spring-Boot/Resilience4j/?yudao) |
172 | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) | 172 | [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
173 -| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |  
174 -| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |  
175 -| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |  
176 -| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |  
177 -| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |  
178 -| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | 173 +| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.6.7 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
  174 +| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.3 | |
  175 +| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.4.1 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
  176 +| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.16.14 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
  177 +| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
  178 +| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - |
179 179
180 ### vue2 前端 180 ### vue2 前端
181 181
@@ -186,19 +186,16 @@ ps:核心功能已经实现,正在对接微信小程序中... @@ -186,19 +186,16 @@ ps:核心功能已经实现,正在对接微信小程序中...
186 186
187 ### vue3 前端 187 ### vue3 前端
188 188
189 -| 框架 | 说明 | 版本 |  
190 -|------------------------------------------------------------------------------|---------------|--------|  
191 -| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 |  
192 -| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.1 |  
193 -| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.9 |  
194 -| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 |  
195 -| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.16 |  
196 -| [vueuse](https://vueuse.org//) | 常用工具集 | 8.9.4 |  
197 -| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 |  
198 -| [vue-router](https://router.vuejs.org/) | vue 路由 | 4.1.2 |  
199 -| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架 | 3.5.6 |  
200 -| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 |  
201 -| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.11 | 189 +| 框架 | 说明 | 版本 |
  190 +|----------------------------------------------------------------------|---------------------|--------|
  191 +| [Vue](https://staging-cn.vuejs.org/) | vue 框架 | 3.2.37 |
  192 +| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 3.0.2 |
  193 +| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.2.9 |
  194 +| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 4.7.4 |
  195 +| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.0.16 |
  196 +| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.1.10 |
  197 +| [windicss](https://cn.windicss.org/) | 下一代工具优先的 CSS 框架| 3.5.6 |
  198 +| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.1 |
202 199
203 ## 🐷 演示图 200 ## 🐷 演示图
204 201
yudao-ui-admin-vue3/package.json
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 "private": false, 6 "private": false,
7 "scripts": { 7 "scripts": {
8 "i": "pnpm install", 8 "i": "pnpm install",
9 - "dev": "vite --mode base", 9 + "dev": "vite --mode base --open",
10 "ts:check": "vue-tsc --noEmit", 10 "ts:check": "vue-tsc --noEmit",
11 "build:pro": "vite build --mode pro", 11 "build:pro": "vite build --mode pro",
12 "build:dev": "vite build --mode dev", 12 "build:dev": "vite build --mode dev",
yudao-ui-admin-vue3/src/locales/en.ts
@@ -171,15 +171,16 @@ export default { @@ -171,15 +171,16 @@ export default {
171 sunday: 'Sunday' 171 sunday: 'Sunday'
172 }, 172 },
173 workplace: { 173 workplace: {
174 - goodMorning: 'Good morning', 174 + welcome: 'Hello',
175 happyDay: 'Wish you happy every day!', 175 happyDay: 'Wish you happy every day!',
176 toady: `It's sunny today`, 176 toady: `It's sunny today`,
  177 + notice: 'Announcement',
177 project: 'Project', 178 project: 'Project',
178 access: 'Project access', 179 access: 'Project access',
179 toDo: 'To do', 180 toDo: 'To do',
180 introduction: 'A serious introduction', 181 introduction: 'A serious introduction',
181 more: 'More', 182 more: 'More',
182 - shortcutOperation: 'Shortcut operation', 183 + shortcutOperation: 'Quick entry',
183 operation: 'Operation', 184 operation: 'Operation',
184 index: 'Index', 185 index: 'Index',
185 personal: 'Personal', 186 personal: 'Personal',
yudao-ui-admin-vue3/src/locales/zh-CN.ts
@@ -171,15 +171,16 @@ export default { @@ -171,15 +171,16 @@ export default {
171 sunday: '周日' 171 sunday: '周日'
172 }, 172 },
173 workplace: { 173 workplace: {
174 - goodMorning: '早安', 174 + welcome: '你好',
175 happyDay: '祝你开心每一天!', 175 happyDay: '祝你开心每一天!',
176 toady: '今日晴', 176 toady: '今日晴',
  177 + notice: '通知公告',
177 project: '项目数', 178 project: '项目数',
178 access: '项目访问', 179 access: '项目访问',
179 toDo: '待办', 180 toDo: '待办',
180 introduction: '一个正经的简介', 181 introduction: '一个正经的简介',
181 more: '更多', 182 more: '更多',
182 - shortcutOperation: '快捷操作', 183 + shortcutOperation: '快捷入口',
183 operation: '操作', 184 operation: '操作',
184 index: '指数', 185 index: '指数',
185 personal: '个人', 186 personal: '个人',
yudao-ui-admin-vue3/src/views/Home/Index.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { ref, reactive } from 'vue'  
3 -import { set } from 'lodash-es'  
4 -import { EChartsOption } from 'echarts'  
5 -import { Echart } from '@/components/Echart' 2 +import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'
6 import { useI18n } from '@/hooks/web/useI18n' 3 import { useI18n } from '@/hooks/web/useI18n'
  4 +import { ref, reactive } from 'vue'
7 import { CountTo } from '@/components/CountTo' 5 import { CountTo } from '@/components/CountTo'
8 -import type { AnalysisTotalTypes } from './types'  
9 -import { useDesign } from '@/hooks/web/useDesign'  
10 -import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus' 6 +import { formatTime } from '@/utils'
  7 +import { Echart } from '@/components/Echart'
  8 +import { EChartsOption } from 'echarts'
  9 +import { radarOption } from './echarts-data'
  10 +import { Highlight } from '@/components/Highlight'
  11 +import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
  12 +import { set } from 'lodash-es'
  13 +import { useCache } from '@/hooks/web/useCache'
11 import { pieOptions, barOptions, lineOptions } from './echarts-data' 14 import { pieOptions, barOptions, lineOptions } from './echarts-data'
12 15
13 const { t } = useI18n() 16 const { t } = useI18n()
  17 +const { wsCache } = useCache()
14 const loading = ref(true) 18 const loading = ref(true)
15 -const { getPrefixCls } = useDesign()  
16 -const prefixCls = getPrefixCls('panel') 19 +const avatar = wsCache.get('user').user.avatar
  20 +const username = wsCache.get('user').user.nickname
17 const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption 21 const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
18 -  
19 -let totalState = reactive<AnalysisTotalTypes>({  
20 - users: 0,  
21 - messages: 0,  
22 - moneys: 0,  
23 - shoppings: 0 22 +// 获取统计数
  23 +let totalSate = reactive<WorkplaceTotal>({
  24 + project: 0,
  25 + access: 0,
  26 + todo: 0
24 }) 27 })
25 28
26 const getCount = async () => { 29 const getCount = async () => {
27 const data = { 30 const data = {
28 - users: 102400,  
29 - messages: 81212,  
30 - moneys: 9280,  
31 - shoppings: 13600 31 + project: 40,
  32 + access: 2340,
  33 + todo: 10
32 } 34 }
33 - totalState = Object.assign(totalState, data) 35 + totalSate = Object.assign(totalSate, data)
  36 +}
  37 +
  38 +// 获取项目数
  39 +let projects = reactive<Project[]>([])
  40 +const getProject = async () => {
  41 + const data = [
  42 + {
  43 + name: 'Github',
  44 + icon: 'akar-icons:github-fill',
  45 + message: 'workplace.introduction',
  46 + personal: 'Archer',
  47 + time: new Date()
  48 + },
  49 + {
  50 + name: 'Vue',
  51 + icon: 'logos:vue',
  52 + message: 'workplace.introduction',
  53 + personal: 'Archer',
  54 + time: new Date()
  55 + },
  56 + {
  57 + name: 'Angular',
  58 + icon: 'logos:angular-icon',
  59 + message: 'workplace.introduction',
  60 + personal: 'Archer',
  61 + time: new Date()
  62 + },
  63 + {
  64 + name: 'React',
  65 + icon: 'logos:react',
  66 + message: 'workplace.introduction',
  67 + personal: 'Archer',
  68 + time: new Date()
  69 + },
  70 + {
  71 + name: 'Webpack',
  72 + icon: 'logos:webpack',
  73 + message: 'workplace.introduction',
  74 + personal: 'Archer',
  75 + time: new Date()
  76 + },
  77 + {
  78 + name: 'Vite',
  79 + icon: 'vscode-icons:file-type-vite',
  80 + message: 'workplace.introduction',
  81 + personal: 'Archer',
  82 + time: new Date()
  83 + }
  84 + ]
  85 + projects = Object.assign(projects, data)
34 } 86 }
35 87
  88 +// 获取通知公告
  89 +let notice = reactive<Notice[]>([])
  90 +const getNotice = async () => {
  91 + const data = [
  92 + {
  93 + title: '系统升级版本',
  94 + type: '通知',
  95 + keys: ['通知', '升级'],
  96 + date: new Date()
  97 + },
  98 + {
  99 + title: '系统凌晨维护',
  100 + type: '公告',
  101 + keys: ['公告', '维护'],
  102 + date: new Date()
  103 + },
  104 + {
  105 + title: '系统升级版本',
  106 + type: '通知',
  107 + keys: ['通知', '升级'],
  108 + date: new Date()
  109 + },
  110 + {
  111 + title: '系统凌晨维护',
  112 + type: '公告',
  113 + keys: ['公告', '维护'],
  114 + date: new Date()
  115 + }
  116 + ]
  117 + notice = Object.assign(notice, data)
  118 +}
  119 +
  120 +// 获取快捷入口
  121 +let shortcut = reactive<Shortcut[]>([])
  122 +
  123 +const getShortcut = async () => {
  124 + const data = [
  125 + {
  126 + name: 'Github',
  127 + icon: 'akar-icons:github-fill',
  128 + url: 'github.io'
  129 + },
  130 + {
  131 + name: 'Vue',
  132 + icon: 'logos:vue',
  133 + url: 'vuejs.org'
  134 + },
  135 + {
  136 + name: 'Vite',
  137 + icon: 'vscode-icons:file-type-vite',
  138 + url: 'https://vitejs.dev/'
  139 + },
  140 + {
  141 + name: 'Angular',
  142 + icon: 'logos:angular-icon',
  143 + url: 'github.io'
  144 + },
  145 + {
  146 + name: 'React',
  147 + icon: 'logos:react',
  148 + url: 'github.io'
  149 + },
  150 + {
  151 + name: 'Webpack',
  152 + icon: 'logos:webpack',
  153 + url: 'github.io'
  154 + }
  155 + ]
  156 + shortcut = Object.assign(shortcut, data)
  157 +}
  158 +
  159 +// 获取指数
  160 +let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption
  161 +
  162 +const getRadar = async () => {
  163 + const data = [
  164 + { name: 'workplace.quote', max: 65, personal: 42, team: 50 },
  165 + { name: 'workplace.contribution', max: 160, personal: 30, team: 140 },
  166 + { name: 'workplace.hot', max: 300, personal: 20, team: 28 },
  167 + { name: 'workplace.yield', max: 130, personal: 35, team: 35 },
  168 + { name: 'workplace.follow', max: 100, personal: 80, team: 90 }
  169 + ]
  170 + set(
  171 + radarOptionData,
  172 + 'radar.indicator',
  173 + data.map((v) => {
  174 + return {
  175 + name: t(v.name),
  176 + max: v.max
  177 + }
  178 + })
  179 + )
  180 + set(radarOptionData, 'series', [
  181 + {
  182 + name: '指数',
  183 + type: 'radar',
  184 + data: [
  185 + {
  186 + value: data.map((v) => v.personal),
  187 + name: t('workplace.personal')
  188 + },
  189 + {
  190 + value: data.map((v) => v.team),
  191 + name: t('workplace.team')
  192 + }
  193 + ]
  194 + }
  195 + ])
  196 +}
36 // 用户来源 197 // 用户来源
37 const getUserAccessSource = async () => { 198 const getUserAccessSource = async () => {
38 const data = [ 199 const data = [
@@ -121,7 +282,16 @@ const getMonthlySales = async () =&gt; { @@ -121,7 +282,16 @@ const getMonthlySales = async () =&gt; {
121 } 282 }
122 283
123 const getAllApi = async () => { 284 const getAllApi = async () => {
124 - await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()]) 285 + await Promise.all([
  286 + getCount(),
  287 + getProject(),
  288 + getNotice(),
  289 + getShortcut(),
  290 + getRadar(),
  291 + getUserAccessSource(),
  292 + getWeeklyUserActivity(),
  293 + getMonthlySales()
  294 + ])
125 loading.value = false 295 loading.value = false
126 } 296 }
127 297
@@ -129,188 +299,177 @@ getAllApi() @@ -129,188 +299,177 @@ getAllApi()
129 </script> 299 </script>
130 300
131 <template> 301 <template>
132 - <el-row :gutter="20" justify="space-between" :class="prefixCls">  
133 - <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">  
134 - <el-card shadow="hover" class="mb-20px">  
135 - <el-skeleton :loading="loading" animated :rows="2">  
136 - <template #default>  
137 - <div :class="`${prefixCls}__item flex justify-between`"> 302 + <div>
  303 + <el-card shadow="never">
  304 + <el-skeleton :loading="loading" animated>
  305 + <el-row :gutter="20" justify="space-between">
  306 + <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
  307 + <div class="flex items-center">
  308 + <img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" />
138 <div> 309 <div>
139 - <div  
140 - :class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"  
141 - >  
142 - <Icon icon="svg-icon:peoples" :size="40" /> 310 + <div class="text-20px text-700">
  311 + {{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
  312 + </div>
  313 + <div class="mt-10px text-14px text-gray-500">
  314 + {{ t('workplace.toady') }},20℃ - 32℃!
143 </div> 315 </div>
144 </div> 316 </div>
145 - <div class="flex flex-col justify-between">  
146 - <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{  
147 - t('analysis.newUser')  
148 - }}</div> 317 + </div>
  318 + </el-col>
  319 + <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
  320 + <div class="flex h-70px items-center justify-end <sm:mt-10px">
  321 + <div class="px-8px text-right">
  322 + <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div>
149 <CountTo 323 <CountTo
150 - class="text-20px font-700 text-right" 324 + class="text-20px"
151 :start-val="0" 325 :start-val="0"
152 - :end-val="102400" 326 + :end-val="totalSate.project"
153 :duration="2600" 327 :duration="2600"
154 /> 328 />
155 </div> 329 </div>
156 - </div>  
157 - </template>  
158 - </el-skeleton>  
159 - </el-card>  
160 - </el-col>  
161 -  
162 - <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">  
163 - <el-card shadow="hover" class="mb-20px">  
164 - <el-skeleton :loading="loading" animated :rows="2">  
165 - <template #default>  
166 - <div :class="`${prefixCls}__item flex justify-between`">  
167 - <div>  
168 - <div  
169 - :class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"  
170 - >  
171 - <Icon icon="svg-icon:message" :size="40" />  
172 - </div>  
173 - </div>  
174 - <div class="flex flex-col justify-between">  
175 - <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{  
176 - t('analysis.unreadInformation')  
177 - }}</div> 330 + <el-divider direction="vertical" />
  331 + <div class="px-8px text-right">
  332 + <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div>
178 <CountTo 333 <CountTo
179 - class="text-20px font-700 text-right" 334 + class="text-20px"
180 :start-val="0" 335 :start-val="0"
181 - :end-val="81212" 336 + :end-val="totalSate.todo"
182 :duration="2600" 337 :duration="2600"
183 /> 338 />
184 </div> 339 </div>
185 - </div>  
186 - </template>  
187 - </el-skeleton>  
188 - </el-card>  
189 - </el-col>  
190 -  
191 - <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">  
192 - <el-card shadow="hover" class="mb-20px">  
193 - <el-skeleton :loading="loading" animated :rows="2">  
194 - <template #default>  
195 - <div :class="`${prefixCls}__item flex justify-between`">  
196 - <div>  
197 - <div  
198 - :class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"  
199 - >  
200 - <Icon icon="svg-icon:money" :size="40" />  
201 - </div>  
202 - </div>  
203 - <div class="flex flex-col justify-between">  
204 - <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{  
205 - t('analysis.transactionAmount')  
206 - }}</div> 340 + <el-divider direction="vertical" border-style="dashed" />
  341 + <div class="px-8px text-right">
  342 + <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div>
207 <CountTo 343 <CountTo
208 - class="text-20px font-700 text-right" 344 + class="text-20px"
209 :start-val="0" 345 :start-val="0"
210 - :end-val="9280" 346 + :end-val="totalSate.access"
211 :duration="2600" 347 :duration="2600"
212 /> 348 />
213 </div> 349 </div>
214 </div> 350 </div>
215 - </template> 351 + </el-col>
  352 + </el-row>
  353 + </el-skeleton>
  354 + </el-card>
  355 + </div>
  356 +
  357 + <el-row class="mt-10px" :gutter="20" justify="space-between">
  358 + <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">
  359 + <el-card shadow="never">
  360 + <template #header>
  361 + <div class="flex justify-between">
  362 + <span>{{ t('workplace.project') }}</span>
  363 + <el-link type="primary" :underline="false">{{ t('workplace.more') }}</el-link>
  364 + </div>
  365 + </template>
  366 + <el-skeleton :loading="loading" animated>
  367 + <el-row>
  368 + <el-col
  369 + v-for="(item, index) in projects"
  370 + :key="`card-${index}`"
  371 + :xl="8"
  372 + :lg="8"
  373 + :md="12"
  374 + :sm="24"
  375 + :xs="24"
  376 + >
  377 + <el-card shadow="hover">
  378 + <div class="flex items-center">
  379 + <Icon :icon="item.icon" :size="25" class="mr-10px" />
  380 + <span class="text-16px">{{ item.name }}</span>
  381 + </div>
  382 + <div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>
  383 + <div class="mt-20px text-12px text-gray-400 flex justify-between">
  384 + <span>{{ item.personal }}</span>
  385 + <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
  386 + </div>
  387 + </el-card>
  388 + </el-col>
  389 + </el-row>
216 </el-skeleton> 390 </el-skeleton>
217 </el-card> 391 </el-card>
218 - </el-col>  
219 392
220 - <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">  
221 - <el-card shadow="hover" class="mb-20px">  
222 - <el-skeleton :loading="loading" animated :rows="2">  
223 - <template #default>  
224 - <div :class="`${prefixCls}__item flex justify-between`">  
225 - <div>  
226 - <div  
227 - :class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"  
228 - >  
229 - <Icon icon="svg-icon:shopping" :size="40" />  
230 - </div>  
231 - </div>  
232 - <div class="flex flex-col justify-between">  
233 - <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{  
234 - t('analysis.totalShopping')  
235 - }}</div>  
236 - <CountTo  
237 - class="text-20px font-700 text-right"  
238 - :start-val="0"  
239 - :end-val="13600"  
240 - :duration="2600"  
241 - />  
242 - </div>  
243 - </div>  
244 - </template> 393 + <el-card shadow="never" class="mt-10px">
  394 + <el-skeleton :loading="loading" animated>
  395 + <el-row :gutter="20" justify="space-between">
  396 + <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
  397 + <el-card shadow="hover" class="mb-20px">
  398 + <el-skeleton :loading="loading" animated>
  399 + <Echart :options="pieOptionsData" :height="300" />
  400 + </el-skeleton>
  401 + </el-card>
  402 + </el-col>
  403 + <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
  404 + <el-card shadow="hover" class="mb-20px">
  405 + <el-skeleton :loading="loading" animated>
  406 + <Echart :options="barOptionsData" :height="300" />
  407 + </el-skeleton>
  408 + </el-card>
  409 + </el-col>
  410 + <el-col :span="24">
  411 + <el-card shadow="hover" class="mb-20px">
  412 + <el-skeleton :loading="loading" animated :rows="4">
  413 + <Echart :options="lineOptionsData" :height="350" />
  414 + </el-skeleton>
  415 + </el-card>
  416 + </el-col>
  417 + </el-row>
245 </el-skeleton> 418 </el-skeleton>
246 </el-card> 419 </el-card>
247 </el-col> 420 </el-col>
248 - </el-row>  
249 - <el-row :gutter="20" justify="space-between">  
250 - <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">  
251 - <el-card shadow="hover" class="mb-20px"> 421 + <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">
  422 + <el-card shadow="never">
  423 + <template #header>
  424 + <span>{{ t('workplace.shortcutOperation') }}</span>
  425 + </template>
252 <el-skeleton :loading="loading" animated> 426 <el-skeleton :loading="loading" animated>
253 - <Echart :options="pieOptionsData" :height="300" /> 427 + <el-row>
  428 + <el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-20px">
  429 + <div class="flex items-center">
  430 + <Icon :icon="item.icon" class="mr-10px" />
  431 + <el-link type="default" :underline="false" :href="item.url">
  432 + {{ item.name }}
  433 + </el-link>
  434 + </div>
  435 + </el-col>
  436 + </el-row>
254 </el-skeleton> 437 </el-skeleton>
255 </el-card> 438 </el-card>
256 - </el-col>  
257 - <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">  
258 - <el-card shadow="hover" class="mb-20px"> 439 + <el-card shadow="never" class="mt-10px">
  440 + <template #header>
  441 + <div class="flex justify-between">
  442 + <span>{{ t('workplace.notice') }}</span>
  443 + <el-link type="primary" :underline="false">{{ t('workplace.more') }}</el-link>
  444 + </div>
  445 + </template>
259 <el-skeleton :loading="loading" animated> 446 <el-skeleton :loading="loading" animated>
260 - <Echart :options="barOptionsData" :height="300" /> 447 + <div v-for="(item, index) in notice" :key="`dynamics-${index}`">
  448 + <div class="flex items-center">
  449 + <img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" />
  450 + <div>
  451 + <div class="text-14px">
  452 + <Highlight :keys="item.keys.map((v) => t(v))">
  453 + {{ item.type }} : {{ item.title }}
  454 + </Highlight>
  455 + </div>
  456 + <div class="mt-15px text-12px text-gray-400">
  457 + {{ formatTime(item.date, 'yyyy-MM-dd') }}
  458 + </div>
  459 + </div>
  460 + </div>
  461 + <el-divider />
  462 + </div>
261 </el-skeleton> 463 </el-skeleton>
262 </el-card> 464 </el-card>
263 - </el-col>  
264 - <el-col :span="24">  
265 - <el-card shadow="hover" class="mb-20px">  
266 - <el-skeleton :loading="loading" animated :rows="4">  
267 - <Echart :options="lineOptionsData" :height="350" /> 465 + <el-card shadow="never" class="mt-10px">
  466 + <template #header>
  467 + <span>{{ t('workplace.index') }}</span>
  468 + </template>
  469 + <el-skeleton :loading="loading" animated>
  470 + <Echart :options="radarOptionData" :height="400" />
268 </el-skeleton> 471 </el-skeleton>
269 </el-card> 472 </el-card>
270 </el-col> 473 </el-col>
271 </el-row> 474 </el-row>
272 </template> 475 </template>
273 -<style lang="less" scoped>  
274 -@prefix-cls: ~'@{namespace}-panel';  
275 -  
276 -.@{prefix-cls} {  
277 - &__item {  
278 - &--peoples {  
279 - color: #40c9c6;  
280 - }  
281 -  
282 - &--message {  
283 - color: #36a3f7;  
284 - }  
285 -  
286 - &--money {  
287 - color: #f4516c;  
288 - }  
289 -  
290 - &--shopping {  
291 - color: #34bfa3;  
292 - }  
293 -  
294 - &:hover {  
295 - :deep(.@{namespace}-icon) {  
296 - color: #fff !important;  
297 - }  
298 - .@{prefix-cls}__item--icon {  
299 - transition: all 0.38s ease-out;  
300 - }  
301 - .@{prefix-cls}__item--peoples {  
302 - background: #40c9c6;  
303 - }  
304 - .@{prefix-cls}__item--message {  
305 - background: #36a3f7;  
306 - }  
307 - .@{prefix-cls}__item--money {  
308 - background: #f4516c;  
309 - }  
310 - .@{prefix-cls}__item--shopping {  
311 - background: #34bfa3;  
312 - }  
313 - }  
314 - }  
315 -}  
316 -</style>  
yudao-ui-admin-vue3/src/views/Home/Index2.vue
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 -import { useTimeAgo } from '@/hooks/web/useTimeAgo'  
3 -import { ElRow, ElCol, ElSkeleton, ElCard, ElDivider, ElLink } from 'element-plus'  
4 -import { useI18n } from '@/hooks/web/useI18n'  
5 import { ref, reactive } from 'vue' 2 import { ref, reactive } from 'vue'
6 -import { CountTo } from '@/components/CountTo'  
7 -import { formatTime } from '@/utils'  
8 -import { Echart } from '@/components/Echart'  
9 -import { EChartsOption } from 'echarts'  
10 -import { radarOption } from './echarts-data'  
11 -import { Highlight } from '@/components/Highlight'  
12 -import type { WorkplaceTotal, Project, Dynamic, Team } from './types'  
13 import { set } from 'lodash-es' 3 import { set } from 'lodash-es'
14 -import { useCache } from '@/hooks/web/useCache' 4 +import { EChartsOption } from 'echarts'
  5 +import { Echart } from '@/components/Echart'
  6 +import { useI18n } from '@/hooks/web/useI18n'
  7 +import { CountTo } from '@/components/CountTo'
  8 +import type { AnalysisTotalTypes } from './types'
  9 +import { useDesign } from '@/hooks/web/useDesign'
  10 +import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
  11 +import { pieOptions, barOptions, lineOptions } from './echarts-data'
15 12
16 const { t } = useI18n() 13 const { t } = useI18n()
17 -const { wsCache } = useCache()  
18 const loading = ref(true) 14 const loading = ref(true)
19 -const avatar = wsCache.get('user').user.avatar  
20 -const username = wsCache.get('user').user.nickname  
21 -// 获取统计数  
22 -let totalSate = reactive<WorkplaceTotal>({  
23 - project: 0,  
24 - access: 0,  
25 - todo: 0 15 +const { getPrefixCls } = useDesign()
  16 +const prefixCls = getPrefixCls('panel')
  17 +const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
  18 +
  19 +let totalState = reactive<AnalysisTotalTypes>({
  20 + users: 0,
  21 + messages: 0,
  22 + moneys: 0,
  23 + shoppings: 0
26 }) 24 })
27 25
28 const getCount = async () => { 26 const getCount = async () => {
29 const data = { 27 const data = {
30 - project: 40,  
31 - access: 2340,  
32 - todo: 10 28 + users: 102400,
  29 + messages: 81212,
  30 + moneys: 9280,
  31 + shoppings: 13600
33 } 32 }
34 - totalSate = Object.assign(totalSate, data) 33 + totalState = Object.assign(totalState, data)
35 } 34 }
36 35
37 -let projects = reactive<Project[]>([])  
38 -  
39 -// 获取项目数  
40 -const getProject = async () => { 36 +// 用户来源
  37 +const getUserAccessSource = async () => {
41 const data = [ 38 const data = [
42 - {  
43 - name: 'Github',  
44 - icon: 'akar-icons:github-fill',  
45 - message: 'workplace.introduction',  
46 - personal: 'Archer',  
47 - time: new Date()  
48 - },  
49 - {  
50 - name: 'Vue',  
51 - icon: 'logos:vue',  
52 - message: 'workplace.introduction',  
53 - personal: 'Archer',  
54 - time: new Date()  
55 - },  
56 - {  
57 - name: 'Angular',  
58 - icon: 'logos:angular-icon',  
59 - message: 'workplace.introduction',  
60 - personal: 'Archer',  
61 - time: new Date()  
62 - },  
63 - {  
64 - name: 'React',  
65 - icon: 'logos:react',  
66 - message: 'workplace.introduction',  
67 - personal: 'Archer',  
68 - time: new Date()  
69 - },  
70 - {  
71 - name: 'Webpack',  
72 - icon: 'logos:webpack',  
73 - message: 'workplace.introduction',  
74 - personal: 'Archer',  
75 - time: new Date()  
76 - },  
77 - {  
78 - name: 'Vite',  
79 - icon: 'vscode-icons:file-type-vite',  
80 - message: 'workplace.introduction',  
81 - personal: 'Archer',  
82 - time: new Date()  
83 - } 39 + { value: 335, name: 'analysis.directAccess' },
  40 + { value: 310, name: 'analysis.mailMarketing' },
  41 + { value: 234, name: 'analysis.allianceAdvertising' },
  42 + { value: 135, name: 'analysis.videoAdvertising' },
  43 + { value: 1548, name: 'analysis.searchEngines' }
84 ] 44 ]
85 - projects = Object.assign(projects, data) 45 + set(
  46 + pieOptionsData,
  47 + 'legend.data',
  48 + data.map((v) => t(v.name))
  49 + )
  50 + set(pieOptionsData, 'series.data', data)
86 } 51 }
  52 +const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
87 53
88 -// 获取动态  
89 -let dynamics = reactive<Dynamic[]>([])  
90 -  
91 -const getDynamic = async () => { 54 +// 周活跃量
  55 +const getWeeklyUserActivity = async () => {
92 const data = [ 56 const data = [
93 - {  
94 - keys: ['workplace.push', 'Github'],  
95 - time: new Date()  
96 - },  
97 - {  
98 - keys: ['workplace.push', 'Github'],  
99 - time: new Date()  
100 - },  
101 - {  
102 - keys: ['workplace.push', 'Github'],  
103 - time: new Date()  
104 - },  
105 - {  
106 - keys: ['workplace.push', 'Github'],  
107 - time: new Date()  
108 - },  
109 - {  
110 - keys: ['workplace.push', 'Github'],  
111 - time: new Date()  
112 - },  
113 - {  
114 - keys: ['workplace.push', 'Github'],  
115 - time: new Date()  
116 - } 57 + { value: 13253, name: 'analysis.monday' },
  58 + { value: 34235, name: 'analysis.tuesday' },
  59 + { value: 26321, name: 'analysis.wednesday' },
  60 + { value: 12340, name: 'analysis.thursday' },
  61 + { value: 24643, name: 'analysis.friday' },
  62 + { value: 1322, name: 'analysis.saturday' },
  63 + { value: 1324, name: 'analysis.sunday' }
117 ] 64 ]
118 - dynamics = Object.assign(dynamics, data)  
119 -}  
120 -  
121 -// 获取团队  
122 -let team = reactive<Team[]>([])  
123 -  
124 -const getTeam = async () => {  
125 - const data = [  
126 - {  
127 - name: 'Github',  
128 - icon: 'akar-icons:github-fill'  
129 - },  
130 - {  
131 - name: 'Vue',  
132 - icon: 'logos:vue'  
133 - },  
134 - {  
135 - name: 'Angular',  
136 - icon: 'logos:angular-icon'  
137 - },  
138 - {  
139 - name: 'React',  
140 - icon: 'logos:react'  
141 - },  
142 - {  
143 - name: 'Webpack',  
144 - icon: 'logos:webpack'  
145 - }, 65 + set(
  66 + barOptionsData,
  67 + 'xAxis.data',
  68 + data.map((v) => t(v.name))
  69 + )
  70 + set(barOptionsData, 'series', [
146 { 71 {
147 - name: 'Vite',  
148 - icon: 'vscode-icons:file-type-vite' 72 + name: t('analysis.activeQuantity'),
  73 + data: data.map((v) => v.value),
  74 + type: 'bar'
149 } 75 }
150 - ]  
151 - team = Object.assign(team, data) 76 + ])
152 } 77 }
153 78
154 -// 获取指数  
155 -let radarOptionData = reactive<EChartsOption>(radarOption) as EChartsOption 79 +const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
156 80
157 -const getRadar = async () => { 81 +// 每月销售总额
  82 +const getMonthlySales = async () => {
158 const data = [ 83 const data = [
159 - { name: 'workplace.quote', max: 65, personal: 42, team: 50 },  
160 - { name: 'workplace.contribution', max: 160, personal: 30, team: 140 },  
161 - { name: 'workplace.hot', max: 300, personal: 20, team: 28 },  
162 - { name: 'workplace.yield', max: 130, personal: 35, team: 35 },  
163 - { name: 'workplace.follow', max: 100, personal: 80, team: 90 } 84 + { estimate: 100, actual: 120, name: 'analysis.january' },
  85 + { estimate: 120, actual: 82, name: 'analysis.february' },
  86 + { estimate: 161, actual: 91, name: 'analysis.march' },
  87 + { estimate: 134, actual: 154, name: 'analysis.april' },
  88 + { estimate: 105, actual: 162, name: 'analysis.may' },
  89 + { estimate: 160, actual: 140, name: 'analysis.june' },
  90 + { estimate: 165, actual: 145, name: 'analysis.july' },
  91 + { estimate: 114, actual: 250, name: 'analysis.august' },
  92 + { estimate: 163, actual: 134, name: 'analysis.september' },
  93 + { estimate: 185, actual: 56, name: 'analysis.october' },
  94 + { estimate: 118, actual: 99, name: 'analysis.november' },
  95 + { estimate: 123, actual: 123, name: 'analysis.december' }
164 ] 96 ]
165 set( 97 set(
166 - radarOptionData,  
167 - 'radar.indicator',  
168 - data.map((v) => {  
169 - return {  
170 - name: t(v.name),  
171 - max: v.max  
172 - }  
173 - }) 98 + lineOptionsData,
  99 + 'xAxis.data',
  100 + data.map((v) => t(v.name))
174 ) 101 )
175 - set(radarOptionData, 'series', [ 102 + set(lineOptionsData, 'series', [
176 { 103 {
177 - name: '指数',  
178 - type: 'radar',  
179 - data: [  
180 - {  
181 - value: data.map((v) => v.personal),  
182 - name: t('workplace.personal')  
183 - },  
184 - {  
185 - value: data.map((v) => v.team),  
186 - name: t('workplace.team')  
187 - }  
188 - ] 104 + name: t('analysis.estimate'),
  105 + smooth: true,
  106 + type: 'line',
  107 + data: data.map((v) => v.estimate),
  108 + animationDuration: 2800,
  109 + animationEasing: 'cubicInOut'
  110 + },
  111 + {
  112 + name: t('analysis.actual'),
  113 + smooth: true,
  114 + type: 'line',
  115 + itemStyle: {},
  116 + data: data.map((v) => v.actual),
  117 + animationDuration: 2800,
  118 + animationEasing: 'quadraticOut'
189 } 119 }
190 ]) 120 ])
191 } 121 }
192 122
193 const getAllApi = async () => { 123 const getAllApi = async () => {
194 - await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()]) 124 + await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
195 loading.value = false 125 loading.value = false
196 } 126 }
197 127
@@ -199,173 +129,188 @@ getAllApi() @@ -199,173 +129,188 @@ getAllApi()
199 </script> 129 </script>
200 130
201 <template> 131 <template>
202 - <div>  
203 - <el-card shadow="never">  
204 - <el-skeleton :loading="loading" animated>  
205 - <el-row :gutter="20" justify="space-between">  
206 - <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">  
207 - <div class="flex items-center">  
208 - <img :src="avatar" alt="" class="w-70px h-70px rounded-[50%] mr-20px" /> 132 + <el-row :gutter="20" justify="space-between" :class="prefixCls">
  133 + <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
  134 + <el-card shadow="hover" class="mb-20px">
  135 + <el-skeleton :loading="loading" animated :rows="2">
  136 + <template #default>
  137 + <div :class="`${prefixCls}__item flex justify-between`">
209 <div> 138 <div>
210 - <div class="text-20px text-700">  
211 - {{ t('workplace.goodMorning') }} {{ username }} {{ t('workplace.happyDay') }}  
212 - </div>  
213 - <div class="mt-10px text-14px text-gray-500">  
214 - {{ t('workplace.toady') }},20℃ - 32℃! 139 + <div
  140 + :class="`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`"
  141 + >
  142 + <Icon icon="svg-icon:peoples" :size="40" />
215 </div> 143 </div>
216 </div> 144 </div>
217 - </div>  
218 - </el-col>  
219 - <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">  
220 - <div class="flex h-70px items-center justify-end <sm:mt-20px">  
221 - <div class="px-8px text-right">  
222 - <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.project') }}</div> 145 + <div class="flex flex-col justify-between">
  146 + <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
  147 + t('analysis.newUser')
  148 + }}</div>
223 <CountTo 149 <CountTo
224 - class="text-20px" 150 + class="text-20px font-700 text-right"
225 :start-val="0" 151 :start-val="0"
226 - :end-val="totalSate.project" 152 + :end-val="102400"
227 :duration="2600" 153 :duration="2600"
228 /> 154 />
229 </div> 155 </div>
230 - <el-divider direction="vertical" />  
231 - <div class="px-8px text-right">  
232 - <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.toDo') }}</div> 156 + </div>
  157 + </template>
  158 + </el-skeleton>
  159 + </el-card>
  160 + </el-col>
  161 +
  162 + <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
  163 + <el-card shadow="hover" class="mb-20px">
  164 + <el-skeleton :loading="loading" animated :rows="2">
  165 + <template #default>
  166 + <div :class="`${prefixCls}__item flex justify-between`">
  167 + <div>
  168 + <div
  169 + :class="`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`"
  170 + >
  171 + <Icon icon="svg-icon:message" :size="40" />
  172 + </div>
  173 + </div>
  174 + <div class="flex flex-col justify-between">
  175 + <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
  176 + t('analysis.unreadInformation')
  177 + }}</div>
233 <CountTo 178 <CountTo
234 - class="text-20px" 179 + class="text-20px font-700 text-right"
235 :start-val="0" 180 :start-val="0"
236 - :end-val="totalSate.todo" 181 + :end-val="81212"
237 :duration="2600" 182 :duration="2600"
238 /> 183 />
239 </div> 184 </div>
240 - <el-divider direction="vertical" border-style="dashed" />  
241 - <div class="px-8px text-right">  
242 - <div class="text-14px text-gray-400 mb-20px">{{ t('workplace.access') }}</div> 185 + </div>
  186 + </template>
  187 + </el-skeleton>
  188 + </el-card>
  189 + </el-col>
  190 +
  191 + <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
  192 + <el-card shadow="hover" class="mb-20px">
  193 + <el-skeleton :loading="loading" animated :rows="2">
  194 + <template #default>
  195 + <div :class="`${prefixCls}__item flex justify-between`">
  196 + <div>
  197 + <div
  198 + :class="`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`"
  199 + >
  200 + <Icon icon="svg-icon:money" :size="40" />
  201 + </div>
  202 + </div>
  203 + <div class="flex flex-col justify-between">
  204 + <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
  205 + t('analysis.transactionAmount')
  206 + }}</div>
243 <CountTo 207 <CountTo
244 - class="text-20px" 208 + class="text-20px font-700 text-right"
245 :start-val="0" 209 :start-val="0"
246 - :end-val="totalSate.access" 210 + :end-val="9280"
247 :duration="2600" 211 :duration="2600"
248 /> 212 />
249 </div> 213 </div>
250 </div> 214 </div>
251 - </el-col>  
252 - </el-row>  
253 - </el-skeleton>  
254 - </el-card>  
255 - </div>  
256 -  
257 - <el-row class="mt-20px" :gutter="20" justify="space-between">  
258 - <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-20px">  
259 - <el-card shadow="never">  
260 - <template #header>  
261 - <div class="flex justify-between">  
262 - <span>{{ t('workplace.project') }}</span>  
263 - <ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>  
264 - </div>  
265 - </template>  
266 - <el-skeleton :loading="loading" animated>  
267 - <el-row>  
268 - <el-col  
269 - v-for="(item, index) in projects"  
270 - :key="`card-${index}`"  
271 - :xl="8"  
272 - :lg="8"  
273 - :md="12"  
274 - :sm="24"  
275 - :xs="24"  
276 - >  
277 - <el-card shadow="hover">  
278 - <div class="flex items-center">  
279 - <Icon :icon="item.icon" :size="25" class="mr-10px" />  
280 - <span class="text-16px">{{ item.name }}</span>  
281 - </div>  
282 - <div class="mt-15px text-14px text-gray-400">{{ t(item.message) }}</div>  
283 - <div class="mt-20px text-12px text-gray-400 flex justify-between">  
284 - <span>{{ item.personal }}</span>  
285 - <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>  
286 - </div>  
287 - </el-card>  
288 - </el-col>  
289 - </el-row> 215 + </template>
290 </el-skeleton> 216 </el-skeleton>
291 </el-card> 217 </el-card>
  218 + </el-col>
292 219
293 - <el-card shadow="never" class="mt-20px">  
294 - <template #header>  
295 - <div class="flex justify-between">  
296 - <span>{{ t('workplace.dynamic') }}</span>  
297 - <ElLink type="primary" :underline="false">{{ t('workplace.more') }}</ElLink>  
298 - </div>  
299 - </template>  
300 - <el-skeleton :loading="loading" animated>  
301 - <div v-for="(item, index) in dynamics" :key="`dynamics-${index}`">  
302 - <div class="flex items-center">  
303 - <img :src="avatar" alt="" class="w-35px h-35px rounded-[50%] mr-20px" /> 220 + <el-col :xl="6" :lg="6" :md="12" :sm="12" :xs="24">
  221 + <el-card shadow="hover" class="mb-20px">
  222 + <el-skeleton :loading="loading" animated :rows="2">
  223 + <template #default>
  224 + <div :class="`${prefixCls}__item flex justify-between`">
304 <div> 225 <div>
305 - <div class="text-14px">  
306 - <Highlight :keys="item.keys.map((v) => t(v))">  
307 - {{ username }} {{ t('workplace.pushCode') }}  
308 - </Highlight>  
309 - </div>  
310 - <div class="mt-15px text-12px text-gray-400">  
311 - {{ useTimeAgo(item.time) }} 226 + <div
  227 + :class="`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`"
  228 + >
  229 + <Icon icon="svg-icon:shopping" :size="40" />
312 </div> 230 </div>
313 </div> 231 </div>
  232 + <div class="flex flex-col justify-between">
  233 + <div :class="`${prefixCls}__item--text text-16px text-gray-500 text-right`">{{
  234 + t('analysis.totalShopping')
  235 + }}</div>
  236 + <CountTo
  237 + class="text-20px font-700 text-right"
  238 + :start-val="0"
  239 + :end-val="13600"
  240 + :duration="2600"
  241 + />
  242 + </div>
314 </div> 243 </div>
315 - <el-divider />  
316 - </div> 244 + </template>
317 </el-skeleton> 245 </el-skeleton>
318 </el-card> 246 </el-card>
319 </el-col> 247 </el-col>
320 - <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-20px">  
321 - <el-card shadow="never">  
322 - <template #header>  
323 - <span>{{ t('workplace.shortcutOperation') }}</span>  
324 - </template> 248 + </el-row>
  249 + <el-row :gutter="20" justify="space-between">
  250 + <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
  251 + <el-card shadow="hover" class="mb-20px">
325 <el-skeleton :loading="loading" animated> 252 <el-skeleton :loading="loading" animated>
326 - <el-col  
327 - v-for="item in 9"  
328 - :key="`card-${item}`"  
329 - :xl="12"  
330 - :lg="12"  
331 - :md="12"  
332 - :sm="24"  
333 - :xs="24"  
334 - class="mb-10px"  
335 - >  
336 - <ElLink type="default" :underline="false">  
337 - {{ t('workplace.operation') }}{{ item }}  
338 - </ElLink>  
339 - </el-col> 253 + <Echart :options="pieOptionsData" :height="300" />
340 </el-skeleton> 254 </el-skeleton>
341 </el-card> 255 </el-card>
342 -  
343 - <el-card shadow="never" class="mt-20px">  
344 - <template #header>  
345 - <span>{{ t('workplace.index') }}</span>  
346 - </template> 256 + </el-col>
  257 + <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
  258 + <el-card shadow="hover" class="mb-20px">
347 <el-skeleton :loading="loading" animated> 259 <el-skeleton :loading="loading" animated>
348 - <Echart :options="radarOptionData" :height="400" /> 260 + <Echart :options="barOptionsData" :height="300" />
349 </el-skeleton> 261 </el-skeleton>
350 </el-card> 262 </el-card>
351 -  
352 - <el-card shadow="never" class="mt-20px">  
353 - <template #header>  
354 - <span>{{ t('workplace.team') }}</span>  
355 - </template>  
356 - <el-skeleton :loading="loading" animated>  
357 - <el-row>  
358 - <el-col v-for="item in team" :key="`team-${item.name}`" :span="12" class="mb-20px">  
359 - <div class="flex items-center">  
360 - <Icon :icon="item.icon" class="mr-10px" />  
361 - <ElLink type="default" :underline="false">  
362 - {{ item.name }}  
363 - </ElLink>  
364 - </div>  
365 - </el-col>  
366 - </el-row> 263 + </el-col>
  264 + <el-col :span="24">
  265 + <el-card shadow="hover" class="mb-20px">
  266 + <el-skeleton :loading="loading" animated :rows="4">
  267 + <Echart :options="lineOptionsData" :height="350" />
367 </el-skeleton> 268 </el-skeleton>
368 </el-card> 269 </el-card>
369 </el-col> 270 </el-col>
370 </el-row> 271 </el-row>
371 </template> 272 </template>
  273 +<style lang="less" scoped>
  274 +@prefix-cls: ~'@{namespace}-panel';
  275 +
  276 +.@{prefix-cls} {
  277 + &__item {
  278 + &--peoples {
  279 + color: #40c9c6;
  280 + }
  281 +
  282 + &--message {
  283 + color: #36a3f7;
  284 + }
  285 +
  286 + &--money {
  287 + color: #f4516c;
  288 + }
  289 +
  290 + &--shopping {
  291 + color: #34bfa3;
  292 + }
  293 +
  294 + &:hover {
  295 + :deep(.@{namespace}-icon) {
  296 + color: #fff !important;
  297 + }
  298 + .@{prefix-cls}__item--icon {
  299 + transition: all 0.38s ease-out;
  300 + }
  301 + .@{prefix-cls}__item--peoples {
  302 + background: #40c9c6;
  303 + }
  304 + .@{prefix-cls}__item--message {
  305 + background: #36a3f7;
  306 + }
  307 + .@{prefix-cls}__item--money {
  308 + background: #f4516c;
  309 + }
  310 + .@{prefix-cls}__item--shopping {
  311 + background: #34bfa3;
  312 + }
  313 + }
  314 + }
  315 +}
  316 +</style>
yudao-ui-admin-vue3/src/views/Home/types.ts
@@ -12,14 +12,17 @@ export type Project = { @@ -12,14 +12,17 @@ export type Project = {
12 time: Date | number | string 12 time: Date | number | string
13 } 13 }
14 14
15 -export type Dynamic = { 15 +export type Notice = {
  16 + title: string
  17 + type: string
16 keys: string[] 18 keys: string[]
17 - time: Date | number | string 19 + date: Date | number | string
18 } 20 }
19 21
20 -export type Team = { 22 +export type Shortcut = {
21 name: string 23 name: string
22 icon: string 24 icon: string
  25 + url: string
23 } 26 }
24 27
25 export type RadarData = { 28 export type RadarData = {