add dashboard component

This commit is contained in:
simple321vip
2023-01-28 21:41:11 +08:00
parent a45f360006
commit 98c45b3591
13 changed files with 774 additions and 90 deletions
+9
View File
@@ -74,4 +74,13 @@ export default [
} }
}, },
}, },
{
url: '/wiki/api/v1/author/blogs/count',
method: 'GET',
response: () => {
return {
count: 101,
}
},
},
] as MockMethod[] ] as MockMethod[]
+13
View File
@@ -0,0 +1,13 @@
import { MockMethod } from 'vite-plugin-mock'
export default [
{
url: '/bookmark/api/v1/bookmark/count',
method: 'get',
response: () => {
return {
count: 53,
}
}
}
] as MockMethod[]
+20 -1
View File
@@ -92,5 +92,24 @@ const sortBlogs = (data: Object, btId: string) => {
}) })
} }
const countBlogs = () => {
return service({
url: '/wiki/api/v1/author/blogs/count',
method: 'GET',
headers: headers,
})
}
export { listAll, updateBtName, putBlogType, removeBlogType, sortBlogTypes, getContent, putBlog, updateContent, deleteContent, sortBlogs } export {
listAll,
updateBtName,
putBlogType,
removeBlogType,
sortBlogTypes,
getContent,
putBlog,
updateContent,
deleteContent,
sortBlogs,
countBlogs
}
+9 -1
View File
@@ -49,10 +49,18 @@ const delete_bookmarks = (params: Object) => {
}) })
} }
const bookmarks_count = () => {
return service({
url: '/wiki/api/v1/author/blogs/count',
method: 'GET',
})
}
export { export {
search_bookmark, search_bookmark,
put_bookmark, put_bookmark,
delete_bookmark, delete_bookmark,
delete_bookmarks, delete_bookmarks,
update_bookmark update_bookmark,
bookmarks_count
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+1 -82
View File
@@ -52,54 +52,12 @@
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
<!-- <div class="nav-main">
<div style="width: 800px;">
<div>
当前时间{{ time }}
</div>
<div>
期货交易时间&nbsp
<el-icon color="yellow">
<Sunrise />
</el-icon>&nbsp 9:00-11:30 &nbsp
<el-icon color="gold">
<Sunny />
</el-icon>&nbsp 13:30-15:00 &nbsp
<el-icon color="blue">
<Moon />
</el-icon>&nbsp 21:00-23:00
</div>
</div>
<el-dropdown trigger="click" v-show="useTenantStore.tenant.account">
<el-avatar src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>profile</el-dropdown-item>
<el-dropdown-item @click="dialogVisible = true">退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<el-dialog v-model="dialogVisible" title="提示" width="30%">
<span>确认你的登出操作么</span>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="doLogout">确认</el-button>
</span>
</template>
</el-dialog>
<span @click="doLogin" v-show="!useTenantStore.tenant.account">登录</span> -->
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import { reactive, ref } from "vue"
import $moment from "moment"
import router from '@/router/index'
import CustomPic from "@/components/customPic/index.vue"; import CustomPic from "@/components/customPic/index.vue";
import { tenantStore } from '@/store/modules/tenant' import { tenantStore } from '@/store/modules/tenant'
import { settingsStore } from '@/store/modules/settings' import { settingsStore } from '@/store/modules/settings'
import { logout } from '@/api/user'
// -- IMPORT -- // -- IMPORT --
const useTenantStore = tenantStore() const useTenantStore = tenantStore()
@@ -108,56 +66,17 @@ const useSettingsStore = settingsStore()
// -- REACTIVE OBJECT -- // -- REACTIVE OBJECT --
// -- REF OBJECT -- // -- REF OBJECT --
const dialogVisible = ref(false)
// obtain user infomation
useTenantStore.reflush()
// -- EVENT DEFINITION // -- EVENT DEFINITION
const toPerson = () => { const toPerson = () => {
} }
const doLogin = () => { useTenantStore.reflush()
router.push({
path: '/login'
})
}
const doLogout = () => {
dialogVisible.value = false
logout(useTenantStore.tenant.id).catch(() => { console.log(1) }).finally(() => {
useTenantStore.logout()
router.push({
path: '/login'
})
})
}
// show time
let time = ref<String>($moment().format("YYYY年MM月DD日 HH:mm:ss"));
setInterval(() => {
time.value = $moment().format("YYYY年MM月DD日 HH:mm:ss");
}, 1000);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.open_side {
background: url("../../assets/logo.png") center no-repeat;
background-size: cover;
}
.nav-main {
display: flex;
align-items: center;
justify-content: space-between;
/* background-color: bisque; */
}
.right-box { .right-box {
padding-top: 16px; padding-top: 16px;
display: flex; display: flex;
+8 -1
View File
@@ -12,4 +12,11 @@ type Theme = {
hoverText: string, hoverText: string,
} }
export { Tenant, Theme } type DataTimeline = {
from: string,
title: string,
showDayAndMonth: boolean,
message: string,
}
export { Tenant, Theme, DataTimeline }
+17
View File
@@ -0,0 +1,17 @@
import axios from 'axios'
const service = axios.create()
export function Commits(page: number) {
return service({
url: 'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' + page,
method: 'get'
})
}
export function Members() {
return service({
url: 'https://api.github.com/orgs/FLIPPED-AURORA/members',
method: 'get'
})
}
+30
View File
@@ -0,0 +1,30 @@
import axios from 'axios'
import { ref } from 'vue'
const weatherInfo = ref('今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。')
const amapKey = '8e8baa8a7317586c29ec694895de6e0a'
export const useWeatherInfo = () => {
ip()
return weatherInfo
}
export const ip = async () => {
// key换成你自己的 https://console.amap.com/dev/index
// if (amapKey === '') {
if (amapKey.length == 1) {
return false
}
const res = await axios.get('https://restapi.amap.com/v3/ip?key=' + amapKey)
if (res.data.adcode) {
getWeather(res.data.adcode)
}
}
const getWeather = async (code: string) => {
const response = await axios.get('https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code)
if (response.data.status === '1') {
const s = response.data.lives[0]
weatherInfo.value = s.city + ' 天气:' + s.weather + ' 温度:' + s.temperature + '摄氏度 风向:' + s.winddirection + ' 风力:' + s.windpower + '级 空气湿度:' + s.humidity
}
}
+35
View File
@@ -0,0 +1,35 @@
// 对Date的扩展,将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
// eslint-disable-next-line no-extend-native
const format = (date: Date, fmt: string) => {
var o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) }
for (var k in o) {
// if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
}
return fmt
}
const formatTimeToStr = (times: any, pattern: string) => {
if (!pattern) {
pattern = 'yyyy-MM-dd hh:mm:ss'
}
let date = format(new Date(times), pattern)
return date.toLocaleString()
}
export {
formatTimeToStr
}
@@ -0,0 +1,128 @@
<template>
<div class="dashboard-line-box">
<div class="dashboard-line-title">
访问趋势
</div>
<div ref="echart" class="dashboard-line" />
</div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts'
import { nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue'
// import 'echarts/theme/macarons'
var dataAxis: Array<string> = []
for (var i = 1; i < 13; i++) {
dataAxis.push(`${i}`)
}
var data = [
220,
182,
191,
234,
290,
330,
310,
123,
442,
321,
90,
149,
]
var yMax = 500
var dataShadow = []
// eslint-disable-next-line no-redeclare
for (var i = 0; i < data.length; i++) {
dataShadow.push(yMax)
}
const chart = shallowRef()
const echart = ref()
const initChart = () => {
chart.value = echarts.init(echart.value /* 'macarons' */)
setOptions()
}
const setOptions = () => {
chart.value.setOption({
grid: {
left: '40',
right: '20',
top: '40',
bottom: '20',
},
xAxis: {
data: dataAxis,
axisTick: {
show: false,
},
axisLine: {
show: false,
},
z: 10,
},
yAxis: {
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
textStyle: {
color: '#999',
},
},
},
dataZoom: [
{
type: 'inside',
},
],
series: [
{
type: 'bar',
barWidth: '40%',
itemStyle: {
borderRadius: [5, 5, 0, 0],
color: '#188df0',
},
emphasis: {
itemStyle: {
color: '#188df0',
},
},
data: data,
},
],
})
}
onMounted(async () => {
await nextTick()
initChart()
})
onUnmounted(() => {
if (!chart.value) {
return
}
chart.value.dispose()
chart.value = null
})
</script>
<style lang="scss" scoped>
.dashboard-line-box {
.dashboard-line {
background-color: #fff;
height: 360px;
width: 100%;
}
.dashboard-line-title {
font-weight: 600;
margin-bottom: 12px;
}
}
</style>
+122
View File
@@ -0,0 +1,122 @@
<template>
<div class="commit-table">
<div class="commit-table-title">
更新日志
</div>
<div class="log">
<div v-for="(item, key) in dataTimeline" :key="key" class="log-item">
<div class="flex-1 flex key-box">
<span class="key" :class="key < 3 && 'top'">{{ key+ 1 }}</span>
</div>
<div class="flex-5 flex message">{{ item.message }}</div>
<div class="flex-3 flex form">{{ item.from }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { Commits } from '@/service/github'
import { formatTimeToStr } from '@/utils/date'
import { DataTimeline } from "@/entity/index";
const loading = ref(true)
const dataTimeline = reactive<DataTimeline[]>([])
const loadCommits = () => {
Commits(0).then(({ data }) => {
loading.value = false
data.forEach((element: any, index: any) => {
if (element.commit.message && index < 10) {
dataTimeline.push({
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
})
}
})
})
}
loadCommits()
</script>
<style lang="scss" scoped>
.commit-table {
background-color: #fff;
height: 400px;
&-title {
font-weight: 600;
margin-bottom: 12px;
}
.log {
&-item {
display: flex;
justify-content: space-between;
margin-top: 14px;
.key-box {
justify-content: center;
}
.key {
&.top {
background: #314659;
color: #FFFFFF;
;
}
display: inline-flex;
justify-content: center;
align-items: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: #F0F2F5;
text-align: center;
color:rgba($color: #000000, $alpha: 0.65)
}
.message {
color: rgba(0, 0, 0, 0.65);
}
.form {
color: rgba(0, 0, 0, 0.65);
margin-left: 12px;
}
.flex {
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.flex-1 {
flex: 1;
}
.flex-2 {
flex: 2;
}
.flex-3 {
flex: 3;
}
.flex-4 {
flex: 4;
}
.flex-5 {
flex: 5;
}
}
}
}
</style>
+382 -5
View File
@@ -1,15 +1,392 @@
<template> <template>
<div class="about" ref="bookmarks" id="bookmarks" :style="{ <div class="page">
width: '600px', <div class="gva-card-box">
height: '400px' <div class="gva-card gva-top-card">
}"></div> <div class="gva-top-card-left">
<div class="gva-top-card-left-title">早安管理员请开始一天的工作吧</div>
<div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
<div class="gva-top-card-left-rows">
<el-row>
<el-col :span="8" :xs="24" :sm="8">
<div class="flex-center">
<el-icon class="dashboard-icon">
<sort />
</el-icon>
wiki数量 ({{ statistics.wiki_count }})
</div>
</el-col>
<el-col :span="8" :xs="24" :sm="8">
<div class="flex-center">
<el-icon class="dashboard-icon">
<avatar />
</el-icon>
bookmark数量 ({{ statistics.bookmark_count }})
</div>
</el-col>
<!-- <el-col :span="8" :xs="24" :sm="8">
<div class="flex-center">
<el-icon class="dashboard-icon">
<comment />
</el-icon>
好评率 (99%)
</div>
</el-col> -->
</el-row>
</div>
<div class="gva-top-card-left-item">
<div>
{{ time }}
</div>
</div>
<div class="gva-top-card-left-item">
<div>
期货交易时间&nbsp
<el-icon color="yellow">
<Sunrise />
</el-icon>&nbsp 9:00-11:30 &nbsp
<el-icon color="gold">
<Sunny />
</el-icon>&nbsp 13:30-15:00 &nbsp
<el-icon color="blue">
<Moon />
</el-icon>&nbsp 21:00-23:00
</div>
</div>
</div>
<img src="../../assets/dashboard.png" class="gva-top-card-right">
</div>
</div>
<div class="gva-card-box">
<el-card class="gva-card quick-entrance">
<template #header>
<div class="card-header">
<span>快捷入口</span>
</div>
</template>
<el-row :gutter="20">
<el-col v-for="(card, key) in toolCards" :key="key" :span="4" :xs="8" class="quick-entrance-items"
@click="toTarget(card.name)">
<div class="quick-entrance-item">
<div class="quick-entrance-item-icon" :style="{ backgroundColor: card.bg }">
<el-icon>
<component :is="card.icon" :style="{ color: card.color }" />
</el-icon>
</div>
<p>{{ card.label }}</p>
</div>
</el-col>
</el-row>
</el-card>
<!-- <div class="quick-entrance-title"></div> -->
</div>
<div class="gva-card-box">
<div class="gva-card">
<div class="card-header">
<span>数据统计</span>
</div>
<div class="echart-box">
<el-row :gutter="20">
<el-col :xs="24" :sm="18">
<echarts-line />
</el-col>
<el-col :xs="24" :sm="6">
<dashboard-table />
</el-col>
</el-row>
</div>
</div>
</div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import $moment from "moment"
import EchartsLine from '@/view/dashboard/dashboardCharts/index.vue'
import DashboardTable from '@/view/dashboard/dashboardTable/index.vue'
import { useRouter } from 'vue-router'
import { useWeatherInfo } from '@/service/weather'
import { countBlogs } from "@/api/blog"
import { bookmarks_count } from "@/api/bookmark"
// -- IMPORT --
const weatherInfo = useWeatherInfo()
const router = useRouter()
// -- REACTIVE OBJECT --
// -- REF OBJECT --
let time = ref<String>($moment().format("YYYY年MM月DD日 HH:mm:ss"));
const statistics = ref({
wiki_count: 0,
bookmark_count: 0,
})
const toolCards = ref([
{
label: 'Jenjins',
icon: 'camera',
name: 'Jenkins',
color: '#ff9c6e',
bg: 'rgba(255, 156, 110,.3)'
},
{
label: 'argo-cd',
icon: 'monitor',
name: 'argocd',
color: '#69c0ff',
bg: 'rgba(92, 219, 211,.3)'
},
{
label: 'wiki',
icon: 'document',
name: 'wiki',
color: '#b37feb',
bg: 'rgba(185, 192, 255,.3)'
},
{
label: '代码生成器',
icon: 'cpu',
name: 'autoCode',
color: '#ffd666',
bg: 'rgba(255, 214, 102,.3)'
},
{
label: '表单生成器',
icon: 'document-checked',
name: 'formCreate',
color: '#ff85c0',
bg: 'rgba(255, 133, 192,.3)'
},
{
label: '关于我们',
icon: 'user',
name: 'about',
color: '#5cdbd3',
bg: 'rgba(92, 219, 211,.3)'
}
])
// -- EVENT DEFINITION
const toTarget = (name: string) => {
// router.push({ name })
switch (name) {
case 'Jenkins':
window.open('https://www.violin-home.cn/jenkins/', '_blank')
break;
case 'argocd':
window.open('https://www.violin-home.cn/argo-cd/', '_blank')
break;
case 'wiki':
window.open('https://www.violin-home.cn/docs#/guan/', '_blank')
break;
}
}
/**
* AUTO INVOKE FUNCTION
*/
setInterval(() => {
time.value = $moment().format("YYYY年MM月DD日 HH:mm:ss")
}, 1000)
countBlogs().then((resp) => {
const data = resp.data
statistics.value.wiki_count = data.count
})
bookmarks_count().then((resp) => {
const data = resp.data
statistics.value.bookmark_count = data.count
})
</script> </script>
<style> <style lang="scss" scoped>
@mixin flex-center {
display: flex;
align-items: center;
}
.page {
background: #f0f2f5;
padding: 0;
.gva-card-box {
padding: 12px 16px;
&+.gva-card-box {
padding-top: 0px;
}
}
.gva-card {
box-sizing: border-box;
background-color: #fff;
border-radius: 2px;
height: auto;
padding: 26px 30px;
overflow: hidden;
box-shadow: 0 0 7px 1px rgba(0, 0, 0, 0.03);
}
.gva-top-card {
height: 260px;
@include flex-center;
justify-content: space-between;
color: #777;
&-left {
height: 100%;
display: flex;
flex-direction: column;
&-title {
font-size: 22px;
color: #343844;
}
&-dot {
font-size: 16px;
color: #6B7687;
margin-top: 24px;
}
&-rows {
// margin-top: 15px;
margin-top: 18px;
color: #6B7687;
width: 600px;
align-items: center;
}
&-item {
+.gva-top-card-left-item {
margin-top: 24px;
}
margin-top: 14px;
}
}
&-right {
height: 600px;
width: 600px;
margin-top: 28px;
}
}
::v-deep(.el-card__header) {
padding: 0;
border-bottom: none;
}
.card-header {
padding-bottom: 20px;
border-bottom: 1px solid #e8e8e8;
}
.quick-entrance-title {
height: 30px;
font-size: 22px;
color: #333;
width: 100%;
border-bottom: 1px solid #eee;
}
.quick-entrance-items {
@include flex-center;
justify-content: center;
text-align: center;
color: #333;
.quick-entrance-item {
padding: 16px 28px;
margin-top: -16px;
margin-bottom: -16px;
border-radius: 4px;
transition: all 0.2s;
&:hover {
box-shadow: 0px 0px 7px 0px rgba(217, 217, 217, 0.55);
}
cursor: pointer;
height: auto;
text-align: center;
// align-items: center;
&-icon {
width: 50px;
height: 50px !important;
border-radius: 8px;
@include flex-center;
justify-content: center;
margin: 0 auto;
i {
font-size: 24px;
}
}
p {
margin-top: 10px;
}
}
}
.echart-box {
padding: 14px;
}
}
.dashboard-icon {
font-size: 20px;
color: rgb(85, 160, 248);
width: 30px;
height: 30px;
margin-right: 10px;
@include flex-center;
}
.flex-center {
@include flex-center;
}
//小屏幕不显示右侧,将登录框居中
@media (max-width: 750px) {
.gva-card {
padding: 20px 10px !important;
.gva-top-card {
height: auto;
&-left {
&-title {
font-size: 20px !important;
}
&-rows {
margin-top: 15px;
align-items: center;
}
}
&-right {
display: none;
}
}
.gva-middle-card {
&-item {
line-height: 20px;
}
}
.dashboard-icon {
font-size: 18px;
}
}
}
</style> </style>