-
Notifications
You must be signed in to change notification settings - Fork 5
feat(tabs): 实现 Tabs 标签页组件 #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
总结Walkthrough该 PR 添加了一个完整的 Tabs 制表符导航组件系统,包括核心 Vue 组件实现(SkTabs 和 SkTab)、相关类型定义、样式配置、八个示例演示页面和更新的文档说明。 Changes
Sequence Diagram(s)sequenceDiagram
participant User as 用户交互
participant SkTabs as SkTabs 容器
participant SkTab as SkTab 项
participant Context as 注入上下文
User->>SkTabs: 点击制表符
SkTabs->>SkTabs: setActiveIndex(index)
SkTabs->>SkTabs: 发送 'change' 事件
SkTabs->>SkTabs: 更新指示线位置/动画
SkTabs->>Context: 提供 activeIndex 更新
Context->>SkTab: 更新所有子项的 isActive
SkTab->>SkTab: 基于 isActive 条件渲染内容
SkTabs->>User: 显示选中制表符内容
Note over SkTabs,SkTab: 若启用可滚动模式
SkTabs->>SkTabs: 计算 scrollLeft 以居中活跃制表符
SkTabs->>SkTabs: 平滑滚动到目标位置
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 本变更涉及多个相关的新文件和更新,但变更具有一致的模式性。核心逻辑在两个主要组件(SkTabs 和 SkTab)中,需要审查注入上下文的父子通信、动画指示线计算和可滚动功能。八个示例文件遵循相似模式,但每个展示不同用例。类型定义和配置更新较为直接。变更的同质性(大多围绕 Tabs 功能)降低了整体审查复杂度。 Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Deploy Preview for skiyee-ui ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (4)
examples/uni/src/pages-navigation/tabs/events.vue (1)
7-21: 事件处理逻辑正确!
handleChange方法正确实现了日志记录和用户反馈功能,通过unshift添加新日志并限制数组长度为 5 条。逻辑清晰,功能完整。如果希望代码更简洁,可以考虑使用数组切片:
function handleChange(index: number) { const message = `切换到标签 ${index + 1}` - logs.value.unshift(message) - - // 最多保留 5 条日志 - if (logs.value.length > 5) { - logs.value.pop() - } + logs.value = [message, ...logs.value].slice(0, 5) uni.showToast({ title: message, icon: 'none', duration: 1500, }) }不过当前实现也完全可以接受。
packages/skiyee-uni-ui/src/components/sk-tabs.vue (3)
174-177: 建议改进类型安全性
TabData接口中的props字段使用了any类型,降低了类型安全性。可以改为:
interface TabData { uid: number; - props: any; + props: SkTabProps; }类似地,
boundingClientRect回调中的rect和rects参数(Line 213, 228, 231)也可以定义更精确的类型接口。
290-339: 建议增强无障碍访问性当前 Tabs 组件缺少无障碍访问性支持,包括 ARIA 属性和键盘导航功能。
建议添加:
ARIA 属性:
- 标签导航栏添加
role="tablist"- 每个标签添加
role="tab"、aria-selected、aria-controls- 面板区域添加
role="tabpanel"、aria-labelledby键盘导航:
- 左右箭头键切换标签
- Home/End 键跳转到首尾标签
- Tab 键管理焦点
参考实现:
<view role="tab" :aria-selected="activeIndex === idx" :aria-controls="`panel-${tab.uid}`" tabindex="0" @keydown="handleKeydown" >这将显著提升组件的可用性和包容性。
202-222: 建议添加查询错误处理
scrollToActiveTab函数使用选择器查询但未处理查询失败的情况(Line 213-221)。如果 DOM 元素未找到或组件在查询期间被销毁,rect可能为null或undefined。建议添加更严格的检查:
query.select('.sk-tabs-scroll-view').boundingClientRect((rect: any) => { - if (rect) { + if (rect && rect.width) { const containerWidth = rect.width const tabCenter = currentTabInfo.offsetLeft + currentTabInfo.width / 2 const scrollPosition = tabCenter - containerWidth / 2 scrollLeft.value = Math.max(0, scrollPosition) + } else { + console.warn('[SkTabs] Failed to query scroll-view dimensions') } }).exec()类似的检查也应该应用于
updateTabItemsInfo函数(Line 228-243)。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
docs/content/docs/2.components/5.navigation/01.tabs.md(2 hunks)docs/nuxt.config.ts(1 hunks)examples/uni/src/pages-navigation/tabs/animated.vue(1 hunks)examples/uni/src/pages-navigation/tabs/base.vue(1 hunks)examples/uni/src/pages-navigation/tabs/controlled.vue(1 hunks)examples/uni/src/pages-navigation/tabs/default-value.vue(1 hunks)examples/uni/src/pages-navigation/tabs/disabled.vue(1 hunks)examples/uni/src/pages-navigation/tabs/events.vue(1 hunks)examples/uni/src/pages-navigation/tabs/line.vue(1 hunks)examples/uni/src/pages-navigation/tabs/scrollable.vue(1 hunks)examples/uni/src/pages.json(1 hunks)examples/uni/types/components.d.ts(1 hunks)examples/uni/types/pages.d.ts(1 hunks)packages/skiyee-uni-ui/src/components/sk-tab.vue(1 hunks)packages/skiyee-uni-ui/src/components/sk-tabs.vue(1 hunks)packages/skiyee-uni-ui/src/constants/index.ts(1 hunks)packages/skiyee-uni-ui/src/constants/tabs.ts(1 hunks)packages/skiyee-uni-ui/src/styles/index.ts(1 hunks)packages/skiyee-uni-ui/src/styles/sk-tab.ts(0 hunks)packages/skiyee-uni-ui/src/styles/sk-tabs.ts(1 hunks)packages/skiyee-uni-ui/src/types/tabs.ts(1 hunks)
💤 Files with no reviewable changes (1)
- packages/skiyee-uni-ui/src/styles/sk-tab.ts
🧰 Additional context used
🧬 Code graph analysis (3)
packages/skiyee-uni-ui/src/styles/sk-tabs.ts (1)
packages/skiyee-uni-ui/src/styles/index.ts (2)
SkTabsUcv(58-58)SkTabsUcvProps(57-57)
packages/skiyee-uni-ui/src/types/tabs.ts (2)
packages/skiyee-uni-ui/src/types/form.ts (2)
FormContext(98-122)FormFieldState(20-96)packages/skiyee-uni-ui/src/types/roller.ts (1)
RollerGroupContext(13-18)
packages/skiyee-uni-ui/src/constants/tabs.ts (1)
packages/skiyee-uni-ui/src/types/tabs.ts (1)
TabsContext(18-49)
🪛 LanguageTool
docs/content/docs/2.components/5.navigation/01.tabs.md
[locale-violation] ~122-~122: Possible error ortogràfic. Aquesta forma verbal no es correspon amb la varietat seleccionada (central).
Context: ...---------| | scrollable | boolean | false | 是否可横向滚动 | ::CodePreview{path="/page...
(EXIGEIX_VERBS_CENTRAL)
[locale-violation] ~235-~235: Possible error ortogràfic. Aquesta forma verbal no es correspon amb la varietat seleccionada (central).
Context: ...-----------| | disabled | boolean | false | 是否禁用整个标签页 | ::CodePreview{path="/pa...
(EXIGEIX_VERBS_CENTRAL)
[style] ~284-~284: Anglicisme innecessari. Considereu fer servir una altra paraula.
Context: ...odePreview{path="/pages-navigation/tabs/events" height="300"} <!-- automd:file src=".....
(CA_SIMPLE_REPLACE_ANGLICISM_EVENTS)
[locale-violation] ~336-~336: Possible error ortogràfic. Aquesta forma verbal no es correspon amb la varietat seleccionada (central).
Context: ... scrollable | boolean | false | 详见 [Scrollable 横向滚动](#scrollable-横向...
(EXIGEIX_VERBS_CENTRAL)
[locale-violation] ~339-~339: Possible error ortogràfic. Aquesta forma verbal no es correspon amb la varietat seleccionada (central).
Context: ... disabled | boolean | false | 详见 Disabled 禁用状态 ...
(EXIGEIX_VERBS_CENTRAL)
[locale-violation] ~346-~346: Possible error ortogràfic. «Index» és correcte com a forma balear (1a personal singular, present d’indicatiu).
Context: ...--------|----------------| | change | (index: number) | 切换标签时触发 | | click | `(in...
(EXIGEIX_VERBS_CENTRAL)
[locale-violation] ~347-~347: Possible error ortogràfic. «Index» és correcte com a forma balear (1a personal singular, present d’indicatiu).
Context: ...ndex: number)| 切换标签时触发 | |click |(index: number)` | 点击标签时触发 | ### Slots | 插槽名...
(EXIGEIX_VERBS_CENTRAL)
[style] ~353-~353: Anglicisme innecessari. Considereu fer servir una altra paraula.
Context: ...-----|------------------------------| | default | 标签内容,放置 SkTab 组件 | ## 接口 (Tab) #...
(CA_SIMPLE_REPLACE_ANGLICISM_DEFAULT)
[locale-violation] ~362-~362: Possible error ortogràfic. Aquesta forma verbal no es correspon amb la varietat seleccionada (central).
Context: ...abel-标签文本) | | disabled | boolean | false | 是否禁用该标签 | #...
(EXIGEIX_VERBS_CENTRAL)
[style] ~368-~368: Anglicisme innecessari. Considereu fer servir una altra paraula.
Context: ... | |-----------|----------------| | default | 标签面板内容 | ## 交互 正在完善中,敬请期待!😊
(CA_SIMPLE_REPLACE_ANGLICISM_DEFAULT)
🔇 Additional comments (16)
examples/uni/src/pages.json (1)
714-750: LGTM!新增的
pages-navigation子包配置结构合理,与现有的子包配置保持一致。8 个标签页示例的路径定义清晰,类型声明正确。packages/skiyee-uni-ui/src/types/tabs.ts (1)
1-49: LGTM!TabsContext 接口定义完整且结构清晰,与项目中其他上下文接口(如 FormContext、RollerGroupContext)的设计模式保持一致。文档注释详尽,类型定义准确。
packages/skiyee-uni-ui/src/constants/index.ts (1)
14-14: LGTM!新增的 tabs 常量导出与现有的导出模式保持一致,正确扩展了公共 API。
examples/uni/src/pages-navigation/tabs/default-value.vue (1)
1-42: LGTM!示例代码清晰地展示了
default-value属性在非受控模式下的使用。代码结构良好,文档说明充分。examples/uni/src/pages-navigation/tabs/animated.vue (1)
1-82: LGTM!示例代码有效地对比了启用和禁用动画的效果。使用两个独立的状态变量进行对比展示是合理的设计,代码结构清晰。
examples/uni/src/pages-navigation/tabs/base.vue (1)
1-39: LGTM!基础示例简洁明了,正确展示了 v-model 双向绑定的基本用法。代码易于理解,是用户入门的良好起点。
packages/skiyee-uni-ui/src/styles/index.ts (1)
57-59: 导出格式正确!新增的 Tabs 样式导出遵循了现有的命名和组织模式,与其他组件导出保持一致。
examples/uni/src/pages-navigation/tabs/scrollable.vue (1)
1-35: 可滚动标签页示例实现良好!这个示例清晰地展示了可滚动标签页的用法,使用
v-model实现双向绑定,并正确处理了 8 个标签项的渲染和状态显示。packages/skiyee-uni-ui/src/constants/tabs.ts (1)
1-13: 注入键定义规范!使用
Symbol确保键的唯一性,并正确声明为InjectionKey<Readonly<TabsContext>>类型,符合 Vue 3 provide/inject 的最佳实践。examples/uni/src/pages-navigation/tabs/disabled.vue (1)
1-86: 禁用状态示例完整!示例很好地展示了两种禁用场景:单个标签禁用和整体禁用,清晰地演示了
disabled属性在不同层级的使用方式。examples/uni/src/pages-navigation/tabs/line.vue (1)
1-76: 指示器宽度配置示例设计精妙!通过使用不同长度的标签文本(标签1、标签二、标签三三三),能够清晰地展示自动宽度和固定宽度的视觉差异,示例设计考虑周到。
examples/uni/src/pages-navigation/tabs/controlled.vue (1)
1-69: 受控模式示例实现正确!示例清晰地展示了如何通过
v-model实现受控模式,以及如何通过编程方式(setTab方法)控制标签切换。handleChange作为占位符在示例中是合理的。packages/skiyee-uni-ui/src/styles/sk-tabs.ts (1)
21-72: 样式配置设计完善!ucv 配置涵盖了标签页组件的所有关键元素和状态,包括滚动、动画、激活和禁用等变体。使用
sk-unit、sk-element和sk-variant前缀实现了灵活的样式组合,设计合理。examples/uni/src/pages-navigation/tabs/events.vue (1)
28-82: 事件监听示例实现完整!模板部分正确绑定了
@change和@click事件,日志显示区域妥善处理了空状态和列表渲染,示例清晰地展示了如何监听和响应标签页的切换事件。packages/skiyee-uni-ui/src/components/sk-tabs.vue (2)
225-244: 验证指示器定位计算的准确性当前实现通过累加 tab 宽度来手动计算
offsetLeft(Line 230-237),而不是使用boundingClientRect返回的实际位置信息。这种方式假设所有标签是紧密排列的,不存在间距。如果标签之间存在 margin/padding,或者第一个标签距离容器左边缘有偏移,手动累加的
offsetLeft会与实际位置不符,导致指示器位置错位。建议验证以下场景下指示器位置是否正确:
- 标签之间存在 margin 或 gap
- 第一个标签设置了 padding-left
- 容器内部存在 padding
可以考虑使用相对于容器的实际位置来计算:
// 先获取容器位置 const query = uni.createSelectorQuery().in(instance) query.select('.sk-tabs-scroll-view').boundingClientRect((containerRect: any) => { query.selectAll('.sk-tab-item').boundingClientRect((rects: any) => { if (Array.isArray(rects) && containerRect) { tabItemsInfo.value = rects.map((rect: any) => ({ offsetLeft: rect.left - containerRect.left, width: rect.width, })) scrollToActiveTab() } }).exec() }).exec()这样可以获得相对于容器的准确位置,避免手动累加带来的潜在问题。
77-288: 组件整体架构设计良好!SkTabs 组件的整体实现展现了良好的架构设计:
- 正确处理受控/非受控模式(Line 115-127)
- 清晰的标签注册/注销机制(Line 246-261)
- 合理使用
computed优化性能(Line 141-152, 155-171)- 适当的响应式状态管理和上下文共享(Line 264-274)
除了上述提到的具体问题外,整体实现质量较高。
| runtimeConfig: { | ||
| public: { | ||
| demo: 'bundle', | ||
| demo: 'http://localhost:5173/#/', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
避免硬编码 localhost URL
将 demo URL 硬编码为 http://localhost:5173/#/ 会在生产环境或其他开发环境中失效。
建议使用环境变量配置:
runtimeConfig: {
public: {
- demo: 'http://localhost:5173/#/',
+ demo: process.env.DEMO_URL || 'http://localhost:5173/#/',
},
},或在项目根目录创建 .env 文件:
DEMO_URL=http://localhost:5173/#/
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| demo: 'http://localhost:5173/#/', | |
| demo: process.env.DEMO_URL || 'http://localhost:5173/#/', |
🤖 Prompt for AI Agents
In docs/nuxt.config.ts around line 9 the demo URL is hardcoded as
"http://localhost:5173/#/" which breaks in non-local environments; replace the
hardcoded string with a value read from an environment variable (e.g.
process.env.DEMO_URL or via Nuxt runtime/config) and ensure you provide a
default fallback for local dev, add instructions to create a .env file with
DEMO_URL if needed, and update project docs or README to document the new
DEMO_URL environment variable.
| SkTab: typeof import('@skiyee/uni-ui/components/sk-tab.vue')['default'] | ||
| SkTabItem: typeof import('@skiyee/uni-ui/components/sk-tab-item.vue')['default'] | ||
| SkTabs: typeof import('@skiyee/uni-ui/components/sk-tabs.vue')['default'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
验证组件移除的影响
AI 摘要提到移除了多个组件声明(SkBadge, SkCheckbox, SkDialog 等),但仅保留了标签页相关组件。由于这是自动生成的文件,请确认这些被移除的组件在代码库中确实未被使用,或者它们已迁移到其他位置。
运行以下脚本验证被移除的组件是否仍在使用:
🏁 Script executed:
#!/bin/bash
# 搜索可能仍在使用已移除组件的位置
echo "检查 SkBadge 使用情况:"
rg -nP --type=vue -C2 '<SkBadge' examples/uni/src/
echo "检查 SkCheckbox 使用情况:"
rg -nP --type=vue -C2 '<SkCheckbox' examples/uni/src/
echo "检查 SkDialog 使用情况:"
rg -nP --type=vue -C2 '<SkDialog' examples/uni/src/
echo "检查 SkForm 使用情况:"
rg -nP --type=vue -C2 '<SkForm' examples/uni/src/Length of output: 182
我来用更正确的脚本方式重新验证这些被移除组件的使用情况。<!-- [scratchpad_update]
Web search confirms that ripgrep supports .vue files via --type js (since *.vue is included in JS type) or using --glob '*.vue'. The initial script failed because --type=vue is not a defined type, but we can use glob patterns instead.
[actions_update]
Run corrected script using glob patterns to search for removed components in examples/uni/src/
[scratchpad_update_end] -->
🏁 Script executed:
#!/bin/bash
# 使用 glob 模式搜索可能仍在使用已移除组件的位置
echo "检查 SkBadge 使用情况:"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<SkBadge' examples/uni/src/ 2>/dev/null | head -20
echo -e "\n检查 SkCheckbox 使用情况:"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<SkCheckbox' examples/uni/src/ 2>/dev/null | head -20
echo -e "\n检查 SkDialog 使用情况:"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<SkDialog' examples/uni/src/ 2>/dev/null | head -20
echo -e "\n检查 SkForm 使用情况:"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<SkForm' examples/uni/src/ 2>/dev/null | head -20
echo -e "\n检查 SkTab/SkTabItem/SkTabs 使用情况 (应该存在):"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<Sk(Tab|TabItem|Tabs)' examples/uni/src/ 2>/dev/null | head -20Length of output: 7200
🏁 Script executed:
#!/bin/bash
# 检查 SkTabItem 的具体使用情况
echo "检查 SkTabItem 使用情况:"
rg -nP -g '*.vue' -g '*.ts' -g '*.js' '<SkTabItem' examples/uni/src/ 2>/dev/null | head -20
echo -e "\n检查 components.d.ts 文件内容确认:"
cat examples/uni/types/components.d.ts | head -30Length of output: 1108
修复被移除组件的类型声明
验证确认:SkBadge、SkCheckbox、SkDialog、SkForm 这四个组件在源代码中被广泛使用,但其类型声明已被从 components.d.ts 中移除。
- SkBadge:在
examples/uni/src/pages-basic/badge/下至少 20+ 处使用 - SkCheckbox:在
examples/uni/src/pages-form/checkbox/和form/下至少 15+ 处使用 - SkDialog:在
examples/uni/src/pages-feedback/dialog/下至少 3 处使用 - SkForm:在
examples/uni/src/pages-form/form/下至少 12+ 处使用
这会导致 TypeScript 类型检查失败和 IDE 代码补全丧失。应在 components.d.ts 中恢复这四个组件的类型声明:
SkBadge: typeof import('@skiyee/uni-ui/components/sk-badge.vue')['default']
SkCheckbox: typeof import('@skiyee/uni-ui/components/sk-checkbox.vue')['default']
SkDialog: typeof import('@skiyee/uni-ui/components/sk-dialog.vue')['default']
SkForm: typeof import('@skiyee/uni-ui/components/sk-form.vue')['default']
🤖 Prompt for AI Agents
In examples/uni/types/components.d.ts around lines 17 to 19, four component type
declarations (SkBadge, SkCheckbox, SkDialog, SkForm) were removed causing
TypeScript errors and lost IDE completion; restore their declarations by adding
entries mapping each to the corresponding default export in @skiyee/uni-ui
(sk-badge.vue, sk-checkbox.vue, sk-dialog.vue, sk-form.vue) following the
existing pattern used for SkTab/SkTabItem/SkTabs so the file includes all four
lines with the same typeof import(...)[ 'default' ] shape.
| function setActiveIndex(index: number) { | ||
| const tab = tabs.value[index] | ||
| if (props.disabled || tab?.props.disabled) { | ||
| return | ||
| } | ||
| if (index === activeIndex.value) { | ||
| return | ||
| } | ||
| activeIndex.value = index | ||
| emits('change', index) | ||
| emits('click', index) | ||
| nextTick(() => { | ||
| scrollToActiveTab() | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
修正 click 事件的触发逻辑
根据文档说明,click 事件应该在"点击标签时触发",而 change 事件在"切换标签时触发"。但当前实现中,如果点击的是已激活的标签(Line 188-190),会提前返回而不触发 click 事件。这导致 click 事件仅在标签切换时才触发,与文档描述不符。
建议调整事件触发顺序:
function setActiveIndex(index: number) {
const tab = tabs.value[index]
if (props.disabled || tab?.props.disabled) {
return
}
+ emits('click', index)
+
if (index === activeIndex.value) {
return
}
activeIndex.value = index
emits('change', index)
- emits('click', index)
nextTick(() => {
scrollToActiveTab()
})
}这样可以确保:
click事件在每次点击时触发(除非被禁用)change事件仅在标签实际切换时触发
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function setActiveIndex(index: number) { | |
| const tab = tabs.value[index] | |
| if (props.disabled || tab?.props.disabled) { | |
| return | |
| } | |
| if (index === activeIndex.value) { | |
| return | |
| } | |
| activeIndex.value = index | |
| emits('change', index) | |
| emits('click', index) | |
| nextTick(() => { | |
| scrollToActiveTab() | |
| }) | |
| } | |
| function setActiveIndex(index: number) { | |
| const tab = tabs.value[index] | |
| if (props.disabled || tab?.props.disabled) { | |
| return | |
| } | |
| emits('click', index) | |
| if (index === activeIndex.value) { | |
| return | |
| } | |
| activeIndex.value = index | |
| emits('change', index) | |
| nextTick(() => { | |
| scrollToActiveTab() | |
| }) | |
| } |
🤖 Prompt for AI Agents
In packages/skiyee-uni-ui/src/components/sk-tabs.vue around lines 182-199, the
function currently returns early for clicks on the already-active tab and
therefore never emits the 'click' event as documented; change the logic so that
after the disabled checks you always emit 'click' for the clicked tab, then
check if the index equals activeIndex and only return if it does (so 'change' is
only emitted when the active index actually changes). If the index differs, set
activeIndex, emit 'change', and run nextTick(() => scrollToActiveTab()) as
before.
#23
Summary by CodeRabbit
发布说明
新功能
文档