houzhongjian
2024-08-08 820397e43a0b64d35c6d31d2a55475061438593b
提交 | 用户 | 时间
820397 1 <template>
H 2   <div>
3     <el-card shadow="never">
4       <el-skeleton :loading="loading" animated>
5         <el-row :gutter="16" justify="space-between">
6           <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
7             <div class="flex items-center">
8               <el-avatar :src="avatar" :size="70" class="mr-16px">
9                 <img src="@/assets/imgs/avatar.gif" alt="" />
10               </el-avatar>
11               <div>
12                 <div class="text-20px">
13                   {{ t('workplace.welcome') }} {{ username }} {{ t('workplace.happyDay') }}
14                 </div>
15                 <div class="mt-10px text-14px text-gray-500">
16                   {{ t('workplace.toady') }},20℃ - 32℃!
17                 </div>
18               </div>
19             </div>
20           </el-col>
21           <el-col :xl="12" :lg="12" :md="12" :sm="24" :xs="24">
22             <div class="h-70px flex items-center justify-end lt-sm:mt-10px">
23               <div class="px-8px text-right">
24                 <div class="mb-16px text-14px text-gray-400">{{ t('workplace.project') }}</div>
25                 <CountTo
26                   class="text-20px"
27                   :start-val="0"
28                   :end-val="totalSate.project"
29                   :duration="2600"
30                 />
31               </div>
32               <el-divider direction="vertical" />
33               <div class="px-8px text-right">
34                 <div class="mb-16px text-14px text-gray-400">{{ t('workplace.toDo') }}</div>
35                 <CountTo
36                   class="text-20px"
37                   :start-val="0"
38                   :end-val="totalSate.todo"
39                   :duration="2600"
40                 />
41               </div>
42               <el-divider direction="vertical" border-style="dashed" />
43               <div class="px-8px text-right">
44                 <div class="mb-16px text-14px text-gray-400">{{ t('workplace.access') }}</div>
45                 <CountTo
46                   class="text-20px"
47                   :start-val="0"
48                   :end-val="totalSate.access"
49                   :duration="2600"
50                 />
51               </div>
52             </div>
53           </el-col>
54         </el-row>
55       </el-skeleton>
56     </el-card>
57   </div>
58
59   <el-row class="mt-8px" :gutter="8" justify="space-between">
60     <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
61       <el-card shadow="never">
62         <template #header>
63           <div class="h-3 flex justify-between">
64             <span>{{ t('workplace.project') }}</span>
65             <el-link
66               type="primary"
67               :underline="false"
68               href="https://xxxx"
69               target="_blank"
70             >
71               {{ t('action.more') }}
72             </el-link>
73           </div>
74         </template>
75         <el-skeleton :loading="loading" animated>
76           <el-row>
77             <el-col
78               v-for="(item, index) in projects"
79               :key="`card-${index}`"
80               :xl="8"
81               :lg="8"
82               :md="8"
83               :sm="24"
84               :xs="24"
85             >
86               <el-card shadow="hover" class="mr-5px mt-5px">
87                 <div class="flex items-center">
88                   <Icon :icon="item.icon" :size="25" class="mr-8px" />
89                   <span class="text-16px">{{ item.name }}</span>
90                 </div>
91                 <div class="mt-12px text-9px text-gray-400">{{ t(item.message) }}</div>
92                 <div class="mt-12px flex justify-between text-12px text-gray-400">
93                   <span>{{ item.personal }}</span>
94                   <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
95                 </div>
96               </el-card>
97             </el-col>
98           </el-row>
99         </el-skeleton>
100       </el-card>
101
102       <el-card shadow="never" class="mt-8px">
103         <el-skeleton :loading="loading" animated>
104           <el-row :gutter="20" justify="space-between">
105             <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
106               <el-card shadow="hover" class="mb-8px">
107                 <el-skeleton :loading="loading" animated>
108                   <Echart :options="pieOptionsData" :height="280" />
109                 </el-skeleton>
110               </el-card>
111             </el-col>
112             <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
113               <el-card shadow="hover" class="mb-8px">
114                 <el-skeleton :loading="loading" animated>
115                   <Echart :options="barOptionsData" :height="280" />
116                 </el-skeleton>
117               </el-card>
118             </el-col>
119           </el-row>
120         </el-skeleton>
121       </el-card>
122     </el-col>
123     <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
124       <el-card shadow="never">
125         <template #header>
126           <div class="h-3 flex justify-between">
127             <span>{{ t('workplace.shortcutOperation') }}</span>
128           </div>
129         </template>
130         <el-skeleton :loading="loading" animated>
131           <el-row>
132             <el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
133               <div class="flex items-center">
134                 <Icon :icon="item.icon" class="mr-8px" />
135                 <el-link type="default" :underline="false" @click="setWatermark(item.name)">
136                   {{ item.name }}
137                 </el-link>
138               </div>
139             </el-col>
140           </el-row>
141         </el-skeleton>
142       </el-card>
143       <el-card shadow="never" class="mt-8px">
144         <template #header>
145           <div class="h-3 flex justify-between">
146             <span>{{ t('workplace.notice') }}</span>
147             <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
148           </div>
149         </template>
150         <el-skeleton :loading="loading" animated>
151           <div v-for="(item, index) in notice" :key="`dynamics-${index}`">
152             <div class="flex items-center">
153               <el-avatar :src="avatar" :size="35" class="mr-16px">
154                 <img src="@/assets/imgs/avatar.gif" alt="" />
155               </el-avatar>
156               <div>
157                 <div class="text-14px">
158                   <Highlight :keys="item.keys.map((v) => t(v))">
159                     {{ item.type }} : {{ item.title }}
160                   </Highlight>
161                 </div>
162                 <div class="mt-16px text-12px text-gray-400">
163                   {{ formatTime(item.date, 'yyyy-MM-dd') }}
164                 </div>
165               </div>
166             </div>
167             <el-divider />
168           </div>
169         </el-skeleton>
170       </el-card>
171     </el-col>
172   </el-row>
173 </template>
174 <script lang="ts" setup>
175 import { set } from 'lodash-es'
176 import { EChartsOption } from 'echarts'
177 import { formatTime } from '@/utils'
178
179 import { useUserStore } from '@/store/modules/user'
180 import { useWatermark } from '@/hooks/web/useWatermark'
181 import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
182 import { pieOptions, barOptions } from './echarts-data'
183
184 defineOptions({ name: 'Home' })
185
186 const { t } = useI18n()
187 const userStore = useUserStore()
188 const { setWatermark } = useWatermark()
189 const loading = ref(true)
190 const avatar = userStore.getUser.avatar
191 const username = userStore.getUser.nickname
192 const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
193 // 获取统计数
194 let totalSate = reactive<WorkplaceTotal>({
195   project: 0,
196   access: 0,
197   todo: 0
198 })
199
200 const getCount = async () => {
201   const data = {
202     project: 40,
203     access: 2340,
204     todo: 10
205   }
206   totalSate = Object.assign(totalSate, data)
207 }
208
209 // 获取项目数
210 let projects = reactive<Project[]>([])
211 const getProject = async () => {
212   const data = [
213     {
214       name: 'iailab-plat',
215       icon: 'akar-icons:github-fill',
216       message: 'https://xxxx/iailab-plat',
217       personal: 'Spring Boot 单体架构',
218       time: new Date()
219     },
220     {
221       name: 'iailab-plat-ui-vue3',
222       icon: 'logos:vue',
223       message: 'https://xxxx/iailab-plat-ui-vue3',
224       personal: 'Vue3 + element-plus',
225       time: new Date()
226     },
227     {
228       name: 'iailab-plat-ui-vue2',
229       icon: 'logos:vue',
230       message: 'https://xxxx/iailab-plat-ui-vue2',
231       personal: 'Vue2 + element-ui',
232       time: new Date()
233     }
234   ]
235   projects = Object.assign(projects, data)
236 }
237
238 // 获取通知公告
239 let notice = reactive<Notice[]>([])
240 const getNotice = async () => {
241   const data = [
242     {
243       title: '系统支持 JDK 8/17/21,Vue 2/3',
244       type: '通知',
245       keys: ['通知', '8', '17', '21', '2', '3'],
246       date: new Date()
247     },
248     {
249       title: '后端提供 Spring Boot 2.7/3.2 + Cloud 双架构',
250       type: '公告',
251       keys: ['公告', 'Boot', 'Cloud'],
252       date: new Date()
253     },
254     {
255       title: '全部开源,个人与企业可 100% 直接使用,无需授权',
256       type: '通知',
257       keys: ['通知', '无需授权'],
258       date: new Date()
259     },
260     {
261       title: '国内使用最广泛的快速开发平台,超 300+ 人贡献',
262       type: '公告',
263       keys: ['公告', '最广泛'],
264       date: new Date()
265     }
266   ]
267   notice = Object.assign(notice, data)
268 }
269
270 // 获取快捷入口
271 let shortcut = reactive<Shortcut[]>([])
272
273 const getShortcut = async () => {
274   const data = [
275     {
276       name: 'Github',
277       icon: 'akar-icons:github-fill',
278       url: 'github.io'
279     },
280     {
281       name: 'Vue',
282       icon: 'logos:vue',
283       url: 'vuejs.org'
284     },
285     {
286       name: 'Vite',
287       icon: 'vscode-icons:file-type-vite',
288       url: 'https://vitejs.dev/'
289     },
290     {
291       name: 'Angular',
292       icon: 'logos:angular-icon',
293       url: 'github.io'
294     },
295     {
296       name: 'React',
297       icon: 'logos:react',
298       url: 'github.io'
299     },
300     {
301       name: 'Webpack',
302       icon: 'logos:webpack',
303       url: 'github.io'
304     }
305   ]
306   shortcut = Object.assign(shortcut, data)
307 }
308
309 // 用户来源
310 const getUserAccessSource = async () => {
311   const data = [
312     { value: 335, name: 'analysis.directAccess' },
313     { value: 310, name: 'analysis.mailMarketing' },
314     { value: 234, name: 'analysis.allianceAdvertising' },
315     { value: 135, name: 'analysis.videoAdvertising' },
316     { value: 1548, name: 'analysis.searchEngines' }
317   ]
318   set(
319     pieOptionsData,
320     'legend.data',
321     data.map((v) => t(v.name))
322   )
323   pieOptionsData!.series![0].data = data.map((v) => {
324     return {
325       name: t(v.name),
326       value: v.value
327     }
328   })
329 }
330 const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
331
332 // 周活跃量
333 const getWeeklyUserActivity = async () => {
334   const data = [
335     { value: 13253, name: 'analysis.monday' },
336     { value: 34235, name: 'analysis.tuesday' },
337     { value: 26321, name: 'analysis.wednesday' },
338     { value: 12340, name: 'analysis.thursday' },
339     { value: 24643, name: 'analysis.friday' },
340     { value: 1322, name: 'analysis.saturday' },
341     { value: 1324, name: 'analysis.sunday' }
342   ]
343   set(
344     barOptionsData,
345     'xAxis.data',
346     data.map((v) => t(v.name))
347   )
348   set(barOptionsData, 'series', [
349     {
350       name: t('analysis.activeQuantity'),
351       data: data.map((v) => v.value),
352       type: 'bar'
353     }
354   ])
355 }
356
357 const getAllApi = async () => {
358   await Promise.all([
359     getCount(),
360     getProject(),
361     getNotice(),
362     getShortcut(),
363     getUserAccessSource(),
364     getWeeklyUserActivity()
365   ])
366   loading.value = false
367 }
368
369 getAllApi()
370 </script>