feat(skill-card): show author and update time in skill card

Add ownerId and ownerDisplayName fields to SkillSummaryResponse,
and render author and last update time at the bottom of each skill card.

在技能卡片底部新增作者和最近更新时间显示,搜索结果接口新增
ownerId 和 ownerDisplayName 字段。

Log: 技能卡片新增作者和更新时间
Influence: 搜索结果页技能卡片底部显示作者和更新时间信息,接口新增 ownerId/ownerDisplayName 字段。
Signed-off-by: wurongjie <wurongjie@uniontech.com>
This commit is contained in:
wurongjie 2026-08-24 16:57:36 +08:00 committed by XiaoSeS
parent 26f49e6819
commit f43c047a6b
9 changed files with 52 additions and 3 deletions

View file

@ -18,6 +18,8 @@ public record SkillSummaryResponse(
Integer ratingCount,
String namespace,
Instant updatedAt,
String ownerId,
String ownerDisplayName,
boolean canSubmitPromotion,
SkillLifecycleVersionResponse headlineVersion,
SkillLifecycleVersionResponse publishedVersion,

View file

@ -75,6 +75,8 @@ public class JpaMySkillQueryRepository implements MySkillQueryRepository {
skill.getRatingCount(),
namespace != null ? namespace.getSlug() : null,
skill.getUpdatedAt(),
skill.getOwnerId(),
null,
canSubmitPromotion(skill, publishedVersion, namespace),
toLifecycleVersion(headlineVersion),
toLifecycleVersion(publishedVersion),

View file

@ -249,6 +249,8 @@ public class SkillSearchAppService {
skill.getRatingCount(),
namespaceSlug,
skill.getUpdatedAt(),
skill.getOwnerId(),
null,
false,
toLifecycleVersion(projection.headlineVersion()),
toLifecycleVersion(projection.publishedVersion()),

View file

@ -116,6 +116,8 @@ class ClawHubCompatControllerTest {
2,
"global",
Instant.parse("2026-03-13T09:00:00Z"),
null,
null,
false,
new SkillLifecycleVersionResponse(11L, "1.2.0", "PUBLISHED"),
new SkillLifecycleVersionResponse(11L, "1.2.0", "PUBLISHED"),

View file

@ -51,6 +51,8 @@ class ClawHubRegistryFacadeTest {
2,
"global",
updatedAt,
null,
null,
false,
new SkillLifecycleVersionResponse(11L, "1.0.0", "PUBLISHED"),
new SkillLifecycleVersionResponse(11L, "1.0.0", "PUBLISHED"),

View file

@ -71,6 +71,8 @@ class MeControllerTest {
0,
"team-ai",
Instant.parse("2026-03-17T12:00:00Z"),
null,
null,
false,
new SkillLifecycleVersionResponse(11L, "1.0.0", "PUBLISHED"),
new SkillLifecycleVersionResponse(11L, "1.0.0", "PUBLISHED"),

View file

@ -72,7 +72,7 @@ class CliSkillAppServiceTest {
List.of(new SkillSummaryResponse(
1L, "pdf-parser", "PDF Parser", "Parse PDFs",
"PUBLIC", "ACTIVE", 100L, 5, BigDecimal.valueOf(4.5), 10,
"global", Instant.now(), false,
"global", Instant.now(), null, null, false,
new SkillLifecycleVersionResponse(1L, "1.2.0", "PUBLISHED"),
new SkillLifecycleVersionResponse(1L, "1.2.0", "PUBLISHED"),
null, "PUBLISHED", null
@ -100,7 +100,7 @@ class CliSkillAppServiceTest {
new SkillSummaryResponse(
2L, "ready", "Ready", "Installable",
"PUBLIC", "ACTIVE", 0L, 0, BigDecimal.ZERO, 0,
"global", Instant.now(), false,
"global", Instant.now(), null, null, false,
new SkillLifecycleVersionResponse(2L, "1.0.0", "PUBLISHED"),
new SkillLifecycleVersionResponse(2L, "1.0.0", "PUBLISHED"),
null, "PUBLISHED", null

View file

@ -208,6 +208,8 @@ export interface SkillSummary {
ratingCount: number
namespace: string
updatedAt: string
ownerId?: string
ownerDisplayName?: string
canSubmitPromotion: boolean
headlineVersion?: SkillLifecycleVersion
publishedVersion?: SkillLifecycleVersion

View file

@ -5,7 +5,7 @@ import { Card } from '@/shared/ui/card'
import { NamespaceBadge } from '@/shared/components/namespace-badge'
import { getHeadlineVersion } from '@/shared/lib/skill-lifecycle'
import { formatCompactCount } from '@/shared/lib/number-format'
import { Bookmark, ShieldCheck } from 'lucide-react'
import { Bookmark, ShieldCheck, User, Clock } from 'lucide-react'
interface SkillCardProps {
skill: SkillSummary
@ -13,6 +13,25 @@ interface SkillCardProps {
highlightStarred?: boolean
}
function formatRelativeTime(dateString: string): string {
const date = new Date(dateString)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffSeconds = Math.floor(diffMs / 1000)
const diffMinutes = Math.floor(diffSeconds / 60)
const diffHours = Math.floor(diffMinutes / 60)
const diffDays = Math.floor(diffHours / 24)
const diffMonths = Math.floor(diffDays / 30)
const diffYears = Math.floor(diffDays / 365)
if (diffSeconds < 60) return '刚刚'
if (diffMinutes < 60) return `${diffMinutes}分钟前`
if (diffHours < 24) return `${diffHours}小时前`
if (diffDays < 30) return `${diffDays}天前`
if (diffMonths < 12) return `${diffMonths}个月前`
return `${diffYears}年前`
}
/**
* Reusable card for displaying one skill in lists such as landing, namespace, search, and stars.
*/
@ -108,6 +127,22 @@ export function SkillCard({ skill, onClick, highlightStarred = true }: SkillCard
</span>
)}
</div>
{(skill.ownerDisplayName || skill.updatedAt) && (
<div className="mt-2 pt-2 border-t border-border/50 flex items-center gap-3 text-xs text-muted-foreground">
{skill.ownerDisplayName && (
<span className="flex items-center gap-1">
<User className="w-3 h-3" />
{skill.ownerDisplayName}
</span>
)}
{skill.updatedAt && (
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{formatRelativeTime(skill.updatedAt)}
</span>
)}
</div>
)}
</div>
</Card>
)