✨ 升级到Nuxt4
✨ 完成分类 标签的HTML搭建 🐛 修复ssg相关配置 之前错误的使用了SPA 🐛 修复部分ssg兼容问题
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import type { PostMetaData } from '~/types/PostMetaData';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
postsMetaData?: PostMetaData[];
|
||||
}>(), {
|
||||
postsMetaData: () => [],
|
||||
});
|
||||
const emits = defineEmits<{
|
||||
(event: 'filterRuleChange', rule: (data: PostMetaData) => boolean): void;
|
||||
}>();
|
||||
const articleCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'article').length || 0);
|
||||
const announcementCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'announcement').length || 0);
|
||||
const ramblingCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'rambling').length || 0);
|
||||
const countGroup = [
|
||||
{ name: '文章', count: articleCount, type: 'article' },
|
||||
{ name: '絮语', count: ramblingCount, type: 'rambling' },
|
||||
{ name: '公告', count: announcementCount, type: 'announcement' },
|
||||
];
|
||||
const categoryEnableStatus: Ref<boolean[]> = ref(Array(countGroup.length).fill(true));
|
||||
|
||||
const categories = computed(() => {
|
||||
const categoryMap = new Map<string, number>();
|
||||
props.postsMetaData?.forEach((post) => {
|
||||
if (post.category) {
|
||||
categoryMap.set(post.category, (categoryMap.get(post.category) || 0) + 1);
|
||||
}
|
||||
});
|
||||
let categoryArray = Array.from(categoryMap.entries());
|
||||
categoryArray = categoryArray.sort((a, b) => b[1] - a[1]);
|
||||
return categoryArray;
|
||||
});
|
||||
|
||||
const tags = computed(() => {
|
||||
const tagMap = new Map<string, number>();
|
||||
props.postsMetaData?.forEach((post) => {
|
||||
post.tags?.forEach((tag) => {
|
||||
tagMap.set(tag, (tagMap.get(tag) || 0) + 1);
|
||||
});
|
||||
});
|
||||
let tagArray = Array.from(tagMap.entries());
|
||||
tagArray = tagArray.sort((a, b) => b[1] - a[1]);
|
||||
return tagArray;
|
||||
});
|
||||
|
||||
function updateCategoryEnableStatus(index: number) {
|
||||
if (categoryEnableStatus.value.reduce((last, cur) => last && cur, true)) {
|
||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
||||
if (i !== index) {
|
||||
categoryEnableStatus.value[i] = false;
|
||||
}
|
||||
}
|
||||
} else if (!categoryEnableStatus.value.reduce((last, cur, localIndex) => last || (localIndex === index ? false : cur), false)) {
|
||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
||||
categoryEnableStatus.value[i] = true;
|
||||
}
|
||||
} else
|
||||
categoryEnableStatus.value[index] = !categoryEnableStatus.value[index];
|
||||
updateRule();
|
||||
}
|
||||
|
||||
function updateRule() {
|
||||
emits('filterRuleChange', (post) => {
|
||||
for (let i = 0; i < categoryEnableStatus.value.length; i++) {
|
||||
if (categoryEnableStatus.value[i] && post.type === countGroup[i]!.type) {
|
||||
console.log('filter', post.title, 'true');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
console.log('filter', post.title, 'false');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5">
|
||||
<div class="text-2xl ml-1 flex items-center">
|
||||
<Icon class="mr-2" name="material-symbols:category"/>
|
||||
类型
|
||||
</div>
|
||||
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
||||
<div class="flex mt-4">
|
||||
<div
|
||||
v-for="(data, index) of countGroup"
|
||||
:key="data.name"
|
||||
class="flex items-center flex-col flex-1 text-xl cursor-pointer hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300"
|
||||
:class="{'text-old-neutral-400': !categoryEnableStatus[index]}"
|
||||
@click="updateCategoryEnableStatus(index)"
|
||||
>
|
||||
<div>{{ data.name }}</div>
|
||||
<div>{{ data.count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5 mt-4">
|
||||
<div class="text-2xl ml-1 flex items-center">
|
||||
<Icon class="mr-2" name="material-symbols:book"/>
|
||||
分类
|
||||
</div>
|
||||
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
||||
<div
|
||||
v-for="([name,count],index) of categories" :key="index"
|
||||
class="flex justify-between pl-4 pr-4 hover:text-sky-400 dark:hover:text-[#cccaff] transition-colors duration-300">
|
||||
<div class="flex items-center">
|
||||
<Icon
|
||||
name="material-symbols:book-outline"
|
||||
size="17"
|
||||
class="mt-0.5 mr-1"
|
||||
/>
|
||||
<div>{{ name }}</div>
|
||||
</div>
|
||||
<div>{{ count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5 mt-4">
|
||||
<div class="text-2xl ml-1 flex items-center">
|
||||
<Icon class="mr-2" name="material-symbols:bookmarks"/>
|
||||
标签
|
||||
</div>
|
||||
<hr class="border-0 h-[1px] bg-old-neutral-600 mt-3 mb-1"/>
|
||||
<div class="flex flex-wrap">
|
||||
<div
|
||||
v-for="([name,count],index) of tags" :key="index"
|
||||
class="flex items-center justify-between text-[15px] pl-2 pr-2 m-1 rounded-2xl shadow-[0_0_0_1px_#888] hover:text-sky-400 dark:hover:text-[#cccaff] hover:shadow-[0_0_0_1px_#00bcff] dark:hover:shadow-[0_0_0_1px_#cccaff] transition-colors transition-shadow duration-300">
|
||||
<Icon
|
||||
name="clarity:hashtag-solid"
|
||||
size="17"
|
||||
class="mr-1 "
|
||||
/>
|
||||
<div class="mr-1">{{ name }}</div>
|
||||
<div class="">{{ count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,72 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { PostMetaData } from '~/types/PostMetaData';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
postsMetaData?: PostMetaData[];
|
||||
}>(), {
|
||||
postsMetaData: () => [],
|
||||
});
|
||||
const emits = defineEmits<{
|
||||
(event: 'filterRuleChange', rule: string): void;
|
||||
}>();
|
||||
const articleCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'article').length || 0);
|
||||
const announcementCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'announcement').length || 0);
|
||||
const ramblingCount = computed(() => props.postsMetaData?.filter((post) => !post.draft && post.type === 'rambling').length || 0);
|
||||
const countGroup = [
|
||||
{ name: '文章', count: articleCount, type: 'article' },
|
||||
{ name: '絮语', count: ramblingCount, type: 'rambling' },
|
||||
{ name: '公告', count: announcementCount, type: 'announcement' },
|
||||
];
|
||||
const categories = computed(() => {
|
||||
const categoryMap = new Map<string, number>();
|
||||
props.postsMetaData?.forEach((post) => {
|
||||
if (post.category) {
|
||||
categoryMap.set(post.category, (categoryMap.get(post.category) || 0) + 1);
|
||||
}
|
||||
});
|
||||
return categoryMap;
|
||||
});
|
||||
let showType = '';
|
||||
|
||||
function ruleChange(name: string) {
|
||||
if (showType === name || name === '') {
|
||||
showType = '';
|
||||
} else {
|
||||
showType = name;
|
||||
}
|
||||
emits('filterRuleChange', showType);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="transition-colors duration-500">
|
||||
<div>
|
||||
<div class="bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500 p-5">
|
||||
Author: Lichx
|
||||
<div>
|
||||
<div v-if="showType === ''" class="flex">
|
||||
<div
|
||||
v-for="data of countGroup"
|
||||
:key="data.name"
|
||||
class="flex items-center flex-col flex-1 text-xl cursor-pointer hover:text-sky-300 dark:hover:text-[#cccaff] transition-colors duration-300"
|
||||
@click="ruleChange(data.type)"
|
||||
>
|
||||
<div>{{ data.name }}</div>
|
||||
<div>{{ data.count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center hover:text-sky-300 dark:hover:text-[#cccaff] transition-colors duration-300" @click="ruleChange('')">
|
||||
<div class="flex-1 text-2xl flex items-center justify-center">
|
||||
<div>{{ countGroup.filter((x) => x.type === showType)[0].name }}</div>
|
||||
</div>
|
||||
<div class="flex-1 text-2xl flex items-center justify-center">
|
||||
<div class="pr-8">{{ countGroup.filter((x) => x.type === showType)[0].count }}</div>
|
||||
</div>
|
||||
<!-- <Icon-->
|
||||
<!-- name="mingcute:back-line" class="flex-1 text-5xl cursor-pointer dark:hover:text-[#cccaff] hover:text-sky-300 transition-colors duration-300"-->
|
||||
<!-- @click="ruleChange('')"/>-->
|
||||
</div>
|
||||
Contact me:
|
||||
<a href="mailto:li_chx@qq.com" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,6 @@ const props = withDefaults(defineProps<{
|
||||
}>(), {
|
||||
markdown: () => '## Hello World!',
|
||||
});
|
||||
console.log(props.markdown);
|
||||
const eraseHeaderMarkdown = computed(() => props.markdown.replace(/^---[\s\S]*?---\n?/, ''));
|
||||
|
||||
const { colorMode } = storeToRefs(useColorModeStore());
|
||||
@@ -18,7 +17,9 @@ const { colorMode } = storeToRefs(useColorModeStore());
|
||||
|
||||
<template>
|
||||
<div class="pt-0 bg-old-neutral-200 dark:bg-old-neutral-800 transition-colors duration-500">
|
||||
<MdPreview :editor-id="editorId" :theme="colorMode" :model-value="eraseHeaderMarkdown" class="transition-all duration-500 max-w-full"/>
|
||||
<client-only>
|
||||
<MdPreview :editor-id="editorId" :theme="colorMode" :model-value="eraseHeaderMarkdown" class="transition-all duration-500 max-w-full"/>
|
||||
</client-only>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const renderChart = () => {
|
||||
const techStackPercent = props.techStackPercent as number[];
|
||||
if (!chartRef.value) return;
|
||||
const sum = techStackPercent.reduce((acc, val) => acc + val, 0);
|
||||
const fullArr: [string, number, string, string, string][] = techStack.map((name, index) => [name, techStackPercent[index] / sum, techStackLightIconSVG.value[index] || '', techStackDarkIconSVG.value[index] || '', props.techStackThemeColors[index]] as [string, number, string, string, string]).sort((a, b) => b[1] - a[1]);
|
||||
const fullArr: [string, number, string, string, string][] = techStack.map((name, index) => [name, techStackPercent[index]! / sum, techStackLightIconSVG.value[index] || '', techStackDarkIconSVG.value[index] || '', props.techStackThemeColors[index]] as [string, number, string, string, string]).sort((a, b) => b[1] - a[1]);
|
||||
const dataArr: [string, number][] = fullArr.map((x) => [x[0], x[1]]);
|
||||
const barHeight = 20;
|
||||
const gap = 10;
|
||||
@@ -83,8 +83,8 @@ const renderChart = () => {
|
||||
labels: {
|
||||
useHTML: true,
|
||||
formatter: function () {
|
||||
return `<div style="width: 25px; height: 25px;" title="${fullArr[this.pos][0]}">
|
||||
${colorMode.value === 'light' ? fullArr[this.pos][2] : fullArr[this.pos][3]}
|
||||
return `<div style="width: 25px; height: 25px;" title="${fullArr[this.pos]![0]}">
|
||||
${colorMode.value === 'light' ? fullArr[this.pos]![2] : fullArr[this.pos]![3]}
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
@@ -114,7 +114,7 @@ const renderChart = () => {
|
||||
],
|
||||
tooltip: {
|
||||
formatter: function () {
|
||||
return `${fullArr[this.x][0]} ${toPercent(this.y)}`;
|
||||
return `${fullArr[this.x]![0]} ${toPercent(this.y)}`;
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
@@ -216,7 +216,9 @@ const scrollbarOptions = {
|
||||
</svg>
|
||||
</div>
|
||||
<overlay-scrollbars-component v-else class="max-h-full" :options="scrollbarOptions">
|
||||
<div ref="chartRef" class="w-full"/>
|
||||
<client-only>
|
||||
<div ref="chartRef" class="w-full"/>
|
||||
</client-only>
|
||||
</overlay-scrollbars-component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user