refactor: convert post publish dates to human-readable format (#7526)

#### What type of PR is this?

/area ui
/kind improvement
/milestone 2.21.x

#### What this PR does / why we need it:

<img width="1202" alt="image" src="https://github.com/user-attachments/assets/cac050d2-b984-4b48-afe0-e18db220ec19" />

#### Does this PR introduce a user-facing change?

```release-note
将 Console 端文章列表的发布时间改为语义化时间
```
pull/7527/head
Ryan Wang 2025-06-09 23:02:34 +08:00 committed by GitHub
parent 224d78079d
commit 3fa6532d9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 130 additions and 98 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import PostContributorList from "@/components/user/PostContributorList.vue"; import PostContributorList from "@/components/user/PostContributorList.vue";
import { formatDatetime } from "@/utils/date"; import { formatDatetime, relativeTimeTo } from "@/utils/date";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client"; import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client"; import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
@ -356,12 +356,11 @@ watch(
/> />
</template> </template>
</VEntityField> </VEntityField>
<VEntityField> <VEntityField
<template #description> v-if="singlePage.page.spec.publishTime"
<span class="truncate text-xs tabular-nums text-gray-500"> v-tooltip="formatDatetime(singlePage.page.spec.publishTime)"
{{ formatDatetime(singlePage.page.spec.publishTime) }} :description="relativeTimeTo(singlePage.page.spec.publishTime)"
</span> >
</template>
</VEntityField> </VEntityField>
</template> </template>
<template <template

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import PostContributorList from "@/components/user/PostContributorList.vue"; import PostContributorList from "@/components/user/PostContributorList.vue";
import { singlePageLabels } from "@/constants/labels"; import { singlePageLabels } from "@/constants/labels";
import { formatDatetime } from "@/utils/date"; import { formatDatetime, relativeTimeTo } from "@/utils/date";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client"; import type { ListedSinglePage, SinglePage } from "@halo-dev/api-client";
import { coreApiClient } from "@halo-dev/api-client"; import { coreApiClient } from "@halo-dev/api-client";
@ -224,12 +224,11 @@ const handleDelete = async () => {
/> />
</template> </template>
</VEntityField> </VEntityField>
<VEntityField> <VEntityField
<template #description> v-if="singlePage.page.spec.publishTime"
<span class="truncate text-xs tabular-nums text-gray-500"> v-tooltip="formatDatetime(singlePage.page.spec.publishTime)"
{{ formatDatetime(singlePage.page.spec.publishTime) }} :description="relativeTimeTo(singlePage.page.spec.publishTime)"
</span> >
</template>
</VEntityField> </VEntityField>
</template> </template>
<template <template

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import PostContributorList from "@/components/user/PostContributorList.vue"; import PostContributorList from "@/components/user/PostContributorList.vue";
import { formatDatetime } from "@/utils/date"; import { formatDatetime, relativeTimeTo } from "@/utils/date";
import { usePermission } from "@/utils/permission"; import { usePermission } from "@/utils/permission";
import type { ListedPost, Post } from "@halo-dev/api-client"; import type { ListedPost, Post } from "@halo-dev/api-client";
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client"; import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
@ -390,12 +390,11 @@ watch(
/> />
</template> </template>
</VEntityField> </VEntityField>
<VEntityField> <VEntityField
<template #description> v-if="post.post.spec.publishTime"
<span class="truncate text-xs tabular-nums text-gray-500"> v-tooltip="formatDatetime(post.post.spec.publishTime)"
{{ formatDatetime(post.post.spec.publishTime) }} :description="relativeTimeTo(post.post.spec.publishTime)"
</span> >
</template>
</VEntityField> </VEntityField>
</template> </template>
<template <template

View File

@ -205,6 +205,7 @@ const { startFields, endFields } = useEntityFieldItemExtensionPoint<ListedPost>(
priority: 50, priority: 50,
position: "end", position: "end",
component: markRaw(PublishTimeField), component: markRaw(PublishTimeField),
hidden: !props.post.post.spec.publishTime,
props: { props: {
post: props.post, post: props.post,
}, },

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
import { formatDatetime } from "@/utils/date"; import { formatDatetime, relativeTimeTo } from "@/utils/date";
import type { ListedPost } from "@halo-dev/api-client"; import type { ListedPost } from "@halo-dev/api-client";
import { IconTimerLine, VEntityField } from "@halo-dev/components"; import { IconTimerLine, VEntityField } from "@halo-dev/components";
@ -16,8 +16,11 @@ withDefaults(
<VEntityField> <VEntityField>
<template #description> <template #description>
<div class="inline-flex items-center space-x-2"> <div class="inline-flex items-center space-x-2">
<span class="entity-field-description"> <span
{{ formatDatetime(post.post.spec.publishTime) }} v-tooltip="formatDatetime(post.post.spec.publishTime)"
class="entity-field-description"
>
{{ relativeTimeTo(post.post.spec.publishTime) }}
</span> </span>
<IconTimerLine <IconTimerLine
v-if=" v-if="

View File

@ -1,28 +1,18 @@
<script lang="ts" setup> <script lang="ts" setup>
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
import { formatDatetime } from "@/utils/date";
import WidgetCard from "@console/modules/dashboard/components/WidgetCard.vue"; import WidgetCard from "@console/modules/dashboard/components/WidgetCard.vue";
import type { ListedPost } from "@halo-dev/api-client"; import type { ListedPost } from "@halo-dev/api-client";
import { consoleApiClient } from "@halo-dev/api-client"; import { consoleApiClient } from "@halo-dev/api-client";
import { import { VEntityContainer } from "@halo-dev/components";
IconExternalLinkLine,
VEntity,
VEntityContainer,
VEntityField,
VSpace,
} from "@halo-dev/components";
import { useQuery } from "@tanstack/vue-query"; import { useQuery } from "@tanstack/vue-query";
import { OverlayScrollbarsComponent } from "overlayscrollbars-vue"; import { OverlayScrollbarsComponent } from "overlayscrollbars-vue";
import PostListItem from "./components/PostListItem.vue";
const { data } = useQuery<ListedPost[]>({ const { data } = useQuery<ListedPost[]>({
queryKey: ["widget-recent-posts"], queryKey: ["widget-recent-posts"],
queryFn: async () => { queryFn: async () => {
const { data } = await consoleApiClient.content.post.listPosts({ const { data } = await consoleApiClient.content.post.listPosts({
labelSelector: [ labelSelector: [`${postLabels.DELETED}=false`],
`${postLabels.DELETED}=false`,
`${postLabels.PUBLISHED}=true`,
],
sort: ["spec.publishTime,desc"],
page: 1, page: 1,
size: 10, size: 10,
}); });
@ -42,61 +32,11 @@ const { data } = useQuery<ListedPost[]>({
defer defer
> >
<VEntityContainer> <VEntityContainer>
<VEntity v-for="post in data" :key="post.post.metadata.name"> <PostListItem
<template #start> v-for="post in data"
<VEntityField :key="post.post.metadata.name"
:title="post.post.spec.title" :post="post"
:route="{ />
name: 'PostEditor',
query: { name: post.post.metadata.name },
}"
>
<template #description>
<VSpace>
<span class="text-xs text-gray-500">
{{
$t(
"core.dashboard.widgets.presets.recent_published.visits",
{ visits: post.stats.visit || 0 }
)
}}
</span>
<span class="text-xs text-gray-500">
{{
$t(
"core.dashboard.widgets.presets.recent_published.comments",
{ comments: post.stats.totalComment || 0 }
)
}}
</span>
<span class="truncate text-xs tabular-nums text-gray-500">
{{
$t(
"core.dashboard.widgets.presets.recent_published.publishTime",
{
publishTime: formatDatetime(
post.post.spec.publishTime
),
}
)
}}
</span>
</VSpace>
</template>
<template #extra>
<a
v-if="post.post.status?.permalink"
target="_blank"
:href="post.post.status?.permalink"
:title="post.post.status?.permalink"
class="hidden text-gray-600 transition-all hover:text-gray-900 group-hover:inline-block"
>
<IconExternalLinkLine class="h-3.5 w-3.5" />
</a>
</template>
</VEntityField>
</template>
</VEntity>
</VEntityContainer> </VEntityContainer>
</OverlayScrollbarsComponent> </OverlayScrollbarsComponent>
</WidgetCard> </WidgetCard>

View File

@ -0,0 +1,91 @@
<script lang="ts" setup>
import { postLabels } from "@/constants/labels";
import { formatDatetime, relativeTimeTo } from "@/utils/date";
import type { ListedPost } from "@halo-dev/api-client";
import {
IconExternalLinkLine,
VEntity,
VEntityField,
VSpace,
VTag,
} from "@halo-dev/components";
import { computed } from "vue";
const props = defineProps<{
post: ListedPost;
}>();
const isPublished = computed(() => {
return props.post.post.metadata.labels?.[postLabels.PUBLISHED] === "true";
});
const previewUrl = computed(() => {
const { status, metadata } = props.post.post;
if (isPublished.value) {
return status?.permalink;
}
return `/preview/posts/${metadata.name}`;
});
const datetime = computed(() => {
return (
props.post.post.spec.publishTime ||
props.post.post.metadata.creationTimestamp
);
});
</script>
<template>
<VEntity>
<template #start>
<VEntityField
:title="post.post.spec.title"
:route="{
name: 'PostEditor',
query: { name: post.post.metadata.name },
}"
>
<template v-if="isPublished" #description>
<VSpace>
<span class="text-xs text-gray-500">
{{
$t("core.dashboard.widgets.presets.recent_published.visits", {
visits: post.stats.visit || 0,
})
}}
</span>
<span class="text-xs text-gray-500">
{{
$t("core.dashboard.widgets.presets.recent_published.comments", {
comments: post.stats.totalComment || 0,
})
}}
</span>
</VSpace>
</template>
<template #extra>
<VSpace>
<VTag v-if="!isPublished">
{{ $t("core.post.filters.status.items.draft") }}
</VTag>
<a
v-if="previewUrl"
target="_blank"
:href="previewUrl"
:title="previewUrl"
class="hidden text-gray-600 transition-all hover:text-gray-900 group-hover:inline-block"
>
<IconExternalLinkLine class="h-3.5 w-3.5" />
</a>
</VSpace>
</template>
</VEntityField>
</template>
<template #end>
<VEntityField
v-tooltip="formatDatetime(datetime)"
:description="relativeTimeTo(datetime)"
></VEntityField>
</template>
</VEntity>
</template>

View File

@ -66,7 +66,6 @@ core:
title: Recent Posts title: Recent Posts
visits: "{visits} Visits" visits: "{visits} Visits"
comments: "{comments} Comments" comments: "{comments} Comments"
publishTime: Publish Time {publishTime}
notification: notification:
title: Notifications title: Notifications
empty: empty:

View File

@ -64,7 +64,6 @@ core:
title: 最近文章 title: 最近文章
visits: 访问量 {visits} visits: 访问量 {visits}
comments: 评论 {comments} comments: 评论 {comments}
publishTime: 发布日期 {publishTime}
notification: notification:
title: 通知 title: 通知
empty: empty:

View File

@ -64,7 +64,6 @@ core:
title: 最近文章 title: 最近文章
visits: 訪問量 {visits} visits: 訪問量 {visits}
comments: 留言 {comments} comments: 留言 {comments}
publishTime: 發佈日期 {publishTime}
notification: notification:
title: 通知 title: 通知
empty: empty:

View File

@ -3,7 +3,7 @@ import StatusDotField from "@/components/entity-fields/StatusDotField.vue";
import HasPermission from "@/components/permission/HasPermission.vue"; import HasPermission from "@/components/permission/HasPermission.vue";
import PostContributorList from "@/components/user/PostContributorList.vue"; import PostContributorList from "@/components/user/PostContributorList.vue";
import { postLabels } from "@/constants/labels"; import { postLabels } from "@/constants/labels";
import { formatDatetime } from "@/utils/date"; import { formatDatetime, relativeTimeTo } from "@/utils/date";
import PostTag from "@console/modules/contents/posts/tags/components/PostTag.vue"; import PostTag from "@console/modules/contents/posts/tags/components/PostTag.vue";
import type { ListedPost } from "@halo-dev/api-client"; import type { ListedPost } from "@halo-dev/api-client";
import { ucApiClient } from "@halo-dev/api-client"; import { ucApiClient } from "@halo-dev/api-client";
@ -238,8 +238,11 @@ function handleDelete() {
<VEntityField v-if="post.post.spec.publishTime"> <VEntityField v-if="post.post.spec.publishTime">
<template #description> <template #description>
<div class="inline-flex items-center space-x-2"> <div class="inline-flex items-center space-x-2">
<span class="entity-field-description"> <span
{{ formatDatetime(post.post.spec.publishTime) }} v-tooltip="formatDatetime(post.post.spec.publishTime)"
class="entity-field-description"
>
{{ relativeTimeTo(post.post.spec.publishTime) }}
</span> </span>
<IconTimerLine <IconTimerLine
v-if=" v-if="