设计组件技能Skill design-component

共情账本的UI组件设计技能,专注于讲故事卡片、数据展示模式和AI内容丰富,包含文化敏感性和AI应用

前端开发 0 次安装 0 次浏览 更新于 3/1/2026

name: 设计组件技能 description: 为共情账本设计的组件技能。用于讲故事卡片、故事卡片、个人资料展示以及任何需要文化敏感性的UI组件。提供数据映射、AI丰富模式和设计系统指南。

设计组件技能

此技能为在共情账本中设计和实现UI组件提供全面的指导,特别关注讲故事卡片、数据展示模式和AI驱动的内容丰富。

设计系统基础

色彩板(CSS变量)

/* 使用语义色彩支持暗模式 */
--background     /* 页面背景 */
--foreground     /* 主要文本 */
--card           /* 卡片表面 */
--card-foreground/* 卡片文本 */
--muted          /* 柔和背景 */
--muted-foreground /* 二级文本 */
--popover        /* 下拉/弹出背景 */
--border         /* 边框 */
--primary        /* 主要操作 */
--accent         /* 辅助/高亮(阳光黄) */
--destructive    /* 错误/警告 */

文化色彩含义

颜色 含义 用途
琥珀/金 长者智慧,特色 长者徽章,特色指示器
翡翠 成长,社区 故事计数,活跃状态
紫色 神圣,知识 知识守护者徽章
陶土 地球,联系 文化归属
鼠尾草 平静,尊重 通用UI元素

讲故事者卡片数据模型

核心展示字段

interface StorytellerCardData {
  // 身份(始终显示)
  id: string
  display_name: string
  avatar_url?: string
  pronouns?: string

  // 文化背景(可用时显示)
  cultural_background?: string
  cultural_affiliations?: string[]
  traditional_territory?: string
  languages_spoken?: string[]

  // 状态指示器
  is_elder: boolean
  is_featured: boolean
  status: 'active' | 'inactive' | 'pending'
  traditional_knowledge_keeper?: boolean

  // 故事指标
  story_count: number
  featured_quote?: string
  expertise_themes?: string[]

  // 专业背景
  occupation?: string
  years_of_experience?: number
  specialties?: string[]

  // AI丰富的领域
  ai_summary?: string
  theme_expertise?: string[]
  connection_strength?: number
  suggested_connections?: string[]
}

数据优先级层次

第一层 - 始终显示:
├── display_name
├── avatar(或首字母回退)
├── cultural_background
└── story_count

第二层 - 在卡片上显示:
├── 长者状态徽章
├── 特色徽章
├── 前3个专业
├── 主要位置
└── 特色引用(如果可用)

第三层 - 在悬停/展开时显示:
├── 完整简介
├── 所有专业
├── 语言
├── 组织
└── 主题专长

第四层 - 仅在个人资料页面上显示:
├── 联系信息
├── 完整故事列表
├── 连接图
└── 详细分析

卡片变体

默认卡片

<StorytellerCard
  storyteller={storyteller}
  variant="default"
  showStories={true}
  showActions={true}
/>
  • 最小宽度320px,响应式
  • 头像,名称,文化背景
  • 故事计数,专业(最多3个)
  • 长者/特色徽章
  • 悬停效果带箭头

紧凑卡片

<StorytellerCard
  storyteller={storyteller}
  variant="compact"
/>
  • 宽度280px,内联布局
  • 头像(更小),仅名称
  • 故事计数作为徽章
  • 适用于侧边栏,列表

特色卡片

<StorytellerCard
  storyteller={storyteller}
  variant="featured"
/>
  • 全宽,更大的头像
  • 特色引用显示
  • 主题专长徽章
  • 渐变背景
  • 增强悬停动画

列表视图行

<StorytellerListCard storyteller={storyteller} />
  • 全宽水平
  • 更多数据可见
  • 右侧操作按钮
  • 适用于管理视图

AI丰富机会

1. 生物增强

// API: POST /api/storytellers/{id}/enhance-bio
interface BioEnhancement {
  original_bio: string
  enhanced_bio: string        // 语法,流程改进
  key_themes: string[]        // 从生物中提取
  suggested_specialties: string[]
  cultural_keywords: string[]
}

2. 特色引用提取

// 从讲故事者的故事中提取引人注目的引用
interface QuoteExtraction {
  quotes: Array<{
    text: string
    story_id: string
    themes: string[]
    impact_score: number
  }>
  suggested_featured: string  // 最佳引用用于卡片
}

3. 主题专长分析

// 分析所有故事以确定专长领域
interface ThemeExpertise {
  primary_themes: string[]      // 最多讨论的前3个
  secondary_themes: string[]    // 支持主题
  unique_perspective: string    // 使他们独特
  theme_depth_scores: Record<string, number>
}

4. 连接建议

// 查找具有互补主题的讲故事者
interface ConnectionSuggestion {
  storyteller_id: string
  connection_type: 'theme_overlap' | 'geographic' | 'community'
  overlap_score: number
  reason: string  // "Both share expertise in healing stories"
}

5. 摘要生成

// 生成简洁的讲故事者摘要
interface StoritellerSummary {
  one_liner: string      // "Elder from Wurundjeri Country..."
  card_summary: string   // 50-100字符用于卡片
  full_summary: string   // 200-300字符用于个人资料
  voice_style: string    // "Warm and reflective storytelling"
}

组件实现模式

头像与状态指示器

<div className="relative">
  <Avatar
    src={storyteller.avatar_url}
    fallback={getInitials(storyteller.display_name)}
    className="w-16 h-16 border-2 border-background shadow-md"
  />

  {/* 状态徽章 - 定位顶部右侧 */}
  <div className="absolute -top-1 -right-1 flex gap-1">
    {storyteller.is_featured && (
      <Badge variant="featured" size="icon">
        <Star className="w-3 h-3" />
      </Badge>
    )}
    {storyteller.is_elder && (
      <Badge variant="elder" size="icon">
        <Crown className="w-3 h-3" />
      </Badge>
    )}
  </div>
</div>

文化背景展示

{/* 尊重文化展示 */}
<div className="flex items-center gap-2 text-muted-foreground">
  <MapPin className="w-4 h-4 text-terracotta-500" />
  <span className="text-sm">
    {storyteller.cultural_background}
    {storyteller.traditional_territory && (
      <span className="text-xs ml-1">
        ({storyteller.traditional_territory})
      </span>
    )}
  </span>
</div>

故事指标展示

<div className="flex items-center gap-4">
  <div className="flex items-center gap-1.5">
    <BookOpen className="w-4 h-4 text-emerald-500" />
    <span className="font-semibold">{storyteller.story_count}</span>
    <span className="text-muted-foreground text-sm">
      {storyteller.story_count === 1 ? 'Story' : 'Stories'}
    </span>
  </div>

  {storyteller.years_of_experience && (
    <div className="flex items-center gap-1.5">
      <Calendar className="w-4 h-4 text-blue-500" />
      <span className="font-semibold">{storyteller.years_of_experience}</span>
      <span className="text-muted-foreground text-sm">Years</span>
    </div>
  )}
</div>

主题/专业徽章

{/* 在卡片上最多显示3个,其余在悬停/详细 */}
<div className="flex flex-wrap gap-1.5">
  {storyteller.specialties?.slice(0, 3).map((specialty, i) => (
    <Badge
      key={specialty}
      variant="secondary"
      className={cn(
        "text-xs",
        specialtyColors[i % specialtyColors.length]
      )}
    >
      {specialty}
    </Badge>
  ))}
  {(storyteller.specialties?.length || 0) > 3 && (
    <Badge variant="outline" className="text-xs text-muted-foreground">
      +{storyteller.specialties!.length - 3} more
    </Badge>
  )}
</div>

特色引用展示

{storyteller.featured_quote && (
  <div className="mt-4 p-3 bg-muted/50 rounded-lg border-l-2 border-accent">
    <Quote className="w-4 h-4 text-accent mb-1" />
    <p className="text-sm italic text-foreground/80 line-clamp-2">
      "{storyteller.featured_quote}"
    </p>
  </div>
)}

AI丰富API模式

触发丰富

// POST /api/storytellers/{id}/enrich
async function enrichStoryteller(storytellerId: string) {
  const response = await fetch(`/api/storytellers/${storytellerId}/enrich`, {
    method: 'POST',
    body: JSON.stringify({
      enrich_bio: true,
      extract_quotes: true,
      analyze_themes: true,
      suggest_connections: true
    })
  })

  // 返回丰富作业ID以轮询
  return response.json()
}

显示丰富数据

// 检查AI丰富的领域
const hasAIData = storyteller.ai_summary || storyteller.theme_expertise?.length

{hasAIData && (
  <div className="flex items-center gap-1 text-xs text-muted-foreground">
    <Sparkles className="w-3 h-3 text-accent" />
    <span>AI Enhanced</span>
  </div>
)}

数据库领域AI丰富

配置文件表添加

-- AI生成摘要
ALTER TABLE profiles ADD COLUMN ai_summary TEXT;

-- 从故事分析中提取的主题专长(来自故事分析)
ALTER TABLE profiles ADD COLUMN theme_expertise TEXT[];

-- AI生成的特色引用选择
ALTER TABLE profiles ADD COLUMN featured_quote TEXT;
ALTER TABLE profiles ADD COLUMN featured_quote_story_id UUID;

-- 连接强度分数
ALTER TABLE profiles ADD COLUMN connection_scores JSONB DEFAULT '{}';

-- 丰富状态
ALTER TABLE profiles ADD COLUMN ai_enrichment_status TEXT DEFAULT 'pending';
ALTER TABLE profiles ADD COLUMN ai_enriched_at TIMESTAMPTZ;

讲故事者连接视图

CREATE VIEW storyteller_connections AS
SELECT
  p1.id as storyteller_id,
  p2.id as connected_storyteller_id,
  array_length(
    ARRAY(SELECT unnest(p1.theme_expertise) INTERSECT SELECT unnest(p2.theme_expertise)),
    1
  ) as theme_overlap,
  p1.cultural_background = p2.cultural_background as same_culture
FROM profiles p1
CROSS JOIN profiles p2
WHERE p1.id != p2.id
  AND p1.is_storyteller = true
  AND p2.is_storyteller = true;

组件清单

创建卡片组件前

  • [ ] 确定所有所需数据字段
  • [ ] 定义哪些字段是必需的与可选的
  • [ ] 计划缺失数据的回退状态
  • [ ] 考虑暗模式颜色
  • [ ] 添加悬停状态
  • [ ] 计划加载骨架
  • [ ] 添加辅助标签

文化敏感性

  • [ ] 使用尊重的术语
  • [ ] 突出显示长者状态
  • [ ] 尊重地显示文化背景
  • [ ] 承认传统领土
  • [ ] 不挪用文化符号
  • [ ] 尊重地显示语言

AI丰富

  • [ ] 标记AI生成的内容
  • [ ] 允许用户覆盖AI建议
  • [ ] 优雅地处理缺失的AI数据
  • [ ] 显示丰富进行中状态

参考组件

组件 位置 目的
StorytellerCard src/components/storyteller/storyteller-card.tsx 主要卡片组件
UnifiedStorytellerCard src/components/storyteller/unified-storyteller-card.tsx 灵活的卡片变体
ElegantStorytellerCard src/components/storyteller/elegant-storyteller-card.tsx 高级设计变体
StoryCard src/components/story/story-card.tsx 故事展示卡片
QuoteCard src/components/ui/quote-card.tsx 引用展示
ThemeBadge src/components/ui/theme-badge.tsx 主题展示

联合仪表板设计模式(NEW - Sprint 4)

同意状态徽章

目的: 视觉指示联合同意状态

变体:

type ConsentStatus = 'approved' | 'pending' | 'revoked' | 'expired'

const statusConfig = {
  approved: { color: 'sage', icon: CheckCircle, label: 'Active' },
  pending: { color: 'amber', icon: Clock, label: 'Pending' },
  revoked: { color: 'ember', icon: XCircle, label: 'Revoked' },
  expired: { color: 'muted', icon: AlertCircle, label: 'Expired' }
}

设计:

<Badge className={cn(
  "flex items-center gap-1.5",
  status === 'approved' && "bg-sage-100 text-sage-900",
  status === 'pending' && "bg-amber-100 text-amber-900",
  status === 'revoked' && "bg-ember-100 text-ember-900",
  status === 'expired' && "bg-muted text-muted-foreground"
)}>
  <Icon className="h-3 w-3" />
  {label}
</Badge>

同意卡片布局

目的: 显示单个联合同意与网站信息和控件

结构:

┌─────────────────────────────────────────────┐
│ [Site Logo]  JusticeHub                    │
│              justicehub.org.au              │
│                                             │
│ 状态:[Active Badge]                      │
│ 文化水平:Public                      │
│ 创建:Jan 5, 2026                        │
│                                             │
│ 📊 456 views • Last accessed 2 hours ago    │
│                                             │
│ [View Analytics] [Revoke Access]            │
└─────────────────────────────────────────────┘

颜色:

  • 卡片边框: 使用网站品牌颜色(如果提供)
  • 状态徽章: 遵循上述状态配置
  • 文化水平: 使用文化颜色(粘土/鼠尾草/天空)
  • 操作: 主要(鼠尾草)用于分析,破坏性(火红)用于撤销

嵌入令牌显示

目的: 显示令牌并注意安全遮罩

模式:

<div className="flex items-center gap-2">
  <code className="flex-1 px-3 py-2 bg-muted rounded font-mono text-sm">
    {tokenMasked ? 'LRK•••••••••••XA' : token}
  </code>
  <Button variant="ghost" size="sm" onClick={() => setTokenMasked(!tokenMasked)}>
    {tokenMasked ? <Eye className="h-4 w-4" /> : <EyeOff className="h-4 w-4" />}
  </Button>
  <Button variant="ghost" size="sm" onClick={handleCopy}>
    <Copy className="h-4 w-4" />
  </Button>
</div>

安全:

  • 默认:令牌遮罩
  • 点击显示(临时,10秒)
  • 复制按钮带有确认提示
  • 永远不记录未遮罩的令牌

分析图表样式

目的: 跨仪表板的一致图表设计

颜色(Recharts):

const siteColors = {
  justicehub: '#C85A54', // Ember
  actfarm: '#6B8E72',    // Sage
  theharvest: '#D97757', // Clay
  actplacemat: '#4A90A4' // Sky
}

// 使用
<Line
  type="monotone"
  dataKey="justicehub"
  stroke={siteColors.justicehub}
  strokeWidth={2}
  dot={{ fill: siteColors.justicehub, r: 4 }}
  activeDot={{ r: 6 }}
/>

网格 & 轴:

<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis
  dataKey="date"
  stroke="hsl(var(--muted-foreground))"
  fontSize={12}
/>
<YAxis
  stroke="hsl(var(--muted-foreground))"
  fontSize={12}
/>

撤销对话框

目的: 用文化信息确认同意撤销

布局:

<Dialog>
  <DialogHeader>
    <DialogTitle className="text-ember-900">
      撤销JusticeHub的同意?
    </DialogTitle>
    <DialogDescription>
      这将立即移除JusticeHub对您故事的访问权限。
      他们将不再能够在他们的平台上展示它。
    </DialogDescription>
  </DialogHeader>

  <div className="space-y-4">
    <div className="rounded-lg bg-sky-50 p-4 border border-sky-200">
      <p className="text-sm text-sky-900 font-medium">
        ✨ 您完全控制
      </p>
      <p className="text-sm text-sky-700 mt-1">
        您的故事仍然在共情账本上。您可以随时再次授予同意。
      </p>
    </div>

    <div className="space-y-2">
      <Label htmlFor="reason">撤销原因(可选)</Label>
      <Textarea
        id="reason"
        placeholder="例如,故事需要更新,未准备好外部分享..."
        rows={3}
      />
      <p className="text-xs text-muted-foreground">
        这有助于我们改进平台(与JusticeHub共享匿名同意,如果同意是活跃的)。
      </p>
    </div>
  </div>

  <DialogFooter>
    <Button variant="outline" onClick={onCancel}>
      保持同意
    </Button>
    <Button variant="destructive" onClick={handleRevoke}>
      撤销访问
    </Button>
  </DialogFooter>
</Dialog>

信息原则:

  • ✅ 确认讲故事者控制(“您完全控制”)
  • ✅ 清晰解释后果(“立即移除访问权限”)
  • ✅ 保证可逆性(“再次授予同意”)
  • ❌ 没有内疚语言(“您确定吗?”)
  • ❌ 没有恐惧语言(“这不能撤销”)

文化许可级别指示器

目的: 用文化背景显示内容敏感性级别

级别:

const culturalLevels = {
  public: {
    color: 'sage',
    icon: Globe,
    label: 'Public',
    description: 'Safe to share widely'
  },
  community: {
    color: 'clay',
    icon: Users,
    label: 'Community',
    description: 'Indigenous communities only'
  },
  restricted: {
    color: 'amber',
    icon: Lock,
    label: 'Restricted',
    description: 'Requires elder approval'
  },
  sacred: {
    color: 'ember',
    icon: ShieldAlert,
    label: 'Sacred',
    description: 'Not for external sharing'
  }
}

组件:

<div className="flex items-center gap-2">
  <Icon className="h-4 w-4 text-{color}-700" />
  <div>
    <p className="text-sm font-medium">{label}</p>
    <p className="text-xs text-muted-foreground">{description}</p>
  </div>
</div>

联合空状态

尚未同意:

<div className="text-center py-12 space-y-4">
  <div className="mx-auto w-16 h-16 rounded-full bg-sage-100 flex items-center justify-center">
    <Share2 className="h-8 w-8 text-sage-700" />
  </div>
  <div>
    <h3 className="text-lg font-semibold">No syndication consents yet</h3>
    <p className="text-muted-foreground mt-1">
      Your stories are safe with you. When you're ready to share with
      external platforms like JusticeHub, you'll see them here.
    </p>
  </div>
  <Button onClick={handleCreateConsent}>
    <Plus className="mr-2 h-4 w-4" />
    Share a Story
  </Button>
</div>

全部同意已撤销:

<div className="text-center py-12 space-y-4">
  <div className="mx-auto w-16 h-16 rounded-full bg-sky-100 flex items-center justify-center">
    <ShieldCheck className="h-8 w-8 text-sky-700" />
  </div>
  <div>
    <h3 className="text-lg font-semibold">You're in control</h3>
    <p className="text-muted-foreground mt-1">
      All your stories have been removed from external platforms.
      You can re-share whenever you're ready.
    </p>
  </div>
</div>

何时使用此技能

调用时:

  • 设计新卡片组件
  • 向讲故事者/故事卡片添加字段
  • 实施AI丰富功能
  • 创建个人资料展示
  • 构建带有讲故事者数据的列表视图
  • 向UI添加文化指标
  • 实施悬停/展开状态
  • 创建加载骨架
  • 设计联合仪表板UI
  • 构建同意管理界面
  • 创建分析可视化
  • 实施撤销工作流程