未验证 提交 e41f14ee 编辑于 作者: cyole's avatar cyole
浏览文件

Add component docs demos and tests

上级 962a1402
# 空状态 Empty # 空状态 Empty
TODO: 补充默认空状态、自定义描述、自定义图片和业务空态示例。 Empty 用于在列表、表格、详情等区域没有任何数据时给出占位与下一步动作的引导。
## 何时使用
- 列表/表格在无数据时需要给出明确的"没有内容"反馈。
- 用户首次进入某个功能模块,需要引导其进行第一次操作。
- 不要在加载中也展示 Empty,加载阶段请使用 Loading。
## 演示 ## 演示
下面的示例分别演示:
- `basic`:不传任何属性时显示默认文案"暂无数据"。
- `description`:通过 `description` 自定义提示语。
- `image`:通过具名插槽 `image` 替换默认占位块。
- `slot`:默认插槽内嵌入按钮等引导操作的元素。
<!-- DEMO --> <!-- DEMO -->
<demo vue="empty/demos/basic.vue" /> <demo vue="empty/demos/basic.vue" />
<demo vue="empty/demos/description.vue" />
<demo vue="empty/demos/image.vue" />
<demo vue="empty/demos/slot.vue" />
<!-- DEMO --> <!-- DEMO -->
...@@ -14,7 +30,17 @@ TODO: 补充默认空状态、自定义描述、自定义图片和业务空态 ...@@ -14,7 +30,17 @@ TODO: 补充默认空状态、自定义描述、自定义图片和业务空态
<!-- API --> <!-- API -->
TODO: 对照 `src/empty/src/interface.ts` 补充 Props 和 Slots 表格。 ### Empty Props
<!-- API --> | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| description | `string` | `'暂无数据'` | 提示文案,被默认插槽覆盖 |
### Empty Slots
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| image | `()` | 占位图区域,不传则使用内置灰色占位块 |
| default | `()` | 描述区域,优先级高于 `description` 属性 |
<!-- API -->
<template>
<ZEmpty>
<div style="display: flex; flex-direction: column; align-items: center; gap: 8px;">
<span>还没有任何成员</span>
<ZButton type="primary">
立即邀请
</ZButton>
</div>
</ZEmpty>
</template>
import { mount } from '@vue/test-utils'
import { ZEmpty } from '../index'
describe('empty', () => {
it('renders the default description text', () => {
const wrapper = mount(ZEmpty)
expect(wrapper.text()).toContain('暂无数据')
})
it('renders a custom description prop', () => {
const wrapper = mount(ZEmpty, {
props: { description: '没有匹配的搜索结果' },
})
expect(wrapper.text()).toContain('没有匹配的搜索结果')
expect(wrapper.text()).not.toContain('暂无数据')
})
it('prefers default slot over description prop', () => {
const wrapper = mount(ZEmpty, {
props: { description: '默认' },
slots: { default: '<a href="#">点击新建一条</a>' },
})
expect(wrapper.text()).toContain('点击新建一条')
expect(wrapper.text()).not.toContain('默认')
expect(wrapper.find('a').exists()).toBe(true)
})
it('renders a custom image slot when provided', () => {
const wrapper = mount(ZEmpty, {
slots: {
image: '<img src="/empty.svg" alt="empty">',
},
})
const img = wrapper.find('img')
expect(img.exists()).toBe(true)
expect(img.attributes('src')).toBe('/empty.svg')
})
})
# 加载 Loading # 加载 Loading
TODO: 补充局部加载、加载文案、包裹内容和关闭加载态示例。 Loading 用于在区域内展示加载遮罩,告诉用户当前块正在请求或处理数据。
## 何时使用
- 表格、卡片、表单等局部区域需要异步加载数据时。
- 用户触发了一个耗时动作(导出、提交大表单),希望阻断这个区域的交互。
- 整页加载更适合使用骨架屏或全局 Loading 方案,不要在 Loading 外层套整个页面 div。
## 演示 ## 演示
下面的示例分别演示:
- `basic`:默认 `spinning``true`,搭配 `tip` 显示加载文案。
- `no-tip`:不传 `tip` 时只显示一个旋转圆环。
- `toggle`:通过 `spinning` 切换遮罩开关,关闭后子元素恢复可点击。
<!-- DEMO --> <!-- DEMO -->
<demo vue="loading/demos/basic.vue" /> <demo vue="loading/demos/basic.vue" />
<demo vue="loading/demos/no-tip.vue" />
<demo vue="loading/demos/toggle.vue" />
<!-- DEMO --> <!-- DEMO -->
...@@ -14,7 +28,17 @@ TODO: 补充局部加载、加载文案、包裹内容和关闭加载态示例 ...@@ -14,7 +28,17 @@ TODO: 补充局部加载、加载文案、包裹内容和关闭加载态示例
<!-- API --> <!-- API -->
TODO: 对照 `src/loading/src/interface.ts` 补充 Props 和 Slots 表格。 ### Loading Props
<!-- API --> | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| spinning | `boolean` | `true` | 是否显示加载遮罩 |
| tip | `string` | `undefined` | 加载提示文案,留空则不渲染文案行 |
### Loading Slots
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| default | `()` | 被加载遮罩覆盖的内容 |
<!-- API -->
<template>
<ZLoading>
<div
style="height: 96px; border: 1px solid #e5e6eb; border-radius: 8px;"
/>
</ZLoading>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const spinning = ref(true)
</script>
<template>
<div style="display: flex; flex-direction: column; gap: 12px;">
<ZButton @click="spinning = !spinning">
{{ spinning ? '关闭加载' : '开启加载' }}
</ZButton>
<ZLoading :spinning="spinning" tip="数据加载中">
<div
style="height: 96px; padding: 16px; border: 1px solid #e5e6eb; border-radius: 8px;"
>
被包裹的内容
</div>
</ZLoading>
</div>
</template>
import { mount } from '@vue/test-utils'
import { ZLoading } from '../index'
describe('loading', () => {
it('shows the mask and spinner when spinning (default)', () => {
const wrapper = mount(ZLoading, {
slots: { default: '<p>内容</p>' },
})
expect(wrapper.findAll('div')).toHaveLength(2)
expect(wrapper.findAll('span').length).toBeGreaterThanOrEqual(1)
expect(wrapper.text()).toContain('内容')
})
it('hides the mask when spinning is false', () => {
const wrapper = mount(ZLoading, {
props: { spinning: false },
slots: { default: '<p>内容</p>' },
})
expect(wrapper.findAll('div')).toHaveLength(1)
expect(wrapper.findAll('span')).toHaveLength(0)
expect(wrapper.text()).toContain('内容')
})
it('renders the tip text only when provided', () => {
const withTip = mount(ZLoading, {
props: { tip: '加载中…' },
})
const withoutTip = mount(ZLoading)
expect(withTip.text()).toContain('加载中…')
expect(withoutTip.text()).not.toContain('加载中')
})
it('does not render tip when not spinning', () => {
const wrapper = mount(ZLoading, {
props: { spinning: false, tip: '加载中…' },
})
expect(wrapper.text()).not.toContain('加载中')
})
})
<template>
<div style="display: flex; flex-direction: column; gap: 16px;">
<ZProgress :percent="-10" />
<ZProgress :percent="0" />
<ZProgress :percent="50" />
<ZProgress :percent="100" color="#2f9e44" />
<ZProgress :percent="200" color="#d92d20" />
</div>
</template>
<template>
<div style="display: flex; flex-direction: column; gap: 16px;">
<ZProgress :percent="40" color="#1677ff" />
<ZProgress :percent="65" color="#2f9e44" />
<ZProgress :percent="80" color="#f59f00" />
<ZProgress :percent="95" color="#d92d20" />
</div>
</template>
# 进度条 Progress # 进度条 Progress
TODO: 补充基础进度、自定义颜色、隐藏文字和边界值示例。 Progress 用于直观展示一项任务的完成度,例如上传、下载、表单填写进度等。
## 何时使用
- 任务有明确的完成度(百分比)时使用,例如上传文件大小、表单步骤完成度。
- 不知道具体进度的耗时操作请使用 Loading,而不是固定百分比的 Progress。
## 演示 ## 演示
下面的示例分别演示:
- `basic`:传入 `percent` 即可,默认显示百分比文字。
- `boundary`:组件会把 `percent` 自动截取到 `0~100`,避免越界。
- `color`:通过 `color` 自定义进度条颜色。
- `no-text``showText``false` 时只显示进度条。
<!-- DEMO --> <!-- DEMO -->
<demo vue="progress/demos/basic.vue" /> <demo vue="progress/demos/basic.vue" />
<demo vue="progress/demos/boundary.vue" />
<demo vue="progress/demos/color.vue" />
<demo vue="progress/demos/no-text.vue" />
<!-- DEMO --> <!-- DEMO -->
...@@ -14,7 +29,12 @@ TODO: 补充基础进度、自定义颜色、隐藏文字和边界值示例。 ...@@ -14,7 +29,12 @@ TODO: 补充基础进度、自定义颜色、隐藏文字和边界值示例。
<!-- API --> <!-- API -->
TODO: 对照 `src/progress/src/interface.ts` 补充 Props 表格。 ### Progress Props
<!-- API --> | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| percent | `number` | `0` | 进度百分比,组件内部会自动截取到 `0~100` |
| color | `string` | `'#1677ff'` | 进度条颜色 |
| showText | `boolean` | `true` | 是否在右侧显示百分比文字 |
<!-- API -->
<template>
<div style="display: flex; flex-direction: column; gap: 16px;">
<ZProgress :percent="30" :show-text="false" />
<ZProgress :percent="70" :show-text="false" color="#2f9e44" />
</div>
</template>
import { mount } from '@vue/test-utils'
import { ZProgress } from '../index'
describe('progress', () => {
it('exposes the progressbar role with aria attributes', () => {
const wrapper = mount(ZProgress, {
props: { percent: 42 },
})
expect(wrapper.attributes('role')).toBe('progressbar')
expect(wrapper.attributes('aria-valuenow')).toBe('42')
expect(wrapper.attributes('aria-valuemin')).toBe('0')
expect(wrapper.attributes('aria-valuemax')).toBe('100')
})
it('shows the percentage text by default', () => {
const wrapper = mount(ZProgress, {
props: { percent: 30 },
})
expect(wrapper.text()).toContain('30%')
})
it('hides the text when showText is false', () => {
const wrapper = mount(ZProgress, {
props: { percent: 30, showText: false },
})
expect(wrapper.text()).not.toContain('%')
})
it('clamps percent below 0 to 0', () => {
const wrapper = mount(ZProgress, {
props: { percent: -10 },
})
expect(wrapper.attributes('aria-valuenow')).toBe('0')
expect(wrapper.text()).toContain('0%')
})
it('clamps percent above 100 to 100', () => {
const wrapper = mount(ZProgress, {
props: { percent: 200 },
})
expect(wrapper.attributes('aria-valuenow')).toBe('100')
expect(wrapper.text()).toContain('100%')
})
it('reflects the clamped percent on the bar width style', () => {
const wrapper = mount(ZProgress, {
props: { percent: 60, color: 'rgb(0, 128, 0)' },
})
const bar = wrapper.findAll('div').at(-1)!
const style = bar.attributes('style') ?? ''
expect(style).toContain('width: 60%')
expect(style).toContain('background-color: rgb(0, 128, 0)')
})
it('supports the default percent of 0', () => {
const wrapper = mount(ZProgress)
expect(wrapper.attributes('aria-valuenow')).toBe('0')
expect(wrapper.text()).toContain('0%')
})
})
<template>
<ZSectionTitle title="用户列表" description="包含本部门所有用户的基本信息。">
<template #actions>
<div style="display: flex; gap: 8px;">
<ZButton>导出</ZButton>
<ZButton type="primary">
新增用户
</ZButton>
</div>
</template>
</ZSectionTitle>
</template>
<template>
<ZSectionTitle title="本周数据">
<template #description>
<span>
统计周期为 5 月 1 日至 5 月 7 日,
<a href="#" style="color: #1677ff;">查看历史报表</a>
</span>
</template>
</ZSectionTitle>
</template>
# 区块标题 SectionTitle # 区块标题 SectionTitle
TODO: 补充基础标题、描述文案、右侧操作和插槽自定义示例。 SectionTitle 用于在页面中分隔不同区块,提供统一的标题、描述与右侧操作位。
## 何时使用
- 一个页面内有多个并列模块,每个模块需要标题和说明。
- 标题旁需要放刷新、筛选、新增等次级操作。
- 仅有一行小标题时,不要使用 SectionTitle,直接用 `<h3>` 即可,避免视觉冗余。
## 演示 ## 演示
下面的示例分别演示:
- `actions``actions` 插槽用于在标题右侧放刷新、新增等按钮。
- `basic`:带标题和描述的区块标题。
- `description-slot``description` 插槽用于放含链接、标签或多行文字的描述。
<!-- DEMO --> <!-- DEMO -->
<demo vue="section-title/demos/actions.vue" />
<demo vue="section-title/demos/basic.vue" /> <demo vue="section-title/demos/basic.vue" />
<demo vue="section-title/demos/description-slot.vue" />
<!-- DEMO --> <!-- DEMO -->
...@@ -14,7 +28,19 @@ TODO: 补充基础标题、描述文案、右侧操作和插槽自定义示例 ...@@ -14,7 +28,19 @@ TODO: 补充基础标题、描述文案、右侧操作和插槽自定义示例
<!-- API --> <!-- API -->
TODO: 对照 `src/section-title/src/interface.ts` 补充 Props 和 Slots 表格。 ### SectionTitle Props
<!-- API --> | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| title | `string` | `''` | 标题文案;可被默认插槽覆盖 |
| description | `string` | `undefined` | 描述文案;可被 `description` 插槽覆盖;不填则不渲染描述行 |
### SectionTitle Slots
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| default | `()` | 标题区域,优先级高于 `title` 属性 |
| description | `()` | 描述区域,优先级高于 `description` 属性 |
| actions | `()` | 标题右侧的操作区域 |
<!-- API -->
import { mount } from '@vue/test-utils'
import { ZSectionTitle } from '../index'
describe('sectionTitle', () => {
it('renders the title prop inside an <h2>', () => {
const wrapper = mount(ZSectionTitle, {
props: { title: '基础信息' },
})
expect(wrapper.find('h2').exists()).toBe(true)
expect(wrapper.find('h2').text()).toContain('基础信息')
})
it('prefers the default slot over the title prop', () => {
const wrapper = mount(ZSectionTitle, {
props: { title: '默认标题' },
slots: { default: '<span class="custom">自定义标题</span>' },
})
expect(wrapper.find('.custom').exists()).toBe(true)
expect(wrapper.find('h2').text()).not.toContain('默认标题')
})
it('renders the description prop in a paragraph', () => {
const wrapper = mount(ZSectionTitle, {
props: {
title: '基础信息',
description: '请如实填写',
},
})
expect(wrapper.find('p').exists()).toBe(true)
expect(wrapper.find('p').text()).toBe('请如实填写')
})
it('prefers the description slot over the description prop', () => {
const wrapper = mount(ZSectionTitle, {
props: {
title: '基础信息',
description: '默认描述',
},
slots: {
description: '<em class="custom">自定义描述</em>',
},
})
expect(wrapper.find('p .custom').exists()).toBe(true)
expect(wrapper.find('p').text()).not.toContain('默认描述')
})
it('omits the description paragraph when neither prop nor slot is provided', () => {
const wrapper = mount(ZSectionTitle, {
props: { title: '基础信息' },
})
expect(wrapper.find('p').exists()).toBe(false)
})
it('renders the actions slot beside the title', () => {
const wrapper = mount(ZSectionTitle, {
props: { title: '基础信息' },
slots: { actions: '<button class="action">编辑</button>' },
})
expect(wrapper.find('button.action').exists()).toBe(true)
})
})
<template>
<ZStatCard title="本月新增订单" :value="328" unit="笔">
<template #extra>
<ZTag color="#e8f3ff" text-color="#1677ff">
实时
</ZTag>
</template>
</ZStatCard>
</template>
# 统计卡片 StatCard # 统计卡片 StatCard
TODO: 补充基础数据卡片、单位、趋势文案、extra 插槽和 footer 插槽示例。 StatCard 用于在仪表盘里突出展示一个核心指标,配合单位、趋势和补充信息使用。
## 何时使用
- 数据看板里需要把核心 KPI 单独以大数字呈现。
- 配合 `trend` 与 footer 插槽展示同比/环比变化。
- 不要在普通信息卡片里强行使用 StatCard,它的视觉重量比较重。
## 演示 ## 演示
下面的示例分别演示:
- `basic`:基础数值展示,搭配单位。
- `extra``extra` 插槽用于放置标签、刷新按钮等次级元素。
- `trend``trend` 控制 footer 的语义颜色(`up` / `down` / `neutral`)。
<!-- DEMO --> <!-- DEMO -->
<demo vue="stat-card/demos/basic.vue" /> <demo vue="stat-card/demos/basic.vue" />
<demo vue="stat-card/demos/extra.vue" />
<demo vue="stat-card/demos/trend.vue" />
<!-- DEMO --> <!-- DEMO -->
...@@ -14,7 +28,20 @@ TODO: 补充基础数据卡片、单位、趋势文案、extra 插槽和 footer ...@@ -14,7 +28,20 @@ TODO: 补充基础数据卡片、单位、趋势文案、extra 插槽和 footer
<!-- API --> <!-- API -->
TODO: 对照 `src/stat-card/src/interface.ts` 补充 Props 和 Slots 表格。 ### StatCard Props
<!-- API --> | 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| title | `string` | `''` | 卡片标题,通常是指标名 |
| value | `string \| number` | `''` | 主数值,支持字符串或数字 |
| unit | `string` | `undefined` | 数值后的单位;不传则不渲染单位元素 |
| trend | `'up' \| 'down' \| 'neutral'` | `'neutral'` | footer 的趋势语义色 |
### StatCard Slots
| 名称 | 参数 | 说明 |
| --- | --- | --- |
| extra | `()` | 标题右侧的次级信息或操作 |
| footer | `()` | 卡片底部的趋势/补充说明;不传则不渲染 footer |
<!-- API -->
<template>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<ZStatCard title="今日访问量" :value="1280" unit="次" trend="up">
<template #footer>
<span>较昨日 +12.5%</span>
</template>
</ZStatCard>
<ZStatCard title="跳出率" :value="42" unit="%" trend="down">
<template #footer>
<span>较昨日 -3.2%</span>
</template>
</ZStatCard>
<ZStatCard title="活跃成员" :value="128" trend="neutral">
<template #footer>
<span>与昨日持平</span>
</template>
</ZStatCard>
</div>
</template>
import { mount } from '@vue/test-utils'
import { ZStatCard } from '../index'
describe('statCard', () => {
it('renders title and value', () => {
const wrapper = mount(ZStatCard, {
props: { title: '今日订单', value: 128 },
})
expect(wrapper.text()).toContain('今日订单')
expect(wrapper.text()).toContain('128')
})
it('supports a string value', () => {
const wrapper = mount(ZStatCard, {
props: { title: '状态', value: '良好' },
})
expect(wrapper.text()).toContain('良好')
})
it('renders the unit when provided', () => {
const wrapper = mount(ZStatCard, {
props: { title: '今日销售额', value: 1280, unit: '' },
})
expect(wrapper.text()).toContain('1280')
expect(wrapper.text()).toContain('')
})
it('omits the unit element when unit is not provided', () => {
const wrapper = mount(ZStatCard, {
props: { title: '今日订单', value: 128 },
})
expect(wrapper.text()).toContain('128')
expect(wrapper.text()).not.toContain('')
})
it('renders the extra slot in the header', () => {
const wrapper = mount(ZStatCard, {
props: { title: '今日订单', value: 128 },
slots: { extra: '<span class="badge">实时</span>' },
})
expect(wrapper.find('.badge').exists()).toBe(true)
})
it('renders the footer slot only when provided', () => {
const without = mount(ZStatCard, {
props: { title: 't', value: 0 },
})
const withFooter = mount(ZStatCard, {
props: { title: 't', value: 0 },
slots: { footer: '<span class="trend">+10%</span>' },
})
expect(without.find('.trend').exists()).toBe(false)
expect(withFooter.find('.trend').exists()).toBe(true)
})
it('mounts every supported trend variant', () => {
for (const trend of ['up', 'down', 'neutral'] as const) {
const wrapper = mount(ZStatCard, {
props: { title: 't', value: 1, trend },
slots: { footer: trend },
})
expect(wrapper.text()).toContain(trend)
}
})
})
支持 Markdown
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册