news 2026/9/30 8:20:32

前端页面写不好,往往不是Vue不会,而是设计文档没读懂

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端页面写不好,往往不是Vue不会,而是设计文档没读懂

前端页面写不好,往往不是Vue不会,而是设计文档没读懂

我第一次独立写前端页面是三年前。那时候刚学完 Vue2,看完文档觉得自己行了,领导让我做个"用户管理"列表页,我打开 IDE 就开始写el-table。

写了两小时,页面能跑,数据也能显示。我兴冲冲地发给前端同事看,他看了三十秒,问了我五个问题:

  • 这个页面在"只读角色"下是不是应该隐藏操作列?
  • 手机号字段后端返回的是带星号的,你这里为什么又 format 了一遍?
  • 删除之后是刷新当前页还是回到第一页?
  • 批量选择跨页保持吗?
  • 这个"状态"字段的字典值,你写死在前端还是从接口拿?

我一个都答不上来。不是因为我 Vue 写得烂,而是我根本没看过设计文档——那个项目压根就没有完整的前端页面设计文档,只有一份后端的接口清单。

那次返工了三轮。后来我慢慢明白一件事:后端转前端,最大的坎不是语法,是你不知道一个页面该有多少"看不见的约定"。这些约定全在设计文档里,而后端出身的人习惯性地跳过文档直接看接口。

这篇就讲讲我现在怎么读前端页面设计文档,以及飞算JavaAI的/前端开发是怎么把这些文档变成可运行页面的。

一、为什么后端转前端特别容易跳过设计文档

后端写代码有个天然优势:接口就是契约。给我一个 Swagger 地址,我能把整个 service 层写出来,因为入参出参类型都定死了,剩下的只是逻辑。

前端不是这样。前端的输入除了接口,还有一大堆"没写在接口里"的东西:

  • 这个字段在表格里要不要显示,宽度多少,超长怎么截断;
  • 这个按钮什么条件下禁用;
  • 这个页面从菜单进来和从详情页跳转进来,行为是不是一样;
  • 列表筛选条件要不要持久化到 URL;
  • 权限点到底是控制到按钮,还是控制到列。

这些东西接口文档里一个字都没有,全在前端页面设计文档里。后端转前端的人不知道有这份文档存在,或者知道但觉得"先写出来再说",于是就开始返工循环。

我现在的习惯是:接口文档决定我能不能调通,设计文档决定我能不能一次做对。两者的权重,设计文档还更高一些。

二、飞算JavaAI的前端开发,是被上游文档卡住的

这一点我第一次用的时候还挺意外。飞算JavaAI(https://www.feisuanyz.com)的/前端开发指令,入口是智能会话 → 添加指令 → 选/前端开发,但它不是你输了指令它就开写。

它依赖/前后端设计产出的一整套文档:工作区上下文、技术栈决策、数据库设计、接口设计、技术需求覆盖、后端设计规范基线。如果这些文件缺失,它会直接提示你回到/前后端设计去补齐,而不是硬着头皮生成一堆对不上的代码。

我记得有个纯后端项目,我直接跑/前端开发,它给我回了缺失提示,让我先把前端相关的设计文档补上。当时我嫌麻烦,后来发现这个阻断是对的——没有页面设计文档就生成页面,产出的东西跟随机拼凑没区别。

完整链路是这样的:

  1. /需求分析:产出需求文档 + 业务设计文档,落在项目 docs 目录;
  2. /前后端设计:产出数据库设计、接口设计、前端页面设计、技术栈决策等;
  3. /前端开发:严格依据前端页面设计文档生成代码,实现高保真 UI 与交互逻辑;
  4. npm install→npm run dev,跑起来看效果。

我特别认可第 2 步和第 3 步的分离。很多团队(包括我以前)是把"设计"和"写代码"混在一起的,边写边想,最后页面是出来了,但没人说得清它为什么长这样。拆开之后,设计文档成了可评审的交付物,我可以在评审阶段就砍掉不合理的地方,而不是等代码写完再改。

不过要说实话:生成的页面我只当作"第一版骨架"。它能把结构、字段、接口调用搭对,但细节上我每次都要改。这个后面单独说。

三、前端页面设计文档里,真正要看的五类信息

我把前端页面设计文档的信息分成五类。每次拿到文档,我按这个顺序过一遍,缺任何一类我都会退回要求补齐,而不是自己脑补。

类别具体要确认什么缺失时的后果
页面结构页面分区(筛选区/工具栏/表格/分页)、组件层级、路由路径与参数布局全靠猜,返工率最高
字段映射每个展示项对应哪个接口字段、格式化规则、字典来源、空值兜底字段名对不上,或格式二次加工出错
接口调用调用哪个接口、何时触发(挂载/点击/翻页)、并发与串行关系、失败重试请求打多次,或数据覆盖错乱
状态管理哪些状态放组件内、哪些进全局 store、哪些同步到 URL刷新丢状态,返回列表筛选条件没了
权限控制权限点编码、控制粒度(页面/按钮/列)、无权限时的表现(隐藏 or 禁用)越权可见,或者直接白屏

1. 页面结构

看这一块的时候,我重点看路由参数。比如一个编辑页/user/edit/:id,我就知道:必须处理 id 缺失的情况、必须支持从列表带 id 跳入、必须支持浏览器刷新。这些如果文档里没写路由,我八成会写成"从 store 里取当前选中行",然后一刷新页面就白屏——这个坑我踩过两次。

2. 字段映射

这是最琐碎但最容易出事的一类。我关心三点:

字段名是不是驼峰。后端 Java 是驼峰,但有些老接口返回下划线,前端转不转?必须在文档里定死,否则调试时满屏undefined。

格式化在哪一侧做。时间戳转日期,后端转还是前端转?我现在的约定是:后端返回原始值 + 前端统一格式化,但金额和小数位数由后端定(避免精度问题)。这个不写清楚,就会出现前端 format 了一遍已脱敏/已格式化的数据,显示成***或者2024-01-01 00:00:00的二次加工错误——就是开头同事问我的那个问题。

字典从哪来。状态、类型这类字段,字典是前端写死还是从/dict接口拉?我的原则是业务字典必须走接口,只有纯展示用的枚举(比如性别)才写死。写死的字典一旦后端改了枚举值,前端就显示空白。

3. 接口调用

除了"调哪个接口",我还会看触发时机和依赖关系。最典型的是级联下拉:省市区三级联动,是父级变了立刻清子级,还是保留旧值?这个文档里不写,前端就会做出"选了新的省,市还显示上一个省的数据"这种 bug。

4. 状态管理

我的判据很简单:需要跨页面共享或刷新后保留的,进 URL 或 store;只在当前组件内部流转的,放 local state。列表页的筛选条件我一律同步到 URL query,因为用户会复制链接发给同事,这个是管理后台的高频操作。

5. 权限控制

这块是安全事故高发区。我要求文档必须写清权限点编码和无权限时的表现。隐藏和禁用是两种完全不同的语义:隐藏意味着"不知道有这功能",禁用意味着"知道但不能用"。多数管理后台用隐藏,但涉及"申请开通"这类引导性操作时要用禁用。

我读文档的顺序,和一个反面案例

拿到一份前端页面设计文档,我现在的阅读顺序是固定的:权限 → 路由 → 字段 → 接口 → 状态。

为什么权限排第一?因为权限决定页面骨架。如果某个角色看不到工具栏,那你的组件树里可能压根不该渲染那一块,而不是渲染出来再隐藏。先读权限,能让你在搭结构的时候就少一层嵌套。路由排第二,因为它决定组件的入参和生命周期。字段和接口是填充物,放中间。状态最后看,因为它是在前四者都确定之后才浮现出来的东西。

反面案例说一个我自己的。去年做一个"工单详情"页,设计文档里写了"详情页支持从列表点击跳入",我照做了。但文档里还有一句"支持从邮件通知链接直接打开",我没细看,用的是从列表路由传参的方式拿工单 ID。结果运营同学从邮件点进来,页面一片空白,因为 URL 里没有那个参数,而我没做从 query 读取的兜底。

这个 bug 修起来只要五分钟——加一句route.query.id ?? route.params.id。但它在生产环境躺了三天,因为运营以为"工单系统是坏的",直接绕过去用微信找研发手工处理。事后复盘,问题不在我 Vue 写得不好,在于我跳过了文档里那一行不起眼的话。

从那以后我给自己定了个规矩:设计文档里每一句带"支持"两个字的话,都必须能在代码里找到对应实现。找不到的,要么补实现,要么回去问清楚为什么没做。

四、从设计文档到 Vue 组件:一个完整映射

光讲原则没意思,我拿一个"告警规则列表页"的真实文档片段演示一遍映射过程。

设计文档里的描述(简化后):

页面路径/alert/rule,权限点alert:rule:list。顶部为筛选区(规则名称模糊查询、状态下拉、启用开关);工具栏含"新建规则"(权限点alert:rule:add)与"批量删除"(权限点alert:rule:batchDel,需选中至少一行才可用);主体为表格,列包括规则名称、告警级别、状态、最近触发时间、创建人、操作;分页为服务端分页,默认 20 条;筛选条件同步至 URL query;状态列用字典alert_rule_status;无alert:rule:edit权限时操作列的编辑按钮隐藏。

我把这段拆成组件结构,是这么落的:

views/alert/rule/ ├── index.vue // 页面容器:拼装筛选区+工具栏+表格+分页 ├── components/ │ ├── RuleFilter.vue // 筛选区:受控组件,v-model 双向绑定 │ └── RuleTable.vue // 表格:纯展示,通过 props 收数据、emit 抛事件 ├── composables/ │ └── useRuleList.ts // 接口调用 + 分页 + URL 同步 └── types.ts // 与后端 VO 对齐的 TS 类型

关键的映射规则是:文档里的"区"对应组件,文档里的"状态"对应 composable。筛选区、表格是组件;分页页码、筛选条件、选中行是状态,统一进 composable,不散在组件里。

下面是useRuleList.ts的核心逻辑,注意 URL 同步那一段:

// composables/useRuleList.tsimport{ref,watch}from'vue'import{useRoute,useRouter}from'vue-router'import{fetchRulePage,typeRuleQuery,typeRuleVO}from'@/api/alert/rule'exportfunctionuseRuleList(){constroute=useRoute()constrouter=useRouter()// 筛选条件初始值从 URL query 恢复,保证刷新和分享链接都有效constquery=ref<RuleQuery>({name:(route.query.nameasstring)??'',status:(route.query.statusasstring)??'',enabled:route.query.enabled==='true',pageNum:Number(route.query.pageNum??1),pageSize:Number(route.query.pageSize??20),})constrows=ref<RuleVO[]>([])consttotal=ref(0)constloading=ref(false)asyncfunctionload(){loading.value=truetry{constres=awaitfetchRulePage(query.value)// 后端统一 Result<T> 包装,业务码非 0 时由拦截器抛出rows.value=res.data.items total.value=res.data.total}finally{loading.value=false}}// 筛选条件变化 → 同步回 URL,并重置到第一页watch(()=>({...query.value}),(val)=>{router.replace({query:{...val,pageNum:String(val.pageNum)}})load()},{deep:true})return{query,rows,total,loading,load}}

这里有两个点值得说。一是筛选条件变化必须重置 pageNum,否则你在第 5 页改了筛选条件,请求的还是第 5 页,很可能返回空列表,用户以为"查不到数据"。这个 bug 我见过太多次了。二是router.replace而不是push,避免用户点返回键时在筛选历史里绕不出来。

表格组件里,权限控制我用统一的指令处理,不要在模板里写v-if="hasPerm('xxx')"这种散落判断:

<!-- components/RuleTable.vue --> <template> <el-table :data="rows" v-loading="loading" @selection-change="onSelect"> <el-table-column type="selection" width="48" /> <el-table-column prop="ruleName" label="规则名称" min-width="180" show-overflow-tooltip /> <el-table-column prop="level" label="告警级别" width="100"> <template #default="{ row }"> <!-- 级别用字典渲染,字典来自全局字典 store --> <el-tag :type="levelTagType(row.level)">{{ dictLabel('alert_level', row.level) }}</el-tag> </template> </el-table-column> <el-table-column prop="status" label="状态" width="90"> <template #default="{ row }"> {{ dictLabel('alert_rule_status', row.status) }} </template> </el-table-column> <el-table-column prop="lastTriggerTime" label="最近触发" width="170"> <template #default="{ row }"> <!-- 后端返回毫秒时间戳,前端统一格式化;空值必须有兜底 --> {{ row.lastTriggerTime ? formatTime(row.lastTriggerTime) : '—' }} </template> </el-table-column> <el-table-column label="操作" width="140" fixed="right"> <template #default="{ row }"> <!-- 权限点控制到按钮级,无权限时隐藏而非禁用 --> <el-button v-perm="'alert:rule:edit'" link type="primary" @click="emit('edit', row)"> 编辑 </el-button> <el-button v-perm="'alert:rule:del'" link type="danger" @click="emit('delete', row)"> 删除 </el-button> </template> </el-table-column> </el-table> </template>

v-perm是个自定义指令,逻辑很简单——拿全局权限列表比对,不在列表里就直接remove()掉 DOM 节点。这样做的好处是权限判断只有一处实现,评审时也好检查:

// directives/perm.tsimporttype{Directive}from'vue'import{useUserStore}from'@/store/user'exportconstperm:Directive<HTMLElement,string>={mounted(el,binding){constcodes=useUserStore().permCodesif(!codes.includes(binding.value)){el.remove()// 直接移除,不留占位}},}

接口层我建议单独抽一个api/目录,并且类型直接由 OpenAPI 生成,不要手写。手写 TS 类型等于把接口契约抄了一遍,抄错的概率不低:

// api/alert/rule.ts —— 该文件由 openapi-generator 产出,不要手改importrequestfrom'@/utils/request'importtype{Result,PageResult,RuleVO,RuleQuery}from'./types'exportfunctionfetchRulePage(params:RuleQuery){returnrequest.get<Result<PageResult<RuleVO>>>('/api/v1/alert/rules',{params})}exportfunctiondeleteRules(ids:number[],idempotencyKey:string){returnrequest.post<Result<null>>('/api/v1/alert/rules/batch-delete',{ids},{headers:{'Idempotency-Key':idempotencyKey}})}

注意批量删除带了Idempotency-Key——这就是设计文档里"接口调用"那一类要写清的东西。如果文档没标,前端根本不知道要传这个头,后端也没强制校验,最后就是重复提交产生脏数据。

五、飞算JavaAI生成的页面,我每次都要改这几处

/前端开发生成的页面能跑,结构也对,但我每次都会改:

字典来源。它倾向于把状态、类型的中文映射直接写死在前端的 map 里。这在管理后台是隐患,我一律改成走全局字典接口。

权限粒度。它通常会做页面级和按钮级,但列级权限基本没有。涉及敏感字段(成本、手机号)的列,我手动加v-perm。

空值和超长兜底。生成的表格列经常没有show-overflow-tooltip,长文本会把行高撑开;空值显示成空白而不是—。这些都是小改,但很影响观感。

URL 同步。这一块它基本不管,筛选条件不会进 URL。我都会按上面的useRuleList模式重做一遍。

分页重置。前面说的"改筛选不重置页码",生成的代码里普遍存在。

改完这五处,页面才算能进提测。

六、一句话总结

Vue 语法三天能学完,"一个页面该有多少约定"要踩一年坑才补得齐;先读文档,再写代码,是后端转前端最省时间的一条路。

我的设计文档检查清单(拿到文档先过一遍,缺一项就退回):

  1. 页面路由路径和参数写了吗?刷新场景考虑了吗?
  2. 每个展示字段对应的接口字段名、格式化规则、空值兜底标了吗?
  3. 字典是走接口还是写死,写清楚了吗?
  4. 接口的触发时机、依赖关系、失败语义标了吗?
  5. 哪些状态进 store、哪些进 URL,分清楚了吗?
  6. 权限点编码给了吗?控制到按钮还是列?无权限是隐藏还是禁用?
  7. 分页默认值和"改条件是否重置页码"定了吗?

这七条能在文档阶段对齐,页面基本一次成型。对齐不了,就等着在联调阶段一条条吵回来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 8:18:09

1000万人口城市打车软件市场测算:从订单量到运力冷启动全攻略

做打车软件的人&#xff0c;最常被投资人问的一个问题就是&#xff1a;你的目标市场有多大。如果答案是一句“1000万人口的国内市场”&#xff0c;那基本上等于没回答。1000万人口只是一个起点&#xff0c;它要能拆出三层东西&#xff1a;到底有多少人愿意装你的App并真的下单、…

作者头像 李华
网站建设 2026/9/30 8:17:24

MySQL死锁测试与锁粒度优化:从压测复现到索引改造的完整复盘

做数据库死锁测试&#xff0c;绝大部分情况下都是被线上故障逼出来的&#xff0c;我这次也不例外。起因是压测环境并发一跑到128&#xff0c;死锁告警就开始刷屏&#xff0c;一个小时累计触发上百次&#xff0c;库存扣减和订单创建两类事务频繁回滚&#xff0c;应用连接池里塞满…

作者头像 李华
网站建设 2026/9/30 8:17:23

反向传播原理:从链式法则到梯度调试的硬核解析

1. 这门课不是讲“AI有多神奇”&#xff0c;而是拆解“AI凭什么能思考”“人工智能原理&#xff08;7&#xff09;”这个标题乍看平平无奇&#xff0c;像极了大学教务系统里一个被自动编号的课程代号——没有副标题&#xff0c;没有亮点提示&#xff0c;甚至没写清楚是第几版教…

作者头像 李华
网站建设 2026/9/30 8:16:39

云数据中心整体规划方案:从容量推导到网络存储的完整设计指南

简介&#xff1a;《云数据中心整体规划方案》演示文稿是一份面向政务云、教育云、警务云等场景的数据中心建设规划资料。内容以行业趋势研判为起点&#xff0c;对比传统数据中心与云数据中心在运营方式上的差异&#xff0c;引出软件定义数据中心理念&#xff0c;并重点展开计算…

作者头像 李华
网站建设 2026/9/30 8:16:36

Stable Diffusion Inpaint深度解析:局部重绘原理与图片修复实操

搞AI绘画这几年&#xff0c;Stable Diffusion&#xff08;简称SD&#xff09;已经成了我工作流里离不开的工具。不管是给电商图换背景、修老照片&#xff0c;还是做设计提案的创意探索&#xff0c;最常用到也最容易被忽视的一个功能&#xff0c;就是inpaint&#xff08;局部重绘…

作者头像 李华