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 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 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 180 ### vue2 前端
181 181  
... ... @@ -186,19 +186,16 @@ ps:核心功能已经实现,正在对接微信小程序中...
186 186  
187 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 6 "private": false,
7 7 "scripts": {
8 8 "i": "pnpm install",
9   - "dev": "vite --mode base",
  9 + "dev": "vite --mode base --open",
10 10 "ts:check": "vue-tsc --noEmit",
11 11 "build:pro": "vite build --mode pro",
12 12 "build:dev": "vite build --mode dev",
... ...
yudao-ui-admin-vue3/src/locales/en.ts
... ... @@ -171,15 +171,16 @@ export default {
171 171 sunday: 'Sunday'
172 172 },
173 173 workplace: {
174   - goodMorning: 'Good morning',
  174 + welcome: 'Hello',
175 175 happyDay: 'Wish you happy every day!',
176 176 toady: `It's sunny today`,
  177 + notice: 'Announcement',
177 178 project: 'Project',
178 179 access: 'Project access',
179 180 toDo: 'To do',
180 181 introduction: 'A serious introduction',
181 182 more: 'More',
182   - shortcutOperation: 'Shortcut operation',
  183 + shortcutOperation: 'Quick entry',
183 184 operation: 'Operation',
184 185 index: 'Index',
185 186 personal: 'Personal',
... ...
yudao-ui-admin-vue3/src/locales/zh-CN.ts
... ... @@ -171,15 +171,16 @@ export default {
171 171 sunday: '周日'
172 172 },
173 173 workplace: {
174   - goodMorning: '早安',
  174 + welcome: '你好',
175 175 happyDay: '祝你开心每一天!',
176 176 toady: '今日晴',
  177 + notice: '通知公告',
177 178 project: '项目数',
178 179 access: '项目访问',
179 180 toDo: '待办',
180 181 introduction: '一个正经的简介',
181 182 more: '更多',
182   - shortcutOperation: '快捷操作',
  183 + shortcutOperation: '快捷入口',
183 184 operation: '操作',
184 185 index: '指数',
185 186 personal: '个人',
... ...
yudao-ui-admin-vue3/src/views/Home/Index.vue
1 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 3 import { useI18n } from '@/hooks/web/useI18n'
  4 +import { ref, reactive } from 'vue'
7 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 14 import { pieOptions, barOptions, lineOptions } from './echarts-data'
12 15  
13 16 const { t } = useI18n()
  17 +const { wsCache } = useCache()
14 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 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 29 const getCount = async () => {
27 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 198 const getUserAccessSource = async () => {
38 199 const data = [
... ... @@ -121,7 +282,16 @@ const getMonthlySales = async () =&gt; {
121 282 }
122 283  
123 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 295 loading.value = false
126 296 }
127 297  
... ... @@ -129,188 +299,177 @@ getAllApi()
129 299 </script>
130 300  
131 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 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 315 </div>
144 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 323 <CountTo
150   - class="text-20px font-700 text-right"
  324 + class="text-20px"
151 325 :start-val="0"
152   - :end-val="102400"
  326 + :end-val="totalSate.project"
153 327 :duration="2600"
154 328 />
155 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 333 <CountTo
179   - class="text-20px font-700 text-right"
  334 + class="text-20px"
180 335 :start-val="0"
181   - :end-val="81212"
  336 + :end-val="totalSate.todo"
182 337 :duration="2600"
183 338 />
184 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 343 <CountTo
208   - class="text-20px font-700 text-right"
  344 + class="text-20px"
209 345 :start-val="0"
210   - :end-val="9280"
  346 + :end-val="totalSate.access"
211 347 :duration="2600"
212 348 />
213 349 </div>
214 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 390 </el-skeleton>
217 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 418 </el-skeleton>
246 419 </el-card>
247 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 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 437 </el-skeleton>
255 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 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 463 </el-skeleton>
262 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 471 </el-skeleton>
269 472 </el-card>
270 473 </el-col>
271 474 </el-row>
272 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 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 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 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 13 const { t } = useI18n()
17   -const { wsCache } = useCache()
18 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 26 const getCount = async () => {
29 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 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 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 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 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 123 const getAllApi = async () => {
194   - await Promise.all([getCount(), getProject(), getDynamic(), getTeam(), getRadar()])
  124 + await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
195 125 loading.value = false
196 126 }
197 127  
... ... @@ -199,173 +129,188 @@ getAllApi()
199 129 </script>
200 130  
201 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 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 143 </div>
216 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 149 <CountTo
224   - class="text-20px"
  150 + class="text-20px font-700 text-right"
225 151 :start-val="0"
226   - :end-val="totalSate.project"
  152 + :end-val="102400"
227 153 :duration="2600"
228 154 />
229 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 178 <CountTo
234   - class="text-20px"
  179 + class="text-20px font-700 text-right"
235 180 :start-val="0"
236   - :end-val="totalSate.todo"
  181 + :end-val="81212"
237 182 :duration="2600"
238 183 />
239 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 207 <CountTo
244   - class="text-20px"
  208 + class="text-20px font-700 text-right"
245 209 :start-val="0"
246   - :end-val="totalSate.access"
  210 + :end-val="9280"
247 211 :duration="2600"
248 212 />
249 213 </div>
250 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 216 </el-skeleton>
291 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 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 230 </div>
313 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 243 </div>
315   - <el-divider />
316   - </div>
  244 + </template>
317 245 </el-skeleton>
318 246 </el-card>
319 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 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 254 </el-skeleton>
341 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 259 <el-skeleton :loading="loading" animated>
348   - <Echart :options="radarOptionData" :height="400" />
  260 + <Echart :options="barOptionsData" :height="300" />
349 261 </el-skeleton>
350 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 268 </el-skeleton>
368 269 </el-card>
369 270 </el-col>
370 271 </el-row>
371 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 12 time: Date | number | string
13 13 }
14 14  
15   -export type Dynamic = {
  15 +export type Notice = {
  16 + title: string
  17 + type: string
16 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 23 name: string
22 24 icon: string
  25 + url: string
23 26 }
24 27  
25 28 export type RadarData = {
... ...