Browse Source
feat(component): add ArticleMetaBar with reading-time estimate and meta icons on article pages
main
feat(component): add ArticleMetaBar with reading-time estimate and meta icons on article pages
main
4 changed files with 107 additions and 22 deletions
-
19src/components/ArticleCard.vue
-
74src/components/ArticleMetaBar.vue
-
12src/utils/readingTime.ts
-
24src/views/ArticleDetailView.vue
@ -0,0 +1,74 @@ |
|||
<script setup lang="ts"> |
|||
import { computed } from 'vue' |
|||
import { estimateReadingTime, formatDay } from '@/utils/readingTime' |
|||
|
|||
const props = withDefaults( |
|||
defineProps<{ |
|||
createdAt?: string |
|||
viewCount?: number |
|||
content?: string |
|||
mode?: 'card' | 'detail' |
|||
}>(), |
|||
{ createdAt: '', viewCount: 0, content: '', mode: 'card' } |
|||
) |
|||
|
|||
const day = computed(() => (props.createdAt ? formatDay(props.createdAt) : '')) |
|||
const duration = computed(() => estimateReadingTime(props.content ?? '')) |
|||
const showView = computed(() => props.mode === 'detail') |
|||
</script> |
|||
|
|||
<template> |
|||
<span class="meta-bar" :class="`meta-bar--${mode}`"> |
|||
<span v-if="day" class="meta-item"> |
|||
<svg class="meta-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
|||
<rect x="2" y="3" width="12" height="11" rx="1.5" /> |
|||
<line x1="2" y1="7" x2="14" y2="7" /> |
|||
<line x1="5.5" y1="1.5" x2="5.5" y2="4.5" /> |
|||
<line x1="10.5" y1="1.5" x2="10.5" y2="4.5" /> |
|||
</svg> |
|||
<span>{{ day }}</span> |
|||
</span> |
|||
<span v-if="showView" class="meta-item"> |
|||
<svg class="meta-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
|||
<path d="M1.5 8C3 5.1 5.3 3.5 8 3.5S13 5.1 14.5 8C13 10.9 10.7 12.5 8 12.5S3 10.9 1.5 8Z" /> |
|||
<circle cx="8" cy="8" r="2.1" /> |
|||
</svg> |
|||
<span class="tnum">{{ viewCount }}</span> |
|||
</span> |
|||
<span v-if="duration" class="meta-item"> |
|||
<svg class="meta-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> |
|||
<circle cx="8" cy="8" r="6" /> |
|||
<path d="M8 5V8L10 9.2" /> |
|||
</svg> |
|||
<span>{{ duration }}</span> |
|||
</span> |
|||
</span> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.meta-bar { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
flex-wrap: wrap; |
|||
gap: 14px; |
|||
color: var(--ac-text-disabled); |
|||
font-size: 13px; |
|||
} |
|||
|
|||
.meta-item { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.meta-icon { |
|||
width: 14px; |
|||
height: 14px; |
|||
opacity: 0.85; |
|||
} |
|||
|
|||
.meta-bar--card .meta-icon { |
|||
width: 13px; |
|||
height: 13px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,12 @@ |
|||
export function estimateReadingTime(content: string = ''): string { |
|||
const noWhitespace = content.replace(/\s/g, '').length |
|||
const minutes = Math.ceil(noWhitespace / 400) |
|||
return `${minutes} 分钟` |
|||
} |
|||
|
|||
export function formatDay(iso: string): string { |
|||
const d = new Date(iso) |
|||
if (isNaN(d.getTime())) return iso |
|||
const pad = (n: number) => String(n).padStart(2, '0') |
|||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue