8 changed files with 657 additions and 14 deletions
-
23src/assets/index.ts
-
208src/components/admin/MainWrapper.vue
-
2src/components/blogs/HomePage.vue
-
18src/router/admin.ts
-
15src/router/home.ts
-
23src/router/index.ts
-
18src/store/index.ts
-
364src/views/admin/DashBoardView.vue
@ -0,0 +1,23 @@ |
|||
import { defineComponent, h } from 'vue'; |
|||
import { createFromIconfontCN } from '@ant-design/icons-vue'; |
|||
|
|||
function createIconComponent(iconType:string){ |
|||
const IconComponent = createFromIconfontCN({ |
|||
scriptUrl: '//at.alicdn.com/t/c/font_4513281_240ozh2p7bxh.js', // 在 iconfont.cn 上生成
|
|||
}); |
|||
|
|||
return defineComponent({ |
|||
setup() { |
|||
return () => h(IconComponent , { type: iconType }); |
|||
}, |
|||
}); |
|||
} |
|||
const TitleOutLined = createIconComponent( 'icon-xiongmaochizhuye'); |
|||
const BlogOutLined=createIconComponent( 'icon-blog'); |
|||
const SystemOutLined=createIconComponent( 'icon-system'); |
|||
const PhotoOutLined=createIconComponent( 'icon-photo'); |
|||
const FileOutLined=createIconComponent( 'icon-file'); |
|||
const DiaryOutLined=createIconComponent( 'icon-diary'); |
|||
const CommentOutLined=createIconComponent( 'icon-comment'); |
|||
const TypeOutLined=createIconComponent( 'icon-type'); |
|||
export {TitleOutLined,BlogOutLined,SystemOutLined,PhotoOutLined,FileOutLined,DiaryOutLined,CommentOutLined,TypeOutLined} |
@ -0,0 +1,208 @@ |
|||
<template> |
|||
<a-flex> |
|||
<div class="menu" :style="{ width: state.menuWidth }"> |
|||
<div class="menu_header"> |
|||
<TitleOutLined :class="collapsed ? 'small' : 'large'" /> |
|||
<span v-if="!collapsed">{{ state.name }}</span> |
|||
</div> |
|||
<a-menu class="menu_navigate" v-model:openKeys="state.openKeys" v-model:selectedKeys="state.selectedKeys" |
|||
mode="inline" theme="light" :inline-collapsed="state.collapsed" :items="items" style="border-inline-end: none;" |
|||
@click="menuItemclick"> |
|||
</a-menu> |
|||
</div> |
|||
<div class="main" style="flex: 1;"> |
|||
<div class="main_header"> |
|||
<a-space> |
|||
<a-button @click="toggleCollapsed"> |
|||
<MenuUnfoldOutlined v-if="state.collapsed" /> |
|||
<MenuFoldOutlined v-else /> |
|||
</a-button> |
|||
<a-button type="text" @click="jumpdashboard">首页</a-button> |
|||
</a-space> |
|||
<div class="user"> |
|||
<span>{{ username }}</span> |
|||
<div> |
|||
<a-button type="primary" @click="showModal" danger> |
|||
<LogoutOutlined /> |
|||
</a-button> |
|||
<a-modal v-model:open="open" title="提示" @ok="handleOk" @cancel="handleCancel" ok-text="确认" cancel-text="取消"> |
|||
<p>确定要注销吗?</p> |
|||
</a-modal> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<RouterView /> |
|||
</div> |
|||
</a-flex> |
|||
|
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
import { computed, ref, onMounted, h, reactive } from 'vue'; |
|||
import { TitleOutLined } from "@/assets/index" |
|||
import { RouterView, useRouter } from 'vue-router' |
|||
import { MenuFoldOutlined, MenuUnfoldOutlined, LogoutOutlined, DashboardOutlined } from '@ant-design/icons-vue'; |
|||
import { get } from "@/tools/request" |
|||
import { message } from 'ant-design-vue'; |
|||
import { useAuthStore } from '@/store/index' |
|||
import { BlogOutLined, SystemOutLined, PhotoOutLined, FileOutLined, DiaryOutLined, CommentOutLined, TypeOutLined } from "@/assets" |
|||
const state = reactive({ |
|||
collapsed: false, |
|||
openKeys: [1], |
|||
selectedKeys: ['1'], |
|||
name: '博客后台系统', |
|||
menuWidth: '10%' |
|||
}); |
|||
const items = reactive([ |
|||
{ |
|||
key: '1', |
|||
icon: () => h(DashboardOutlined), |
|||
label: '仪表盘', |
|||
title: '仪表盘', |
|||
url: '/admin/dashboard' |
|||
}, |
|||
{ |
|||
key: '2', |
|||
icon: () => h(BlogOutLined), |
|||
label: '博客管理', |
|||
title: '博客管理', |
|||
url: '/admin/blogmanage' |
|||
}, |
|||
{ |
|||
key: '3', |
|||
icon: () => h(DiaryOutLined), |
|||
label: '日记管理', |
|||
title: '日记管理', |
|||
url: '/admin/diarymanage' |
|||
}, |
|||
{ |
|||
key: '4', |
|||
icon: () => h(TypeOutLined), |
|||
label: '分类管理', |
|||
title: '分类管理', |
|||
url: '/admin/typemanage' |
|||
}, |
|||
{ |
|||
key: '5', |
|||
icon: () => h(CommentOutLined), |
|||
label: '评论管理', |
|||
title: '评论管理', |
|||
url: '/admin/commentmanage' |
|||
}, |
|||
{ |
|||
key: '6', |
|||
icon: () => h(PhotoOutLined), |
|||
label: '相册管理', |
|||
title: '相册管理', |
|||
url: '/admin/imagemanage' |
|||
}, |
|||
{ |
|||
key: '7', |
|||
icon: () => h(FileOutLined), |
|||
label: '文件管理', |
|||
title: '文件管理', |
|||
url: '/admin/filemanage' |
|||
}, |
|||
{ |
|||
key: '8', |
|||
icon: () => h(SystemOutLined), |
|||
label: '系统设置', |
|||
title: '系统设置', |
|||
url: '/admin/systemmanage' |
|||
}, |
|||
]); |
|||
const router = useRouter() |
|||
const menuItemclick = function ({ item }: { item: any }) { |
|||
router.push(item.url) |
|||
} |
|||
const collapsed = computed(() => state.collapsed); |
|||
const authStore = useAuthStore() |
|||
const username = ref("") |
|||
const toggleCollapsed = () => { |
|||
state.collapsed = !state.collapsed; |
|||
state.menuWidth = state.collapsed ? 'auto' : '10%' |
|||
}; |
|||
const jumpdashboard = function () { |
|||
state.selectedKeys = ['1'] |
|||
router.push('/admin/dashboard') |
|||
} |
|||
onMounted(async () => { |
|||
const userinfo = await get( |
|||
'/users/me', |
|||
undefined, |
|||
undefined, |
|||
{ |
|||
'Accept': 'application/json', |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
} |
|||
) |
|||
username.value = userinfo.data.username |
|||
}); |
|||
|
|||
const open = ref<boolean>(false); |
|||
|
|||
const showModal = () => { |
|||
open.value = true; |
|||
}; |
|||
|
|||
const handleOk = (e: MouseEvent) => { |
|||
console.log(e); |
|||
authStore.removeToken() |
|||
message.loading("注销成功") |
|||
setTimeout(() => { |
|||
router.push('/login') |
|||
}, 2000); |
|||
open.value = false; |
|||
}; |
|||
|
|||
const handleCancel = (e: MouseEvent) => { |
|||
message.warn('取消注销'); |
|||
} |
|||
|
|||
</script> |
|||
|
|||
<style scoped> |
|||
.menu { |
|||
border-right: 1px solid rgba(5, 5, 5, 0.06); |
|||
} |
|||
|
|||
.main_header { |
|||
display: flex; |
|||
margin: 24px; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.menu_header { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 98px; |
|||
font-size: 20px; |
|||
} |
|||
|
|||
.small { |
|||
font-size: 30px; |
|||
margin: 0 5px 0 5px; |
|||
} |
|||
|
|||
.large { |
|||
font-size: 50px; |
|||
margin: 0 10px 0 10px; |
|||
} |
|||
|
|||
|
|||
.navigate_button { |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
border-block-end: 1px solid rgba(5, 5, 5, 0.06); |
|||
} |
|||
|
|||
.user { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.user>* { |
|||
padding: 0 5px; |
|||
} |
|||
</style> |
@ -0,0 +1,18 @@ |
|||
import type { RouteRecordRaw } from 'vue-router' |
|||
const adminRoute:Array<RouteRecordRaw>=[ |
|||
{ |
|||
path:"/admin", |
|||
redirect:"/admin/dashboard", |
|||
name:'admin', |
|||
component:()=> import("@/components/admin/MainWrapper.vue"), |
|||
children:[ |
|||
{ |
|||
path:"dashboard", |
|||
name:"dashboard", |
|||
component:()=>import("@/views/admin/DashBoardView.vue") |
|||
} |
|||
|
|||
] |
|||
} |
|||
] |
|||
export default adminRoute |
@ -0,0 +1,15 @@ |
|||
import type { RouteRecordRaw } from 'vue-router' |
|||
|
|||
const homeRoute:Array<RouteRecordRaw>=[ |
|||
{ |
|||
path: "/", |
|||
name: "home", |
|||
component: () => import("@/components/blogs/HomePage.vue"), |
|||
}, |
|||
{ |
|||
path: "/login", |
|||
name: "login", |
|||
component: () => import("@/components/admin/SignIn.vue"), |
|||
} |
|||
] |
|||
export default homeRoute |
@ -1,19 +1,16 @@ |
|||
import { createRouter, createWebHistory } from 'vue-router' |
|||
import { createRouter, createWebHistory,type RouteRecordRaw } from 'vue-router'; |
|||
|
|||
import homeRoute from "./home" |
|||
import adminRoute from './admin'; |
|||
|
|||
const route:Array<RouteRecordRaw>=[ |
|||
...homeRoute, |
|||
...adminRoute |
|||
] |
|||
|
|||
const router = createRouter({ |
|||
history: createWebHistory(import.meta.env.BASE_URL), |
|||
routes: [ |
|||
{ |
|||
path: "/", |
|||
name: "page", |
|||
component: () => import("@/components/blogs/HomePage.vue"), |
|||
}, |
|||
{ |
|||
path: "/login", |
|||
name: "login", |
|||
component: () => import("@/components/admin/SignIn.vue"), |
|||
}, |
|||
] |
|||
routes: route |
|||
}) |
|||
|
|||
export default router |
@ -0,0 +1,18 @@ |
|||
import { defineStore } from 'pinia'; |
|||
|
|||
export const useAuthStore = defineStore({ |
|||
id: 'auth', |
|||
state: () => ({ |
|||
token: '', |
|||
username:'' |
|||
}), |
|||
|
|||
actions: { |
|||
setToken(token:string) { |
|||
this.token = token; |
|||
}, |
|||
removeToken(){ |
|||
localStorage.removeItem('token'); |
|||
} |
|||
}, |
|||
}); |
@ -0,0 +1,364 @@ |
|||
<template> |
|||
<div class="content"> |
|||
<div class="card"> |
|||
<a-row :gutter="16" style="justify-content: space-between;"> |
|||
<a-col :span="4"> |
|||
<a-card> |
|||
<a-statistic title="博客数量" :value="count.blogCount" suffix="篇" :value-style="{ color: '#3f8600' }" |
|||
style="margin-right: 50px"> |
|||
<template #prefix> |
|||
<BlogOutLined /> |
|||
</template> |
|||
</a-statistic> |
|||
</a-card> |
|||
</a-col> |
|||
<a-col :span="4"> |
|||
<a-card> |
|||
<a-statistic title="照片数量" :value="count.photoCount" suffix="张" :value-style="{ color: '#3f8600' }" |
|||
style="margin-right: 50px"> |
|||
<template #prefix> |
|||
<PhotoOutLined /> |
|||
</template> |
|||
</a-statistic> |
|||
</a-card> |
|||
</a-col> |
|||
<a-col :span="4"> |
|||
<a-card> |
|||
<a-statistic title="文件总数" :value="count.fileCount" suffix="个" :value-style="{ color: '#3f8600' }" |
|||
style="margin-right: 50px"> |
|||
<template #prefix> |
|||
<FileOutLined /> |
|||
</template> |
|||
</a-statistic> |
|||
</a-card> |
|||
</a-col> |
|||
<a-col :span="4"> |
|||
<a-card> |
|||
<a-statistic title="日记发表" :value="count.diaryCount" suffix="篇" :value-style="{ color: '#3f8600' }" |
|||
style="margin-right: 50px"> |
|||
<template #prefix> |
|||
<DiaryOutLined /> |
|||
</template> |
|||
</a-statistic> |
|||
</a-card> |
|||
</a-col> |
|||
<a-col :span="4"> |
|||
<a-card> |
|||
<a-statistic title="评论数量" :value="count.commentCount" suffix="条" :value-style="{ color: '#3f8600' }" |
|||
style="margin-right: 50px"> |
|||
<template #prefix> |
|||
<CommentOutLined /> |
|||
</template> |
|||
</a-statistic> |
|||
</a-card> |
|||
</a-col> |
|||
</a-row> |
|||
</div> |
|||
<div class="echarts-1"> |
|||
<div ref="blog"></div> |
|||
<div ref="diary"></div> |
|||
<div ref="photo"></div> |
|||
</div> |
|||
<div class="echarts-2"> |
|||
<div ref="own"></div> |
|||
</div> |
|||
<div class="comment"> |
|||
<a-table bordered :data-source="dataSource" :columns="columns" :pagination="false"> |
|||
|
|||
<template #bodyCell="{ column, text, record }"> |
|||
|
|||
<template v-if="column.dataIndex === 'name'"> |
|||
<div class="editable-cell"> |
|||
|
|||
<div class="editable-cell-text-wrapper"> |
|||
{{ text || ' ' }} |
|||
|
|||
</div> |
|||
</div> |
|||
</template> |
|||
<template v-else-if="column.dataIndex === 'operation'"> |
|||
<a-popconfirm v-if="dataSource.length" title="确认删除么?" @confirm="onDelete(record.key)"> |
|||
<a>删除</a> |
|||
</a-popconfirm> |
|||
</template> |
|||
</template> |
|||
</a-table> |
|||
</div> |
|||
</div> |
|||
|
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
import { BlogOutLined, PhotoOutLined, FileOutLined, DiaryOutLined, CommentOutLined } from "@/assets"; |
|||
import { reactive, ref, onMounted } from 'vue'; |
|||
import type { Ref } from 'vue'; |
|||
import { createEcharts } from '@/hooks/intex' |
|||
|
|||
const count = reactive({ |
|||
blogCount: '12', |
|||
photoCount: '13', |
|||
fileCount: '14', |
|||
diaryCount: '15', |
|||
commentCount: '16' |
|||
}) |
|||
|
|||
const blog = ref(null); |
|||
const diary = ref(null); |
|||
const photo = ref(null); |
|||
const own = ref(null); |
|||
|
|||
|
|||
onMounted(() => { |
|||
const blogData = { |
|||
title: { |
|||
text: '周博客统计', |
|||
left: 'center' |
|||
}, |
|||
tooltip: { |
|||
trigger: 'item' |
|||
}, |
|||
legend: { |
|||
orient: 'vertical', |
|||
left: 'left' |
|||
}, |
|||
series: [ |
|||
{ |
|||
name: '数量', |
|||
type: 'pie', |
|||
radius: '50%', |
|||
data: [ |
|||
{ value: 1048, name: '星期一' }, |
|||
{ value: 735, name: '星期二' }, |
|||
{ value: 580, name: '星期三' }, |
|||
{ value: 484, name: '星期四' }, |
|||
{ value: 300, name: '星期五' }, |
|||
{ value: 300, name: '星期六' }, |
|||
{ value: 300, name: '星期日' } |
|||
], |
|||
emphasis: { |
|||
itemStyle: { |
|||
shadowBlur: 10, |
|||
shadowOffsetX: 0, |
|||
shadowColor: 'rgba(0, 0, 0, 0.5)' |
|||
} |
|||
} |
|||
} |
|||
] |
|||
}; |
|||
const diaryData = { |
|||
title: { |
|||
text: '周日记统计', |
|||
left: 'center' |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] |
|||
}, |
|||
yAxis: { |
|||
type: 'value' |
|||
}, |
|||
series: [ |
|||
{ |
|||
data: [820, 932, 901, 934, 1290, 1330, 1320], |
|||
type: 'line', |
|||
smooth: true |
|||
} |
|||
] |
|||
}; |
|||
const photoData = { |
|||
title: { |
|||
text: '周照片统计', |
|||
left: 'center' |
|||
}, |
|||
xAxis: { |
|||
type: 'category', |
|||
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'] |
|||
}, |
|||
yAxis: { |
|||
type: 'value' |
|||
}, |
|||
series: [ |
|||
{ |
|||
data: [120, 200, 150, 80, 70, 110, 130], |
|||
type: 'bar' |
|||
} |
|||
] |
|||
}; |
|||
const ownData = { |
|||
title: { |
|||
text: '完全统计', |
|||
left: 'center', |
|||
}, |
|||
tooltip: { |
|||
trigger: 'axis', |
|||
axisPointer: { |
|||
type: 'shadow' |
|||
} |
|||
}, |
|||
legend: { |
|||
data: ['月日记量', '月博客量', '月照片量', '月评论量', '月文件量'], |
|||
top: '10%', |
|||
}, |
|||
toolbox: { |
|||
show: true, |
|||
orient: 'vertical', |
|||
left: 'right', |
|||
bottom: '10%', |
|||
feature: { |
|||
mark: { show: true }, |
|||
magicType: { show: true, type: ['line', 'bar', 'stack'] }, |
|||
restore: { show: true }, |
|||
saveAsImage: { show: true } |
|||
} |
|||
}, |
|||
xAxis: [ |
|||
{ |
|||
type: 'category', |
|||
axisTick: { show: false }, |
|||
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], |
|||
} |
|||
], |
|||
yAxis: [ |
|||
{ |
|||
type: 'value' |
|||
} |
|||
], |
|||
series: [ |
|||
{ |
|||
name: '月日记量', |
|||
type: 'bar', |
|||
barGap: 0, |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [320, 332, 301, 334, 390, 320, 332, 301, 334, 390, 320, 332] |
|||
}, |
|||
{ |
|||
name: '月博客量', |
|||
type: 'bar', |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [220, 182, 191, 234, 290, 220, 182, 191, 234, 290, 220, 182] |
|||
}, |
|||
{ |
|||
name: '月照片量', |
|||
type: 'bar', |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [150, 232, 201, 154, 190, 220, 182, 191, 234, 290, 220, 182] |
|||
}, |
|||
{ |
|||
name: '月评论量', |
|||
type: 'bar', |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [98, 77, 101, 99, 40, 201, 154, 190, 220, 182, 191, 201, 154] |
|||
}, |
|||
{ |
|||
name: '月文件量', |
|||
type: 'bar', |
|||
emphasis: { |
|||
focus: 'series' |
|||
}, |
|||
data: [98, 77, 101, 99, 40, 40, 201, 154, 190, 220, 182, 154, 190] |
|||
} |
|||
], |
|||
grid: [ |
|||
{ |
|||
bottom: '10%' |
|||
} |
|||
] |
|||
}; |
|||
|
|||
createEcharts(blog, blogData) |
|||
createEcharts(diary, diaryData) |
|||
createEcharts(photo, photoData) |
|||
createEcharts(own, ownData) |
|||
|
|||
}); |
|||
|
|||
interface DataItem { |
|||
key: string; |
|||
name: string; |
|||
blog: string; |
|||
content: string; |
|||
time: String |
|||
} |
|||
|
|||
const columns = [ |
|||
{ |
|||
title: '评论人', |
|||
dataIndex: 'name', |
|||
width: '10%', |
|||
}, |
|||
{ |
|||
title: '评论博客', |
|||
dataIndex: 'blog', |
|||
width: '15%', |
|||
}, |
|||
{ |
|||
title: '评论内容', |
|||
dataIndex: 'content', |
|||
}, |
|||
{ |
|||
title: '评论时间', |
|||
dataIndex: 'time', |
|||
width: '10%', |
|||
}, |
|||
{ |
|||
title: '操作', |
|||
dataIndex: 'operation', |
|||
}, |
|||
]; |
|||
const dataSource: Ref<DataItem[]> = ref<DataItem[]>([ |
|||
{ |
|||
key: '0', |
|||
name: 'wupeng', |
|||
blog: "linux系统安装", |
|||
content: '教程比较详细', |
|||
time: '2014' |
|||
}, |
|||
{ |
|||
key: '1', |
|||
name: 'wulei', |
|||
blog: "linux命令", |
|||
content: '写的很好,值得借鉴!', |
|||
time: '2014' |
|||
}, |
|||
]); |
|||
const onDelete = (key: string) => { |
|||
dataSource.value = dataSource.value.filter(item => item.key !== key); |
|||
}; |
|||
|
|||
|
|||
|
|||
</script> |
|||
|
|||
<style scoped> |
|||
.content{ |
|||
margin: 24px; |
|||
} |
|||
.card, |
|||
.comment, |
|||
.echarts-1, |
|||
.echarts-2 { |
|||
background-color: #ececec; |
|||
padding: 24px; |
|||
} |
|||
|
|||
.echarts-1 { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
} |
|||
.echarts-1 div{ |
|||
width: calc(85vw/3); |
|||
height: calc((85vw/3)/(5/3)); |
|||
} |
|||
.echarts-2 div{ |
|||
width: calc(85vw); |
|||
height: calc((85vw/3)/2); |
|||
} |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue