diff --git a/src/assets/index.ts b/src/assets/index.ts new file mode 100644 index 0000000..02f77a3 --- /dev/null +++ b/src/assets/index.ts @@ -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} \ No newline at end of file diff --git a/src/components/admin/MainWrapper.vue b/src/components/admin/MainWrapper.vue new file mode 100644 index 0000000..cd46669 --- /dev/null +++ b/src/components/admin/MainWrapper.vue @@ -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> \ No newline at end of file diff --git a/src/components/blogs/HomePage.vue b/src/components/blogs/HomePage.vue index 8c7c3ac..032b8d3 100644 --- a/src/components/blogs/HomePage.vue +++ b/src/components/blogs/HomePage.vue @@ -1,5 +1,5 @@ <template> - + <div>ceshi</div> </template> <script setup lang='ts'> diff --git a/src/router/admin.ts b/src/router/admin.ts index e69de29..1925f4d 100644 --- a/src/router/admin.ts +++ b/src/router/admin.ts @@ -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 \ No newline at end of file diff --git a/src/router/home.ts b/src/router/home.ts new file mode 100644 index 0000000..d3a35a8 --- /dev/null +++ b/src/router/home.ts @@ -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 diff --git a/src/router/index.ts b/src/router/index.ts index 51c2857..24b7c92 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -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 diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..293f49d --- /dev/null +++ b/src/store/index.ts @@ -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'); + } + }, +}); diff --git a/src/views/admin/DashBoardView.vue b/src/views/admin/DashBoardView.vue new file mode 100644 index 0000000..50323f9 --- /dev/null +++ b/src/views/admin/DashBoardView.vue @@ -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> \ No newline at end of file