加载中...
当前视口: 1920px × 600px场景: 四项品牌权益展示
打开链接功能特性
- ✅ 链接卡片: 图标 + 标题 + 描述 + 链接按钮的完整卡片结构
- ✅ 响应式布局: 桌面端网格(1→2→4 列),移动端 Swiper 轮播
- ✅ 移动端分组: 每个轮播页显示 4 个卡片,自动分页
- ✅ 形状变体: 支持圆角(round)和方形(square)两种卡片样式
- ✅ 曝光追踪: 集成
useExposureHook 实现自动曝光埋点 - ✅ Swiper 轮播: 支持 FreeMode、鼠标滚轮和分页指示器
- ✅ 固定高度: 不同断点下卡片自适应高度保持视觉一致
- ✅ 文本截断: 标题和描述超长自动截断(line-clamp-2)
Props 参数
BrandCardLink Props
| 参数 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
data | BrandCardLinkData | - | ✅ | 卡片数据配置 |
className | string | '' | ❌ | 自定义类名 |
ref | React.Ref<HTMLDivElement> | - | ❌ | 根容器 ref |
BrandCardLinkData 配置
| 参数 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
items | BrandCardLinkItem[] | - | ✅ | 卡片列表数据 |
itemShape | 'round' | 'square' | undefined | ❌ | 卡片形状,不传则使用默认样式 |
BrandCardLinkItem 配置
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
icon | Img | ❌ | 卡片图标对象 |
title | string | ✅ | 卡片标题文本 |
description | string | ✅ | 卡片描述文本 |
link | string | ✅ | 链接地址 URL |
linkText | string | ✅ | 链接按钮显示文本 |
type | 'icon' | 'avatar' | ❌ | 图标类型(当前实现中未使用) |
avatarList | Array<{ avatar: Img }> | ❌ | 头像列表(当前实现中未使用) |
Img 对象
| 参数 | 类型 | 必需 | 说明 |
|---|---|---|---|
url | string | ✅ | 图片 URL 地址 |
alt | string | ✅ | 图片替代文本(无障碍) |
类型定义
import type { Img } from '@anker-in/headless-ui'
interface BrandCardLinkData {
/** 卡片列表 */
items: BrandCardLinkItem[]
/** 卡片形状: round(圆角) | square(方形) */
itemShape?: 'round' | 'square'
}
interface BrandCardLinkItem {
/** 卡片图标 */
icon?: Img
/** 卡片标题 */
title: string
/** 卡片描述 */
description: string
/** 链接地址 */
link: string
/** 链接按钮文本 */
linkText: string
/** 图标类型(未使用) */
type?: 'icon' | 'avatar'
/** 头像列表(未使用) */
avatarList?: Array<{ avatar: Img }>
}
interface Img {
/** 图片 URL */
url: string
/** 替代文本 */
alt: string
}使用示例
示例 1: 基础卡片组(4 个卡片)
import { BrandCardLink } from '@anker-in/headless-ui/biz'
import scenarios from './scenarios.json'
export default function BasicExample() {
const data = {
items: [
{
icon: {
url: 'https://cdn.example.com/warranty.svg',
alt: 'Warranty',
},
title: '24-Month Warranty',
description: 'Comprehensive Protection',
link: '/warranty',
linkText: 'Learn More',
},
{
icon: {
url: 'https://cdn.example.com/shipping.svg',
alt: 'Free Shipping',
},
title: 'Free Shipping',
description: 'On All Orders',
link: '/shipping',
linkText: 'View Policy',
},
{
icon: {
url: 'https://cdn.example.com/support.svg',
alt: '24/7 Support',
},
title: '24/7 Customer Support',
description: 'Always Here to Help',
link: '/support',
linkText: 'Contact Us',
},
{
icon: {
url: 'https://cdn.example.com/secure.svg',
alt: 'Secure Payment',
},
title: 'Secure Payment',
description: 'SSL Encrypted',
link: '/security',
linkText: 'Learn More',
},
],
}
return <BrandCardLink data={data} />
}效果:
- 桌面端: 4 列网格布局,所有卡片一次性显示
- 移动端: 单页轮播显示 4 个卡片(无分页器)
示例 2: 圆角卡片样式
<BrandCardLink
data={{
items: [
{
icon: {
url: 'https://cdn.example.com/fast-charge.svg',
alt: 'Fast Charging',
},
title: 'Fast Charging',
description: 'Get 50% Charge in 30 Minutes',
link: '/features/fast-charging',
linkText: 'Discover',
},
{
icon: {
url: 'https://cdn.example.com/battery.svg',
alt: 'Long Battery',
},
title: 'Long Battery Life',
description: 'Up to 48 Hours of Use',
link: '/features/battery',
linkText: 'Discover',
},
],
itemShape: 'round', // 圆角卡片
}}
/>效果: 卡片应用 rounded-box 类,呈现圆角边框
示例 3: 方形卡片样式
<BrandCardLink
data={{
items: [
{
icon: {
url: 'https://cdn.example.com/eco.svg',
alt: 'Eco Friendly',
},
title: 'Eco-Friendly Materials',
description: '100% Recyclable Packaging',
link: '/sustainability',
linkText: 'Read More',
},
{
icon: {
url: 'https://cdn.example.com/award.svg',
alt: 'Award Winning',
},
title: 'Award-Winning Design',
description: 'CES Innovation Award 2025',
link: '/awards',
linkText: 'Read More',
},
],
itemShape: 'square', // 方形卡片
}}
/>效果: 卡片应用 rounded-none 类,呈现完全方形无圆角
示例 4: 8 个卡片(移动端分 2 页)
<BrandCardLink
data={{
items: [
{ icon: {...}, title: 'Feature 1', description: 'Desc 1', link: '/f1', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 2', description: 'Desc 2', link: '/f2', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 3', description: 'Desc 3', link: '/f3', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 4', description: 'Desc 4', link: '/f4', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 5', description: 'Desc 5', link: '/f5', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 6', description: 'Desc 6', link: '/f6', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 7', description: 'Desc 7', link: '/f7', linkText: 'Learn' },
{ icon: {...}, title: 'Feature 8', description: 'Desc 8', link: '/f8', linkText: 'Learn' },
],
itemShape: 'round',
}}
/>效果:
- 桌面端: 4 列网格,2 行显示 8 个卡片
- 移动端: 第 1 页显示前 4 个卡片,第 2 页显示后 4 个卡片,带分页指示器
示例 5: 自定义背景色
<BrandCardLink
data={{
items: [...],
itemShape: 'round',
}}
className="[&_.brand-equity-item]:bg-gray-100"
/>说明: 使用 Tailwind 的任意值语法覆盖默认背景色 #DAE7F2
响应式行为
布局方式
| 屏幕尺寸 | 屏幕宽度 | 布局模式 | 列数/分页 | Tailwind 类 |
|---|---|---|---|---|
| 移动端 | < 768px | Swiper 轮播 | 4 items/页 | - |
| 平板 | ≥ 768px | Grid 网格 | 2 列 | tablet:grid-cols-2 |
| 笔记本 | ≥ 1025px | Grid 网格 | 4 列 | laptop:grid-cols-4 |
布局切换逻辑:
- 移动端(<768px): 使用 Swiper 组件实现水平轮播,每页垂直排列 4 个卡片
- 桌面端(≥768px): 使用 CSS Grid 静态网格布局,无轮播
卡片高度
| 断点 | 屏幕宽度 | 高度 | 内边距 | Tailwind 类 |
|---|---|---|---|---|
| 移动端 | < 1025px | 160px | 16px | h-[160px] p-4 |
| 笔记本 | 1025px - 1439px | 192px | 16px | laptop:h-[192px] laptop:p-4 |
| 桌面 | 1440px - 1919px | 192px | 24px | desktop:h-[192px] desktop:p-6 |
| 大屏 | ≥ 1920px | 240px | 32px | lg-desktop:h-[240px] lg-desktop:p-8 |
设计考虑: 固定高度确保所有卡片视觉一致,避免不同内容长度导致的高度差异
文本尺寸
标题 (title):
| 断点 | 字号 | Tailwind 类 |
|---|---|---|
| 移动端 | 14px | text-[14px] |
| 桌面 | 16px | desktop:text-[16px] |
| 大屏 | 18px | lg-desktop:text-[18px] |
描述 (description):
| 断点 | 字号 | Tailwind 类 |
|---|---|---|
| 移动端 | 20px | text-[20px] |
| 大屏 | 24px | lg-desktop:text-[24px] |
链接文本 (linkText):
| 断点 | 字号 | Tailwind 类 |
|---|---|---|
| 移动端 | 14px | text-sm |
| 笔记本及以上 | 16px | laptop:text-base |
设计规范
卡片数量建议
| 场景 | 推荐数量 | 移动端表现 | 桌面端表现 |
|---|---|---|---|
| 核心权益 | 4 个 | 单页显示,无分页器 | 单行 4 列 |
| 完整服务 | 8 个 | 2 页显示,带分页器 | 2 行 × 4 列 |
| 扩展特性 | 12 个 | 3 页显示,带分页器 | 3 行 × 4 列 |
注意: 移动端每页固定显示 4 个卡片,超过 4 个会自动分页
文本长度限制
| 字段 | 中文 | 英文 | 截断方式 |
|---|---|---|---|
title | ≤ 10 字 | ≤ 20 字符 | line-clamp-2 (最多 2 行) |
description | ≤ 20 字 | ≤ 40 字符 | line-clamp-2 (最多 2 行) |
linkText | ≤ 6 字 | ≤ 12 字符 | 不截断 |
示例:
标题: "This is a Very Long Title That Will Be Truncated After Two Lines..."图标要求
- 格式: SVG 或 PNG(建议 SVG 保证清晰度)
- 尺寸: 建议 200×200px 或更大(组件内自适应)
- 风格: 单色或简洁双色图标,避免过于复杂的设计
- 背景: 透明背景,便于与卡片背景融合
- alt 文本: 必须提供有意义的替代文本,描述图标含义
无障碍性
语义化 HTML
// 标题使用 <h3> 标签
<h3 className="...">{item.title}</h3>
// 链接使用语义化 Link 组件
<Link href={item.link}>
{item.linkText}
<svg aria-hidden="true">...</svg>
</Link>键盘导航
- ✅ Tab 键: 按顺序聚焦到每个卡片的链接按钮
- ✅ Enter/Space: 激活当前聚焦的链接
- ✅ 移动端轮播: 支持触摸滑动和鼠标滚轮导航
屏幕阅读器
- ✅ 图标有
alt属性,通过 Picture 组件自动实现 - ✅ 描述文本有
title属性,鼠标悬停显示完整文本 - ✅ 链接按钮语义清晰,屏幕阅读器可正确识别
建议改进
-
⚠️ 建议: 为整个卡片添加
aria-label,描述完整内容<div aria-label={`${item.title}: ${item.description}. ${item.linkText}`}> ... </div> -
⚠️ 建议: 为移动端轮播容器添加 ARIA 属性
<div role="region" aria-label="Brand features carousel"> <Swiper>...</Swiper> </div>
颜色对比度
- 默认背景色
#DAE7F2与文本颜色对比度建议≥ 4.5:1(WCAG AA 标准) - 链接文本应使用品牌色且对比度充足
- 测试工具: 使用浏览器 DevTools 的 Accessibility 面板验证
性能优化
React Hooks 优化
// 使用 React.forwardRef 和 useImperativeHandle 优化 ref 传递
const BrandCardLink = React.forwardRef((props, ref) => {
const innerRef = useRef<HTMLDivElement>(null)
useImperativeHandle(ref, () => innerRef.current!)
// ...
})响应式加载
// 使用 useMediaQuery 避免不必要的移动端组件渲染
const isMobile = useMediaQuery('(max-width: 767px)')
// 桌面端: 简单 Grid 布局,无 Swiper 依赖
// 移动端: 按需加载 Swiper 组件优势:
- 减少桌面端的 JavaScript Bundle 大小
- 避免不必要的 Swiper 实例化
曝光追踪优化
// useExposure Hook 自动防止重复曝光
useExposure(innerRef, {
componentType: 'copy',
componentName: 'store_benefits',
})特性:
- 仅在组件首次进入视口时触发
- 基于 Intersection Observer API,性能开销低
- 自动上报组件类型和名称
建议优化
- ✅ 图片懒加载: 为 icon 图片添加
loading="lazy"属性 - ✅ CDN 加速: 图标资源使用 CDN 分发,减少加载时间
- ✅ 预加载关键资源: 对首屏可见的图标进行 preload
常见问题
1. 为什么移动端每页固定显示 4 个卡片?
这是设计决策,确保移动端体验一致且每页内容完整。如需修改每页卡片数量,可以调整分组逻辑:
// 修改 chunkArray 的第二个参数
const itemsArray = isMobile ? chunkArray(items, 6) : items // 改为每页 6 个2. 如何自定义轮播分页器样式?
分页器默认居中显示,通过覆盖 Swiper 的 CSS 类自定义:
.swiper-pagination-bullet {
background: #000;
opacity: 0.5;
width: 8px;
height: 8px;
}
.swiper-pagination-bullet-active {
background: var(--brand-primary);
opacity: 1;
width: 24px;
border-radius: 4px;
}3. 卡片高度为什么固定?
为了保证视觉一致性,避免不同内容长度导致的高度不一致。如需自适应高度,可以移除固定高度类:
// 移除 h-[160px]、laptop:h-[192px] 等类
// 保留 min-h-[160px] 设置最小高度避免过矮4. 如何处理卡片点击事件?
当前实现中 onClick 回调未暴露。如需添加,可以修改 BrandCardLinkItem 类型并在 Link 组件中绑定:
interface BrandCardLinkItem {
// 现有字段...
onClick?: () => void // 新增
}
// 组件内部
<Link href={item.link} onClick={item.onClick}>
{item.linkText}
</Link>5. type 和 avatarList 字段有什么用?
这两个字段在当前实现中未使用,可能是为未来功能预留的接口(如头像展示模式)。暂时可以忽略这两个字段。
6. 如何隐藏链接箭头图标?
通过 CSS 隐藏:
.brand-equity-item svg {
display: none;
}或使用 className 覆盖:
<BrandCardLink
className="[&_.brand-equity-item_svg]:hidden"
data={{...}}
/>7. 背景颜色可以自定义吗?
当前背景色硬编码为 #DAE7F2(浅蓝色)。自定义方式:
<BrandCardLink
data={{...}}
className="[&_.brand-equity-item]:bg-gray-100" // 覆盖背景色
/>或直接修改组件源码中的颜色值
技术实现
移动端分组逻辑
/**
* 将数组按指定大小分组
* @param arr - 原始数组
* @param size - 每组大小
*/
const chunkArray = <T,>(arr: T[], size: number): T[][] => {
const chunks: T[][] = []
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size))
}
return chunks
}
// 使用示例
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9]
const grouped = chunkArray(items, 4)
// 结果: [[1,2,3,4], [5,6,7,8], [9]]应用场景:
- 8 个卡片 → 2 页(每页 4 个)
- 9 个卡片 → 3 页(前 2 页 4 个,最后 1 页 1 个)
- 12 个卡片 → 3 页(每页 4 个)
Swiper 配置详解
const swiperConfig = {
modules: [FreeMode, Mousewheel, Pagination],
freeMode: true, // 自由模式,滑动更流畅无吸附
mousewheel: {
forceToAxis: true // 强制单方向滚动,防止斜向滑动
},
pagination: {
clickable: true, // 分页器点(dots)可点击切换
el: paginationRef.current // 自定义分页器 DOM 位置
},
breakpoints: {
0: {
spaceBetween: 12 // 移动端卡片间距 12px
}
}
}FreeMode 说明:
- 启用后滑动无吸附效果,用户可自由停止在任意位置
- 适合内容浏览场景,不适合精准分页切换
Mousewheel 说明:
forceToAxis: true确保鼠标滚轮仅触发水平滚动- 防止用户垂直滚动页面时意外触发轮播
形状变体实现
// round(圆角)
itemShape === 'round'
// 应用类: rounded-box
// 实际圆角取决于 Tailwind 配置中的 border-radius.box 值
// square(方形)
itemShape === 'square'
// 应用类: rounded-none
// 完全移除圆角,呈现直角矩形
// 默认(未设置)
itemShape === undefined
// 不应用任何形状类,使用组件默认样式条件渲染:
<div
className={cn(
'brand-equity-item',
{
'rounded-box': itemShape === 'round',
'rounded-none': itemShape === 'square',
}
)}
>
...
</div>相关资源
源码位置
- 组件源码:
packages/ui/src/biz-components/BrandCardLink/BrandCardLink.tsx - 类型定义:
packages/ui/src/biz-components/BrandCardLink/types.ts - Storybook:
packages/ui/src/stories/brandCardLink.stories.tsx
依赖组件
- Picture: 图片展示组件,支持响应式和懒加载
- Link: 链接组件,支持内部路由和外部链接
- Swiper: 第三方轮播库(swiper/react)
- useMediaQuery: 响应式断点 Hook
- useExposure: 曝光追踪 Hook
相关文档
- Picture 组件文档 - 了解图片优化和懒加载
- Link 组件文档 - 了解链接路由和事件处理
- Swiper 官方文档 - 查看完整配置选项
- 无障碍性指南 - WCAG 2.1 快速参考
维护者: DTC IT Team 最后更新: 2026-01-17 版本: v1.0.0
Last updated on