Skip to content

网页原型设计

约 2535 字大约 8 分钟

Web原型设计

2026-09-12

统计数据加载中...

前言

想检查一个网页在手机、平板和电脑上的效果,不一定要先登录账号、连接后端,再准备一批真实数据。

如果项目已经有页面和交互,可以直接复用这些代码,把网络请求和外部操作替换成本地演示数据。这样得到的原型不仅能看布局,还能操作搜索、菜单、弹窗和列表,发现的问题也能直接修回项目。

这篇讲的是已有前端项目的离线交互原型,不是从零画设计稿,也不是证明真实后端已经可用。

页面不重写,只替换外部依赖

原型和正式版使用同一套路由、布局、组件、样式与状态逻辑。区别放在请求和平台能力的边界:

路由、页面、组件、状态管理

       service / platform

       ┌─────┴─────┐
     正常构建     演示构建
     真实服务     本地数据与替代实现

比如一个收藏按钮,点击后的状态变化、确认框和提示仍走原来的代码;只有请求不再发往真实服务器。

不要另写一套 HTML 模仿页面,再给演示页单独补样式。否则原型修好了,正式页面还要再改一次,两边也容易逐渐不同。

先确定实现边界

先找出页面依赖哪些请求、存储和外部能力,再决定替换位置。以下内容需要从代码和构建配置中确认,不是统一的目录约定。

需要确认查看位置与处理方式
页面入口和共享组件查路由、布局与状态管理;演示构建复用它们,不另建一套路由树
请求入口查 service、Axios 实例或 fetch 封装;在统一传输层接入本地响应
数据格式查页面取值、类型和错误判断;返回结构与原接口一致,包括分页和错误格式
平台能力查文件、媒体、外链、登录与同步调用;在对应接口提供演示实现
构建开关与输出查现有脚本和构建器;演示开关默认关闭,输出到独立目录
存储位置查 localStorage、IndexedDB 等命名;演示使用独立命名空间,提供重置入口
启动方式查资源路径和路由模式;需要静态服务时选空闲端口,不沿用其他服务的监听端口

固定的是共享页面、隔离副作用和验证顺序;请求路径、响应字段、存储键名、构建命令要按实际代码确定。缺少某一层就不必为了套结构额外创建服务。

用构建开关切换演示模式

用单独的构建命令明确选择演示模式,不根据域名、网络是否断开或登录失败临时猜测。

演示构建应同时完成:

  • 启用本地请求和平台适配。
  • 注入演示初始数据,禁用真实账号读取。
  • 不注入统计脚本,不启动同步、日志上传等常驻任务。
  • 将输出写入独立目录,不覆盖正式产物。

开关集中定义,例如 OFFLINE_DEMO,由所用构建器转换成编译期常量;名称只是示例。正常构建必须显式保持关闭,不能因为上一次构建残留的环境变量误用演示数据。

只有一个开关还不够:分别构建正常版和演示版,检查两者实际选择了哪套适配器。

在请求层提供演示数据

页面仍然调用原来的 service,由请求层返回 fixture,也就是预先准备的本地数据。

使用 Axios 的项目可以在演示模式替换自定义 adapter;使用 fetch 封装的项目在对应传输入口替换。不要在每个页面里写一遍判断。Axios 的返回约定可参考 自定义请求适配配置

本地分发至少要识别 HTTP 方法、路径、查询参数和请求体。下面是一个演示接口处理函数,返回业务响应体,不是可直接赋给 Axios 的完整 adapter:

const initialItems = [{ id: 'demo-1', title: '示例项目', liked: false }]
let items = initialItems.map(item => ({ ...item }))

export async function demoResponse({ method = 'GET', url }) {
  // 示例基址仅用于解析相对 URL,不发起网络请求
  const parsed = new URL(url, 'https://demo.invalid')
  const verb = method.toUpperCase()

  if (verb === 'GET' && parsed.pathname === '/api/items') {
    const query = parsed.searchParams.get('q') || ''
    return {
      code: 0,
      data: items.filter(item => item.title.includes(query))
        .map(item => ({ ...item })),
    }
  }

  if (verb === 'POST' && parsed.pathname === '/api/items/demo-1/like') {
    items = items.map(item => item.id === 'demo-1'
      ? { ...item, liked: true } : item)
    return { code: 0, data: { liked: true } }
  }

  throw new Error(`未覆盖的演示接口:${verb} ${parsed.pathname}`)
}

export function resetDemo() {
  items = initialItems.map(item => ({ ...item }))
}

接入时按请求库约定封装响应,合并查询参数、解析请求体,并保留项目需要的取消和错误语义。示例中的路径、字段和成功码都应换成真实接口约定。

未覆盖的接口明确报错,不回退到真实网络。 这样新增页面漏了 fixture 时能及时发现,而不是悄悄请求正式服务。

写操作要前后一致

只让 POST 返回成功,可能出现“收藏成功,切回来却没收藏”的情况。

如果真实 store 的乐观更新已经维持了整个演示流程,接口可以只作本地确认;如果页面会重新查询,就要像示例一样更新本地模型,让后续读取反映修改。PUT、PATCH、DELETE 也按各自业务处理,不能把所有变更统一当成成功。

fixture 还要覆盖空列表、长标题、分页末页、加载和失败状态。返回一批整齐的短标题,只能证明最理想的画面没问题。

隔离存储和平台能力

沿用原平台接口,在演示构建中换成不会触碰真实资源的实现:

能力演示处理
用户、收藏、设置从演示初始数据恢复,修改仅影响演示状态
文件扫描返回虚构列表,不读取本机目录
文件选择、删除、真实下载返回明确的取消或不支持结果,不假装文件已处理
外部链接、登录阻止真实跳转,不读取账号 Cookie 或令牌
同步、日志、通知通道不启动轮询、WebSocket 或上传任务
媒体可演示队列和控制状态;真实播放另行验证,或使用随包提供的本地素材

存储可以使用类似 demo: 的前缀,但要检查是否与同源下其他应用冲突。IndexedDB、缓存名称也要隔离;重置只清理演示自己的键和数据库,不调用全局清空。

同源 Cookie 并不会因为换了 localStorage 前缀就被隔离。优先使用独立的演示 origin,并检查认证初始化和请求拦截器是否仍在读取真实凭据。

图片、字体、CSS 背景图和媒体同样会发请求。要真正离线,它们必须随产物提供,或使用本地占位资源;只替换 API 不够。

保留真实状态变化

演示环境复用真实 action:搜索条件改变、列表排序、选中条目、弹窗确认,都应产生原来定义的状态变化。

只关闭状态变化引发的外部副作用。例如媒体应用可以保留播放队列、上一项和下一项操作,但不解析在线媒体地址、不上传播放记录。

这时能验证的是控制状态和布局,不是解码、音频播放或锁屏续播。对于未实现的能力,给出明确提示,避免把按钮变色当作功能完成。

响应式问题也修在共享组件中,不给演示版增加一套专用 CSS。

构建和验收

根据项目现有工具执行正常构建、演示构建及相关测试。命令名称由仓库脚本决定,不假设一定存在 build:demo

通常使用本地静态服务器打开产物;不要默认双击 HTML 就能运行,模块加载和路由可能受 file:// 限制。服务端口先确认空闲,资源 base 与访问路径一致;使用 history 路由时还需配置页面回退。

检查项通过条件
共享代码演示使用真实路由、组件和状态逻辑,没有复制第二套页面
数据与交互搜索、筛选、变更后重新读取符合预期,重置后恢复初始状态
异常分支空数据、错误、取消、分页边界有对应表现
响应式手机、平板、桌面和横屏下无意外横向溢出,菜单、抽屉和弹窗可操作
请求隔离全流程没有业务 API、统计、同步、日志或外部媒体请求
存储隔离演示操作及重置不影响正常版本数据
正常构建演示开关关闭,原请求与平台实现仍正常接入

尺寸可以从 320×568375×667768×9001440×900 开始,再补项目断点附近的宽度。检查按钮点击范围、长文本和弹窗滚动,不只看首页截图。

Network 检查从页面加载前开始,覆盖导航、操作和等待后台任务触发的过程。自动化测试可以阻止非演示 origin 的请求,发现尝试外联就判失败;这种拦截是兜底,不能代替正确的适配实现。

最后禁用缓存,在阻断外网但保留本地静态服务访问的条件下重测。真实登录、权限、文件操作、媒体和后端同步,仍需要在各自环境单独验收。

后续增加页面时,先补对应接口和平台数据,再检查共享组件、交互及网络。原型与产品维护的是同一套页面,而不是两份逐渐分开的实现。

暂无评论

暂无评论,来添加第一条评论吧!