前言
我在调材料列表页的时候,最早关注的是展开态双栏。左边放列表,右边放详情,点一条记录以后右侧直接切换内容,这种结构在 Pura X Max 展开态里确实能减少页面跳转。尤其是材料整理、会议纪要、客户记录这类页面,用户经常需要在多条记录之间来回看,列表和详情同时出现会省掉不少切换动作。
但我把应用拖进分屏以后,页面的状态就变了。原来能放下左侧列表和右侧详情的宽度,被分屏切掉一部分以后,左侧列表开始变窄,右侧详情也跟着压缩。标题还能显示,按钮也还能点击,可每个区域都变得勉强,左边不像列表,右边也不像详情。
Pura X Max 外屏是 5.4 英寸,内屏是 7.7 英寸,展开态确实适合承载更多信息;同时 HarmonyOS 支持全屏、分屏、自由窗口这几种窗口形态,同一个页面随时可能从完整宽窗口变成窄窗口。也就是说,双栏不能当成页面的固定结构,它更像是一种"宽窗口下才启用的能力"。窗口一旦缩窄,页面要能退回单栏;如果窗口再窄一点,卡片内部还要继续收起辅助字段。
这次我把页面分成三种状态来处理:
- 宽窗口:左侧列表,右侧详情
- 窄窗口:当前记录在上,列表在下
- 极窄窗口:只保留标题、状态和主操作
这三个状态让分屏以后页面还保留基本可用性,宽的时候展示更多,窄的时候先保证能读、能点、能继续处理当前记录。

一、双栏在分屏里会先挤压内容
1.1 展开态双栏没有问题
我一开始做展开态页面时,会很自然地想到列表详情结构。左侧列表负责切换,右侧详情负责展示当前记录,这个结构在宽窗口里很有价值。
材料整理页就是典型场景。用户可能需要连续看几条材料,点左侧记录,右侧详情马上变化,不用每次都进入新页面再返回列表。宽度足够时,这种结构能减少页面层级,也能让用户保留上下文。
在代码上,这种结构通常就是一个 Row。
arkts
Row({ space: 18 }) {
this.ListPanel()
this.DetailPanel(this.getSelectedItem())
}
宽窗口里这样写没问题。左侧给一个相对固定的列表宽度,右侧吃掉剩余空间,页面能同时呈现两种信息。这个结构的问题出现在窗口缩窄以后。
1.2 分屏以后两边都开始吃紧
我把页面切到分屏宽度后,第一眼看到的是左侧列表卡片变得很窄。标题本来可以显示两行,缩窄以后只剩一小段,摘要也被截断。右侧详情区域虽然还在,但正文换行变多,按钮和状态信息挤在一起,整个页面读起来很费劲。
这个时候页面并不是完全不可用,真正麻烦的是两个区域都只能勉强工作。左侧列表失去了快速扫读的能力,右侧详情也没有足够空间承载完整内容。继续保留双栏,反而让列表和详情互相抢宽度。
我在分屏里会先看这几个点:
- 左侧列表的标题还能不能读完整
- 右侧详情是否还能承载正文
- 主按钮有没有被挤到难点的位置
- 用户是否还需要同时看到列表和详情
当这些条件都开始变差时,我不会继续保留双栏。这个页面更适合退回单栏,让当前记录先占据上方区域,再把列表放到下方。这样做虽然少了一点"大屏感",但用户至少还能正常处理当前材料。

二、我把页面分成三档
2.1 宽窗口保留双栏
宽窗口下,我仍然保留双栏结构。Pura X Max 展开态有足够横向空间时,左侧列表和右侧详情并排出现,用户能一边切换记录,一边查看详情内容。
这时列表宽度可以固定在一个相对稳定的范围,比如 330vp。右侧详情区域使用剩余空间,负责展示标题、摘要、来源、负责人、正文和主操作。
arkts
private readonly wideWidth: number = 820;
我把 820vp 作为宽窗口门槛。这个值不是固定标准,真实项目里可以根据列表宽度、详情内容和页面边距调整。材料列表标题比较长,就要多留一些空间;右侧详情字段比较少,门槛可以稍微降低。
2.2 窄窗口退回单栏
窗口缩窄到中间状态时,我不会继续保留双栏。页面会退回单栏,上方先显示当前处理记录,下方再显示列表。
这种结构牺牲了列表详情同时展示,但换来的是当前内容区域不会被左右挤压。用户在分屏里更可能是在处理一条具体记录,而不是反复浏览大量详情。把当前记录放在上方,可以让用户先完成眼前这件事。
中间状态用 narrowWidth 控制:
arkts
private readonly narrowWidth: number = 520;
当窗口宽度达到 520vp,但还没有达到 820vp 时,页面进入 narrow。这个状态下,当前处理卡片会显示标题、状态、摘要和主按钮,列表继续放在下方。
2.3 极窄窗口只留核心内容
再往下缩,窗口进入极窄状态。这个时候继续展示摘要、来源、时间、标签,页面会显得很挤。极窄窗口更像一个临时处理入口,不适合承担完整信息展示。
我会把卡片内部再收一层,只保留标题、状态和主按钮。摘要、来源、时间这些信息暂时隐藏,等窗口恢复到更宽状态时再显示。
arkts
private getLayoutMode(): string {
const width = this.getEffectiveWidth();
if (width >= this.wideWidth) {
return 'wide';
}
if (width >= this.narrowWidth) {
return 'narrow';
}
return 'tiny';
}
这里把页面分成 wide、narrow、tiny 三种状态。真实项目里可以把这些字符串换成枚举,或者抽到统一的布局工具里。这个示例先保持简单,重点是把降级规则集中到一个函数里,避免 UI 分支散落在各个组件里。

三、状态要留在页面层
3.1 选中记录不能跟丢布局
分屏降级不只是布局变化。用户正在处理哪条记录,也要保留下来。
我在这个示例里保留了 selectedId。无论页面处于 wide、narrow 还是 tiny,当前选中的记录都从同一个状态里读取。
arkts
@State private selectedId: number = 1;
private getSelectedItem(): MaterialItem {
const found = this.materials.find((item: MaterialItem) => item.id === this.selectedId);
return found ? found : this.materials[0];
}
这样做的好处是,用户在宽窗口里选中某条记录后,切到窄窗口或者极窄窗口,当前处理内容仍然是同一条。布局变了,正在处理的上下文没有丢。
真实项目里这个点很容易被忽略。如果选中态放在某个具体布局组件里,比如只放在右侧详情面板里,那么面板一旦消失,状态可能也跟着重置。把状态放到页面层,会省掉很多切换时的补丁逻辑。
3.2 操作次数也不能重置
还有一个 actionCount,用来模拟用户点击主按钮后的操作次数。
arkts
@State private actionCount: number = 0;
private handleAction() {
this.actionCount += 1;
}
这个状态看起来很简单,但它可以用来验证布局切换时状态是否保留。宽窗口里点击操作按钮后,再切到窄窗口或极窄窗口,操作次数仍然存在。真实项目里,这类状态可能是保存进度、已读状态、待提交标记、正在处理的任务 ID。
分屏适配如果只处理布局,很容易遗漏这些上下文状态。我的习惯是先把页面状态放到外层,再让不同布局去消费这些状态。这样 wide、narrow、tiny 三种展示方式可以变化,业务状态仍然是一份。

四、实际运行效果
这里我提供了"宽屏""窄屏""极窄"三个演示按钮,主要是为了在同一个模拟器里快速观察布局降级。真实项目里不需要这些按钮,页面会直接跟随真实窗口宽度变化。
宽屏状态下,页面显示左列表、右详情。这个状态适合 Pura X Max 展开态,或者任何足够宽的窗口。左侧记录负责切换,右侧详情展示当前内容。

窄屏状态下,页面退回单栏。上方是当前处理记录,下方是材料列表。这个状态更接近分屏后的中等窗口,详情不再和列表并排,当前记录先被放到页面顶部。

极窄状态下,卡片内部继续收起辅助信息。标题、状态和主按钮保留,摘要、来源、时间等信息暂时隐藏。这个状态适合更窄的分屏比例、小尺寸自由窗口,或者只需要快速处理当前记录的场景。

五、实际放在项目中
5.1 演示宽度要删掉
这里我用了 previewWidth 和几个演示按钮,方便在一台模拟器里观察三种状态。真实项目里不需要这些东西。
arkts
private getEffectiveWidth(): number {
if (this.previewWidth > 0) {
return this.previewWidth;
}
return this.pageWidth;
}
迁回真实项目时,可以直接返回 pageWidth。
arkts
private getEffectiveWidth(): number {
return this.pageWidth;
}
宽度仍然可以通过 onAreaChange 获取。这个事件会在组件区域变化时触发,适合记录页面根容器当前宽度。注意这里记录的是组件区域变化,不是设备型号变化。
5.2 降级规则可以抽出去
如果项目里有多个页面都要处理分屏降级,我不建议每个页面都复制 getLayoutMode()。可以把 wide、narrow、tiny 这类断点规则抽到一个工具里。
比如:
arkts
private getLayoutMode(): string {
const width = this.getEffectiveWidth();
if (width >= this.wideWidth) {
return 'wide';
}
if (width >= this.narrowWidth) {
return 'narrow';
}
return 'tiny';
}
真实项目里可以把它改成枚举,或者统一放到布局配置中。列表页、详情页、表单页可以共享同一套基础断点,但每个页面内部显示什么字段,仍然要根据业务决定。
5.3 tiny 状态不要承载太多内容
极窄窗口里,我只保留标题、状态和主操作。这个取舍很直接,因为窗口已经没有足够空间承载摘要、来源、负责人这些辅助字段。
如果 tiny 状态还要继续放完整摘要、多个标签、来源时间和次要操作,页面会变成一堆挤在一起的内容块。这里我宁愿让用户先完成主操作,更多信息等窗口变宽后再显示,或者进入详情页处理。
这条规则可以迁移到很多页面里。悬浮窗、极窄分屏、临时窗口,都不适合展示完整内容。它们更适合保留一件事:当前处理对象是什么,以及用户下一步能做什么。
总结
Pura X Max 展开态适合双栏,但分屏以后,双栏不一定还能成立。宽窗口下左列表右详情可以减少跳转;窗口缩窄后,单栏会给当前记录留下更多空间;再窄一些时,卡片内部也要继续收起辅助字段,只保留标题、状态和主操作。
我处理这类页面时,会把双栏当成宽窗口能力,而不是默认结构。页面宽的时候展示更多,窄的时候先保证当前记录能读、能点、能继续处理。布局在变,选中记录和操作状态要保留下来,这一点比单纯切换 Row 和 Column 更容易被忽略。
附:完整代码
arkts
interface MaterialItem {
id: number;
title: string;
status: string;
source: string;
time: string;
tag: string;
owner: string;
summary: string;
detail: string;
action: string;
}
@Entry
@Component
struct Index {
// 页面真实宽度,由 onAreaChange 写入
@State private pageWidth: number = 0;
// 演示宽度,只用于在同一个模拟器里切换 wide / narrow / tiny 三种状态
@State private previewWidth: number = 0;
// 当前选中记录放在页面层,避免布局切换后丢失上下文
@State private selectedId: number = 1;
// 模拟操作次数,用来观察布局降级后操作状态是否保留
@State private actionCount: number = 0;
// narrow 以下进入极窄状态,wide 以上才显示列表详情双栏
private readonly narrowWidth: number = 520;
private readonly wideWidth: number = 820;
private readonly materials: MaterialItem[] = [
{
id: 1,
title: '社区物业缴费提醒',
status: '待处理',
source: '拍照整理',
time: '09:20',
tag: '通知',
owner: '物业服务中心',
summary: '识别到缴费截止日期、金额明细和办理地点。',
detail: '这条记录来自一张社区物业缴费通知。全屏展开态下适合左侧列表、右侧详情;分屏变窄后,页面退回单栏会给当前记录留下更多空间。',
action: '添加提醒'
},
{
id: 2,
title: 'Pura X Max 适配会议纪要',
status: '待确认',
source: '语音转写',
time: '10:45',
tag: '会议',
owner: '产品研发组',
summary: '整理出分屏、横屏、悬停态和详情页适配任务。',
detail: '会议记录类页面经常需要在列表中连续切换。宽窗口下可以使用左右双栏,窗口缩窄后回到单栏,当前记录会更容易阅读。',
action: '确认任务'
},
{
id: 3,
title: '客户需求变更记录',
status: '待处理',
source: '文本整理',
time: '13:10',
tag: '项目',
owner: '客户成功组',
summary: '本次变更涉及首页布局、权限配置和消息提醒。',
detail: '需求变更记录通常需要查看完整说明和处理动作。分屏后继续保留双栏,左侧列表和右侧详情都会被压缩。',
action: '同步排期'
},
{
id: 4,
title: '活动报名确认单',
status: '已保存',
source: '相册导入',
time: '15:25',
tag: '表单',
owner: '活动运营',
summary: '提取到报名人、联系方式、活动时间和签到地址。',
detail: '报名确认类材料字段较多,极窄窗口下只保留标题、状态和主操作,其他信息放到更宽窗口或详情中展示。',
action: '加入日程'
},
{
id: 5,
title: '门诊复查预约提示',
status: '已整理',
source: '拍照整理',
time: '16:40',
tag: '提醒',
owner: '个人记录',
summary: '提取到复查时间、科室、楼层和注意事项。',
detail: '提醒类记录在窄窗口中更适合快速处理,不需要把所有辅助字段都展示出来。',
action: '保存提醒'
}
];
// Demo 中优先使用演示宽度,真实项目里可以直接返回 pageWidth
private getEffectiveWidth(): number {
if (this.previewWidth > 0) {
return this.previewWidth;
}
return this.pageWidth;
}
// 把布局状态集中在一个函数里,避免 UI 分支散落到每个组件内部
private getLayoutMode(): string {
const width = this.getEffectiveWidth();
if (width >= this.wideWidth) {
return 'wide';
}
if (width >= this.narrowWidth) {
return 'narrow';
}
return 'tiny';
}
private isWide(): boolean {
return this.getLayoutMode() === 'wide';
}
private isNarrow(): boolean {
return this.getLayoutMode() === 'narrow';
}
private isTiny(): boolean {
return this.getLayoutMode() === 'tiny';
}
private getContentWidth(): Length {
if (this.previewWidth > 0) {
return this.previewWidth;
}
return '100%';
}
private getPagePadding(): number {
if (this.isWide()) {
return 24;
}
if (this.isNarrow()) {
return 16;
}
return 12;
}
private getTitleSize(): number {
if (this.isWide()) {
return 28;
}
if (this.isNarrow()) {
return 23;
}
return 20;
}
private getModeText(): string {
if (this.isWide()) {
return 'wide · 双栏布局';
}
if (this.isNarrow()) {
return 'narrow · 单栏布局';
}
return 'tiny · 核心内容';
}
private getModeDesc(): string {
if (this.isWide()) {
return '宽窗口下显示左侧列表和右侧详情。';
}
if (this.isNarrow()) {
return '窄窗口下当前记录在上,列表在下。';
}
return '极窄窗口下只保留标题、状态和主操作。';
}
private getSelectedItem(): MaterialItem {
const found = this.materials.find((item: MaterialItem) => item.id === this.selectedId);
return found ? found : this.materials[0];
}
private setPreview(width: number) {
this.previewWidth = width;
}
private handleAction() {
this.actionCount += 1;
}
private getStatusColor(status: string): string {
if (status === '待处理') {
return '#B25E00';
}
if (status === '待确认') {
return '#7C3AED';
}
return '#276749';
}
private getStatusBgColor(status: string): string {
if (status === '待处理') {
return '#FFF4E5';
}
if (status === '待确认') {
return '#F1EAFE';
}
return '#E7F5EE';
}
@Builder
private PreviewButton(text: string, width: number) {
Text(text)
.fontSize(12)
.fontColor(this.previewWidth === width ? '#FFFFFF' : '#2F8F83')
.textAlign(TextAlign.Center)
.padding({ left: 10, right: 10, top: 7, bottom: 7 })
.backgroundColor(this.previewWidth === width ? '#2F8F83' : '#E6F4F1')
.borderRadius(999)
.onClick(() => {
this.setPreview(width);
})
}
@Builder
private StatusPill(status: string) {
Text(status)
.fontSize(12)
.fontColor(this.getStatusColor(status))
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor(this.getStatusBgColor(status))
.borderRadius(999)
}
@Builder
private MetaPill(text: string) {
Text(text)
.fontSize(12)
.fontColor('#4B5563')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#F3F4F6')
.borderRadius(999)
}
@Builder
private HeaderPanel() {
Column({ space: 10 }) {
Row({ space: 10 }) {
Column({ space: 4 }) {
Text('分屏窗口下布局自动降级')
.fontSize(this.getTitleSize())
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(this.getModeText())
.fontSize(14)
.fontColor('#2F8F83')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
Text('窗口 ' + Math.round(this.pageWidth).toString() + 'vp')
.fontSize(12)
.fontColor('#374151')
.padding({ left: 10, right: 10, top: 6, bottom: 6 })
.backgroundColor('#FFFFFF')
.borderRadius(999)
}
.width('100%')
Text('演示宽度:' + Math.round(this.getEffectiveWidth()).toString() + 'vp。' + this.getModeDesc())
.fontSize(14)
.fontColor('#6B7280')
.lineHeight(21)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 8 }) {
this.PreviewButton('自动', 0)
this.PreviewButton('宽屏', 960)
this.PreviewButton('窄屏', 640)
this.PreviewButton('极窄', 420)
}
.width('100%')
}
.width('100%')
}
@Builder
private MaterialCard(item: MaterialItem) {
Column({ space: this.isTiny() ? 10 : 12 }) {
Row({ space: 8 }) {
this.StatusPill(item.status)
if (!this.isTiny()) {
this.MetaPill(item.tag)
}
Blank()
if (this.selectedId === item.id) {
Text('当前')
.fontSize(12)
.fontColor('#2F8F83')
}
}
.width('100%')
Text(item.title)
.fontSize(this.isTiny() ? 16 : 17)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
.maxLines(this.isTiny() ? 1 : 2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (!this.isTiny()) {
Text(item.summary)
.fontSize(13)
.fontColor('#6B7280')
.lineHeight(19)
.maxLines(this.isWide() ? 2 : 1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 8 }) {
Text(item.source)
.fontSize(12)
.fontColor('#6B7280')
Text('·')
.fontSize(12)
.fontColor('#9CA3AF')
Text(item.time)
.fontSize(12)
.fontColor('#6B7280')
}
.width('100%')
}
if (this.isTiny()) {
Button(item.action)
.fontSize(13)
.fontColor('#FFFFFF')
.height(34)
.width('100%')
.backgroundColor('#2F8F83')
.borderRadius(17)
.onClick(() => {
this.selectedId = item.id;
this.handleAction();
})
}
}
.width('100%')
.padding(this.isTiny() ? 12 : 15)
.backgroundColor(this.selectedId === item.id ? '#EEF7F5' : '#FFFFFF')
.borderRadius(this.isTiny() ? 16 : 18)
.border({
width: this.selectedId === item.id ? 1.5 : 1,
color: this.selectedId === item.id ? '#2F8F83' : '#E5E7EB'
})
.shadow({
radius: this.selectedId === item.id ? 10 : 7,
color: '#10000000',
offsetX: 0,
offsetY: 4
})
.onClick(() => {
this.selectedId = item.id;
})
}
@Builder
private ListPanel() {
Scroll() {
Column({ space: 12 }) {
ForEach(this.materials, (item: MaterialItem) => {
this.MaterialCard(item)
}, (item: MaterialItem) => item.id.toString())
}
.width('100%')
.padding({ bottom: 20 })
}
.layoutWeight(1)
.width('100%')
.edgeEffect(EdgeEffect.Spring)
}
@Builder
private DetailPanel(item: MaterialItem) {
Column({ space: 16 }) {
Row() {
this.StatusPill(item.status)
Blank()
this.MetaPill(item.tag)
}
.width('100%')
Text(item.title)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
.lineHeight(31)
Text(item.summary)
.fontSize(15)
.fontColor('#4B5563')
.lineHeight(23)
Row({ space: 10 }) {
this.MetaBlock('来源', item.source)
this.MetaBlock('时间', item.time)
this.MetaBlock('负责人', item.owner)
}
.width('100%')
Column({ space: 8 }) {
Text('内容详情')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
Text(item.detail)
.fontSize(15)
.fontColor('#4B5563')
.lineHeight(24)
}
.width('100%')
.padding(16)
.backgroundColor('#F9FAFB')
.borderRadius(18)
Column({ space: 8 }) {
Text('操作状态')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
Text('当前操作已触发 ' + this.actionCount.toString() + ' 次。切换窗口宽度后,选中项和操作状态仍然保留。')
.fontSize(14)
.fontColor('#6B7280')
.lineHeight(22)
}
.width('100%')
.padding(16)
.backgroundColor('#F3F8F7')
.borderRadius(18)
Blank()
Button(item.action)
.fontSize(15)
.fontColor('#FFFFFF')
.height(44)
.width('100%')
.backgroundColor('#2F8F83')
.borderRadius(22)
.onClick(() => {
this.handleAction();
})
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#FFFFFF')
.borderRadius(24)
.shadow({
radius: 12,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
}
@Builder
private MetaBlock(label: string, value: string) {
Column({ space: 4 }) {
Text(label)
.fontSize(12)
.fontColor('#9CA3AF')
Text(value)
.fontSize(14)
.fontColor('#374151')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.layoutWeight(1)
.padding(12)
.backgroundColor('#F9FAFB')
.borderRadius(14)
}
@Builder
private CompactSelectedPanel(item: MaterialItem) {
Column({ space: 10 }) {
Row() {
Text('当前处理')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#111827')
Blank()
this.StatusPill(item.status)
}
.width('100%')
Text(item.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#111827')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (this.isNarrow()) {
Text(item.summary)
.fontSize(14)
.fontColor('#4B5563')
.lineHeight(21)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
Button(item.action)
.fontSize(14)
.fontColor('#FFFFFF')
.height(40)
.width('100%')
.backgroundColor('#2F8F83')
.borderRadius(20)
.onClick(() => {
this.handleAction();
})
}
.width('100%')
.padding(this.isTiny() ? 12 : 16)
.backgroundColor('#FFFFFF')
.borderRadius(20)
.shadow({
radius: 10,
color: '#10000000',
offsetX: 0,
offsetY: 4
})
}
@Builder
private MainContent() {
if (this.isWide()) {
Row({ space: 18 }) {
Column() {
this.ListPanel()
}
.width(330)
.height('100%')
Column() {
this.DetailPanel(this.getSelectedItem())
}
.layoutWeight(1)
.height('100%')
}
.width('100%')
.height('100%')
} else {
Column({ space: 14 }) {
this.CompactSelectedPanel(this.getSelectedItem())
Column() {
this.ListPanel()
}
.layoutWeight(1)
.width('100%')
}
.width('100%')
.height('100%')
}
}
build() {
Column() {
Column({ space: 16 }) {
this.HeaderPanel()
Column() {
this.MainContent()
}
.layoutWeight(1)
.width('100%')
}
.width(this.getContentWidth())
.height('100%')
.padding({
left: this.getPagePadding(),
right: this.getPagePadding(),
top: 18,
bottom: 16
})
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Center)
.backgroundColor('#F6F7F9')
.onAreaChange((_: Area, newValue: Area) => {
const width = Number(newValue.width);
if (!Number.isNaN(width) && width > 0) {
this.pageWidth = width;
}
})
}
}