鸿蒙原生 ArkTS 布局深度解析:Tabs + vertical 垂直标签侧边栏实战(HarmonyOS NEXT / API 24)
项目演示




一、前言
在移动应用和桌面应用的用户界面设计中,标签导航(Tabs)是最基础也最核心的交互模式之一。随着多设备协同和大屏体验的日益重要,传统的水平标签导航已经无法满足所有场景的需求。垂直标签侧边栏布局作为一种更适合大屏展示的导航模式,逐渐成为鸿蒙应用开发中的重要布局方式。
HarmonyOS NEXT(API 24)的 ArkUI 框架提供了原生 Tabs 组件,通过 vertical 属性能够以极低的代码量实现垂直标签侧边栏布局。与传统的自定义布局方案不同,鸿蒙的 Tabs 组件从底层就支持垂直排列、手势滑动切换、动画过渡、以及标签与内容的联动效果,而且无需引入任何第三方依赖。
本文将以一个完整的企业级应用后台管理系统为例,深入剖析如何在 HarmonyOS NEXT 中使用 ArkTS 语言实现垂直标签侧边栏布局。文章将覆盖基础概念、属性详解、完整代码示例、API 24 新增特性、自定义样式、常见问题及解决方案、性能优化建议等方方面面,力求让读者对鸿蒙原生垂直标签侧边栏布局建立系统的理解。
二、Tabs 组件基础概念
2.1 Tabs 组件概述
Tabs 是鸿蒙 ArkUI 框架中的核心导航组件,用于在一个页面内快速实现视图内容的切换。它由两个主要部分组成:
- TabContent:内容页,用于展示当前选中标签对应的内容
- TabBar:导航页签栏,用于显示标签名称和图标
根据不同的导航类型,布局会有区别:
| 导航类型 | TabBar 位置 | 适用场景 |
|---|---|---|
| 底部导航 | 底部 | 移动端应用主界面 |
| 顶部导航 | 顶部 | 内容分类切换 |
| 侧边导航 | 左侧或右侧 | 桌面端应用、后台管理系统、横屏界面 |
2.2 Tabs 组件的接口定义
在 API 24 中,Tabs 组件的接口定义如下:
Tabs(value?: {
barPosition?: BarPosition,
index?: number,
controller?: TabsController
})
参数说明:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| barPosition | BarPosition | 否 | BarPosition.Start | 设置标签栏位置 |
| index | number | 否 | 0 | 设置当前显示的标签页索引 |
| controller | TabsController | 否 | - | 设置 Tabs 控制器,用于程序化控制标签切换 |
2.3 BarPosition 枚举说明
BarPosition 枚举用于控制标签栏的位置,其行为受 vertical 属性影响:
| 枚举值 | vertical = false(水平模式) | vertical = true(垂直模式) |
|---|---|---|
| Start | 标签栏位于顶部 | 标签栏位于左侧 |
| End | 标签栏位于底部 | 标签栏位于右侧 |
2.4 TabContent 组件
TabContent 是 Tabs 的子组件,每个 TabContent 对应一个标签页:
TabContent() {
// 内容区域
}
.tabBar('标签名称')
TabContent 不支持直接设置宽度和高度属性,其尺寸由父组件 Tabs 自动计算。
三、vertical 属性详解
3.1 vertical 属性的作用
vertical 属性是实现垂直标签侧边栏布局的核心属性:
Tabs({ barPosition: BarPosition.Start }) {
// TabContent 子组件
}
.vertical(true)
| 值 | 效果 |
|---|---|
| false(默认) | 标签栏水平排列,内容区垂直排列 |
| true | 标签栏垂直排列(侧边栏),内容区水平排列 |
3.2 vertical 属性对布局的影响
当 vertical 设置为 true 时,布局发生以下变化:
- 标签栏方向:从水平排列变为垂直排列
- 内容区方向:从垂直堆叠变为水平排列
- 滑动方向:从水平滑动变为垂直滑动
- barWidth/barHeight:barWidth 控制侧边栏宽度,barHeight 控制内容区高度
3.3 vertical 与 barPosition 的组合效果
// 左侧侧边栏
Tabs({ barPosition: BarPosition.Start }) { ... }.vertical(true)
// 右侧侧边栏
Tabs({ barPosition: BarPosition.End }) { ... }.vertical(true)
四、barPosition 属性详解
4.1 barPosition 的两种取值
enum BarPosition {
Start, // 0
End // 1
}
4.2 barPosition 在不同模式下的表现
| barPosition | vertical = false | vertical = true |
|---|---|---|
| Start | 标签栏在顶部 | 标签栏在左侧 |
| End | 标签栏在底部 | 标签栏在右侧 |
4.3 构造参数 vs 属性方法
barPosition 既可以作为构造参数传入,也可以作为属性方法调用:
// 方式一:构造参数
Tabs({ barPosition: BarPosition.Start }) { ... }
// 方式二:属性方法
Tabs() { ... }.barPosition(BarPosition.Start)
两种方式效果相同,但推荐使用构造参数方式,代码更清晰。
五、垂直标签侧边栏布局实战
5.1 基础实现
下面是一个完整的垂直标签侧边栏布局示例:
interface TabItem {
title: string;
icon: string;
bgColor: string;
content: string;
}
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
tabs: TabItem[] = [
{ title: '首页', icon: '🏠', bgColor: '#f5f5f5', content: '首页内容区域' },
{ title: '分类', icon: '📚', bgColor: '#fff8e1', content: '分类浏览区域' },
{ title: '消息', icon: '💬', bgColor: '#e3f2fd', content: '消息通知区域' },
{ title: '我的', icon: '👤', bgColor: '#fce4ec', content: '个人中心区域' }
];
build() {
Tabs({ barPosition: BarPosition.Start, index: this.currentIndex }) {
ForEach(this.tabs, (item: TabItem) => {
TabContent() {
Column() {
Text(item.content)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
Text(`当前标签:${item.title}`)
.fontSize(20)
.fontColor('#666666')
.margin({ top: 20 })
Text(item.icon)
.fontSize(60)
.margin({ top: 40 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor(item.bgColor)
}
.tabBar(item.title)
}, (item: TabItem) => item.title)
}
.vertical(true)
.barWidth('22%')
.height('100%')
.width('100%')
.backgroundColor('#ffffff')
.onChange((index: number) => {
this.currentIndex = index;
})
}
}
5.2 代码解析
5.2.1 数据结构设计
interface TabItem {
title: string; // 标签标题
icon: string; // 标签图标
bgColor: string; // 内容区背景色
content: string; // 内容区描述
}
使用接口定义数据结构是 ArkTS 的最佳实践,可以避免隐式类型转换导致的编译错误。
5.2.2 状态管理
@State currentIndex: number = 0;
@State 装饰器使状态变化能够触发 UI 刷新,实现标签切换时的视图更新。
5.2.3 Tabs 构造参数
Tabs({ barPosition: BarPosition.Start, index: this.currentIndex })
barPosition: BarPosition.Start:设置标签栏在左侧(垂直模式下)index: this.currentIndex:绑定当前选中的标签页索引
5.2.4 ForEach 动态渲染
ForEach(this.tabs, (item: TabItem) => {
// TabContent 组件
}, (item: TabItem) => item.title)
- 第一个参数:数据源数组
- 第二个参数:生成组件的回调函数
- 第三个参数:key 生成函数,用于优化渲染性能
5.2.5 关键属性链
.vertical(true) // 垂直模式
.barWidth('22%') // 侧边栏宽度
.height('100%') // 高度充满父容器
.width('100%') // 宽度充满父容器
.backgroundColor('#ffffff') // 背景色
.onChange((index: number) => {
this.currentIndex = index;
})
5.3 运行效果
该示例运行后将展示:
- 左侧 22% 宽度的垂直标签侧边栏
- 四个标签:首页、分类、消息、我的
- 右侧内容区根据选中标签显示不同背景色和内容
- 点击标签可切换内容区
六、API 24 新增特性
6.1 nestedScroll 属性(API 24+)
nestedScroll 属性用于控制 Tabs 组件与内部可滚动组件的嵌套滚动行为:
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
List() {
// List 内容
}
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
.tabBar('标签')
}
.vertical(true)
NestedScrollMode 枚举说明:
| 枚举值 | 说明 |
|---|---|
| SELF_FIRST | 自身优先滚动 |
| PARENT_FIRST | 父容器优先滚动 |
| SELF_ONLY | 仅自身滚动 |
| PARENT_ONLY | 仅父容器滚动 |
6.2 cachedMaxCount 属性(API 19+)
cachedMaxCount 用于控制预加载的标签页数量:
Tabs() { ... }
.vertical(true)
.cachedMaxCount(3) // 预加载前后各3个标签页
6.3 barFloatingStyle 属性
barFloatingStyle 用于设置浮动标签栏样式:
Tabs() { ... }
.vertical(true)
.barFloatingStyle({
width: FloatingTabBarWidth.MATCH_PARENT,
material: UIMaterial.OPAQUE
})
6.4 animationCurve 属性(API 20+)
animationCurve 用于设置切换动画曲线:
Tabs() { ... }
.vertical(true)
.animationCurve(Curve.EaseInOut)
.animationDuration(400)
七、自定义 TabBar
7.1 使用字符串 TabBar
最简单的 TabBar 方式是直接使用字符串:
TabContent() { ... }
.tabBar('首页')
7.2 使用自定义组件 TabBar
可以使用 @Builder 装饰器定义自定义 TabBar:
@Entry
@Component
struct Index {
@State currentIndex: number = 0;
tabTitles: string[] = ['首页', '分类', '消息', '我的'];
tabIcons: string[] = ['🏠', '📚', '💬', '👤'];
build() {
Tabs({ barPosition: BarPosition.Start, index: this.currentIndex }) {
ForEach(this.tabTitles, (title: string, index: number) => {
TabContent() {
Column() {
Text(`当前页面:${title}`)
.fontSize(28)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
.tabBar(this.TabBarBuilder(title, this.tabIcons[index], index))
}, (title: string) => title)
}
.vertical(true)
.barWidth('22%')
.height('100%')
.width('100%')
.onChange((index: number) => {
this.currentIndex = index;
})
}
@Builder
TabBarBuilder(title: string, icon: string, index: number) {
Column() {
Text(icon)
.fontSize(32)
Text(title)
.fontSize(18)
.fontColor(index === this.currentIndex ? '#007DFF' : '#666666')
.margin({ top: 8 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor(index === this.currentIndex ? '#e8f4fd' : '#ffffff')
}
}
7.3 使用 TabBarOptions(API 18+)
API 18 及以上版本支持 TabBarOptions 对象:
TabContent() { ... }
.tabBar({
builder: this.TabBarBuilder('首页', '🏠', 0),
selectedMode: SelectedMode.Default
})
7.4 自定义选中状态样式
@Builder
TabBarBuilder(title: string, icon: string, index: number) {
Column() {
Text(icon)
.fontSize(32)
.fontColor(index === this.currentIndex ? '#007DFF' : '#999999')
Text(title)
.fontSize(18)
.fontColor(index === this.currentIndex ? '#007DFF' : '#666666')
.margin({ top: 8 })
// 选中指示器
if (index === this.currentIndex) {
Column()
.width(4)
.height(32)
.backgroundColor('#007DFF')
.borderRadius({ right: 2 })
.margin({ right: 8 })
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(FlexAlign.Center)
}
八、高级特性
8.1 TabsController 控制器
TabsController 用于程序化控制标签切换:
@Entry
@Component
struct Index {
private tabsController: TabsController = new TabsController();
@State currentIndex: number = 0;
build() {
Column() {
Button('切换到第二个标签')
.onClick(() => {
this.tabsController.changeIndex(1);
})
Tabs({ barPosition: BarPosition.Start, controller: this.tabsController }) {
TabContent() {
Text('页面1')
}
.tabBar('标签1')
TabContent() {
Text('页面2')
}
.tabBar('标签2')
}
.vertical(true)
.height('100%')
}
.height('100%')
}
}
TabsController 方法:
| 方法 | 说明 |
|---|---|
| changeIndex(index: number) | 切换到指定索引的标签页 |
| animateToIndex(index: number, duration?: number) | 带动画切换到指定索引的标签页 |
8.2 监听标签切换事件
Tabs() { ... }
.vertical(true)
.onChange((index: number) => {
console.info(`切换到标签页:${index}`);
})
.onTabBarClick((index: number) => {
console.info(`点击了标签:${index}`);
})
.onAnimationStart((index: number) => {
console.info(`动画开始,目标标签:${index}`);
})
.onAnimationEnd((index: number) => {
console.info(`动画结束,当前标签:${index}`);
})
8.3 禁用滑动切换
Tabs() { ... }
.vertical(true)
.scrollable(false) // 禁用滑动切换,只能通过点击标签切换
8.4 设置分割线
Tabs() { ... }
.vertical(true)
.divider({
strokeWidth: 1,
color: '#eeeeee',
startMargin: 20,
endMargin: 20
})
8.5 设置标签栏背景色
Tabs() { ... }
.vertical(true)
.barBackgroundColor('#f8f8f8')
九、企业级实战案例
9.1 场景描述
假设我们要开发一个企业后台管理系统,需要实现以下功能:
- 左侧垂直侧边栏导航
- 多个功能模块切换
- 嵌套滚动列表
- 自定义标签样式
- 响应式布局适配
9.2 完整实现
interface MenuItem {
id: number;
title: string;
icon: string;
badge?: number;
}
interface PageConfig {
title: string;
backgroundColor: string;
items: string[];
}
@Entry
@Component
struct AdminDashboard {
@State currentIndex: number = 0;
private tabsController: TabsController = new TabsController();
menuItems: MenuItem[] = [
{ id: 1, title: '仪表盘', icon: '📊' },
{ id: 2, title: '用户管理', icon: '👥', badge: 12 },
{ id: 3, title: '订单管理', icon: '📋', badge: 5 },
{ id: 4, title: '产品管理', icon: '📦' },
{ id: 5, title: '数据分析', icon: '📈' },
{ id: 6, title: '系统设置', icon: '⚙️' }
];
pageConfigs: PageConfig[] = [
{
title: '仪表盘',
backgroundColor: '#f5f5f5',
items: ['今日访问量:12,345', '活跃用户:2,341', '订单数:456', '转化率:3.2%']
},
{
title: '用户管理',
backgroundColor: '#fff8e1',
items: ['张三 - 普通用户', '李四 - VIP用户', '王五 - 管理员', '赵六 - 普通用户', '钱七 - VIP用户']
},
{
title: '订单管理',
backgroundColor: '#e3f2fd',
items: ['订单#001 - 待付款', '订单#002 - 已发货', '订单#003 - 待收货', '订单#004 - 已完成']
},
{
title: '产品管理',
backgroundColor: '#fce4ec',
items: ['产品A - 库存120', '产品B - 库存85', '产品C - 库存200', '产品D - 库存15']
},
{
title: '数据分析',
backgroundColor: '#e8f5e9',
items: ['日环比:+12.5%', '周环比:+8.3%', '月环比:+15.2%', '年度趋势:上升']
},
{
title: '系统设置',
backgroundColor: '#f3e5f5',
items: ['账户设置', '通知设置', '权限管理', '数据备份', '关于系统']
}
];
build() {
Tabs({
barPosition: BarPosition.Start,
index: this.currentIndex,
controller: this.tabsController
}) {
ForEach(this.menuItems, (menu: MenuItem, index: number) => {
TabContent() {
this.PageContent(index)
}
.tabBar(this.CustomTabBar(menu, index))
}, (menu: MenuItem) => menu.id.toString())
}
.vertical(true)
.barWidth(200)
.height('100%')
.width('100%')
.backgroundColor('#ffffff')
.onChange((index: number) => {
this.currentIndex = index;
})
}
@Builder
PageContent(index: number) {
Column() {
Text(this.pageConfigs[index].title)
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.margin({ top: 20, left: 20 })
List({ space: 10 }) {
ForEach(this.pageConfigs[index].items, (item: string) => {
ListItem() {
Text(item)
.fontSize(20)
.fontColor('#666666')
.padding(16)
.backgroundColor('#ffffff')
.borderRadius(8)
.width('100%')
}
}, (item: string) => item)
}
.width('90%')
.height('70%')
.margin({ top: 30 })
.nestedScroll({
scrollForward: NestedScrollMode.SELF_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
})
}
.width('100%')
.height('100%')
.backgroundColor(this.pageConfigs[index].backgroundColor)
}
@Builder
CustomTabBar(menu: MenuItem, index: number) {
Column({ space: 8 }) {
Row({ space: 12 }) {
Text(menu.icon)
.fontSize(28)
.fontColor(index === this.currentIndex ? '#007DFF' : '#999999')
Column() {
Text(menu.title)
.fontSize(18)
.fontColor(index === this.currentIndex ? '#007DFF' : '#666666')
if (menu.badge !== undefined && menu.badge > 0) {
Text(menu.badge.toString())
.fontSize(12)
.fontColor('#ffffff')
.backgroundColor('#FF4D4F')
.width(20)
.height(20)
.borderRadius(10)
.textAlign(TextAlign.Center)
.margin({ top: 4 })
}
}
}
if (index === this.currentIndex) {
Column()
.width(4)
.height(24)
.backgroundColor('#007DFF')
.borderRadius({ right: 2 })
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.alignItems(FlexAlign.Center)
.backgroundColor(index === this.currentIndex ? '#F0F7FF' : '#ffffff')
}
}
9.3 案例解析
该企业级后台管理系统示例包含以下特性:
- 数据结构设计:使用
MenuItem和PageConfig两个接口定义侧边栏菜单和页面配置 - Badge 标记:支持消息数量角标显示
- 嵌套滚动:使用
nestedScroll属性实现列表滚动 - 自定义 TabBar:包含图标、文字、选中指示器和 Badge
- 响应式布局:固定侧边栏宽度为 200vp,内容区自适应
十、常见问题及解决方案
10.1 编译错误:Cannot find module ‘@ohos.arkui’
问题描述:
Error Message: Cannot find module '@ohos.arkui' or its corresponding type declarations.
解决方案:
ArkTS 中基础组件(如 Column、Row、Text、Tabs)都是全局可用的,不需要手动导入。删除错误的导入语句即可:
// 错误:
import { Column, Row, Text, Tabs } from '@ohos.arkui';
// 正确:直接使用,无需导入
10.2 接口声明位置错误
问题描述:
Error: Interface declarations can only appear at the top level.
解决方案:
在 ArkTS 中,接口必须声明在 struct 外部:
// 错误:
@Component
struct Index {
interface TabItem { ... } // 编译错误
}
// 正确:
interface TabItem { ... }
@Component
struct Index { ... }
10.3 Tabs 属性链语法错误
问题描述:
属性链放在了内容块内部导致编译错误
解决方案:
属性链必须放在内容块之后:
// 错误:
Tabs() {
TabContent() { ... }
}
.vertical(true) // 正确位置
10.4 TabContent 宽度无效
问题描述:
设置 TabContent 的 width 属性没有效果
解决方案:TabContent 的宽度由父组件 Tabs 自动计算,不能直接设置。如需控制宽度,应设置 Tabs 的 barWidth 属性:
Tabs() { ... }
.vertical(true)
.barWidth('25%') // 控制侧边栏宽度
10.5 状态不同步
问题描述:
点击标签后,选中状态没有更新
解决方案:
确保在 onChange 回调中更新状态:
@State currentIndex: number = 0;
Tabs({ index: this.currentIndex }) { ... }
.onChange((index: number) => {
this.currentIndex = index; // 必须更新状态
})
十一、性能优化建议
11.1 使用 key 函数优化 ForEach
ForEach(this.tabs, (item) => {
TabContent() { ... }
}, (item) => item.id) // 使用唯一标识作为 key
11.2 控制预加载数量
Tabs() { ... }
.vertical(true)
.cachedMaxCount(2) // 只预加载前后各2个标签页
11.3 避免复杂计算在 build 方法中
@Entry
@Component
struct Index {
@State tabs: TabItem[] = [];
aboutToAppear() {
// 在生命周期方法中进行数据初始化
this.tabs = this.loadTabData();
}
build() {
// 直接使用已准备好的数据
Tabs() {
ForEach(this.tabs, (item) => { ... })
}
}
}
11.4 使用懒加载组件
TabContent() {
LazyForEach(this.dataSource, (item) => {
// 按需渲染内容
})
}
十二、响应式布局适配
12.1 使用媒体查询
@Entry
@Component
struct Index {
@State sidebarWidth: string = '22%';
aboutToAppear() {
const windowWidth = 0; // 获取屏幕宽度
if (windowWidth < 600) {
this.sidebarWidth = '30%';
} else if (windowWidth > 1000) {
this.sidebarWidth = '18%';
}
}
build() {
Tabs() { ... }
.vertical(true)
.barWidth(this.sidebarWidth)
}
}
12.2 使用栅格布局
Tabs() { ... }
.vertical(true)
.barGridAlign({
sm: 4,
md: 3,
lg: 2
})
十三、总结
13.1 核心要点回顾
-
垂直标签侧边栏布局的实现方式:
- 设置
vertical(true)将标签栏变为垂直排列 - 设置
barPosition(BarPosition.Start)将标签栏放在左侧 - 设置
barWidth()控制侧边栏宽度
- 设置
-
状态管理:
- 使用
@State装饰器管理当前选中标签索引 - 在
onChange回调中更新状态
- 使用
-
自定义 TabBar:
- 使用
@Builder装饰器定义自定义样式 - 支持图标、文字、Badge、选中指示器
- 使用
-
API 24 新增特性:
nestedScroll:嵌套滚动控制cachedMaxCount:预加载控制animationCurve:动画曲线设置
13.2 适用场景
垂直标签侧边栏布局适用于以下场景:
- 桌面端应用
- 后台管理系统
- 横屏界面
- 多设备协同应用
13.3 最佳实践
- 使用接口定义数据结构
- 接口声明在 struct 外部
- 属性链放在内容块之后
- 使用 ForEach 的 key 函数优化性能
- 控制预加载数量提升性能
- 避免在 build 方法中进行复杂计算
通过本文的学习,读者应该能够掌握鸿蒙原生垂直标签侧边栏布局的核心技术,包括基础实现、自定义样式、高级特性、API 24 新增功能、常见问题及解决方案等。希望本文能够帮助读者在实际项目中高效地实现垂直标签侧边栏布局,提升应用的用户体验和开发效率。
附录:完整 API 参考
A.1 Tabs 属性速查表
| 属性 | 类型 | 版本 | 说明 |
|---|---|---|---|
| vertical | boolean | 7+ | 是否垂直排列 |
| scrollable | boolean | 7+ | 是否可滑动切换 |
| barWidth | Length | 8+ | 标签栏宽度 |
| barHeight | Length | 8+ | 标签栏高度 |
| barMode | BarMode | 7+ | 标签栏布局模式 |
| animationDuration | number | 7+ | 切换动画时长 |
| barPosition | BarPosition | 9+ | 标签栏位置 |
| divider | DividerStyle | 10+ | 分割线样式 |
| barBackgroundColor | ResourceColor | 10+ | 标签栏背景色 |
| cachedMaxCount | number | 19+ | 预加载数量 |
| nestedScroll | NestedScrollOptions | 24+ | 嵌套滚动配置 |
A.2 Tabs 事件速查表
| 事件 | 版本 | 说明 |
|---|---|---|
| onChange | 7+ | 标签切换事件 |
| onTabBarClick | 10+ | 标签栏点击事件 |
| onAnimationStart | 11+ | 动画开始事件 |
| onAnimationEnd | 11+ | 动画结束事件 |
A.3 相关枚举
enum BarPosition {
Start, // 顶部/左侧
End // 底部/右侧
}
enum BarMode {
Fixed, // 固定模式
Scrollable // 可滚动模式
}
enum NestedScrollMode {
SELF_FIRST, // 自身优先
PARENT_FIRST, // 父容器优先
SELF_ONLY, // 仅自身
PARENT_ONLY // 仅父容器
}
更多推荐


所有评论(0)