✨ 主页添加絮语卡片 归档部分初见雏形
This commit is contained in:
@@ -1,148 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { DataAnomaly, toArticleMetaDataType } from '~/types/ArticleMetaData';
|
||||
import type { ArticleMetaData } from '~/types/ArticleMetaData';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
article?: ArticleMetaData;
|
||||
}>(),
|
||||
{
|
||||
article: () => toArticleMetaDataType({
|
||||
id: 'default ID',
|
||||
title: 'ApiFox / Postman 使用WebSocket连接SignalR进行测试需要注意的小问题',
|
||||
description: 'default Description',
|
||||
created_at: new Date('2025-01-01T00:00:00Z'), // 默认创建时间
|
||||
published_at: new Date('2025-01-01T00:01:00Z'), // 默认发布时间
|
||||
draft: true,
|
||||
updated_at: [new Date('2025-01-01T00:02:00Z'), new Date('2025-01-01T00:03:00Z')], // 默认更新时间
|
||||
tags: ['C#', 'TS', 'Windows Professional version with Webstorm 2025'],
|
||||
tech_stack: new Map([
|
||||
['Vue', 5],
|
||||
['Nuxt', 3],
|
||||
['TypeScript', 4],
|
||||
['JavaScript', 2],
|
||||
['CSS', 1],
|
||||
['HTML', 1],
|
||||
['Node.js', 2],
|
||||
['Python', 3],
|
||||
['Java', 2],
|
||||
['C#', 1],
|
||||
]),
|
||||
}),
|
||||
});
|
||||
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
// const { colorMode } = storeToRefs(useColorModeStore());
|
||||
// console.log(props.article.tech_stack)
|
||||
|
||||
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}分钟`;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5 light:bg-old-neutral-200 dark:bg-old-neutral-800 min-h-64">
|
||||
<div class="text-4xl">
|
||||
{{ props.article.title }}
|
||||
</div>
|
||||
<div class="flex items-center mt-2">
|
||||
|
||||
<div title="发布时间" class="flex items-center">
|
||||
<Icon name="lucide:clock-arrow-up"/>
|
||||
<div class="ml-1">
|
||||
{{ dateFormat(props.article.published_at) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div title="字数" class="flex items-center ml-2">
|
||||
<Icon name="fluent:text-word-count-20-filled"/>
|
||||
<div class="ml-1 inline">
|
||||
{{ props.article.word_count }}字
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div title="预计阅读时间" class="flex items-center ml-2">
|
||||
<Icon name="octicon:stopwatch-16"/>
|
||||
<div class="ml-1 inline">
|
||||
{{ getCostTime(props.article.word_count) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex mt-2 justify-between h-28">
|
||||
<div>
|
||||
<div class="">
|
||||
{{ props.article.description }}
|
||||
</div>
|
||||
</div>
|
||||
<TechStackCard
|
||||
:async-key="props.article.id"
|
||||
:tech-stack="props.article.tech_stack"
|
||||
:tech-stack-icon-names="props.article.tech_stack_icon_names"
|
||||
:tech-stack-theme-colors="props.article.tech_stack_theme_colors"
|
||||
:tech-stack-percent="props.article.tech_stack_percent"
|
||||
class="w-64"
|
||||
/>
|
||||
</div>
|
||||
<hr/>
|
||||
<div class="flex mt-2">
|
||||
<div title="创建时间" class="flex items-center">
|
||||
<Icon name="lucide:file-clock"/>
|
||||
<div class="ml-1">{{ dateFormat(props.article.created_at) }}</div>
|
||||
</div>
|
||||
<div v-if="Array.isArray(props.article.updated_at)" class="flex items-center ml-2">
|
||||
<Icon name="lucide:clock-alert" title="上次更新时间"/>
|
||||
<HoverContent>
|
||||
<template #content>
|
||||
<div class="ml-1">
|
||||
{{ dateFormat(props.article.updated_at[props.article.updated_at.length - 1]) }}
|
||||
</div>
|
||||
</template>
|
||||
<template #hoverContent>
|
||||
<div class="p-1 pr-2">
|
||||
<div v-for="(date,index) of props.article.updated_at" :key="index">
|
||||
<div class="block whitespace-nowrap">
|
||||
{{ '第' + index + '次更新' + dateFormat(date) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</HoverContent>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="Array.isArray(props.article.tags)" class="flex items-center">
|
||||
<Icon name="clarity:tags-solid"/>
|
||||
<div v-for="(tag,index) of props.article.tags" :key="index">
|
||||
<div class="ml-1">{{ tag }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -10,4 +9,4 @@
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
import { MdPreview } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/preview.css';
|
||||
import useColorModeStore from '~/stores/colorModeStore';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
editorId: string;
|
||||
markdown?: string;
|
||||
}>(), {
|
||||
markdown: () => '## Hello World!',
|
||||
});
|
||||
console.log(props.markdown);
|
||||
const eraseHeaderMarkdown = computed(() => props.markdown.replace(/^---[\s\S]*?---\n?/, ''));
|
||||
|
||||
const { colorMode } = storeToRefs(useColorModeStore());
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5 pt-0 bg-old-neutral-200 dark:bg-old-neutral-800">
|
||||
<MdPreview :editor-id="editorId" :theme="colorMode" :model-value="eraseHeaderMarkdown" class="transition-all duration-500"/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:global(.md-editor) {
|
||||
--md-bk-color: #e5e5e5;
|
||||
--md-theme-heading-1-color: #fff;
|
||||
transition-property: all;
|
||||
transition-duration: 500ms;
|
||||
--tw-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
}
|
||||
|
||||
:global(.dark .md-editor) {
|
||||
--md-bk-color: #262626;
|
||||
--md-theme-heading-1-color: #fff;
|
||||
}
|
||||
:global(.dark .md-editor-preview) {
|
||||
--md-color: var(--ui-text);
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import Highcharts from 'highcharts';
|
||||
import { DataAnomaly } from '~/types/ArticleMetaData';
|
||||
import { DataAnomaly } from '~/types/PostMetaData';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import useColorModeStore from '~/stores/colorModeStore';
|
||||
import useIconStore from '~/stores/iconStore';
|
||||
@@ -9,11 +9,11 @@ import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
|
||||
import type { OverflowBehavior, ScrollbarsAutoHideBehavior, ScrollbarsVisibilityBehavior } from 'overlayscrollbars';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
asyncKey: string;
|
||||
techStack?: string[] | DataAnomaly;
|
||||
techStackPercent?: number[] | DataAnomaly;
|
||||
techStackIconNames?: string[] | DataAnomaly;
|
||||
techStackThemeColors?: string[] | DataAnomaly;
|
||||
asyncKey: string;
|
||||
}>(), {
|
||||
techStack: () => ['Vue', 'Nuxt', 'TypeScript', 'JavaScript', 'CSS', 'HTML', 'Node.js', 'Python', 'Java', 'C#'],
|
||||
techStackPercent: () => [5, 3, 4, 2, 1, 1, 2, 3, 2, 1],
|
||||
@@ -26,6 +26,7 @@ const techStackLightIconSVG = ref<string[]>([]);
|
||||
const techStackDarkIconSVG = ref<string[]>([]);
|
||||
const { colorMode } = storeToRefs(useColorModeStore());
|
||||
|
||||
// 没有这个asyncKey会加载不出正确的图标
|
||||
const { pending } = useAsyncData(props.asyncKey, async () => {
|
||||
if (props.techStackIconNames === DataAnomaly.DataNotFound || props.techStackIconNames === DataAnomaly.Invalid)
|
||||
return [];
|
||||
@@ -147,7 +148,7 @@ onMounted(() => {
|
||||
noDataAvailable.value = true;
|
||||
return;
|
||||
}
|
||||
window.addEventListener('resize', renderChart);
|
||||
// window.addEventListener('resize', renderChart);
|
||||
colorModeCallBackKey = useColorModeStore().registerCallBack(renderChart);
|
||||
watch(pending, (isPending) => {
|
||||
if (!isPending) {
|
||||
@@ -158,7 +159,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', renderChart);
|
||||
// window.removeEventListener('resize', renderChart);
|
||||
useColorModeStore().unregisterCallBack(colorModeCallBackKey);
|
||||
});
|
||||
|
||||
@@ -184,17 +185,33 @@ const scrollbarOptions = {
|
||||
<div v-if="noDataAvailable" class="flex items-center justify-center h-full p-8">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 112.01">
|
||||
<g id="_图层_1" data-name="图层 1">
|
||||
<polyline class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" points="1.5 28.63 1.5 1.5 63.5 1.59"/>
|
||||
<polyline class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" points="254.5 83.38 254.5 110.5 192.5 110.41"/>
|
||||
<polyline class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10] stroke-3" points="254.5 28.63 254.5 1.5 192.5 1.59"/>
|
||||
<polyline class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10] stroke-3" points="1.5 83.38 1.5 110.5 63.5 110.41"/>
|
||||
<polyline
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]"
|
||||
points="1.5 28.63 1.5 1.5 63.5 1.59"/>
|
||||
<polyline
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]"
|
||||
points="254.5 83.38 254.5 110.5 192.5 110.41"/>
|
||||
<polyline
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10] stroke-3"
|
||||
points="254.5 28.63 254.5 1.5 192.5 1.59"/>
|
||||
<polyline
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10] stroke-3"
|
||||
points="1.5 83.38 1.5 110.5 63.5 110.41"/>
|
||||
</g>
|
||||
<g id="_图层_2" data-name="图层 2">
|
||||
<text class="[font-family:ArialMT,Arial] text-xl stroke-old-neutral-600 dark:stroke-old-neutral-400 fill-old-neutral-600 dark:fill-old-neutral-400" transform="translate(89.48 61.61)"><tspan x="0" y="0">No Data</tspan></text>
|
||||
<text
|
||||
class="[font-family:ArialMT,Arial] text-xl stroke-old-neutral-600 dark:stroke-old-neutral-400 fill-old-neutral-600 dark:fill-old-neutral-400"
|
||||
transform="translate(89.48 61.61)">
|
||||
<tspan x="0" y="0">No Data</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g id="_图层_3" data-name="图层 3">
|
||||
<line class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" x1="16" y1="11" x2="90" y2="39.8"/>
|
||||
<line class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" x1="166" y1="72.21" x2="240" y2="101"/>
|
||||
<line
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" x1="16"
|
||||
y1="11" x2="90" y2="39.8"/>
|
||||
<line
|
||||
class="fill-none stroke-old-neutral-400 dark:stroke-old-neutral-500 [stroke-miterlimit:10]" x1="166"
|
||||
y1="72.21" x2="240" y2="101"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user