1
0
mirror of https://github.com/sendevia/website.git synced 2026-03-05 23:32:45 +08:00

feat(PageIndicator): add short link copy functionality and update styles

This commit is contained in:
2026-01-12 16:41:15 +08:00
parent 788bfc543e
commit 651713067c
4 changed files with 51 additions and 35 deletions

View File

@@ -1,15 +1,19 @@
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from "vue";
import { useClipboard } from "@vueuse/core";
import { useGlobalData } from "../composables/useGlobalData";
import { usePostStore } from "../stores/posts";
import { useScreenWidthStore } from "../stores/screenWidth";
import { isClient } from "../utils/env";
const { page, frontmatter } = useGlobalData();
const { copy: copyToClipboard, copied: isCopied } = useClipboard();
const screenWidthStore = useScreenWidthStore();
const pageIndicator = ref<HTMLElement | null>(null);
const indicator = ref({ top: "0px", left: "0px", width: "100%", height: "0px", opacity: 0 });
const headings = ref<Array<{ id: string; text: string; level: number }>>([]);
const headingsActiveId = ref<string>("");
const postStore = usePostStore();
let ro: ResizeObserver | null = null;
let mo: MutationObserver | null = null;
@@ -172,6 +176,26 @@ const resizeHandler = () => {
}
};
// 计算文章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;
await copyToClipboard(`${window.location.origin}${shortLink.value}`);
};
if (isClient()) {
onMounted(() => {
screenWidthStore.init();
@@ -255,8 +279,14 @@ if (isClient()) {
</script>
<template>
<div ref="pageIndicator" class="PageIndicator" aria-label="页面目录">
<p>在此页上</p>
<div ref="pageIndicator" class="PageIndicator">
<div class="label">
<p class="text">在此页上</p>
<p class="icon">link</p>
<p class="article-id" title="复制短链" v-if="articleId" @click="copyShortLink">
{{ isCopied ? `已复制` : articleId }}
</p>
</div>
<h3>{{ frontmatter.title ? frontmatter.title : page.title }}</h3>
<div

View File

@@ -2,10 +2,8 @@
import { ref, onMounted, onBeforeUnmount, computed } from "vue";
import { useClipboard, useTimestamp, useDateFormat } from "@vueuse/core";
import { useGlobalData } from "../composables/useGlobalData";
import { usePostStore } from "../stores/posts";
import { isClient } from "../utils/env";
const postStore = usePostStore();
const { page, frontmatter } = useGlobalData();
const { copy: copyToClipboard, copied: isCopied } = useClipboard();
@@ -65,26 +63,6 @@ const formattedLastUpdated = computed(() => {
return `${formatTimeAgo(uDate)}编辑`;
});
// 计算文章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;
await copyToClipboard(`${window.location.origin}${shortLink.value}`);
};
// 图片查看器相关逻辑
const showImageViewer = ref(false);
const currentImageIndex = ref(0);
@@ -225,13 +203,9 @@ if (isClient()) {
{{ formattedLastUpdated }}
</p>
</ClientOnly>
<p class="id" v-if="articleId">文章ID {{ articleId }}</p>
</div>
<ButtonGroup v-if="frontmatter?.external_links" :links="frontmatter.external_links" size="m" layout="vertical" />
<PageIndicator />
<MaterialButton v-if="articleId" :color="'text'" :icon="'content_copy'" @click="copyShortLink">
复制短链
</MaterialButton>
</div>
<ImageViewer
v-if="showImageViewer"

View File

@@ -6,12 +6,28 @@
user-select: none;
-moz-user-select: none;
p {
@include mixin.typescale-style("label-small");
.label {
display: flex;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
gap: 4px;
margin-inline-start: 18px;
z-index: 1;
p {
@include mixin.typescale-style("label-small");
&.icon {
@include mixin.material-symbols($size: 16);
}
&.article-id {
text-transform: uppercase;
cursor: pointer;
}
}
}
h3 {

View File

@@ -831,10 +831,6 @@
&.date-update::before {
content: "update";
}
&.id::before {
content: "flag";
}
}
}
}