This commit is contained in:
2025-11-26 15:17:27 +08:00
commit a8e4f56999
350 changed files with 25023 additions and 0 deletions

View File

@@ -0,0 +1,612 @@
<template>
<div class="container posts-content">
<TransitionGroup class="posts-list" name="list" tag="div">
<article class="post" v-for="post in postsList" :key="post.href">
<span v-if="post.pinned" class="pinned"></span>
<header class="post-header">
<div v-if="post.cover" class="cover-container">
<img
:src="post.cover"
class="cover-image"
:alt="post.title + '-cover'"
loading="lazy"
/>
</div>
<div class="header-content">
<div class="title">
<div class="title-dot" v-if="!post.cover"></div>
<h1 class="name">
<a :href="base + post.href">{{ post.title }}</a>
</h1>
</div>
<div class="meta-info-bar">
<span class="iconfont icon-time time"></span>
<div class="time-info">
<time datetime="">{{ formatDate(post.create) }}</time>
</div>
<div class="wordcount seperator">{{ post.wordCount }}</div>
</div>
<ul class="tags">
<li v-for="tag in post.tags">
<a :href="`${base}tags/`" @click="state.currTag = tag"
><i class="iconfont icon-tag"></i> {{ tag }}</a
>
</li>
</ul>
<div class="excerpt">
<p>{{ post.excerpt }}</p>
</div>
</div>
</header>
</article>
</TransitionGroup>
<div v-if="totalPage != 1" class="pagination">
<button
:disabled="currPage === 1"
:class="{ hide: currPage === 1 }"
id="up"
@click="goToPage(currPage - 1)"
>
<i class="iconfont icon-arrow"></i>
</button>
<div class="page-numbers">
<!-- 第一页 -->
<button class="page-number" :class="{ active: currPage === 1 }" @click="goToPage(1)">
1
</button>
<!-- 页码省略号 -->
<span v-if="showLeftEllipsis" class="ellipsis">...</span>
<!-- 当前页码 -->
<button
v-for="page in visiblePageNumbers"
:key="page"
class="page-number"
:class="{ active: currPage === page }"
@click="goToPage(page)"
>
{{ page }}
</button>
<!-- 页码省略号 -->
<span v-if="showRightEllipsis" class="ellipsis">...</span>
<!-- 尾页 -->
<button
v-if="totalPage > 1"
class="page-number"
:class="{ active: currPage === totalPage }"
@click="goToPage(totalPage)"
>
{{ totalPage }}
</button>
</div>
<button
:disabled="currPage >= totalPage"
:class="{ hide: currPage >= totalPage }"
id="next"
@click="goToPage(currPage + 1)"
>
<i class="iconfont icon-arrow"></i>
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useData } from 'vitepress'
import { ref, computed, onMounted, watch } from 'vue'
import { data as posts } from '../utils/posts.data'
import { useStore } from '../store'
const { state } = useStore()
const { page } = useData()
const base = useData().site.value.base
// 日期格式化
function formatDate(timestamp: number): string {
const date = new Date(timestamp)
return new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).format(date)
}
// 文章传值
const finalPosts = computed(() => {
if (page.value.filePath === 'index.md') {
return posts
} else if (page.value.filePath === 'tags/index.md') {
return state.selectedPosts
}
return []
})
// 文章列表长度
const pageSize = ref(5)
// 使用store中的currPage
const currPage = computed({
get: () => state.currPage,
set: (value) => (state.currPage = value),
})
onMounted(() => {
// 获取URL信息
updatePageFromUrl()
// 监听前进后退
window.addEventListener('popstate', () => {
updatePageFromUrl()
})
})
function updatePageFromUrl() {
const urlParams = new URLSearchParams(window.location.search)
const pageParam = urlParams.get('page')
if (
pageParam &&
!isNaN(parseInt(pageParam)) &&
parseInt(pageParam) > 0 &&
parseInt(pageParam) <= totalPage.value
) {
state.currPage = parseInt(pageParam)
} else {
state.currPage = 1
}
}
// 更新页码逻辑
function goToPage(page: number) {
if (page < 1 || page > totalPage.value) return
state.currPage = page
// 获取URL信息
const url = new URL(window.location.href)
// 非首页时获取URL页码
if (page > 1) {
url.searchParams.set('page', page.toString())
} else {
url.searchParams.delete('page')
}
// Tag页面页码逻辑
const tagParam = url.searchParams.get('tag')
if (tagParam) {
url.searchParams.set('tag', tagParam)
}
window.history.pushState({}, '', url.toString())
}
// 计算要显示的页码
const maxVisiblePages = 3 // 省略号两边显示的页码按钮数量
const visiblePageNumbers = computed(() => {
if (totalPage.value <= 7)
return Array.from({ length: totalPage.value - 2 }, (_, i) => i + 2).filter(
(p) => p > 1 && p < totalPage.value,
)
let startPage = Math.max(2, currPage.value - Math.floor(maxVisiblePages / 2))
let endPage = Math.min(totalPage.value - 1, startPage + maxVisiblePages - 1)
if (endPage - startPage < maxVisiblePages - 1) {
startPage = Math.max(2, endPage - maxVisiblePages + 1)
}
return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i)
})
// 省略号显示逻辑
const showLeftEllipsis = computed(() => {
return totalPage.value > 7 && visiblePageNumbers.value[0] > 2
})
const showRightEllipsis = computed(() => {
return (
totalPage.value > 7 &&
visiblePageNumbers.value[visiblePageNumbers.value.length - 1] < totalPage.value - 1
)
})
const postsList = computed(() => {
return finalPosts.value.slice(
(currPage.value - 1) * pageSize.value,
currPage.value * pageSize.value,
)
})
const totalPage = computed(() => {
return Math.ceil(finalPosts.value.length / pageSize.value) || 1
})
// 监听文章列表
watch(
() => state.selectedPosts,
() => {
// 标签页逻辑获取URL页码
const urlParams = new URLSearchParams(window.location.search)
const pageParam = urlParams.get('page')
// 标签更改时重置页码
const newTotalPages = Math.ceil(state.selectedPosts.length / pageSize.value) || 1
if (!pageParam || currPage.value > newTotalPages) {
currPage.value = 1
// 更新URL
if (pageParam) {
const url = new URL(window.location.href)
url.searchParams.delete('page')
window.history.pushState({}, '', url.toString())
}
}
},
)
</script>
<style scoped lang="less">
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.5s;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
}
.list-leave-active {
position: absolute;
right: 0;
left: 0;
}
.posts-content {
article,
h1,
ul {
margin: 0;
padding: 0;
}
}
.posts-list {
position: relative;
overflow-wrap: break-word;
.post {
display: flex;
flex-direction: column;
margin: 0 0 50px 0;
padding-bottom: 16px;
background-color: var(--foreground-color);
border-radius: 32px;
border-left: solid 16px var(--pot-border-left);
background-image: var(--deco1);
background-size: contain;
background-position: right;
background-repeat: no-repeat;
box-shadow: 0px 0px 8px rgb(var(--blue-shadow-color), 0.8);
transition: all 0.5s;
.pinned {
position: absolute;
width: 42px;
height: 42px;
top: -8px;
right: -8px;
border-radius: 50px;
background: var(--icon-pinned) no-repeat;
background-size: contain;
box-shadow: 0 0 6px rgba(var(--blue-shadow-color), 0.65);
}
.post-header {
display: flex;
gap: 24px;
padding: 32px 40px 0;
// flex-direction: row-reverse;
position: relative;
align-items: stretch;
.cover-container {
flex: 0 0 180px;
height: 140px;
border-radius: 12px;
overflow: hidden;
position: relative;
margin-left: -8px;
margin-bottom: 15px;
align-self: center;
.cover-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
&:hover {
transform: scale(1.05);
}
}
}
.header-content {
flex: 1;
min-width: 0;
flex-direction: column;
.title {
position: relative;
margin-bottom: 8px;
}
.excerpt {
flex: 1;
display: flex;
align-items: flex-end;
}
}
}
@media (max-width: 768px) {
.post-header {
flex-direction: column;
gap: 16px;
padding: 24px 20px 0;
.cover-container {
flex: none;
width: 100%;
height: 240px;
margin-left: 0;
}
}
}
}
}
.post-header {
padding: 32px 40px 0;
.title {
position: relative;
margin-bottom: 8px;
.title-dot {
width: 4px;
height: 20px;
position: absolute;
left: -16px;
top: 9.5px;
background: var(--pot-border-left);
border-radius: 2px;
transition: background 0.5s;
}
.name {
display: flex;
align-items: center;
gap: 15px;
}
a {
color: var(--font-color-grey);
transition: text-shadow 0.5s, color 0.5s;
&:hover {
text-shadow: 0 0 3px var(--font-color-grey);
}
}
}
.meta-info-bar {
display: flex;
margin-bottom: 7px;
opacity: 0.75;
.time {
font-size: 13px;
color: var(--font-color-grey);
margin: 3px 2px 0 0;
font-weight: bold;
}
.seperator::before {
content: '';
display: inline-block;
border-radius: 50%;
height: 4px;
width: 4px;
vertical-align: middle;
background-color: var(--font-color-grey);
margin: 0 16px;
}
}
}
.tags {
display: flex;
align-items: center;
flex-wrap: wrap;
padding: 0;
margin-bottom: 6px;
li {
display: flex;
justify-content: center;
align-items: center;
padding-top: 6px;
margin-right: 16px;
a {
color: var(--font-color-grey);
padding: 3px 5px;
color: var(--font-color-gold);
background-color: var(--btn-background);
border-radius: 5px;
transition: all 0.5s;
&:hover {
background-color: var(--btn-hover);
color: var(--font-color-gold);
}
}
}
}
.pagination {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 50px;
padding: 0;
button {
background-color: transparent;
border-style: none;
cursor: pointer;
}
.hide {
opacity: 0;
cursor: auto;
}
.icon-arrow {
font-size: 36px;
color: var(--icon-color);
}
#up {
animation: arrow-pre 1s ease-in-out infinite alternate;
}
#next {
animation: arrow-next 1s ease-in-out infinite alternate;
}
.page-numbers {
display: flex;
align-items: center;
gap: 8px;
}
.page-number {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
font-size: 16px;
border-radius: 6px;
color: var(--icon-color);
transition: all 0.3s;
&:hover {
background-color: var(--btn-hover);
color: var(--font-color-gold);
}
&.active {
background-color: var(--btn-hover);
color: var(--font-color-gold);
font-weight: bold;
}
}
.ellipsis {
margin: 0 4px;
color: var(--icon-color);
}
}
@keyframes arrow-pre {
from {
transform: translateX(0) rotate(-0.25turn);
}
to {
transform: translateX(10px) rotate(-0.25turn);
}
}
@keyframes arrow-next {
from {
transform: translateX(0) rotate(0.25turn);
}
to {
transform: translateX(-10px) rotate(0.25turn);
}
}
@media (max-width: 768px) {
.posts-list {
.post {
margin: 0 8px 30px 8px;
background-size: cover;
border-left: solid 1.5vh var(--pot-border-left);
.pinned {
width: 27px;
height: 27px;
top: -2px;
right: 12px;
}
}
}
.post-header {
padding: 20px 35px 0;
.name {
font-size: 24px;
}
.title {
margin-bottom: 6px;
.title-dot {
height: 18px;
top: 6px;
}
}
.meta-info-bar {
margin-bottom: 4px;
font-size: 12px;
.time {
font-size: 8px !important;
margin: 3px 2px 0 0 !important;
}
.seperator::before {
margin: 0 8px;
}
}
}
.tags {
li {
padding-top: 4px;
margin-right: 8px;
a {
font-size: 12px;
padding: 4px 6px;
.icon-tag {
font-size: 12px;
}
}
}
}
.excerpt {
padding: 0;
margin-bottom: 4px;
font-size: 12px;
}
.pagination {
margin-top: 32px;
.icon-arrow {
font-size: 32px;
}
.page-number {
width: 28px;
height: 28px;
font-size: 14px;
}
.page-numbers {
gap: 4px;
}
}
}
</style>