mirror of
https://github.com/sendevia/website.git
synced 2026-03-06 07:40:50 +08:00
feat(ArticleLayout): add post metadata display and short link sharing
This commit is contained in:
@@ -90,6 +90,8 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
],
|
||||
metaChunk: true,
|
||||
lastUpdated: true,
|
||||
themeConfig: {
|
||||
defaultColor: "#39c5bb",
|
||||
defaultImpression: "/assets/images/121337686_p0.webp",
|
||||
|
||||
@@ -1,11 +1,130 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import { ref, onMounted, onBeforeUnmount, computed } from "vue";
|
||||
import { useGlobalData } from "../composables/useGlobalData";
|
||||
import { usePostStore } from "../stores/posts";
|
||||
import { isClient } from "../utils/env";
|
||||
|
||||
const showImageViewer = ref(false);
|
||||
const currentImageIndex = ref(0);
|
||||
const articleImages = ref<string[]>([]);
|
||||
const imageOriginPosition = ref({ x: 0, y: 0, width: 0, height: 0 });
|
||||
const { page, frontmatter } = useGlobalData();
|
||||
const postStore = usePostStore();
|
||||
|
||||
// 计算当前文章 ID
|
||||
const articleId = computed(() => {
|
||||
const relativePath = page.value?.relativePath;
|
||||
if (!relativePath) return "";
|
||||
|
||||
const path = relativePath.replace(/\.md$/, "");
|
||||
const lookupUrl = path.startsWith("/") ? path : `/${path}`;
|
||||
|
||||
const post = postStore.getPostByUrl(lookupUrl);
|
||||
return post?.id || "";
|
||||
});
|
||||
|
||||
// 生成短链
|
||||
const shortLink = computed(() => {
|
||||
if (!articleId.value) return "";
|
||||
return `/p/${articleId.value}`;
|
||||
});
|
||||
|
||||
// 复制短链到剪贴板
|
||||
const copyShortLink = async () => {
|
||||
if (!shortLink.value) return;
|
||||
|
||||
const fullUrl = `${window.location.origin}${shortLink.value}`;
|
||||
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
await navigator.clipboard.writeText(fullUrl);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("复制失败:", err);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化时间戳
|
||||
const formatTimestamp = (timestamp: number): string => {
|
||||
if (!timestamp) return "";
|
||||
|
||||
const date = new Date(timestamp);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hours = date.getHours();
|
||||
const minutes = date.getMinutes();
|
||||
|
||||
return `${year}年${month}月${day}日${hours}时${minutes}分`;
|
||||
};
|
||||
|
||||
// 计算时间差
|
||||
const formatTimeAgo = (diffMs: number): string => {
|
||||
if (diffMs <= 0) return "刚刚";
|
||||
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
const diffMinutes = Math.floor(diffSeconds / 60);
|
||||
const diffHours = Math.floor(diffMinutes / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
const diffMonths = Math.floor(diffDays / 30);
|
||||
const diffYears = Math.floor(diffDays / 365);
|
||||
|
||||
if (diffYears > 0) {
|
||||
return `${diffYears}年前`;
|
||||
} else if (diffMonths > 0) {
|
||||
return `${diffMonths}个月前`;
|
||||
} else if (diffDays > 0) {
|
||||
return `${diffDays}天前`;
|
||||
} else if (diffHours > 0) {
|
||||
return `${diffHours}小时前`;
|
||||
} else if (diffMinutes > 0) {
|
||||
return `${diffMinutes}分钟前`;
|
||||
} else {
|
||||
return `${diffSeconds}秒前`;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取发布时间戳
|
||||
const publishTimestamp = computed(() => {
|
||||
const dateStr = frontmatter.value?.date;
|
||||
if (!dateStr) return 0;
|
||||
const timestamp = new Date(dateStr).getTime();
|
||||
return isNaN(timestamp) ? 0 : timestamp;
|
||||
});
|
||||
|
||||
// 获取最后修改时间戳
|
||||
const lastUpdatedTimestamp = computed(() => {
|
||||
const val = page.value?.lastUpdated;
|
||||
if (!val) return 0;
|
||||
if (typeof val === "number") return val;
|
||||
const ts = new Date(val).getTime();
|
||||
return isNaN(ts) ? 0 : ts;
|
||||
});
|
||||
|
||||
// 格式化发布日期
|
||||
const formattedPublishDate = computed(() => {
|
||||
if (!publishTimestamp.value) return "";
|
||||
return formatTimestamp(publishTimestamp.value);
|
||||
});
|
||||
|
||||
// 格式化最后修改时间
|
||||
const formattedLastUpdated = computed(() => {
|
||||
const publishTs = publishTimestamp.value;
|
||||
const lastUpdateTs = lastUpdatedTimestamp.value;
|
||||
|
||||
if (!lastUpdateTs) return "";
|
||||
|
||||
// 判定是否为发布即最后修改
|
||||
const isSameTime = !publishTs || Math.abs(lastUpdateTs - publishTs) < 60000;
|
||||
|
||||
if (isSameTime) {
|
||||
return formatTimestamp(lastUpdateTs);
|
||||
} else {
|
||||
const timeSinceUpdate = Date.now() - lastUpdateTs;
|
||||
const timeAgo = formatTimeAgo(timeSinceUpdate);
|
||||
return `于${timeAgo}编辑`;
|
||||
}
|
||||
});
|
||||
|
||||
function openImageViewer(index: number, event: MouseEvent) {
|
||||
const contentElement = document.querySelector("#article-content");
|
||||
@@ -137,9 +256,15 @@ if (isClient()) {
|
||||
<Header />
|
||||
<main id="article-content">
|
||||
<Content />
|
||||
<MaterialButton v-if="articleId" :text="'复制短链'" :color="'outlined'" @click="copyShortLink" />
|
||||
<PrevNext />
|
||||
</main>
|
||||
<div id="article-indicator">
|
||||
<div id="article-beside">
|
||||
<div class="post-info">
|
||||
<p class="date-publish" v-if="formattedPublishDate">发布于 {{ formattedPublishDate }}</p>
|
||||
<p class="date-update">{{ formattedLastUpdated }}</p>
|
||||
<p class="id" v-if="articleId">文章ID {{ articleId }}</p>
|
||||
</div>
|
||||
<PageIndicator />
|
||||
</div>
|
||||
<ImageViewer
|
||||
|
||||
@@ -698,11 +698,48 @@ main#article-content {
|
||||
}
|
||||
}
|
||||
|
||||
div#article-indicator {
|
||||
div#article-beside {
|
||||
grid-column: 10 / 13;
|
||||
|
||||
position: sticky;
|
||||
top: 120px;
|
||||
|
||||
.post-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
|
||||
margin-inline-start: 16px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
p {
|
||||
@include mixin.typescale-style("label-small");
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
gap: 3px;
|
||||
|
||||
color: var(--md-sys-color-outline);
|
||||
|
||||
&::before {
|
||||
@include mixin.material-symbols($size: 14);
|
||||
}
|
||||
|
||||
&.date-publish::before {
|
||||
content: "calendar_today";
|
||||
}
|
||||
|
||||
&.date-update::before {
|
||||
content: "update";
|
||||
}
|
||||
|
||||
&.id::before {
|
||||
content: "flag";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1600px) {
|
||||
@@ -710,7 +747,7 @@ div#article-indicator {
|
||||
grid-column: 1 / 10;
|
||||
}
|
||||
|
||||
div#article-indicator {
|
||||
div#article-beside {
|
||||
grid-column: 10 / 13;
|
||||
}
|
||||
}
|
||||
@@ -720,7 +757,7 @@ div#article-indicator {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
|
||||
div#article-indicator {
|
||||
div#article-beside {
|
||||
grid-column: 7 / 9;
|
||||
}
|
||||
}
|
||||
@@ -730,8 +767,8 @@ div#article-indicator {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
|
||||
div#article-indicator {
|
||||
grid-column: 5 / 7;
|
||||
div#article-beside {
|
||||
grid-column: 1 / 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,7 +777,7 @@ div#article-indicator {
|
||||
grid-column: 1 / 5;
|
||||
}
|
||||
|
||||
div#article-indicator {
|
||||
grid-column: 3 / 5;
|
||||
div#article-beside {
|
||||
grid-column: 1 / 5;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user