Index.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. <template>
  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. <el-row class="mt-8px" :gutter="8" justify="space-between">
  59. <el-col :xl="16" :lg="16" :md="24" :sm="24" :xs="24" class="mb-8px">
  60. <!-- <el-card shadow="never">
  61. <template #header>
  62. <div class="h-3 flex justify-between">
  63. <span>{{ t('workplace.project') }}</span>
  64. <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
  65. </div>
  66. </template>
  67. <el-skeleton :loading="loading" animated>
  68. <el-row>
  69. <el-col
  70. v-for="(item, index) in projects"
  71. :key="`card-${index}`"
  72. :xl="8"
  73. :lg="8"
  74. :md="8"
  75. :sm="24"
  76. :xs="24"
  77. >
  78. <el-card shadow="hover">
  79. <div class="flex items-center">
  80. <Icon :icon="item.icon" :size="25" class="mr-8px" />
  81. <span class="text-16px">{{ item.name }}</span>
  82. </div>
  83. <div class="mt-16px text-14px text-gray-400">{{ t(item.message) }}</div>
  84. <div class="mt-16px flex justify-between text-12px text-gray-400">
  85. <span>{{ item.personal }}</span>
  86. <span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
  87. </div>
  88. </el-card>
  89. </el-col>
  90. </el-row>
  91. </el-skeleton>
  92. </el-card> -->
  93. <el-card shadow="never" class="mt-8px">
  94. <el-skeleton :loading="loading" animated>
  95. <el-row :gutter="20" justify="space-between">
  96. <el-col :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
  97. <el-card shadow="hover" class="mb-8px">
  98. <el-skeleton :loading="loading" animated>
  99. <Echart :options="pieOptionsData" :height="280" />
  100. </el-skeleton>
  101. </el-card>
  102. </el-col>
  103. <el-col :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
  104. <el-card shadow="hover" class="mb-8px">
  105. <el-skeleton :loading="loading" animated>
  106. <Echart :options="barOptionsData" :height="280" />
  107. </el-skeleton>
  108. </el-card>
  109. </el-col>
  110. </el-row>
  111. </el-skeleton>
  112. </el-card>
  113. </el-col>
  114. <el-col :xl="8" :lg="8" :md="24" :sm="24" :xs="24" class="mb-8px">
  115. <!-- <el-card shadow="never">
  116. <template #header>
  117. <div class="h-3 flex justify-between">
  118. <span>{{ t('workplace.shortcutOperation') }}</span>
  119. </div>
  120. </template>
  121. <el-skeleton :loading="loading" animated>
  122. <el-row>
  123. <el-col v-for="item in shortcut" :key="`team-${item.name}`" :span="8" class="mb-8px">
  124. <div class="flex items-center">
  125. <Icon :icon="item.icon" class="mr-8px" />
  126. <el-link type="default" :underline="false" @click="setWatermark(item.name)">
  127. {{ item.name }}
  128. </el-link>
  129. </div>
  130. </el-col>
  131. </el-row>
  132. </el-skeleton>
  133. </el-card> -->
  134. <el-card shadow="never" class="mt-8px">
  135. <template #header>
  136. <div class="h-3 flex justify-between">
  137. <span>{{ t('workplace.notice') }}</span>
  138. <el-link type="primary" :underline="false">{{ t('action.more') }}</el-link>
  139. </div>
  140. </template>
  141. <el-skeleton :loading="loading" animated>
  142. <div v-for="(item, index) in notice" :key="`dynamics-${index}`">
  143. <div class="flex items-center">
  144. <el-avatar :src="avatar" :size="35" class="mr-16px">
  145. <img src="@/assets/imgs/avatar.gif" alt="" />
  146. </el-avatar>
  147. <div>
  148. <div class="text-14px">
  149. <Highlight :keys="item.keys.map((v) => t(v))">
  150. {{ item.type }} : {{ item.title }}
  151. </Highlight>
  152. </div>
  153. <div class="mt-16px text-12px text-gray-400">
  154. {{ formatTime(item.date, 'yyyy-MM-dd') }}
  155. </div>
  156. </div>
  157. </div>
  158. <el-divider />
  159. </div>
  160. </el-skeleton>
  161. </el-card>
  162. </el-col>
  163. </el-row>
  164. </template>
  165. <script lang="ts" setup>
  166. import { set } from 'lodash-es'
  167. import { EChartsOption } from 'echarts'
  168. import { formatTime } from '@/utils'
  169. import { useUserStore } from '@/store/modules/user'
  170. import { useWatermark } from '@/hooks/web/useWatermark'
  171. import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
  172. import { pieOptions, barOptions } from './echarts-data'
  173. defineOptions({ name: 'Home' })
  174. const { t } = useI18n()
  175. const userStore = useUserStore()
  176. const { setWatermark } = useWatermark()
  177. const loading = ref(true)
  178. const avatar = userStore.getUser.avatar
  179. const username = userStore.getUser.nickname
  180. const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
  181. // 获取统计数
  182. let totalSate = reactive<WorkplaceTotal>({
  183. project: 0,
  184. access: 0,
  185. todo: 0
  186. })
  187. const getCount = async () => {
  188. const data = {
  189. project: 40,
  190. access: 2340,
  191. todo: 10
  192. }
  193. totalSate = Object.assign(totalSate, data)
  194. }
  195. // 获取项目数
  196. let projects = reactive<Project[]>([])
  197. const getProject = async () => {
  198. const data = [
  199. {
  200. name: 'Github',
  201. icon: 'akar-icons:github-fill',
  202. message: 'workplace.introduction',
  203. personal: 'Archer',
  204. time: new Date()
  205. },
  206. {
  207. name: 'Vue',
  208. icon: 'logos:vue',
  209. message: 'workplace.introduction',
  210. personal: 'Archer',
  211. time: new Date()
  212. },
  213. {
  214. name: 'Angular',
  215. icon: 'logos:angular-icon',
  216. message: 'workplace.introduction',
  217. personal: 'Archer',
  218. time: new Date()
  219. },
  220. {
  221. name: 'React',
  222. icon: 'logos:react',
  223. message: 'workplace.introduction',
  224. personal: 'Archer',
  225. time: new Date()
  226. },
  227. {
  228. name: 'Webpack',
  229. icon: 'logos:webpack',
  230. message: 'workplace.introduction',
  231. personal: 'Archer',
  232. time: new Date()
  233. },
  234. {
  235. name: 'Vite',
  236. icon: 'vscode-icons:file-type-vite',
  237. message: 'workplace.introduction',
  238. personal: 'Archer',
  239. time: new Date()
  240. }
  241. ]
  242. projects = Object.assign(projects, data)
  243. }
  244. // 获取通知公告
  245. let notice = reactive<Notice[]>([])
  246. const getNotice = async () => {
  247. const data = [
  248. {
  249. title: '系统升级版本',
  250. type: '通知',
  251. keys: ['通知', '升级'],
  252. date: new Date()
  253. },
  254. {
  255. title: '系统凌晨维护',
  256. type: '公告',
  257. keys: ['公告', '维护'],
  258. date: new Date()
  259. },
  260. {
  261. title: '系统升级版本',
  262. type: '通知',
  263. keys: ['通知', '升级'],
  264. date: new Date()
  265. },
  266. {
  267. title: '系统凌晨维护',
  268. type: '公告',
  269. keys: ['公告', '维护'],
  270. date: new Date()
  271. }
  272. ]
  273. notice = Object.assign(notice, data)
  274. }
  275. // 获取快捷入口
  276. let shortcut = reactive<Shortcut[]>([])
  277. const getShortcut = async () => {
  278. const data = [
  279. {
  280. name: 'Github',
  281. icon: 'akar-icons:github-fill',
  282. url: 'github.io'
  283. },
  284. {
  285. name: 'Vue',
  286. icon: 'logos:vue',
  287. url: 'vuejs.org'
  288. },
  289. {
  290. name: 'Vite',
  291. icon: 'vscode-icons:file-type-vite',
  292. url: 'https://vitejs.dev/'
  293. },
  294. {
  295. name: 'Angular',
  296. icon: 'logos:angular-icon',
  297. url: 'github.io'
  298. },
  299. {
  300. name: 'React',
  301. icon: 'logos:react',
  302. url: 'github.io'
  303. },
  304. {
  305. name: 'Webpack',
  306. icon: 'logos:webpack',
  307. url: 'github.io'
  308. }
  309. ]
  310. shortcut = Object.assign(shortcut, data)
  311. }
  312. // 用户来源
  313. const getUserAccessSource = async () => {
  314. const data = [
  315. { value: 335, name: 'analysis.directAccess' },
  316. { value: 310, name: 'analysis.mailMarketing' },
  317. { value: 234, name: 'analysis.allianceAdvertising' },
  318. { value: 135, name: 'analysis.videoAdvertising' },
  319. { value: 1548, name: 'analysis.searchEngines' }
  320. ]
  321. set(
  322. pieOptionsData,
  323. 'legend.data',
  324. data.map((v) => t(v.name))
  325. )
  326. pieOptionsData!.series![0].data = data.map((v) => {
  327. return {
  328. name: t(v.name),
  329. value: v.value
  330. }
  331. })
  332. }
  333. const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
  334. // 周活跃量
  335. const getWeeklyUserActivity = async () => {
  336. const data = [
  337. { value: 13253, name: 'analysis.monday' },
  338. { value: 34235, name: 'analysis.tuesday' },
  339. { value: 26321, name: 'analysis.wednesday' },
  340. { value: 12340, name: 'analysis.thursday' },
  341. { value: 24643, name: 'analysis.friday' },
  342. { value: 1322, name: 'analysis.saturday' },
  343. { value: 1324, name: 'analysis.sunday' }
  344. ]
  345. set(
  346. barOptionsData,
  347. 'xAxis.data',
  348. data.map((v) => t(v.name))
  349. )
  350. set(barOptionsData, 'series', [
  351. {
  352. name: t('analysis.activeQuantity'),
  353. data: data.map((v) => v.value),
  354. type: 'bar'
  355. }
  356. ])
  357. }
  358. const getAllApi = async () => {
  359. await Promise.all([
  360. getCount(),
  361. getProject(),
  362. getNotice(),
  363. getShortcut(),
  364. getUserAccessSource(),
  365. getWeeklyUserActivity()
  366. ])
  367. loading.value = false
  368. }
  369. getAllApi()
  370. </script>