一、概述 

在使用H5进行应用页面开发时,开发者往往会面临一多适配的挑战。当前不仅缺乏拿来即用的方案,而且适配过程复杂、工作量大。为达到HarmonyOS一多体验,本文将介绍一套面向多设备适配的H5响应式布局方案,涵盖了自适应显隐、栅格、侧边栏和分栏四部分响应式能力,帮助开发者在保证体验一致性的同时提升多设备适配开发效率。

本文针对自适应显隐、栅格、侧边栏和分栏四个组件进行详细的说明,结合组件效果提供对应场景的开发案例,指导开发实践。

组件库安装

根据您使用的包管理器和H5框架,请选择对应的安装命令。

NPM

// Vue2 
npm install @hadss/web_adaptive_layout_vue2 
npm install @hadss/web_adaptive_layout_ui 
// Vue3 
npm install @hadss/web_adaptive_layout_vue3 
npm install @hadss/web_adaptive_layout_ui 
// React 
npm install @hadss/web_adaptive_layout_react 
npm install @hadss/web_adaptive_layout_ui

YARN

// Vue2 
yarn add @hadss/web_adaptive_layout_vue2 
yarn add @hadss/web_adaptive_layout_ui 
// Vue3 
yarn add @hadss/web_adaptive_layout_vue3 
yarn add @hadss/web_adaptive_layout_ui 
// React 
yarn add @hadss/web_adaptive_layout_react 
yarn add @hadss/web_adaptive_layout_ui

二、自适应显示和隐藏实现多设备适配

 引入显隐能力管理类DisplayPriorityLayout,为特定元素添加显隐能力。主要是通过在HTML元素上增加data-priority-status自定义属性来控制显隐开关,在子元素上设置布局优先级(data-display-priority)属性来控制显隐的优先级。当布局主轴方向剩余尺寸不足以满足全部元素时,按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素。具有相同布局优先级的元素将同时显示或者隐藏。

显隐能力导入方式

  • Vue2/Vue3/React
// xxx.vue
import { initializeDisplayPriorityLayout, DisplayPriorityLayout } from '@hadss/web_adaptive_layout_ui'

布局效果

按照布局优先级大小,从小到大依次隐藏,直到容器能够完整显示剩余元素,效果如下:

sm

md

lg

 显隐能力API

  • 显隐能力管理类DisplayPriorityLayout

实例化时需传入HTML元素

属性

说明

参数

返回值

initializeLayout

初始化显隐能力

-

-

destroy

销毁事件监听

-

-

 

  • HTML自定义属性

属性

说明

类型

默认值

data-priority-status

设置当前元素是否开启显隐控制功能

Boolean

false

data-display-priority

设置当前元素在父容器中的显示优先级

小数点后的数字不做优先级区分,即区间为[x, x+1)内的数字视为相同优先级。例如:1.0和1.9为同一优先级。

属性值均不大于1时,优先级没有区别。

属性值大于1时,值越大则优先级越高。

若父容器空间不足,隐藏低优先级的子元素。若某一优先级的子元素被隐藏,则优先级更低的子元素也都被隐藏。

Number

1

  •  显隐能力函数

属性

说明

参数值

返回值

initializeDisplayPriorityLayout

识别所有设置data-priority-status属性的元素,为其初始化显隐能力

-

显隐能力管理类的实例集合:DisplayPriorityLayout[]

返回的实例集合可调用destroy()接口来销毁所有元素上的监听事件,具体见使用说明。

 推荐使用方式(以Vue3为例)

 1、导入显隐能力函数和显隐能力管理类

import { initializeDisplayPriorityLayout, DisplayPriorityLayout } from '@hadss/web_adaptive_layout_ui';

2、 在需要使用的HTML元素上增加data-priority-status自定义属性并设置为true,不使用则设置为false。子元素增加data-display-priority优先级属性。

<div ref="containerRef" data-priority-status="true">
  <div class="item" data-display-priority="5.0">Item 1</div>
  <div class="item" data-display-priority="4">Item 2</div>
  <div class="item" data-display-priority="1">Item 3</div>
  <div class="item" data-display-priority="4">Item 2</div>
  <div class="item" data-display-priority="5">Item 1</div>
</div>

 3. 提供两种添加显隐能力的方式,需要在组件实例挂载完成后使用,组件实例卸载时需要销毁事件监听。

  • 方式一:调用显隐能力函数,批量添加显隐能力
// 需要在onMounted中执行
let layouts: DisplayPriorityLayout[] = [];
onMounted(() => {
  layouts = initializeDisplayPriorityLayout();
});
 
// 组件实例卸载时销毁事件监听
onUnmounted(() => {
  layouts.forEach((layout: DisplayPriorityLayout) => {
    layout.destroy();
  });
});
  • 方式二:引入显隐能力管理类,为特定元素添加显隐能力
// 需要在onMounted中执行
let displayPriorityLayout: DisplayPriorityLayout | null = null;
const containerRef = ref<HTMLElement>(null);
onMounted(() => {
  displayPriorityLayout = new DisplayPriorityLayout(containerRef.value);
  displayPriorityLayout.initializeLayout();
});
 
// 组件实例卸载时销毁事件监听
onUnmounted(() => {
  displayPriorityLayout?.destroy();
});

使用限制

  1. 仅支持非表单、表格的块级元素和内联块级元素;
  2. 容器里面的子元素不能设置绝对定位、固定定位和浮动等会导致子元素脱离文档流的样式;
  3. 仅支持单行样式,如一行或一列,不支持grid布局。

场景案例

实现效果

sm

md

lg

 注意:实现效果中的红色框仅表示组件位置,不代表对应组件的边框。

关键代码(以vue3为例)

  • 在容器标签上设置data-priority-status="true"以开启显隐能力,在子标签上设置data-display-priority属性来控制显隐的优先级。
  • 实例化显隐能力管理类并传入容器标签dom元素,调用initializeLayout()方法为元素添加显隐能力。

示例如下:

// web_adaptive_ui\samples\vue3_sample\multidevice_sample\src\views\PlayList\components\CurPlaySong.vue 
<template> 
  <div class="bottom-container"> 
    <div style="flex: 1"></div> 
    <div :class="{ 'all-icon': isLargerPage }"> 
      <div 
        ref="iconContainer" 
        data-priority-status="true" 
        :style="{ 
          width: displayPriorityBoxWidth + 'px', 
          left: displayPriorityBoxLeft + 'px', 
        }" 
      > 
        <div 
          v-for="(icon, index) in icons" 
          :key="index" 
          :data-display-priority="icon.priority" 
          class="icon-container" 
        > 
          <img 
            :src="require('../../../assets/playList/' + icon.source)" 
            :style="icon.style" 
          /> 
        </div> 
      </div> 
    </div> 
  </div> 
</template> 
<script lang="ts" setup> 
import { onMounted, onBeforeUnmount, ref } from 'vue'; 
import { DisplayPriorityLayout, BreakpointManager } from '@hadss/web_adaptive_layout_ui'; 
import { defineProps } from 'vue'; 
const props = defineProps({ 
  song: { 
    type: Object, 
    required: true, 
  }, 
}); 
const iconSpace = ref(0); 
const icons = ref([ 
  { priority: 1, source: 'play_mode.png', style: { width: '24px', height: '24px' } }, 
  { priority: 1, source: 'control_left.png', style: { width: '28px', height: '28px', right: iconSpace.value + 'px'} }, 
  { priority: 10, source: 'play_icon.png', style: { width: '24px', height: '24px' } }, 
  { priority: 1, source: 'control_right.png', style: { width: '28px', height: '28px', left: iconSpace.value + 'px' } }, 
  { priority: 10, source: 'play_list_icon.png', style: { width: '24px', height: '24px' } }, 
]); 
const displayPriorityLayout = ref<DisplayPriorityLayout | null>(null); 
const iconContainer = ref<HTMLElement | null>(null); 
const breakpointManager = new BreakpointManager(); 
const unsubscribe = ref<(() => void) | null>(null); 
// 横幅中所有的图标的总宽度.用于显示和隐藏 
const displayPriorityBoxWidth = ref(0); 
const displayPriorityBoxLeft = ref(0); 
const isLargerPage = ref(false); 
onMounted(() => { 
  if (iconContainer.value) { 
    displayPriorityLayout.value = new DisplayPriorityLayout(iconContainer.value); 
    displayPriorityLayout.value.initializeLayout(); 
  } 
  // 订阅断点变化 
  unsubscribe.value = breakpointManager.subscribeToBreakpoint(() => { 
    displayPriorityBoxWidth.value = breakpointManager.useBreakpointValue({ 
      sm: 70, 
      md: 212, 
      lg: 310, 
    }); 
    displayPriorityBoxLeft.value = breakpointManager.useBreakpointValue({ 
      md: 0, 
      lg: -350, 
      xl: -600, 
    }); 
    isLargerPage.value = breakpointManager.useBreakpointValue({ 
      md: false, 
      lg: true, 
    }); 
    iconSpace.value = breakpointManager.useBreakpointValue({ 
      md: 0, 
      lg: 20, 
    }); 
    // 使用新的间距更新图标样式 
    icons.value[1].style.right = iconSpace.value + 'px'; 
    icons.value[3].style.left = iconSpace.value + 'px'; 
  }); 
}); 
onBeforeUnmount(() => { 
  // 销毁显隐对象 
  displayPriorityLayout.value?.destroy(); 
  if (unsubscribe.value) { 
    unsubscribe.value(); 
  } 
}); 
</script>

 三、栅格布局响应式组件实现多设备适配

H5栅格布局响应式组件,包含父组件MultiGridRow和子组件MultiGridCol两部分。旨在为布局提供规律性的结构,解决多尺寸多设备的动态布局问题,保证不同设备上各个模块的布局一致性。

组件导入方式

  • Vue2
// xxx.vue
import { MultiGridRow, MultiGridCol } from '@hadss/web_adaptive_layout_vue2';
  • Vue3
// xxx.vue
import { MultiGridRow, MultiGridCol } from '@hadss/web_adaptive_layout_vue3';
  • React
// xxx.tsx
import { MultiGridRow, MultiGridCol } from '@hadss/web_adaptive_layout_react';

 布局效果

栅格组件基于默认断点区间,在不同断点下配置不同的栅格数,如下图所示:

sm

md

lg

 MultiGridRow父组件API

1. Attribute

属性

说明

类型

可选值

默认值

columns

设置布局列数。

1.取值为大于0的整数

2.object类型参数支持{xs: 8, sm: 12, md: 12, lg: 12, xl: 16}类型的参数

Number / Object

-

12

gutter

栅格布局间距。

1. String/Number类型的值即为横纵布局间距

2. object类型参数支持{x: 12, y: {xs: 5, sm: 8, md: 10}}类型的参数

String / Number / Object

-

0

breakpoints

设置断点值的断点数列以及基于窗口或容器尺寸的相应参照。

Object

breakpointRefrence值可选“componentSize”、“windowSize”。分别代表参照组件宽度及参照窗口宽度。

{

breakpointValue: { xs: 0, sm: 320, md: 600, lg: 840 },

breakpointRefrence: "windowSize",

}

direction

栅格布局排列方向

String

row / row-reverse

row

 

2. Event

事件名

说明

参数

onGridItemClick

点击网格子项触发的事件

Function({index, name})

"index":选中子项的索引值

"name":选中子项的标识名称

onBreakpointChange

断点发生变化时触发回调

Function(breakpoints)

断点值,取值为"xs"、"sm"、"md"、"lg"、"xl"、"xxl"。

 MultiGridCol子组件API

属性

说明

类型

可选值

默认值

span

栅格子组件占用栅格容器组件的列数。span为0表示该元素不参与布局计算,即不会被渲染。

1.取值为大于0的整数

2.object类型参数支持{xs: 8, sm: 12, md: 12, lg: 12, xl: 16}类型的参数

Number / Object

-

1

offset

栅格子组件相对于原本位置偏移的列数。

1.取值为大于0的整数

2.object类型参数支持{xs: 8, sm: 12, md: 12, lg: 12, xl: 16}类型的参数

Number / Object

-

0

order

元素的序号,根据栅格子组件的序号,从小到大对栅格子组件做排序。

1.取值为大于0的整数

2.object类型参数支持{xs: 8, sm: 12, md: 12, lg: 12, xl: 16}类型的参数

当子组件不设置order或者设置相同的order, 子组件按照代码顺序展示。

当子组件部分设置order,部分不设置order时,未设置order的子组件依次排序靠前,设置了order的子组件按照数值从小到大排列。

Number / Object

-

0

label

设置网格项的文本内容(不支持断点)

String

-

-

icon

设置网格项的图标链接(不支持断点,只支持网络链接)

String

-

-

 推荐使用方式(以Vue3为例)

<template>
  <MultiGridRow
    :gutter="gutter"
  >
    <MultiGridCol :span="spanFirst">
      // 子组件内容1
    </MultiGridCol>
    <MultiGridCol :span="spanSecond">
      // 子组件内容2
    </MultiGridCol>
    <MultiGridCol :span="spanSecond">
      // 子组件内容3
    </MultiGridCol>
  </MultiGridRow>
</template>
<script lang="ts" setup>
import { reactive } from 'vue';  
import { MultiGridRow, MultiGridCol } from '@hadss/web_adaptive_layout_vue3';
 
const gutter = reactive({
  x: { sm: 0, md: 240 }
});
const spanFirst = reactive({
  md: 12,
  lg: 4,
});
const spanSecond = reactive({
  sm: 12,
  md: 6,
  lg: 4,
});
</script>

 场景案例

实现效果

sm

md

lg

 注意:实现效果中的红色框仅表示组件位置,不代表对应组件的边框。

关键代码(以vue3为例)

代码示例如下:

// web_adaptive_ui\samples\vue3_sample\multidevice_sample\src\views\PlayList\index.vue 
<template> 
  <div class="play-list-container"> 
    <div :class="{ 'container-other-obj': isLargerPage }"> 
      <PlaylistHeaderTable v-if="isLargerPage" /> 
      <div class="table-list"> 
        <div class="tableListInner"> 
          <MultiGridRow 
            :columns="columns" 
            :gutter="gutter" 
            :style="{ 
              marginLeft: distanceBorderLift + 'px', 
              marginRight: distanceBorderRight + 'px', 
            }" 
          > 
            <MultiGridCol 
              v-for="(item, index) in songs" 
              :key="index" 
              class="song-item" 
              :span="span" 
            > 
              <div class="song-info"> 
                <div class="song-title">{{ item.title }}</div> 
                <div class="artist-container"> 
                  <img 
                    src="../../assets/playList/acoustic.png" 
                    class="acoustic" 
                  /> 
                  <div class="song-artist">{{ item.artist }}</div> 
                </div> 
              </div> 
              <div class="flex-style" /> 
              <img 
                src="../../assets/playList/song_item_icon.png" 
                class="song-icon" 
                :style="{ 
                  paddingRight: songIconLift + 'px', 
                }" 
              /> 
            </MultiGridCol> 
          </MultiGridRow> 
        </div> 
      </div> 
    </div> 
  </div> 
</template> 
<script lang="ts" setup> 
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; 
import { BreakpointManager } from '@hadss/web_adaptive_layout_ui'; 
import { MultiGridRow, MultiGridCol } from '@hadss/web_adaptive_layout_vue3'; 
import { songs } from './model'; 
 
const breakpointManager = new BreakpointManager(); 
const isLargerPage = ref(false); 
const distanceBorderLift = ref(16); 
const distanceBorderRight = ref(16); 
const distanceTableTop = ref(0); 
const songIconLift = ref(0); 
const columns = reactive({
  md: 12,
  lg: 8,
});
const gutter = reactive({
  x: { sm: 0, md: 240 }
});
const span = reactive({
  sm: 12,
  md: 6,
  lg: 4,
});
const unsubscribe = breakpointManager.subscribeToBreakpoint(() => { 
  isLargerPage.value = breakpointManager.useBreakpointValue({ 
    md: false, 
    lg: true, 
  }); 
  distanceBorderLift.value = breakpointManager.useBreakpointValue({ 
    sm: 16, 
    md: 24, 
  }); 
  distanceBorderRight.value = breakpointManager.useBreakpointValue({ 
    sm: 16, 
    md: 0, 
  }); 
  songIconLift.value = breakpointManager.useBreakpointValue({ 
    sm: 0, 
    md: 24, 
  }); 
}); 
onBeforeUnmount(() => { 
  if (unsubscribe) { 
    unsubscribe(); 
  } 
}); 
</script>

 四、侧边栏布局响应式组件实现多设备适配

H5侧边栏响应式组件为显示和隐藏侧边栏提供了容器,通过子组件定义侧边栏和内容区,第一个子组件表示侧边栏,第二个子组件表示内容区。

组件导入方式

  • Vue2
// xxx.vue
import { SideBarContainerVue } from '@hadss/web_adaptive_layout_vue2';
  • Vue3
// xxx.vue
import { SideBarContainerVue } from '@hadss/web_adaptive_layout_vue3';
  • React
// xxx.tsx
import { SideBarContainerReact } from '@hadss/web_adaptive_layout_react';

 默认布局效果

侧边栏组件基于屏幕宽度,动态控制侧边栏是否显示,实现二分栏布局,如下图所示:

sm

md

lg

 MultiSideBar组件API

1. Attribute

属性

说明

类型

是否必填

默认值

sideBarContainerType

设置侧边栏的显示类型

SideBarContainerType

Embed

showSideBar

设置是否显示侧边栏

Boolean

true

controlButton

设置侧边栏控制按钮的属性

ButtonStyle

-

showControlButton

设置是否显示控制按钮

Boolean

true

sideBarWidth

设置侧边栏的宽度。设置为小于0的值时按默认值显示。受最小宽度和最大宽度限制,不在限制区域内取最近的点

String / Number

240, 单位:px

minSideBarWidth

设置侧边栏最小宽度。设置为小于0的值时按默认值显示。值不能超过侧边栏容器本身宽度,超过使用侧边栏容器本身宽度

String / Number

240, 单位:px

maxSideBarWidth

设置侧边栏最大宽度。设置为小于0的值时按默认值显示。值不能超过侧边栏容器本身宽度,超过使用侧边栏容器本身宽度

String / Number

280, 单位:px

autoHide

设置当侧边栏拖拽到小于最小宽度后,是否自动隐藏。受minSideBarWidth属性方法影响,minSideBarWidth属性方法未设置值使用默认值

Boolean

true

sideBarPosition

设置侧边栏显示位置

SideBarPosition

Start

divider

设置分割线的样式

DividerStyle / null

-

minContentWidth

设置SideBarContainer组件内容区可显示的最小宽度

String / Number

360, 单位:px

 SideBarContainerType类型

名称

说明

Embed

侧边栏嵌入到组件内,和内容区并列显示。

组件尺寸小于minContentWidth + minSideBarWidth,并且未设置showSideBar时,侧边栏自动隐藏。

未设置minSideBarWidth或者minContentWidth采用未设置接口的默认值进行计算。

组件在自动隐藏后,如果通过点击控制按钮唤出侧边栏,则侧边栏悬浮在内容区上显示。

Overlay

侧边栏浮在内容区上面

AUTO

组件尺寸大于等于minSideBarWidth+minContentWidth时,采用Embed模式显示。

组件尺寸小于minSideBarWidth+minContentWidth时,采用Overlay模式显示。

未设置minSideBarWidth或minContentWidth时,会使用未设置接口的默认值进行计算,若计算的值小于600px,则使用600px做为模式切换的断点值。

 ButtonStyle类型

名称

类型

必填

说明

left

Number

设置侧边栏控制按钮距离容器左界限的间距。

默认值:16, 单位:px

top

Number

设置侧边栏控制按钮距离容器上界限的间距。

默认值:48, 单位:px

width

Number

设置侧边栏控制按钮的宽度。

默认值:24, 单位:px

height

Number

设置侧边栏控制按钮的高度。

默认值:24, 单位:px

icons

{ shown: String; hidden: String }

设置侧边栏控制按钮的图标

shown: 设置侧边栏显示时控制按钮的图标。

说明:资源获取错误时,使用默认图标。

hidden: 设置侧边栏隐藏时控制按钮的图标。

图标资源需要用require进行加载和传值

 SideBarPosition类型

名称

说明

Start

侧边栏位于容器左侧

End

侧边栏位于容器右侧

 DividerStyle类型

名称

类型

必填

说明

strokeWidth

String / Number

分割线的线宽。

默认值:1, 单位:px

color

String

分割线的颜色。

默认值:#000000,3%

startMargin

String / Number

分割线与侧边栏顶端的距离。

默认值:0, 单位:px

endMargin

String / Number

分割线与侧边栏底端的距离。

默认值:0, 单位:px

 2. Event

事件名

说明

参数

onChangeStatus

当侧边栏的状态在显示和隐藏之间切换时触发回调

value:Boolean

true表示显示,false表示隐藏。

 推荐使用方式(以Vue3为例)

<template>
  <SideBarContainerVue
    sideBarWidth="400"
    minSideBarWidth="200px"
    maxSideBarWidth="900"
    :minContentWidth="minContentWidth"
    :divider="divider"
    :showSideBar="showSideBar"
    :sideBarContainerType="sideBarContainerType"
    :autoHide="true"
    sideBarPosition="Start"
    :showControlButton="true"
    :controlButton="controlButton"
    :onChangeStatus="changeEvent"
  >
    <div>
      侧边栏内容
    </div>
    <div>
      <p>提供侧边栏可以显示和隐藏的侧边栏容器,通过子组件定义侧边栏和内容区,第一个子组件表示侧边栏,第二个子组件表示内容区。</p>
    </div>
  </SideBarContainerVue>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { SideBarContainerVue } from '@hadss/web_adaptive_layout_vue3';
const showSideBar = ref(true);
const divider = reactive({
  strokeWidth: '3px',
  color: 'rgba(0, 0, 0, 0.3)',
  startMargin: '2px',
  endMargin: '2px',
});
const sideBarContainerType = ref('Embed');
const minContentWidth = ref(400);
const controlButton = reactive({
  icons: {
    shown: require('../../assets/icons/icon_001.png'),
    hidden: require('../../assets/icons/icon_002.png'),
  }
});
const changeEvent = (value: boolean) => {
  console.log(`当前状态为${value}`);
}
</script>

 ​​​​​​​​​​​​​​五、分栏布局响应式组件适配多设备

栏布局响应式组件,包含父组件NavigationContainer、导航栏组件NavigationBar和内容区组件NavigationContent三部分。在空间充足时,可以实现将窗口划分为两栏,用于展示多类内容,结合侧边栏组件可以实现三分栏布局。

组件导入方式

  • Vue2
// xxx.vue
import { NavigationContainerVue, NavigationBarVue, NavigationContentVue } from '@hadss/web_adaptive_layout_vue2';
  •  Vue3
// xxx.vue
import { NavigationContainerVue, NavigationBarVue, NavigationContentVue } from '@hadss/web_adaptive_layout_vue3';
  •  React
// xxx.tsx
import { NavigationContainerReact, NavigationBarReact, NavigationContentReact } from '@hadss/web_adaptive_layout_react';

 默认布局效果

分栏组件基于屏幕宽度,动态控制导航栏的显示模式,实现二分栏布局,如下图所示:

sm

md

lg

 NavigationContainer父组件API

1. Attribute

属性

说明

类型

是否必填

默认值

navBarWidth

设置导航栏宽度。仅在Navigation组件分栏时生效。

String / Number

默认值:240

 

单位:px

navBarWidthRange

设置导航栏最小和最大宽度(双栏模式下生效)。

String / Number[]

默认值:最小默认值 240,最大默认值为组件宽度的40% ,且不大于 432,未正确设置的值按照默认值计算。

单位:px

minContentWidth

导航栏内容区最小宽度。

String / Number

默认值:360

单位:px

mode

导航栏的显示模式。

NavigationMode

默认值:NavigationMode.Auto

自适应:基于组件宽度自适应单栏和双栏。

navigationPageName

导航栏页面的路由名称,属性参考对应框架的README介绍

String

-

navBarPosition

设置导航栏位置。仅在Navigation组件分栏时生效。

NavBarPosition

NavBarPosition.Start

 

NavigationMode类型

名称

说明

Stack

导航栏与内容区独立显示,相当于两个页面。

Split

导航栏与内容区分两栏显示。以下navBarWidthRange的值用[minNavBarWidth,maxNavBarWidth]表示

1.当navBarWidth属性的值,在navBarWidthRange属性的值范围以外时,navBarWidth按如下规则显示:navBarWidth < minNavBarWidth时,navBarWidth修正为minNavBarWidth;navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1) > maxNavBarWidth时,navBarWidth修正为maxNavBarWidth;navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1) < minNavBarWidth时,navBarWidth修正为minNavBarWidth;navBarWidth > maxNavBarWidth,且组件宽度 - minContentWidth - 分割线宽度(1)在navBarWidthRange范围内,navBarWidth修正为组件宽度 - 分割线宽度(1) - minContentWidth。

2.当navBarWidth属性的值,在navBarWidthRange属性的值范围以内时,navBarWidth按如下规则显示:minNavBarWidth + minContentWidth + 分割线宽度(1) >= 组件宽度时,navBarWidth修正为minNavBarWidth;minNavBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度,且navBarWidth + minContentWidth + 分割线宽度(1) >= 组件宽度时,navBarWidth修正为组件宽度 - 分割线宽度(1) - minContentWidth;minNavBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度,且navBarWidth + minContentWidth + 分割线宽度(1) < 组件宽度时,navBarWidth为设置的值。

3.缩小组件尺寸时,先缩小内容区的尺寸至minContentWidth,然后再缩小导航栏的尺寸至minNavBarWidth。若继续缩小,先缩小内容区,内容区消失后再缩小导航栏。

4.设置导航栏为固定尺寸时,若持续缩小组件尺寸,导航栏最后压缩显示。

5.若只设置了navBarWidth属性,则导航栏宽度为navBarWidth,且分割线不可拖动。

Auto

窗口宽度>=600px时,采用Split模式显示;窗口宽度<600px时,采用Stack模式显示,600px等于minNavBarWidth(240px) + minContentWidth (360px)。

 NavBarPosition类型

名称

说明

Start

双栏显示时,主列在主轴方向首部。

End

双栏显示时,主列在主轴方向尾部。

 2. Event

事件名

说明

参数

onNavBarStateChange

导航栏显示状态切换时触发该回调。

isVisible为true时表示显示,为false时表示隐藏。

onNavigationModeChange

当Navigation首次显示或者单双栏状态发生变化时触发该回调。

mode为NavigationMode.Split时,当前Navigation显示为双栏;mode为NavigationMode.Stack时,当前Navigation显示为单栏。

 推荐使用方式(以Vue3为例)

1. 导入分栏组件

import { NavigationContainerVue, NavigationBarVue, NavigationContentVue } from '@hadss/web_adaptive_layout_vue3';

 2. template中渲染组件

<template>
  <NavigationContainerVue :navBarWidth="navBarWidth"
    :navigationPageName="navigationPageName"
    :onNavBarStateChange="onNavBarStateChange"
    :onNavigationModeChange="onNavigationModeChange">
    <NavigationBarVue>
      // 导航栏页面
    </NavigationBarVue>
    // 基于路由表挂载内容区页面
    <NavigationContentVue></NavigationContentVue>
  </NavigationContainerVue>
</template>

 3. 在路由表中配置内容区路由信息

Navigation为导航栏页面,children里的路由对应内容区页面。

// router/index.ts
const routes: Array<RouteRecordRaw> = [
  {
    path: '/navigation',
    name: 'Navigation',
    component: Navigation,
    children: [
      {
        path: 'pageContent1',
        component: PageContent1,
      },
      {
        path: 'pageContent2',
        component: PageContent2,
      },
    ]
  },
  ...
]

 4. 设置组件属性 其中navigationPageName必须传递,值为导航栏页面的路由name值。

<script lang="ts" setup>
import { NavigationContainerVue, NavigationBarVue, NavigationContentVue } from '@hadss/web_adaptive_layout_vue3'; 
 
const navBarWidth = ref(200);
const navigationPageName = ref('Navigation');
const onNavBarStateChange = (isVisible: boolean) => {
  // ...
}
const onNavigationModeChange = (mode: string) => {
  // ...
}
</script>

 

​​​​​​​场景案例

实现效果

sm

md

lg

 关键代码(以vue3为例)

代码示例如下:

1. 路由表配置

import MessageList from '../views/MessageList/MessageList.vue';
import DetailContainer from '../views/MessageList/DetailContainer.vue';
 
const routes: Array<RouteConfig> = [
  ...
  {
    path: '/messageList',
    name: 'MessageList',
    component: MessageList,
    children: [
      {
        path: 'detailContainer',
        name: 'DetailContainer',
        component: DetailContainer,
      },
    ],
  },
];

 2. 分栏组件页面示例代码

// web_adaptive_ui\samples\vue3_sample\multidevice_sample\src\views\MessageList\MessageList.vue
<template>
  <div class="adaptive-layout">
    <NavigationContainerVue
      :mode="mode"
      :navigationPageName="navigationPageName"
      :navBarWidth="navBarWidth"
      :navBarWidthRange="navBarWidthRange"
      :minContentWidth="minContentWidth"
      :onNavigationModeChange="onNavigationModeChange"
    >
      <NavigationBarVue>
        // 导航栏内容
        <div class="list-container">
          <div class="container">
            <ul class="content message-list">
              <li
                v-for="item in messageDataList"
                :key="item.id"
                class="message-item"
                :class="{ 'item-active': activeItem.id === item.id }"
              >
                <div class="item">
                  // 消息列表
                </div>
              </li>
            </ul>
          </div>
        </div>
      </NavigationBarVue>
      // 内容区挂载位置
      <NavigationContentVue></NavigationContentVue>
    </NavigationContainerVue>
  </div>
</template>
 
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { NavigationContainerVue, NavigationContentVue, NavigationBarVue } from '@hadss/web_adaptive_layout_vue3';
import { messageData } from '../../mocks/mockData';
 
const router = useRouter();
const route = useRoute();
const messageDataList = ref(messageData);
const mode = ref<string>('Auto');
const navBarWidth = ref<number>(470);
const navigationPageName = ref<string>('MessageList');
const navBarWidthRange = ref<number[]>([260, 700]);
const minContentWidth = ref<number>(340);
const activeItem = ref({
  id: '11',
  name: '张晓萌',
  message: '好的,我明天过来',
  time: '13:55',
  avatar: require('../../assets/images/touxiang.svg'),
  unread: null,
});
const unsubscribe = ref(null);
const onNavigationModeChange = (mode: string) => {
  if (mode === 'Stack' && route.path !== '/messageList') {
    router.push('/messageList');
  } else if (mode === 'Split' && route.path !== '/messageList/detailContainer') {
    router.push('/messageList/detailContainer');
  }
};
</script>

 3. 内容区页面示例代码

// web_adaptive_ui\samples\vue3_sample\multidevice_sample\src\views\MessageList\DetailContainer.vue
<template>
  <div class="detail-container">
    <div class="container">
      // 聊天内容
      <div class="title-wrap">
        <div v-show="isXSorSm" className="go-back" @click="goHome">
          <img className="title-img" src="../../assets/images/left.png" />
        </div>
        ...
      </div>       
    </div>
  </div>
</template>
 
<script lang="ts" setup>
import { ref, onMounted, watch, computed } from "vue";
import { useRouter } from "vue-router";
import { MessageList } from "../../types/types";
 
const router = useRouter();
const goHome = () => {
  router.push("/messageList");
};
</script>

 ​​​​​​​​​​​​​​六、示例代码

基于H5框架的多设备开发sample示例代码地址

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐