You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
674 lines
19 KiB
674 lines
19 KiB
<template>
|
|
<Simplebar @scroll="handleScroll">
|
|
<!-- 头部导航菜单 -->
|
|
<div class="headerMenu" v-if="show_menu">
|
|
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" style="border-bottom: none;" />
|
|
<a-input-search v-model:value="value" placeholder="search" style="width: 200px" @search="onSearch" />
|
|
</div>
|
|
<div class="author" v-if="show_author"></div>
|
|
|
|
<!-- 轮播 -->
|
|
<div class="carousel" v-if="show_carousel">
|
|
<a-carousel autoplay>
|
|
<div class="img"><img src="/src/assets/images/nav1.png" alt=""></div>
|
|
<div class="img"><img src="/src/assets/images/nav10.png" alt=""></div>
|
|
<div class="img"><img src="/src/assets/images/nav13.png" alt=""></div>
|
|
</a-carousel>
|
|
</div>
|
|
<!-- 操作按钮滚动 -->
|
|
<div class="anchorDown" v-if="show_anchornDown">
|
|
<a-button type="link" shape="circle" size="large" @click="downScroll">
|
|
<template #icon>
|
|
<DownCircleOutlined style="font-size: 35px;color: aliceblue;" spin />
|
|
</template>
|
|
</a-button>
|
|
</div>
|
|
|
|
<!-- 主要内容区域 -->
|
|
<div class="mainContainer" :style="mainCss">
|
|
<div class="leftBar">
|
|
<a-card hoverable>
|
|
<template #cover>
|
|
<img alt="example" src="/src/assets/images/头像.jpg" />
|
|
</template>
|
|
<h1>sunfree</h1>
|
|
<div class="cardText"></div>
|
|
<div class="button-group">
|
|
<a-button v-for="(button, index) in buttons" :key="index" shape="circle" size="large"
|
|
@click="handleClick(button.url)">
|
|
<component :is="button.icon" />
|
|
</a-button>
|
|
</div>
|
|
<div class="fold-panel">
|
|
<a-collapse v-model:activeKey="activeKey" :bordered="false" expandIconPosition="end">
|
|
<template #expandIcon="{ isActive }">
|
|
<caret-right-outlined :rotate="isActive ? 90 : 0" />
|
|
</template>
|
|
<a-collapse-panel v-for="panel in panels" :key=panel.id :header=panel.header
|
|
:style="customStyle">
|
|
<p>{{ panel.text }}</p>
|
|
</a-collapse-panel>
|
|
</a-collapse>
|
|
</div>
|
|
</a-card>
|
|
<a-card hoverable title="常用链接">
|
|
<div class="button-group">
|
|
<a-button type="dashed" v-for="linkbutton in linkbuttons" :key="linkbutton.id"
|
|
@click="comLinkClick(linkbutton.linkurl)">{{
|
|
linkbutton.linktext
|
|
}}</a-button>
|
|
</div>
|
|
</a-card>
|
|
</div>
|
|
<RouterView />
|
|
<div class="rightBar">
|
|
<a-card hoverable>
|
|
<div class="statistic">
|
|
<a-row>
|
|
<a-col :span="8" v-for="statistic in statistics" :key="statistic.id">
|
|
<a-statistic :title=statistic.title :value=statistic.counts />
|
|
</a-col>
|
|
</a-row>
|
|
</div>
|
|
</a-card>
|
|
<a-card hoverable>
|
|
<template #cover>
|
|
<div class="heatmap" style="border-right: rgba(0, 0, 0, 0.5);">
|
|
<div ref="heat" style="height: 100%;"></div>
|
|
</div>
|
|
</template>
|
|
</a-card>
|
|
|
|
<a-card hoverable>
|
|
<template #cover>
|
|
<div id="aplayer"></div>
|
|
</template>
|
|
</a-card>
|
|
<a-card title="随机文章" :bordered="false" hoverable>
|
|
<div v-for="article in 3">
|
|
<a-image :preview="false" :width="300"
|
|
src="https://cdn.naccl.top/blog/blogHosting/2024/02/B01/f56c5bbe-469c-4eb7-a994-9281d6eed689.png" />
|
|
|
|
<div class="article-text">
|
|
<span>2021-02-01</span>
|
|
<span>操作系统</span>
|
|
</div>
|
|
</div>
|
|
|
|
</a-card>
|
|
|
|
<a-card title="标签云" :bordered="false" hoverable>
|
|
sces
|
|
</a-card>
|
|
</div>
|
|
</div>
|
|
|
|
</Simplebar>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
import { h, reactive, ref, nextTick } from 'vue';
|
|
import type { MenuProps } from 'ant-design-vue';
|
|
import { HomeOutlined, HighlightOutlined, ProfileOutlined, CameraOutlined, UsergroupDeleteOutlined, DownCircleOutlined } from '@ant-design/icons-vue';
|
|
import Typed from 'typed.js';
|
|
import { onMounted, watch } from 'vue';
|
|
import type { CSSProperties } from 'vue';
|
|
import { CaretRightOutlined } from '@ant-design/icons-vue';
|
|
import 'APlayer/dist/APlayer.min.css';
|
|
import APlayer from 'APlayer';
|
|
import { createEcharts } from "@/hooks/intex"
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
import iconComponents from "@/assets/index";
|
|
import { get } from "@/tools/request";
|
|
import { number } from 'echarts';
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
|
|
|
|
/**
|
|
* 导航菜单
|
|
*/
|
|
const show_menu = ref(false);
|
|
const show_carousel = ref(true);
|
|
const show_author = ref(true)
|
|
const show_anchornDown = ref(true)
|
|
const mainCss = reactive({
|
|
marginTop: "0px"
|
|
})
|
|
type MenuProps = any;
|
|
const current = ref<string[]>(['home']);
|
|
const items = ref<MenuProps['items']>([
|
|
{
|
|
key: 'home',
|
|
icon: () => h(HomeOutlined),
|
|
label: h('a', { href: '/' }, '首页'),
|
|
title: '首页',
|
|
|
|
},
|
|
{
|
|
key: 'blog',
|
|
icon: () => h(HighlightOutlined),
|
|
label: h('a', { href: "/blog" }, '博客'),
|
|
title: '博客',
|
|
},
|
|
{
|
|
key: 'diary',
|
|
icon: () => h(ProfileOutlined),
|
|
label: h('a', { href: "/diary" }, '日记'),
|
|
title: '日记',
|
|
},
|
|
{
|
|
key: 'album',
|
|
icon: () => h(CameraOutlined),
|
|
label: '相册',
|
|
title: '相册',
|
|
children: [
|
|
{
|
|
label: '相册1',
|
|
key: 'album1',
|
|
// url: "album1"
|
|
},
|
|
{
|
|
label: '相册2',
|
|
key: 'album2',
|
|
},
|
|
{
|
|
label: '相册3',
|
|
key: 'album3',
|
|
},
|
|
{
|
|
label: '相册4',
|
|
key: 'album4',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: 'chart',
|
|
icon: () => h(UsergroupDeleteOutlined),
|
|
label: h('a', { href: "/chart" }, '收支图'),
|
|
title: '收支图',
|
|
},
|
|
{
|
|
key: 'aboutme',
|
|
icon: () => h(UsergroupDeleteOutlined),
|
|
label: h('a', { href: "/aboutme" }, '关于sunfree'),
|
|
title: '关于sunfree',
|
|
},
|
|
]);
|
|
const updateCarouselVisibility = (routeName: any) => {
|
|
handleScrollEnabled.value = false;
|
|
if (scrollbar.value) {
|
|
scrollbar.value.scrollTop = 0;
|
|
}
|
|
if (routeName === 'home') {
|
|
handleScrollEnabled.value = true;
|
|
show_carousel.value = true;
|
|
show_menu.value = false
|
|
} else {
|
|
show_menu.value = true;
|
|
show_author.value = false;
|
|
show_carousel.value = false;
|
|
show_anchornDown.value = false;
|
|
mainCss.marginTop = '48px';
|
|
}
|
|
};
|
|
router.beforeEach((to, from, next) => {
|
|
updateCarouselVisibility(to.name);
|
|
next();
|
|
});
|
|
// 查询功能
|
|
const articleTitle = ref<string>('');
|
|
const onSearch = (searchValue: string) => {
|
|
console.log('use value', searchValue);
|
|
console.log('or use this.value', articleTitle.value);
|
|
};
|
|
|
|
|
|
/**
|
|
* 滚动条操作
|
|
*/
|
|
const scrollbar = ref<Element | null>(null);
|
|
const handleScrollEnabled = ref(true);
|
|
// 定义滚动条滚到一半显示导航菜单
|
|
const handleScroll = () => {
|
|
if (!handleScrollEnabled.value) return;
|
|
if (scrollbar.value) {
|
|
const scrollOffset = scrollbar.value.scrollTop;
|
|
const halfViewportHeight = scrollbar.value.clientHeight / 2;
|
|
show_menu.value = scrollOffset > halfViewportHeight;
|
|
}
|
|
};
|
|
// 点击按钮,实现滚动到视窗高度距离
|
|
const downScroll = () => {
|
|
if (scrollbar.value) {
|
|
const scrollTop = scrollbar.value.scrollTop;
|
|
const viewportHeight = scrollbar.value.clientHeight;
|
|
const scrollDistance = viewportHeight - scrollTop - 48;
|
|
setTimeout(() => {
|
|
scrollbar.value!.scrollBy({ top: scrollDistance, behavior: 'smooth' });
|
|
}, 300);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 左侧栏
|
|
*/
|
|
// 折叠面板
|
|
const activeKey = ref(['']);
|
|
const customStyle = 'background: #F5F5F5;border-radius: 4px;margin-bottom: 12px;border: 0;overflow: hidden';
|
|
interface panelList {
|
|
id: number,
|
|
header: string,
|
|
text: string
|
|
}
|
|
const panels = ref<panelList[]>([])
|
|
const panelData = async () => {
|
|
try {
|
|
const response = await get("/classtic/list");
|
|
if (response) {
|
|
panels.value = response.data.data.map((item: any) => ({
|
|
id: item.id,
|
|
header: item.header,
|
|
text: item.TEXT,
|
|
}));
|
|
} else {
|
|
console.error("Response data structure is not as expected:");
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch data", error);
|
|
}
|
|
}
|
|
// console.log(panels)
|
|
// 社交按钮
|
|
const buttons = ref([
|
|
{ icon: iconComponents.CravatarLined, url: 'https://cravatar.cn/' },
|
|
{ icon: iconComponents.QQLined, url: '/qqcode' },
|
|
{ icon: iconComponents.WechatLined, url: '/wechatcode' },
|
|
{ icon: iconComponents.MusicLined, url: 'https://music.163.com/#/playlist?id=160266689' },
|
|
{ icon: iconComponents.GitHubLined, url: 'https://gitee.com/c_panda' },
|
|
]);
|
|
const handleClick = (url: string) => {
|
|
window.open(url)
|
|
}
|
|
// 链接按钮
|
|
interface comlinkList {
|
|
id: number,
|
|
linktext: string,
|
|
linkurl: string
|
|
}
|
|
const linkbuttons = ref<comlinkList[]>([])
|
|
const comLinkData = async () => {
|
|
try {
|
|
const response = await get("/comlink/list");
|
|
if (response) {
|
|
linkbuttons.value = response.data.data.map((items: any) => ({
|
|
id: items.id,
|
|
linktext: items.linktext,
|
|
linkurl: items.linkurl
|
|
}))
|
|
} else {
|
|
console.log("response data is not exist")
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch data", error);
|
|
}
|
|
|
|
}
|
|
const comLinkClick = (url: string) => {
|
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
window.open (url,"_blank"); // 使用 window.location.href 打开外部链接
|
|
} else {
|
|
router.push(url); // 否则使用 Vue Router 的 push 方法导航
|
|
}
|
|
}
|
|
/**
|
|
* 右侧栏
|
|
*/
|
|
// 统计
|
|
const statistics = reactive([
|
|
{
|
|
id: "1",
|
|
title: "DAYS",
|
|
counts: "111"
|
|
},
|
|
{
|
|
id: "2",
|
|
title: "DIARYS",
|
|
counts: "111"
|
|
},
|
|
{
|
|
id: "3",
|
|
title: "BLOGS",
|
|
counts: "111"
|
|
},
|
|
])
|
|
// 日历热力图
|
|
const heat = ref(null);
|
|
const newData = <any>[];
|
|
// 初始化60天的数据
|
|
const data = generateDates(60);
|
|
console.log(data)
|
|
// 重新排列数据
|
|
for (let i = 0; i < 60; i += 15) {
|
|
// 取出每个15天的数据,并反转顺序
|
|
const chunk = data.slice(i, i + 15).reverse();
|
|
newData.push(...chunk);
|
|
}
|
|
|
|
function generateDates(numDays: number) {
|
|
const dates = [];
|
|
const currentDate = new Date();
|
|
for (let i = 0; i < numDays; i++) {
|
|
const date = new Date(currentDate);
|
|
date.setDate(currentDate.getDate() - i);
|
|
dates.push(
|
|
{ date: date.toISOString().split('T')[0], writCount: 0 }
|
|
); // 包含日期和评论数
|
|
}
|
|
return dates;
|
|
}
|
|
const writList = [5, 4, 3, 2, 1, 0]
|
|
writList.forEach((item, index) => {
|
|
data[index].writCount = item;
|
|
})
|
|
const formattedData = newData.map((value: any, index: number) => [index % 15, Math.floor(index / 15), value.writCount]);
|
|
const heatMapData = {
|
|
tooltip: {
|
|
position: 'top',
|
|
formatter: function (params: any) {
|
|
const item = newData[params.dataIndex];
|
|
if (item.writCount > 0) {
|
|
return `${item.date}<br/>COMMENTS: ${item.writCount}`;
|
|
} else {
|
|
return `${item.date}`;
|
|
}
|
|
}
|
|
},
|
|
grid: {
|
|
left: '0', // 左边距
|
|
right: '0', // 右边距
|
|
top: '0', // 上边距
|
|
bottom: '0',
|
|
},
|
|
xAxis: {
|
|
type: 'category',
|
|
data: Array.from({ length: 15 }, (_, i) => `Col ${i + 1}`),
|
|
splitArea: {
|
|
show: true
|
|
},
|
|
show: false
|
|
},
|
|
yAxis: {
|
|
type: 'category',
|
|
data: ['Row 1', 'Row 2', 'Row 3', 'Row 4'],
|
|
splitArea: {
|
|
show: false
|
|
},
|
|
show: false
|
|
},
|
|
visualMap: {
|
|
min: 0,
|
|
max: 10,
|
|
calculable: true,
|
|
orient: 'horizontal',
|
|
left: 'center',
|
|
bottom: '15%',
|
|
show: false,
|
|
inRange: {
|
|
color: ['rgba(182,181,178,0.01)', 'rgb(157,156,153,1)'] // 0评论是白色, 非0评论是黑色
|
|
}
|
|
},
|
|
series: [{
|
|
type: 'heatmap',
|
|
data: formattedData,
|
|
itemStyle: {
|
|
borderColor: 'rgb(231,229,225,0.5)', // 设置边框颜色
|
|
borderWidth: 0.5, // 设置边框宽度
|
|
},
|
|
label: {
|
|
show: false,
|
|
},
|
|
emphasis: {
|
|
itemStyle: {
|
|
shadowBlur: 10,
|
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
|
borderColor: '#fff', // 鼠标悬停时的边框颜色
|
|
borderWidth: 2 // 鼠标悬停时的边框宽度
|
|
}
|
|
}
|
|
}]
|
|
};
|
|
// 音乐组件
|
|
const random = ref();
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
scrollbar.value = document.querySelector('.simplebar-content-wrapper');
|
|
panelData()
|
|
comLinkData()
|
|
nextTick(() => {
|
|
const authorElement = document.querySelector('.author');
|
|
if (authorElement) {
|
|
new Typed('.author', {
|
|
strings: ['SunFree.'],
|
|
typeSpeed: 200,
|
|
backSpeed: 150,
|
|
loop: true,
|
|
loopCount: Infinity,
|
|
showCursor: false
|
|
});
|
|
};
|
|
const cardTextElement = document.querySelector('.cardText');
|
|
if (cardTextElement) {
|
|
// 定义个人座右铭
|
|
new Typed('.cardText', {
|
|
strings: ['做三流测试,品瀚霖人生!'],
|
|
typeSpeed: 200,
|
|
backSpeed: 150,
|
|
loop: true, // 开启循环
|
|
loopCount: Infinity, // 无限循环
|
|
showCursor: false // 取消光标
|
|
});
|
|
};
|
|
const aplayerContainer = document.getElementById('aplayer');
|
|
if (aplayerContainer) {
|
|
new APlayer({
|
|
container: aplayerContainer,
|
|
mini: false,
|
|
autoplay: false,
|
|
theme: '#FADFA3',
|
|
loop: 'all',
|
|
order: 'random',
|
|
preload: 'auto',
|
|
volume: 0.7,
|
|
mutex: true,
|
|
listFolded: true,
|
|
listMaxHeight: 90,
|
|
lrcType: 3,
|
|
audio: [
|
|
{
|
|
name: 'name1',
|
|
artist: 'artist1',
|
|
url: 'url1.mp3',
|
|
cover: 'cover1.jpg',
|
|
lrc: 'lrc1.lrc',
|
|
theme: '#ebd0c2'
|
|
},
|
|
{
|
|
name: 'name2',
|
|
artist: 'artist2',
|
|
url: 'url2.mp3',
|
|
cover: 'cover2.jpg',
|
|
lrc: 'lrc2.lrc',
|
|
theme: '#46718b'
|
|
}
|
|
]
|
|
});
|
|
}
|
|
});
|
|
createEcharts(heat, heatMapData);
|
|
|
|
|
|
})
|
|
watch(
|
|
() => route.name,
|
|
(newRouteName) => {
|
|
updateCarouselVisibility(newRouteName);
|
|
if (newRouteName) {
|
|
const menuItem = items.value.find(item => item.key === newRouteName);
|
|
if (menuItem) {
|
|
current.value = [menuItem.key];
|
|
} else {
|
|
current.value = ['home']; // 如果找不到对应的菜单项,默认选中 'home'
|
|
}
|
|
} else {
|
|
current.value = ['home']; // 如果 route.name 不存在,默认选中 'home'
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
</script>
|
|
<style scoped>
|
|
.headerMenu {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 100%;
|
|
height: 48px;
|
|
border-bottom: 1px solid rgba(5, 5, 5, 0.06);
|
|
position: fixed;
|
|
background-color: white;
|
|
transform: translateX(-50%);
|
|
top: 0;
|
|
left: 50%;
|
|
z-index: 999;
|
|
}
|
|
|
|
|
|
|
|
/* 作者名称 */
|
|
.author {
|
|
position: absolute;
|
|
display: inline-block;
|
|
font-size: 100px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
background: linear-gradient(270deg, #f5f4f2, #d6cdc7, #4b4949);
|
|
background-size: 400% 400%;
|
|
-webkit-background-clip: text;
|
|
color: transparent;
|
|
background-clip: text;
|
|
animation: salon-light-animation 3s ease infinite;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 999;
|
|
}
|
|
|
|
@keyframes salon-light-animation {
|
|
0% {
|
|
background-position: 0% 50%;
|
|
}
|
|
|
|
50% {
|
|
background-position: 100% 50%;
|
|
}
|
|
|
|
100% {
|
|
background-position: 0% 50%;
|
|
}
|
|
}
|
|
|
|
.carousel img {
|
|
height: 100vh;
|
|
width: 100vw;
|
|
}
|
|
|
|
.anchorDown {
|
|
position: absolute;
|
|
bottom: 100px;
|
|
left: 50%;
|
|
border-style: hidden;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
.mainContainer {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding-top: 48px;
|
|
background-color: rgba(5, 5, 5, 0.08);
|
|
}
|
|
|
|
.leftBar {
|
|
width: 15%;
|
|
}
|
|
|
|
.leftBar img {
|
|
/* 图片自适应容器并保持宽高比例 */
|
|
aspect-ratio: 1/1;
|
|
}
|
|
|
|
.leftBar>* {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.leftBar h1 {
|
|
text-align: center;
|
|
font-family: Georgia, 'Times New Roman', Times, serif;
|
|
}
|
|
|
|
.leftBar .cardText {
|
|
min-height: 60px;
|
|
text-align: center;
|
|
}
|
|
|
|
.leftBar>:first-child .button-group {
|
|
display: flex;
|
|
margin: 0 12px 24px 12px;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.leftBar>:nth-child(2) .button-group {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.leftBar>:nth-child(2) .button-group>* {
|
|
width: 40%;
|
|
margin: 12px;
|
|
}
|
|
|
|
|
|
.rightBar {
|
|
width: 15%;
|
|
}
|
|
|
|
.rightBar>* {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.rightBar>:nth-child(4) div {
|
|
display: flex;
|
|
justify-content: center;
|
|
position: relative;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.rightBar>:nth-child(4) div .article-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: absolute;
|
|
bottom: 5%;
|
|
left: 10%;
|
|
color: rgb(187, 185, 187);
|
|
}
|
|
|
|
#aplayer {
|
|
margin: 0;
|
|
}
|
|
|
|
.statistic {
|
|
text-align: center;
|
|
}
|
|
</style>
|