feat: blog
This commit is contained in:
@@ -14,6 +14,7 @@ export function nav() {
|
|||||||
{ text: '8.基础学科', link: '/8.基础学科/8.基础学科' },
|
{ text: '8.基础学科', link: '/8.基础学科/8.基础学科' },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ text: '博客', link: '/blog/' },
|
||||||
{ text: 'Wiki史', link: '/wiki史' },
|
{ text: 'Wiki史', link: '/wiki史' },
|
||||||
{
|
{
|
||||||
text: '友链', items:
|
text: '友链', items:
|
||||||
|
|||||||
21
blog/index.md
Normal file
21
blog/index.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: Blogs
|
||||||
|
editLink: false
|
||||||
|
aside: false
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import PostList from '../components/PostList.vue'
|
||||||
|
import Blogger from '../components/Blogger.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
# Friends <Badge type="warning" text="beta" />
|
||||||
|
|
||||||
|
<Suspense><Blogger ></Blogger></Suspense>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Recent Posts <Badge type="warning" text="beta" />
|
||||||
|
|
||||||
|
<Suspense><PostList ></PostList></Suspense>
|
||||||
51
components/Blogger.vue
Normal file
51
components/Blogger.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import VPTeamMembersItem from './BloggerItem.vue'
|
||||||
|
const size = 'small'
|
||||||
|
const props= {friends:(await (await fetch("https://raw.githubusercontent.com/NX-Official/friends-link-plus/main/output/friends.json")).json()).friends}
|
||||||
|
const members = props.friends
|
||||||
|
const classes = computed(() => [
|
||||||
|
size ?? 'medium',
|
||||||
|
`count-${members.length}`
|
||||||
|
])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="VPTeamMembers" :class="classes">
|
||||||
|
<div class="container">
|
||||||
|
<div v-for="member in members" :key="member.name" class="item">
|
||||||
|
<VPTeamMembersItem :size="size" :member="member" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPTeamMembers.small .container {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(224px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembers.small.count-1 .container { max-width: 276px; }
|
||||||
|
.VPTeamMembers.small.count-2 .container { max-width: calc(276px * 2 + 24px); }
|
||||||
|
.VPTeamMembers.small.count-3 .container { max-width: calc(276px * 3 + 24px * 2); }
|
||||||
|
|
||||||
|
.VPTeamMembers.medium .container {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(256px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 375px) {
|
||||||
|
.VPTeamMembers.medium .container {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(288px, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembers.medium.count-1 .container { max-width: 368px; }
|
||||||
|
.VPTeamMembers.medium.count-2 .container { max-width: calc(368px * 2 + 24px); }
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
gap: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 1152px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
234
components/BloggerItem.vue
Normal file
234
components/BloggerItem.vue
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
<script setup >
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
size: String,
|
||||||
|
member: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
const goUrl = (url)=> {
|
||||||
|
window.open(url,"_blank")
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article class="VPTeamMembersItem" :class="[size ?? 'medium']">
|
||||||
|
<div class="profile">
|
||||||
|
<figure class="avatar">
|
||||||
|
<img class="avatar-img" :src="member.avatar" :alt="member.name">
|
||||||
|
</figure>
|
||||||
|
<div class="data">
|
||||||
|
<h1 class="name">
|
||||||
|
{{ member.name }}
|
||||||
|
</h1>
|
||||||
|
<p v-if="member.title || member.org" class="affiliation">
|
||||||
|
<span v-if="member.title" class="title">
|
||||||
|
{{ member.title }}
|
||||||
|
</span>
|
||||||
|
<span v-if="member.title && member.org" class="at">
|
||||||
|
@
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p v-if="member.description" class="desc" v-html="member.description"/>
|
||||||
|
<div v-if="member.url" class="links">
|
||||||
|
<svg v-if="member.rss" @click="goUrl(member.rss)" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="currentColor" d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm1.5 2.5c5.523 0 10 4.477 10 10a1 1 0 1 1-2 0a8 8 0 0 0-8-8a1 1 0 0 1 0-2zm0 4a6 6 0 0 1 6 6a1 1 0 1 1-2 0a4 4 0 0 0-4-4a1 1 0 0 1 0-2zm.5 7a1.5 1.5 0 1 1 0-3a1.5 1.5 0 0 1 0 3z"/></svg>
|
||||||
|
<svg v-if="member.url" @click="goUrl(member.url)" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 1000 1000"><path fill="currentColor" d="M196.099.156C88.379.156.219 88.317.219 196.036v607.851c0 107.72 88.16 195.849 195.88 195.849H803.95c107.72 0 195.849-88.129 195.849-195.849V196.036C999.799 88.316 911.67.156 803.95.156H196.099zm338.012 186.777c94.026 7.596 148.647 68.316 155.146 145.948c5.05 39.953 3.637 69.6-2.127 91.418c53.633 1.773 116.39 25.822 120.388 113.098v85.911c-5.561 75.026-32.259 167.679-169.225 187.339c-30.84 4.427-65.452 1.464-110.345 1.157c-45.139-1.19-84.496-.975-118.917-.344c-138.941 2.545-215.468-58.535-216.185-189.28c-1.116-44.494.65-92.734.501-152.581c-.558-37.925-.768-77.544.094-114.381c.267-7.189-.671-15.069 1.22-22.807c5.98-84.295 73.682-133.698 152.706-144.665l186.745-.814zM377.963 304.192c-80.036 13.104-83.991 104.44 0 115.726h151.924c80.036-13.104 83.991-104.44 0-115.726H377.963zm-34.508 252.82c-82.395 12.791-83.607 116.536 2.941 127.271l279.1 1.001c81.695-12.682 81.454-116.322-2.941-127.302l-279.1-.97z"/></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.VPTeamMembersItem {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .profile {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .data {
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .avatar {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .name {
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .affiliation {
|
||||||
|
padding-top: 4px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .desc {
|
||||||
|
padding-top: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.small .links {
|
||||||
|
margin: 0 -16px -20px;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .profile {
|
||||||
|
padding: 48px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .data {
|
||||||
|
padding-top: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .avatar {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .name {
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
line-height: 28px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .affiliation {
|
||||||
|
padding-top: 4px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .desc {
|
||||||
|
padding-top: 16px;
|
||||||
|
max-width: 288px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPTeamMembersItem.medium .links {
|
||||||
|
margin: 0 -16px -12px;
|
||||||
|
padding: 16px 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
text-align: center;
|
||||||
|
/* margin: 0 auto; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* margin: 0 auto; */
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: var(--vp-shadow-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.affiliation {
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.org.link {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org.link:hover {
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin: 0 auto;
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-line;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
max-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc :deep(a) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-brand);
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
transition: color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links svg {
|
||||||
|
margin: 0 .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links svg path:hover {
|
||||||
|
fill:aquamarine;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-link {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-sponsor);
|
||||||
|
background-color: var(--vp-c-bg-soft);
|
||||||
|
transition: color 0.25s, background-color 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp .sp-link.link:hover,
|
||||||
|
.sp .sp-link.link:focus {
|
||||||
|
outline: none;
|
||||||
|
color: var(--vp-c-white);
|
||||||
|
background-color: var(--vp-c-sponsor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
149
components/Pagination.vue
Normal file
149
components/Pagination.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue';
|
||||||
|
const props = defineProps(['modelValue', 'pageTotal'])
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
const onPrev = () => {
|
||||||
|
if (props.modelValue <= 1) return; // 限制上一页翻页按钮的边界
|
||||||
|
emit('update:modelValue', props.modelValue - 1)
|
||||||
|
}
|
||||||
|
const onNext = () => {
|
||||||
|
if (props.modelValue >= props.pageTotal) return; // 限制下一页翻页按钮的边界
|
||||||
|
emit('update:modelValue', props.modelValue + 1)
|
||||||
|
}
|
||||||
|
const setPageNum = (pageNum) => {
|
||||||
|
if (typeof pageNum !== 'number') return; //如果pageNum不是数值类型则返回
|
||||||
|
if (pageNum < 1) return; // 限制上一页翻页按钮的边界
|
||||||
|
if (pageNum > props.pageTotal) return; // 限制下一页翻页按钮的边界
|
||||||
|
emit('update:modelValue', pageNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
const genPageArray = (current, total, size) => {
|
||||||
|
let arr = []
|
||||||
|
if (total < size + 2) {
|
||||||
|
arr = Array.from({ length: total }, (v, k) => k + 1)
|
||||||
|
} else if (current < size - 2) {
|
||||||
|
arr = Array.from(function* gen(i, l) { while (i < l) yield i++; }(1, size - 2 + 1))
|
||||||
|
arr.push('...')
|
||||||
|
arr.push(total)
|
||||||
|
} else if (total - current < size - 2) {
|
||||||
|
arr.push(1)
|
||||||
|
arr.push('...')
|
||||||
|
arr = arr.concat(Array.from(function* gen(i, l) { while (i < l) yield i++; }(total - size + 2, total + 1)))
|
||||||
|
} else {
|
||||||
|
arr.push(1)
|
||||||
|
arr.push('...')
|
||||||
|
arr = arr.concat(Array.from(function* gen(i, l) { while (i < l) yield i++; }(current - Math.floor((size - 4) / 2), current - Math.floor((size - 4) / 2) + size - 4 + 1)))
|
||||||
|
arr.push('...')
|
||||||
|
arr.push(total)
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
const pageArrayLg = computed(() => {
|
||||||
|
const current = props.modelValue
|
||||||
|
const total = props.pageTotal
|
||||||
|
return genPageArray(current, total, 17)
|
||||||
|
})
|
||||||
|
const pageArrayMd = computed(() => {
|
||||||
|
const current = props.modelValue
|
||||||
|
const total = props.pageTotal
|
||||||
|
return genPageArray(current, total, 10)
|
||||||
|
})
|
||||||
|
const pageArraySm = computed(() => {
|
||||||
|
const current = props.modelValue
|
||||||
|
const total = props.pageTotal
|
||||||
|
return genPageArray(current, total, 6)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="container" v-if="props.pageTotal > 1">
|
||||||
|
<div class="paper-item" @click="onPrev" style="display: flex;"><</div>
|
||||||
|
<div v-for="(item, index) in pageArrayLg" :key="index" @click="setPageNum(item)" class="paper-item paper-lg"
|
||||||
|
:class="{ 'active': item == props.modelValue }">{{ item }}</div>
|
||||||
|
<div v-for="(item, index) in pageArrayMd" :key="index" @click="setPageNum(item)" class="paper-item paper-md"
|
||||||
|
:class="{ 'active': item == props.modelValue }">{{ item }}</div>
|
||||||
|
<div v-for="(item, index) in pageArraySm" :key="index" @click="setPageNum(item)" class="paper-item paper-sm"
|
||||||
|
:class="{ 'active': item == props.modelValue }">{{ item }}</div>
|
||||||
|
<div class="paper-item" @click="onNext" style="display: flex;">></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.container {
|
||||||
|
margin: 1rem 0;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
/* font-weight: 600; */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 780px) {
|
||||||
|
|
||||||
|
.paper-lg,
|
||||||
|
.paper-sm {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-md {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1024px) {
|
||||||
|
|
||||||
|
.paper-md,
|
||||||
|
.paper-sm {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-lg {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 780px) {
|
||||||
|
|
||||||
|
.paper-lg,
|
||||||
|
.paper-md {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-sm {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-item {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
margin: 0 .5rem;
|
||||||
|
padding: 0rem .25rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f6f6f7;
|
||||||
|
border-radius: .25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-item:hover {
|
||||||
|
background-color: #0099ff0d;
|
||||||
|
--un-text-opacity: 1;
|
||||||
|
color: #0099ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper-item.active {
|
||||||
|
background-color: #0099ff0d;
|
||||||
|
--un-text-opacity: 1;
|
||||||
|
color: #0099ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
.paper-item {
|
||||||
|
background-color: #9ca3af0d;
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
242
components/PostItem.vue
Normal file
242
components/PostItem.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<p class="blog-item" >
|
||||||
|
<!-- <i class="pin" v-if="!!pin"></i> -->
|
||||||
|
<!-- 标题 -->
|
||||||
|
<p class="title" v-if="inMobile" @click="openBlog(PostURL)">{{ Title }}</p>
|
||||||
|
<div class="info-container">
|
||||||
|
<!-- 左侧信息 -->
|
||||||
|
<div class="info-part">
|
||||||
|
<!-- 标题 -->
|
||||||
|
<p class="title" v-if="!inMobile" @click="openBlog(PostURL)">{{ Title }}</p>
|
||||||
|
<!-- 简短描述 -->
|
||||||
|
<p class="description" v-if="!!Content" @click="openBlog(PostURL)">
|
||||||
|
{{ Content }}
|
||||||
|
</p>
|
||||||
|
<!-- 底部补充描述 -->
|
||||||
|
<div class="badge-list" v-if="!inMobile">
|
||||||
|
<a class="split" v-if="Author" :href="withBase(AuthorURL)">{{ Author }}</a>
|
||||||
|
<span class="split">{{ showTime }}</span>
|
||||||
|
<span class="split" v-if="tag?.length">{{ tag.join(' · ') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右侧封面图 -->
|
||||||
|
<div
|
||||||
|
v-if="cover"
|
||||||
|
class="cover-img"
|
||||||
|
:style="`background-image: url(${cover});`"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部补充描述 -->
|
||||||
|
<div class="badge-list" v-if="inMobile">
|
||||||
|
<a class="split" v-if="Author" :href="withBase(AuthorURL)">{{ Author }}</a>
|
||||||
|
<span class="split">{{ showTime }}</span>
|
||||||
|
<span class="split" v-if="tag?.length">{{ tag.join(' · ') }}</span>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { withBase } from 'vitepress'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
const openBlog = (url) => {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const inMobile = computed(() => width.value <= 500)
|
||||||
|
function formatDate(d, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
||||||
|
if (!(d instanceof Date)) {
|
||||||
|
d = new Date(d)
|
||||||
|
}
|
||||||
|
const o = {
|
||||||
|
'M+': d.getMonth() + 1, // 月份
|
||||||
|
'd+': d.getDate(), // 日
|
||||||
|
'h+': d.getHours(), // 小时
|
||||||
|
'm+': d.getMinutes(), // 分
|
||||||
|
's+': d.getSeconds(), // 秒
|
||||||
|
'q+': Math.floor((d.getMonth() + 3) / 3), // 季度
|
||||||
|
S: d.getMilliseconds() // 毫秒
|
||||||
|
}
|
||||||
|
if (/(y+)/.test(fmt)) {
|
||||||
|
fmt = fmt.replace(
|
||||||
|
RegExp.$1,
|
||||||
|
`${d.getFullYear()}`.substr(4 - RegExp.$1.length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const 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
|
||||||
|
}
|
||||||
|
function formatShowDate(date) {
|
||||||
|
const source = date ? +new Date(date) : +new Date()
|
||||||
|
const now = +new Date()
|
||||||
|
const diff = now - source
|
||||||
|
const oneSeconds = 1000
|
||||||
|
const oneMinute = oneSeconds * 60
|
||||||
|
const oneHour = oneMinute * 60
|
||||||
|
const oneDay = oneHour * 24
|
||||||
|
const oneWeek = oneDay * 7
|
||||||
|
if (diff < oneMinute) {
|
||||||
|
return `${Math.floor(diff / oneSeconds)}秒前`
|
||||||
|
}
|
||||||
|
if (diff < oneHour) {
|
||||||
|
return `${Math.floor(diff / oneMinute)}分钟前`
|
||||||
|
}
|
||||||
|
if (diff < oneDay) {
|
||||||
|
return `${Math.floor(diff / oneHour)}小时前`
|
||||||
|
}
|
||||||
|
if (diff < oneWeek) {
|
||||||
|
return `${Math.floor(diff / oneDay)}天前`
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatDate(new Date(date), 'yyyy-MM-dd')
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
Title: String,
|
||||||
|
Date: String,
|
||||||
|
Content: String,
|
||||||
|
tag: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
Author: String,
|
||||||
|
cover: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
PostURL: String,
|
||||||
|
AuthorURL: String
|
||||||
|
})
|
||||||
|
|
||||||
|
const showTime = computed(() => {
|
||||||
|
return formatShowDate(props.Date)
|
||||||
|
})
|
||||||
|
|
||||||
|
// function isWrappedWithPreventDefault(element: HTMLElement) {
|
||||||
|
// let parent = element.parentElement
|
||||||
|
|
||||||
|
// while (parent) {
|
||||||
|
// if (parent.hasAttribute('preventDefault')) {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// parent = parent.parentElement
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.blog-item .pin {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
top: -4px;
|
||||||
|
left: -4px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.blog-item:hover .pin {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.blog-item .pin::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 120%;
|
||||||
|
height: 30px;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
45deg,
|
||||||
|
var(--blog-theme-color),
|
||||||
|
var(--blog-theme-color)
|
||||||
|
);
|
||||||
|
transform: rotate(-45deg) translateY(-20px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.23);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-item {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid rgba(82, 82, 89, .32);
|
||||||
|
border-radius: .5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: border-color .25s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.blog-item:hover {
|
||||||
|
border: 1px solid #09f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-part {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
color: var(--description-font-color);
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
.description-html {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.badge-list {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--badge-font-color);
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.badge-list .split:not(:last-child)::after {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1px;
|
||||||
|
height: 8px;
|
||||||
|
margin: 0 10px;
|
||||||
|
background-color: #4e5969;
|
||||||
|
}
|
||||||
|
.cover-img {
|
||||||
|
width: 120px;
|
||||||
|
height: 80px;
|
||||||
|
margin-left: 24px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 120px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
.cover-img {
|
||||||
|
width: 100px;
|
||||||
|
height: 60px;
|
||||||
|
background-size: 100px 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
247
components/PostList.vue
Normal file
247
components/PostList.vue
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import Pagination from './Pagination.vue';
|
||||||
|
import PostItem from './PostItem.vue';
|
||||||
|
const props= {data:(await (await fetch("https://raw.githubusercontent.com/NX-Official/friends-link-plus/main/output/friends.json")).json()).posts}
|
||||||
|
const pageSize = 10
|
||||||
|
// const Date = ref('')
|
||||||
|
const pageTotal = ref(Math.ceil(props.data.length / pageSize))
|
||||||
|
const pageNum = ref(1)
|
||||||
|
// const searchText = ref('')
|
||||||
|
// const onsearchText = ref('')
|
||||||
|
const pluginLists = computed(() => {
|
||||||
|
let data_ = props.data//.filter(item => item.Date == Date.value)
|
||||||
|
// if (!!onsearchText.value) {
|
||||||
|
// data_ = data_.filter(item => item.name.indexOf(onsearchText.value) > -1)
|
||||||
|
// }
|
||||||
|
pageTotal.value = Math.ceil(data_.length / pageSize)
|
||||||
|
data_ = data_.slice((pageNum.value - 1) * pageSize, pageNum.value * pageSize)
|
||||||
|
return data_
|
||||||
|
})
|
||||||
|
// const setDate = (Date_) => {
|
||||||
|
// Date.value = Date_
|
||||||
|
// searchText.value = ""
|
||||||
|
// onSearch()
|
||||||
|
// }
|
||||||
|
// const onSearch = () => {
|
||||||
|
// onsearchText.value = searchText.value
|
||||||
|
// pageNum.value = 1
|
||||||
|
// }
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- <div class="tab">
|
||||||
|
<div opacity="80" text="sm">
|
||||||
|
类型{{ props.data }}
|
||||||
|
</div>
|
||||||
|
<div class="select-list">
|
||||||
|
<button class="select-button" @click="setDate('plugin')" :class="{ 'active': type == 'plugin' }">
|
||||||
|
插件
|
||||||
|
</button>
|
||||||
|
<button class="select-button" @click="setType('adapter')" :class="{ 'active': type == 'adapter' }">
|
||||||
|
适配器
|
||||||
|
</button>
|
||||||
|
<button class="select-button" @click="setType('example')" :class="{ 'active': type == 'example' }">
|
||||||
|
样例
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
<!-- <div class="search-bar">
|
||||||
|
<div class="divider" style="margin-top: 1rem;" />
|
||||||
|
<div class="search" @change="onSearch">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32" class="icon">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="m29 27.586l-7.552-7.552a11.018 11.018 0 1 0-1.414 1.414L27.586 29ZM4 13a9 9 0 1 1 9 9a9.01 9.01 0 0 1-9-9Z" />
|
||||||
|
</svg>
|
||||||
|
<input data-v-48d9a5fe="" class="search-input" type="text" role="search" placeholder="Search..."
|
||||||
|
v-model="searchText">
|
||||||
|
</div>
|
||||||
|
<div class="divider" style="margin-bottom: 1rem;" />
|
||||||
|
</div> -->
|
||||||
|
<Pagination :pageTotal="pageTotal" v-model="pageNum" style="width: 100%;" key="0" />
|
||||||
|
<div class="card-list">
|
||||||
|
<PostItem v-for="(item, index) in pluginLists" :key="index" :Title="item.Title" :Content="item.Content"
|
||||||
|
:Date="item.Date" :Author="item.Author" :tag="item?.tag" :cover="item?.cover" :PostURL="item.PostURL" :AuthorURL="item.AuthorURL"/>
|
||||||
|
</div>
|
||||||
|
<Pagination :pageTotal="pageTotal" v-model="pageNum" style="width: 100%;" key="1" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tab {
|
||||||
|
grid-row-gap: .5rem;
|
||||||
|
row-gap: .5rem;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
grid-template-columns: 80px auto;
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm {
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-list {
|
||||||
|
grid-gap: .5rem;
|
||||||
|
gap: .5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-button {
|
||||||
|
border-radius: .25rem;
|
||||||
|
background-color: #9ca3af0d;
|
||||||
|
padding: .125rem .5rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #9ca3af1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-button.active {
|
||||||
|
background-color: #0099ff0d;
|
||||||
|
--un-text-opacity: 1;
|
||||||
|
color: #0099ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
background-color: rgba(60, 60, 67, .12);
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
padding: .5rem;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-list {
|
||||||
|
display: grid;
|
||||||
|
/* gap: 1.5rem; */
|
||||||
|
/* grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: block;
|
||||||
|
border: 1px solid rgba(82, 82, 89, .32);
|
||||||
|
border-radius: .5rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
transition: border-color .25s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border: 1px solid #09f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
display: flex;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: bold;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-github {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-github:hover {
|
||||||
|
fill: #09f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-des {
|
||||||
|
color: #3C3C43;
|
||||||
|
opacity: .7;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
margin-top: .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-tags {
|
||||||
|
display: flex;
|
||||||
|
margin-top: .5rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
font-size: .875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-tag {
|
||||||
|
margin: 0 .5rem;
|
||||||
|
padding: 0 .5rem;
|
||||||
|
background-color: #9ca3af2b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-tags .card-tag:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
/* background-color: #6e6e6e; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-details {
|
||||||
|
margin: .8rem 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
margin-left: .5rem;
|
||||||
|
font-size: .95rem;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-detail {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: .4rem 1rem;
|
||||||
|
background-color: #f6f6f7;
|
||||||
|
border-radius: .5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-button:hover {
|
||||||
|
color: #09f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-button:hover {
|
||||||
|
fill: #09f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|
||||||
|
.card-button {
|
||||||
|
background-color: #9ca3af0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-des {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user