欢迎光临
我们一直在努力

HarmonyOS 多设备开发最佳实践第一篇:从设计到上架的一套完整方法论

HarmonyOS 多设备开发最佳实践:从设计到上架的一套完整方法论

适合写成 CSDN 高质量技术文章的结构:先讲清楚目标,再拆流程,再给可落地代码,最后补测试和上架闭环。

摘要

HarmonyOS 的多设备开发,不是把手机页面简单拉伸到平板或电脑上,而是围绕不同终端的屏幕形态、交互方式、硬件能力和使用场景,建立一套可以持续复用的开发方法论。

本文结合华为官方多设备开发最佳实践,整理出一条从 UX 设计、架构设计、界面开发、功能开发、预览调试、测试到发布上架的完整路径,并给出响应式布局、自适应布局、能力检测、模块拆分的实战写法,帮助开发者把“多端适配”真正做成“多端体验一致”。


一、为什么多设备开发不能只做“页面缩放”

HarmonyOS 的多设备场景覆盖手机、折叠屏、平板、电脑、智慧屏、穿戴设备等终端。它们的问题不只在于屏幕尺寸不同,还在于:

  • 屏幕比例不同
  • 交互方式不同
  • 信息密度不同
  • 硬件能力不同
  • 使用场景不同

如果只是把手机页面等比放大,通常会出现下面这些问题:

  • 手机端信息拥挤
  • 平板端留白过多
  • 折叠屏展开后布局失衡
  • 电脑端层级不够,操作效率低
  • 穿戴设备里功能太重,根本不适合

所以,多设备开发的重点从来不是“能显示”,而是“在每种设备上都好用”。


二、完整方法论:从设计到上架的一条链路

官方最佳实践可以概括成下面这条开发闭环:

在这里插入图片描述

#mermaid-svg-bOWtLH9NpeuE1nIn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bOWtLH9NpeuE1nIn .error-icon{fill:#552222;}#mermaid-svg-bOWtLH9NpeuE1nIn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bOWtLH9NpeuE1nIn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bOWtLH9NpeuE1nIn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bOWtLH9NpeuE1nIn .marker.cross{stroke:#333333;}#mermaid-svg-bOWtLH9NpeuE1nIn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bOWtLH9NpeuE1nIn p{margin:0;}#mermaid-svg-bOWtLH9NpeuE1nIn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster-label text{fill:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster-label span{color:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster-label span p{background-color:transparent;}#mermaid-svg-bOWtLH9NpeuE1nIn .label text,#mermaid-svg-bOWtLH9NpeuE1nIn span{fill:#333;color:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn .node rect,#mermaid-svg-bOWtLH9NpeuE1nIn .node circle,#mermaid-svg-bOWtLH9NpeuE1nIn .node ellipse,#mermaid-svg-bOWtLH9NpeuE1nIn .node polygon,#mermaid-svg-bOWtLH9NpeuE1nIn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bOWtLH9NpeuE1nIn .rough-node .label text,#mermaid-svg-bOWtLH9NpeuE1nIn .node .label text,#mermaid-svg-bOWtLH9NpeuE1nIn .image-shape .label,#mermaid-svg-bOWtLH9NpeuE1nIn .icon-shape .label{text-anchor:middle;}#mermaid-svg-bOWtLH9NpeuE1nIn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bOWtLH9NpeuE1nIn .rough-node .label,#mermaid-svg-bOWtLH9NpeuE1nIn .node .label,#mermaid-svg-bOWtLH9NpeuE1nIn .image-shape .label,#mermaid-svg-bOWtLH9NpeuE1nIn .icon-shape .label{text-align:center;}#mermaid-svg-bOWtLH9NpeuE1nIn .node.clickable{cursor:pointer;}#mermaid-svg-bOWtLH9NpeuE1nIn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bOWtLH9NpeuE1nIn .arrowheadPath{fill:#333333;}#mermaid-svg-bOWtLH9NpeuE1nIn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bOWtLH9NpeuE1nIn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bOWtLH9NpeuE1nIn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bOWtLH9NpeuE1nIn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bOWtLH9NpeuE1nIn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bOWtLH9NpeuE1nIn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster text{fill:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn .cluster span{color:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bOWtLH9NpeuE1nIn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bOWtLH9NpeuE1nIn rect.text{fill:none;stroke-width:0;}#mermaid-svg-bOWtLH9NpeuE1nIn .icon-shape,#mermaid-svg-bOWtLH9NpeuE1nIn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bOWtLH9NpeuE1nIn .icon-shape p,#mermaid-svg-bOWtLH9NpeuE1nIn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bOWtLH9NpeuE1nIn .icon-shape .label rect,#mermaid-svg-bOWtLH9NpeuE1nIn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bOWtLH9NpeuE1nIn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bOWtLH9NpeuE1nIn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bOWtLH9NpeuE1nIn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

UX 设计

架构设计

界面开发

功能开发

预览调试

应用测试

发布上架

真实用户反馈

这条链路的价值在于,它不是只讲怎么写代码,而是把“设计、实现、验证、迭代”连成了一个可执行的工程流程。

1. 先做 UX 设计

多设备项目最容易犯的错误,是先写页面,再考虑设备适配。这样后面会不断返工。

正确做法是先回答几个关键问题:

  • 哪些信息是主内容
  • 哪些信息是辅助内容
  • 哪些内容在小屏必须保留
  • 哪些内容在大屏才展开
  • 哪些操作适合单手,哪些适合分栏

UX 设计阶段的重点,是先确定信息优先级,而不是先纠结 UI 细节。

2. 再做架构设计

多设备开发里,最怕业务逻辑和设备形态强耦合。

推荐的思路是分层拆分:

  • 页面层负责展示
  • 状态层负责页面状态
  • 业务层负责核心逻辑
  • 数据层负责数据读取和持久化

这样做的好处是:

  • 业务逻辑可以跨设备复用
  • 不同设备的差异集中在展示层处理
  • 后续扩展新设备时,不需要大面积重写

3. 最后再进入界面与功能开发

界面开发负责“长什么样”,功能开发负责“能做什么”。

在多设备场景里,这两者都要服从同一个原则:

先保证核心任务,再扩展高级能力。


三、界面设计:响应式布局和自适应布局一起用

HarmonyOS 多设备界面开发的关键能力,主要就是两个词:

  • 响应式布局
  • 自适应布局

在这里插入图片描述

1. 响应式布局:结构会跟着窗口变化

响应式布局更关注页面结构变化。

典型例子:

  • 手机单列
  • 平板双列
  • 大屏侧边栏常驻
  • 窄屏时导航收起

它解决的是“页面结构怎么变”的问题。

2. 自适应布局:空间会被合理分配

自适应布局更关注空间分配。

典型例子:

  • 左右区域按比例拉伸
  • 主内容区占更多空间
  • 辅助区空间不足时折叠
  • 次要信息在窄屏时自动隐藏

它解决的是“空间怎么分”的问题。

3. 一个可直接用于文章的布局示例

下面这个示例演示了一个典型的多设备布局思路:小屏单列,大屏双栏。

@Entry
@Component
struct DeviceAdaptivePage {
@State windowWidth: number = 0;

private get isWideScreen(): boolean {
return this.windowWidth >= 720;
}

build() {
Column() {
// 顶部导航
Row() {
Text('HarmonyOS 多设备开发')
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 12 })

// 主体区域
if (this.isWideScreen) {
Row() {
Column() {
Text('主内容区')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('宽屏设备下显示更多主信息和操作入口。')
.fontSize(14)
.margin({ top: 8 })
}
.layoutWeight(2)
.padding(16)
.backgroundColor('#F5F7FA')
.borderRadius(12)

Column() {
Text('辅助信息区')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('这里放筛选、提示、统计或相关操作。')
.fontSize(14)
.margin({ top: 8 })
}
.layoutWeight(1)
.margin({ left: 12 })
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
} else {
Column() {
Column() {
Text('主内容区')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('窄屏设备下优先展示核心任务。')
.fontSize(14)
.margin({ top: 8 })
}
.padding(16)
.backgroundColor('#F5F7FA')
.borderRadius(12)

Column() {
Text('辅助信息区')
.fontSize(18)
.fontWeight(FontWeight.Medium)
Text('辅助内容在窄屏下折叠或下移。')
.fontSize(14)
.margin({ top: 8 })
}
.margin({ top: 12 })
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
}
}
.width('100%')
.height('100%')
.backgroundColor('#EEF2F6')
}
}

这个示例适合在文章里解释三个点:

  • 先判断窗口宽度
  • 再决定布局结构
  • 最后再决定内容层级

四、架构设计:把多设备差异收敛到最小

多设备开发最重要的能力之一,是让代码“尽量少改,尽量复用”。

在这里插入图片描述

1. 推荐的分层方式

#mermaid-svg-uOHv3oppxwnSIXUC{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uOHv3oppxwnSIXUC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uOHv3oppxwnSIXUC .error-icon{fill:#552222;}#mermaid-svg-uOHv3oppxwnSIXUC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uOHv3oppxwnSIXUC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uOHv3oppxwnSIXUC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uOHv3oppxwnSIXUC .marker.cross{stroke:#333333;}#mermaid-svg-uOHv3oppxwnSIXUC svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uOHv3oppxwnSIXUC p{margin:0;}#mermaid-svg-uOHv3oppxwnSIXUC .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uOHv3oppxwnSIXUC .cluster-label text{fill:#333;}#mermaid-svg-uOHv3oppxwnSIXUC .cluster-label span{color:#333;}#mermaid-svg-uOHv3oppxwnSIXUC .cluster-label span p{background-color:transparent;}#mermaid-svg-uOHv3oppxwnSIXUC .label text,#mermaid-svg-uOHv3oppxwnSIXUC span{fill:#333;color:#333;}#mermaid-svg-uOHv3oppxwnSIXUC .node rect,#mermaid-svg-uOHv3oppxwnSIXUC .node circle,#mermaid-svg-uOHv3oppxwnSIXUC .node ellipse,#mermaid-svg-uOHv3oppxwnSIXUC .node polygon,#mermaid-svg-uOHv3oppxwnSIXUC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uOHv3oppxwnSIXUC .rough-node .label text,#mermaid-svg-uOHv3oppxwnSIXUC .node .label text,#mermaid-svg-uOHv3oppxwnSIXUC .image-shape .label,#mermaid-svg-uOHv3oppxwnSIXUC .icon-shape .label{text-anchor:middle;}#mermaid-svg-uOHv3oppxwnSIXUC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uOHv3oppxwnSIXUC .rough-node .label,#mermaid-svg-uOHv3oppxwnSIXUC .node .label,#mermaid-svg-uOHv3oppxwnSIXUC .image-shape .label,#mermaid-svg-uOHv3oppxwnSIXUC .icon-shape .label{text-align:center;}#mermaid-svg-uOHv3oppxwnSIXUC .node.clickable{cursor:pointer;}#mermaid-svg-uOHv3oppxwnSIXUC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uOHv3oppxwnSIXUC .arrowheadPath{fill:#333333;}#mermaid-svg-uOHv3oppxwnSIXUC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uOHv3oppxwnSIXUC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uOHv3oppxwnSIXUC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uOHv3oppxwnSIXUC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uOHv3oppxwnSIXUC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uOHv3oppxwnSIXUC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uOHv3oppxwnSIXUC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uOHv3oppxwnSIXUC .cluster text{fill:#333;}#mermaid-svg-uOHv3oppxwnSIXUC .cluster span{color:#333;}#mermaid-svg-uOHv3oppxwnSIXUC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uOHv3oppxwnSIXUC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uOHv3oppxwnSIXUC rect.text{fill:none;stroke-width:0;}#mermaid-svg-uOHv3oppxwnSIXUC .icon-shape,#mermaid-svg-uOHv3oppxwnSIXUC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uOHv3oppxwnSIXUC .icon-shape p,#mermaid-svg-uOHv3oppxwnSIXUC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uOHv3oppxwnSIXUC .icon-shape .label rect,#mermaid-svg-uOHv3oppxwnSIXUC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uOHv3oppxwnSIXUC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uOHv3oppxwnSIXUC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uOHv3oppxwnSIXUC :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

页面层 / UI

状态层 / ViewModel

业务层 / Business

数据层 / Data Source

本地存储 / 云端 / 接口

2. 为什么要这样拆

这样拆的好处是很实际的:

  • UI 只处理显示和交互
  • 状态层只维护页面状态
  • 业务层只处理规则和流程
  • 数据层只处理来源和存储

多设备适配时,通常只需要在 UI 层做差异化处理,业务层不需要为每个终端重写一遍。

3. 一个状态驱动的示例

export enum LayoutMode {
Compact = 'compact',
Medium = 'medium',
Expanded = 'expanded'
}

export function resolveLayoutMode(windowWidth: number): LayoutMode {
if (windowWidth < 600) {
return LayoutMode.Compact;
}
if (windowWidth < 840) {
return LayoutMode.Medium;
}
return LayoutMode.Expanded;
}

这个函数适合放到业务无关的公共工具里。界面只要拿到布局模式,就能决定:

  • 是否显示双栏
  • 是否显示侧边栏
  • 是否合并次要信息
  • 是否增强操作密度

五、功能开发:先判断能力,再决定是否启用

多设备开发里,不同设备支持的能力不完全一样。

因此,功能开发建议遵守这条原则:

先判断能力是否存在,再决定功能是否启用,最后再做降级方案。

1. 为什么要做能力检测

如果某个功能只在部分设备可用,却直接在所有设备里调用,就容易出现:

  • 页面异常
  • 功能不可用
  • UI 入口失效
  • 用户体验不一致

2. 一个“能力判断 + 降级”的写法

interface DeviceCapability {
canSplitScreen: boolean;
canUseLargePanel: boolean;
canUseTouchGesture: boolean;
}

function buildCapabilityView(cap: DeviceCapability): string {
if (cap.canSplitScreen && cap.canUseLargePanel) {
return '显示双栏 + 侧边栏 + 扩展操作区';
}

if (cap.canUseTouchGesture) {
return '显示单栏 + 折叠辅助信息 + 保留核心按钮';
}

return '显示最小可用界面';
}

这个代码的价值不是“语法本身”,而是表达一个工程思路:

  • 大屏不等于只放大
  • 小屏不等于只缩小
  • 功能应该按能力分级开放

六、预览调试、测试和上架:不要把适配留到最后

很多多设备问题,不是在开发时发现的,而是在测试或真机上才暴露出来。

1. 预览调试要尽早做

建议尽快验证这些场景:

  • 窗口拉伸
  • 横竖屏切换
  • 多窗口模式
  • 折叠状态变化
  • 大屏和小屏切换

2. 测试要覆盖“设备差异”

测试不只是验证功能对不对,还要验证体验稳不稳。

建议重点检查:

  • 页面在不同宽高比下是否变形
  • 文字是否被截断
  • 按钮是否遮挡
  • 侧边栏是否能自动折叠
  • 功能在弱能力设备上是否能正常降级

3. 上架之前先做一轮体验回收

正式发布前,最好再回头看一遍:

  • 主流程是否足够短
  • 各设备的核心功能是否都能走通
  • 大屏是否充分利用了空间
  • 小屏是否保住了最重要的信息

七、把方法论总结成一句话

HarmonyOS 多设备开发,不是“一个页面适配所有设备”,而是“围绕不同设备的能力和场景,构建统一而可扩展的体验体系”。

如果要把这篇文章的核心压缩成一个公式,可以写成:

多设备体验 = UX 设计 + 分层架构 + 响应式布局 + 自适应布局 + 能力检测 + 全设备测试


八、结语

真正高质量的多设备应用,不是每个终端看起来都一模一样,而是在不同终端上都能保持同样的任务完成效率、信息清晰度和交互顺畅度。

从设计到上架,HarmonyOS 多设备开发最重要的不是某个单点技巧,而是一套完整的方法论:

  • 先设计信息结构
  • 再拆分架构边界
  • 然后做布局适配
  • 再做能力分级
  • 最后通过测试和发布闭环验证

这才是多设备开发真正值得写成 CSDN 高分文章的地方。

赞(0)
未经允许不得转载:171主机测评 » HarmonyOS 多设备开发最佳实践第一篇:从设计到上架的一套完整方法论
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址