177 lines
5.5 KiB
Vue
177 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
|
|
import { DataAnomaly, defaultMetaData } from '~/types/PostMetaData';
|
|
import type { PostMetaData } from '~/types/PostMetaData';
|
|
import useColorModeStore from '~/stores/colorModeStore';
|
|
|
|
const props = withDefaults(defineProps<{
|
|
rambling?: PostMetaData;
|
|
}>(),
|
|
{
|
|
rambling: () => defaultMetaData,
|
|
});
|
|
const { data: rawbody } = useAsyncData(async () => (await queryCollection('content').where('id', '=', props.rambling.id).first())?.rawbody);
|
|
const collapsed = ref(false);
|
|
|
|
function dateFormat(date: Date | DataAnomaly) {
|
|
if (date === DataAnomaly.DataNotFound || date === DataAnomaly.Invalid) {
|
|
return date;
|
|
}
|
|
return new Date(date).toLocaleDateString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
});
|
|
}
|
|
|
|
function getCostTime(length: number | DataAnomaly) {
|
|
if (length === DataAnomaly.DataNotFound || length === DataAnomaly.Invalid) {
|
|
return length;
|
|
}
|
|
let time = length / 250;
|
|
time = Math.ceil(time);
|
|
const hours = Math.floor(time / 60);
|
|
const minutes = time % 60;
|
|
if (hours > 0) {
|
|
return `${hours}小时${minutes}分钟`;
|
|
} else {
|
|
return `${minutes}分钟`;
|
|
}
|
|
}
|
|
|
|
const safeEditorId = computed(() => {
|
|
const encoded = btoa(encodeURIComponent(props.rambling.id))
|
|
.replace(/[+/=]/g, '_'); // 替换 Base64 中的特殊字符
|
|
return `rambling_${encoded}`;
|
|
});
|
|
|
|
const showLightShadow = ref(false);
|
|
const showDarkShadow = ref(false);
|
|
|
|
function reverseCollapsed() {
|
|
if (collapsed.value) {
|
|
collapsed.value = false;
|
|
return;
|
|
}
|
|
const showLight = showLightShadow.value;
|
|
const showDark = showDarkShadow.value;
|
|
showLightShadow.value = false;
|
|
showDarkShadow.value = false;
|
|
collapsed.value = true;
|
|
setTimeout(() => {
|
|
showLightShadow.value = showLight;
|
|
showDarkShadow.value = showDark;
|
|
}, 500);
|
|
}
|
|
|
|
const colorModeStore = useColorModeStore();
|
|
let colorModeCallBackKey = '';
|
|
onMounted(() => {
|
|
if (colorModeStore.colorMode === 'light') {
|
|
showLightShadow.value = true;
|
|
} else {
|
|
showDarkShadow.value = true;
|
|
}
|
|
colorModeCallBackKey = colorModeStore.registerCallBack(() => {
|
|
if (colorModeStore.colorMode === 'light') {
|
|
setTimeout(() => {
|
|
showLightShadow.value = true;
|
|
}, 500);
|
|
showDarkShadow.value = false;
|
|
} else {
|
|
showLightShadow.value = false;
|
|
setTimeout(() => {
|
|
showDarkShadow.value = true;
|
|
}, 500);
|
|
}
|
|
});
|
|
});
|
|
onUnmounted(() => {
|
|
colorModeStore.unregisterCallBack(colorModeCallBackKey);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64 transition-all duration-500"
|
|
@click="reverseCollapsed">
|
|
<div class="text-4xl">
|
|
絮语:{{ props.rambling.title }}
|
|
</div>
|
|
<div class="flex items-center mt-2 max-w-96 overflow-hidden">
|
|
|
|
<div title="发布时间" class="flex items-center">
|
|
<Icon name="lucide:clock-arrow-up"/>
|
|
<div class="ml-1 text-nowrap">
|
|
{{ dateFormat(props.rambling.published_at) }}
|
|
</div>
|
|
</div>
|
|
|
|
<div title="分类" class="flex items-center ml-2">
|
|
<Icon name="material-symbols:category"/>
|
|
<div class="ml-1 text-nowrap">
|
|
{{ props.rambling.category }}
|
|
</div>
|
|
</div>
|
|
|
|
<div title="字数" class="flex items-center ml-2">
|
|
<Icon name="fluent:text-word-count-20-filled"/>
|
|
<div class="ml-1 text-nowrap">
|
|
{{ props.rambling.word_count }}字
|
|
</div>
|
|
</div>
|
|
|
|
<div title="预计阅读时间" class="flex items-center ml-2">
|
|
<Icon name="octicon:stopwatch-16"/>
|
|
<div class="ml-1 text-nowrap">
|
|
{{ getCostTime(props.rambling.word_count) }}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div
|
|
class="relative flex mt-2 justify-between overflow-hidden transition-all duration-300 ease-in-out dura"
|
|
:class="{'max-h-[8.5rem]' : collapsed, 'max-h-[100vh]':!collapsed}">
|
|
<ReadonlyMdEditor
|
|
v-if="rawbody" :editor-id="safeEditorId" :markdown="rawbody!"
|
|
class="transition-all duration-500 w-full"/>
|
|
<div
|
|
class="absolute bottom-0 left-0 right-0 h-14 bg-gradient-to-t from-old-neutral-200 to-transparent pointer-events-none transition-opacity duration-300"
|
|
:class="collapsed&&showLightShadow?'opacity-100':'opacity-0'"
|
|
/>
|
|
<div
|
|
class="absolute bottom-0 left-0 right-0 h-14 bg-gradient-to-t from-old-neutral-800 to-transparent pointer-events-none transition-opacity duration-300"
|
|
:class="collapsed&&showDarkShadow?'opacity-100':'opacity-0'"
|
|
/>
|
|
</div>
|
|
<hr/>
|
|
<div class="flex mt-2">
|
|
<div title="创建时间" class="flex items-center">
|
|
<Icon name="lucide:file-clock"/>
|
|
<div class="ml-1">{{ dateFormat(props.rambling.created_at) }}</div>
|
|
</div>
|
|
<div v-if="Array.isArray(props.rambling.updated_at)" class="flex items-center ml-2">
|
|
<Icon name="lucide:clock-alert" title="上次更新时间"/>
|
|
<HoverContent>
|
|
<template #content>
|
|
<div class="ml-1">
|
|
{{ dateFormat(props.rambling.updated_at[props.rambling.updated_at.length - 1]) }}
|
|
</div>
|
|
</template>
|
|
<template #hoverContent>
|
|
<div class="p-1 pr-2">
|
|
<div v-for="(date,index) of props.rambling.updated_at" :key="index">
|
|
<div class="block whitespace-nowrap">
|
|
{{ '第' + index + '次更新' + dateFormat(date) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</HoverContent>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|