1. 设计稿还原这件事,到底卡在哪
做前端的人对“还原了吗”这四个字应该都不陌生。设计师在蓝湖上标注好了间距、字号、色值,切图也上传了,然后甩过来一句“这个页面什么时候能还原完”。你打开设计稿,对着标注一个一个量,margin-left: 24px、font-size: 14px、line-height: 22px、color: #333333,写完之后本地跑起来截图对比,发现差了 2px,回去翻标注发现是padding不是margin。改完再截图,颜色又不对,因为设计稿用的是#333但项目里 SCSS 变量定义的是$text-primary: #2c2c2c。
这种来回拉扯的消耗,做过中后台系统或者 C 端页面的人应该都深有体会。一个中等复杂度的页面,从拿到设计稿到像素级还原,熟练工也得花上大半天,中间还要经历“截图-对比-修改-再截图”的循环。设计师那边也难受,他们不知道你改到哪一步了,只能隔一会儿问一句“还原了吗”,问多了你烦,不问他们又不放心。
我最近把 Cursor 和蓝湖通过 MCP 协议接了起来,整个流程变成了:在 Cursor 里选中设计稿对应的组件代码,AI 直接读取蓝湖上的标注数据,自动生成符合项目规范的 Vue 3 组件和 SCSS 样式。设计师在蓝湖上更新了设计稿,我这边重新拉一次标注就能同步。这篇文章就把这套方案的完整落地过程拆开讲清楚,包括 MCP 服务怎么部署、Cursor 怎么配置、Vue 3 + SCSS 的代码怎么生成、遇到问题怎么排查。如果你也在做前端还原,或者想了解 MCP 协议在实际项目里怎么用,这篇内容应该能帮你省下不少折腾的时间。
2. 整体方案设计与技术选型思路
2.1 为什么是 MCP 而不是直接调 API
蓝湖本身提供了开放平台 API,理论上你可以写个脚本去拉标注数据,然后手动或者半自动地生成代码。但这样做有几个问题:第一,你需要自己处理鉴权、分页、数据格式转换,写一堆胶水代码;第二,每次拉数据都要切换上下文,从编辑器跳到终端或者浏览器,思路容易断;第三,AI 没法直接感知到这些数据,你只能把拉下来的 JSON 复制粘贴给 AI,效率很低。
MCP 协议解决的就是这个问题。它的全称是 Model Context Protocol,你可以把它理解成 AI 和外部工具之间的一个标准接口。Cursor 作为 MCP 客户端,可以连接到实现了 MCP 协议的服务端,服务端负责去调蓝湖的 API 拿数据,然后把数据以 AI 能理解的方式暴露出来。这样你在 Cursor 里跟 AI 对话的时候,AI 就能直接读取蓝湖上的设计稿信息,不需要你手动搬运数据。
提示:MCP 是一个软件协议层面的概念,跟硬件协议没有关系。它的核心作用是让 AI 模型能够安全、可控地访问外部数据源和工具。
2.2 为什么选 Vue 3 + SCSS 这套组合
这个方案对技术栈其实没有硬性限制,React、Vue 2、甚至原生 HTML 都能做。我选 Vue 3 + SCSS 是因为当前项目本身就是这套技术栈,而且 Vue 3 的<script setup>语法写起来比较简洁,AI 生成代码的准确率也更高。SCSS 的好处是支持变量、嵌套、mixin,可以把设计稿里的色值、字号、间距统一管理起来,避免硬编码。
具体来说,我会在项目里维护一个_variables.scss文件,把蓝湖上常用的设计 token 定义成 SCSS 变量。AI 生成样式的时候,优先引用这些变量而不是直接写死数值。这样做的好处是,设计师改了主色调,我只需要改一个变量值,所有页面自动生效,不需要全局搜索替换。
2.3 整体数据流是怎样的
整个链路是这样的:蓝湖设计稿 → 蓝湖开放平台 API → MCP 服务端(本地运行)→ Cursor MCP 客户端 → AI 模型 → 生成 Vue 3 组件代码 + SCSS 样式。
MCP 服务端跑在本地,通过标准输入输出或者 SSE 跟 Cursor 通信。Cursor 在启动的时候会读取配置文件,自动拉起 MCP 服务。你在 Cursor 的 AI 对话框里输入指令,比如“帮我还原这个设计稿的卡片组件”,AI 就会通过 MCP 协议去调服务端暴露的工具方法,拿到标注数据,然后结合你项目里的代码规范生成对应的组件代码。
这套方案的核心优势在于:数据是实时拉取的,设计师更新了设计稿,你重新拉一次就能拿到最新标注;AI 直接读取结构化数据,不需要你手动转录;生成的代码可以按照你预设的模板和规范来,减少后期调整的工作量。
3. 核心细节解析与实操要点
3.1 蓝湖 MCP 服务怎么部署
蓝湖官方目前没有提供现成的 MCP 服务,需要自己写一个。我用的方案是基于 Node.js 写一个轻量级的 MCP 服务端,核心逻辑就是调蓝湖开放平台的接口拿标注数据,然后按照 MCP 协议的格式暴露给 Cursor。
先说一下蓝湖开放平台的鉴权方式。你需要在蓝湖的团队设置里生成一个访问令牌,然后每次调接口的时候在请求头里带上这个令牌。具体的接口文档蓝湖开放平台上有,核心接口就两个:一个是获取设计稿列表,一个是获取指定设计稿的标注详情。
MCP 服务端的代码结构大概是这样:
// mcp-server.js import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const server = new Server({ name: 'lanhu-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, }); // 注册工具:获取设计稿标注 server.setRequestHandler('tools/list', async () => ({ tools: [ { name: 'get_design_spec', description: '获取蓝湖设计稿的标注数据', inputSchema: { type: 'object', properties: { designId: { type: 'string', description: '设计稿 ID' }, pageName: { type: 'string', description: '页面名称' }, }, required: ['designId'], }, }, ], })); server.setRequestHandler('tools/call', async (request) => { if (request.params.name === 'get_design_spec') { const { designId, pageName } = request.params.arguments; const spec = await fetchLanhuSpec(designId, pageName); return { content: [{ type: 'text', text: JSON.stringify(spec, null, 2) }], }; } }); const transport = new StdioServerTransport(); await server.connect(transport);fetchLanhuSpec这个函数就是去调蓝湖的 API,拿到标注数据之后做一层清洗,把 AI 不需要的字段去掉,只保留图层名称、位置、尺寸、颜色、字体、间距这些关键信息。
注意:蓝湖 API 返回的数据结构比较深,图层是嵌套的。我在清洗数据的时候会把嵌套结构拍平,每个图层保留一个
path字段记录它的层级关系,这样 AI 理解起来更直观。
3.2 Cursor 这边怎么配置 MCP
Cursor 的 MCP 配置入口在设置里,找到 Features 下面的 MCP Servers,点添加。配置文件的格式是这样的:
{ "mcpServers": { "lanhu": { "command": "node", "args": ["/path/to/your/mcp-server.js"], "env": { "LANHU_TOKEN": "your_token_here" } } } }command填node,args填你 MCP 服务端脚本的绝对路径,env里把蓝湖的访问令牌传进去。保存之后 Cursor 会自动拉起这个服务,你可以在 MCP Servers 列表里看到它的状态,绿色圆点表示连接正常。
这里有个细节要注意:Cursor 启动 MCP 服务的时候,工作目录可能不是你项目所在的目录。所以脚本里如果用相对路径读文件,可能会找不到。我的做法是在脚本里用process.env或者__dirname来定位路径,避免踩这个坑。
配置好之后,你在 Cursor 的 AI 对话框里输入指令,AI 就能感知到这个 MCP 服务提供的工具。你可以直接说“帮我拉一下蓝湖上这个设计稿的标注”,AI 会问你设计稿 ID 和页面名称,然后调工具拿数据。
3.3 Vue 3 组件代码的生成规范怎么定
AI 生成代码的质量,很大程度上取决于你给的约束条件。我在项目根目录放了一个.cursorrules文件,里面写清楚了组件命名规范、目录结构、SCSS 变量引用规则、以及一些代码风格要求。
比如组件命名,我要求所有组件文件用 PascalCase,比如UserProfileCard.vue,而不是user-profile-card.vue。SCSS 类名用 BEM 风格,块名跟组件名保持一致。样式里引用变量而不是硬编码色值,比如color: $text-primary而不是color: #333。
.cursorrules里还可以写一些示例代码,让 AI 参考。比如我给了一个标准的卡片组件模板:
<template> <div class="user-profile-card"> <div class="user-profile-card__avatar"> <img :src="avatar" :alt="name" /> </div> <div class="user-profile-card__info"> <h3 class="user-profile-card__name">{{ name }}</h3> <p class="user-profile-card__desc">{{ description }}</p> </div> </div> </template> <script setup lang="ts"> interface Props { avatar: string; name: string; description: string; } defineProps<Props>(); </script> <style lang="scss" scoped> .user-profile-card { display: flex; align-items: center; padding: $spacing-md; background: $bg-white; border-radius: $radius-md; &__avatar { width: 48px; height: 48px; border-radius: 50%; overflow: hidden; } &__info { margin-left: $spacing-sm; } &__name { font-size: $font-size-lg; color: $text-primary; } &__desc { font-size: $font-size-sm; color: $text-secondary; } } </style>有了这个模板,AI 生成新组件的时候就会参照这个结构来,命名风格、变量引用、BEM 写法都能保持一致。
3.4 SCSS 变量体系怎么跟设计稿对齐
这一步是整个方案里最关键的。如果 SCSS 变量跟设计稿的标注对不上,AI 生成的代码还是得手动改。我的做法是先把蓝湖上常用的设计 token 整理出来,在项目里建一个styles/_variables.scss文件。
具体操作是:打开蓝湖的设计稿,把常用的色值、字号、间距、圆角、阴影这些属性列出来,然后定义成 SCSS 变量。比如:
// 颜色 $text-primary: #1f1f1f; $text-secondary: #666666; $text-placeholder: #999999; $bg-white: #ffffff; $bg-gray: #f5f5f5; $border-color: #e0e0e0; $primary-color: #1890ff; // 字号 $font-size-xs: 12px; $font-size-sm: 14px; $font-size-md: 16px; $font-size-lg: 18px; $font-size-xl: 24px; // 间距 $spacing-xs: 4px; $spacing-sm: 8px; $spacing-md: 16px; $spacing-lg: 24px; $spacing-xl: 32px; // 圆角 $radius-sm: 4px; $radius-md: 8px; $radius-lg: 12px;定义好之后,在.cursorrules里告诉 AI:生成样式时优先使用这些变量,如果设计稿的标注值跟变量值有偏差,以变量值为准,并在注释里说明偏差。这样做的好处是,整个项目的视觉风格统一,不会出现这个页面用#333那个页面用#2c2c2c的情况。
提示:如果设计稿的标注值跟变量值偏差超过 2px 或者色差肉眼可见,建议先跟设计师确认是不是设计稿更新了但变量没同步,不要直接硬编码。
4. 实操过程与核心环节实现
4.1 从蓝湖拉取标注数据的完整流程
假设设计师在蓝湖上给了一个新的页面设计稿,我需要还原成 Vue 3 组件。第一步是拿到设计稿的 ID。打开蓝湖的设计稿页面,URL 里有一串 ID,比如https://lanhuapp.com/web/#/item/project/detail?pid=xxxxx,这个pid就是设计稿 ID。
拿到 ID 之后,在 Cursor 的 AI 对话框里输入:
帮我拉取蓝湖设计稿 pid=xxxxx 的标注数据,页面名称是"用户列表"AI 会通过 MCP 服务去调蓝湖的 API,把标注数据拉下来。返回的数据大概长这样:
{ "pageName": "用户列表", "layers": [ { "name": "卡片容器", "type": "frame", "x": 0, "y": 0, "width": 375, "height": 200, "backgroundColor": "#ffffff", "borderRadius": 8, "padding": { "top": 16, "right": 16, "bottom": 16, "left": 16 } }, { "name": "头像", "type": "image", "x": 16, "y": 16, "width": 48, "height": 48, "borderRadius": 24 }, { "name": "用户名", "type": "text", "x": 80, "y": 20, "width": 100, "height": 22, "fontSize": 16, "fontWeight": 500, "color": "#1f1f1f", "content": "张三" } ] }拿到数据之后,我会让 AI 先分析一下图层结构,确认它理解得对不对。比如我会问:“这个设计稿的布局方式是什么?用 flex 还是 grid 更合适?”AI 会根据图层的位置和尺寸关系给出建议。这一步很重要,因为如果 AI 对布局的理解有偏差,后面生成的代码结构就会有问题。
4.2 生成 Vue 3 组件代码的实操记录
确认布局方式之后,我让 AI 生成组件代码。指令大概是这样的:
根据刚才拉取的标注数据,生成一个 Vue 3 组件,组件名是 UserListCard,使用 script setup 语法,样式用 SCSS,引用项目里的变量文件。AI 生成的代码:
<template> <div class="user-list-card"> <div class="user-list-card__avatar"> <img :src="avatar" :alt="name" /> </div> <div class="user-list-card__content"> <h3 class="user-list-card__name">{{ name }}</h3> <p class="user-list-card__desc">{{ description }}</p> </div> </div> </template> <script setup lang="ts"> interface Props { avatar: string; name: string; description: string; } defineProps<Props>(); </script> <style lang="scss" scoped> .user-list-card { display: flex; align-items: flex-start; padding: $spacing-md; background: $bg-white; border-radius: $radius-md; &__avatar { width: 48px; height: 48px; border-radius: 50%; overflow: hidden; flex-shrink: 0; img { width: 100%; height: 100%; object-fit: cover; } } &__content { margin-left: $spacing-md; flex: 1; } &__name { font-size: $font-size-md; font-weight: 500; color: $text-primary; line-height: 22px; } &__desc { margin-top: $spacing-xs; font-size: $font-size-sm; color: $text-secondary; line-height: 20px; } } </style>这段代码基本可以直接用,但有几个细节我做了调整。第一,设计稿里头像和内容的间距是 16px,我用了$spacing-md,正好对应。第二,用户名的行高设计稿标注是 22px,我直接写了line-height: 22px,因为 SCSS 变量里没有定义行高变量,这种一次性的值可以直接写。第三,描述文字的行高是 20px,同样直接写。
实操心得:AI 生成的代码不要直接复制粘贴就用,一定要过一遍。重点检查三个方面:布局方式是否符合预期、变量引用是否正确、边界情况有没有处理。比如头像加载失败的时候显示什么,文字过长的时候怎么截断,这些 AI 不一定能考虑到。
4.3 样式还原的精度校验方法
代码写完之后,怎么确认还原精度?我的做法是在本地跑起来,用浏览器的开发者工具截图,然后跟蓝湖的设计稿做对比。具体操作是:在 Chrome 里打开页面,按 F12 打开开发者工具,用设备模拟器切成设计稿的尺寸(比如 375px 宽),然后截图。把截图和蓝湖的设计稿并排放在一起,肉眼对比。
如果发现偏差,我会用开发者工具的元素检查功能,看具体是哪个属性不对。比如间距差了 4px,就去看是margin还是padding的问题。颜色不对,就用取色器吸一下实际渲染的颜色,跟设计稿的色值对比。
这里有个小技巧:在 Chrome 开发者工具里,选中元素之后按Ctrl+Shift+C可以快速定位到对应的 DOM 节点,然后在 Styles 面板里直接修改样式值,实时看效果。确认没问题之后,再把修改同步回代码里。
对于精度要求高的项目,我会用 Playwright 写一个截图对比脚本,自动截取页面截图跟设计稿做像素级对比。不过这个方案搭建成本比较高,适合对还原精度要求极高的场景。日常开发用肉眼对比加开发者工具微调就够了。
4.4 设计师更新设计稿后的同步流程
设计师改了设计稿之后,我这边不需要重新生成整个组件,只需要让 AI 对比新旧标注数据的差异,然后针对性地修改代码。具体操作是:重新拉一次标注数据,然后跟 AI 说:
这是更新后的标注数据,跟之前的对比一下,告诉我哪些地方变了,然后修改对应的代码。AI 会对比两份数据,找出差异点,比如某个间距从 16px 变成了 12px,某个色值从#333变成了#666,然后定位到代码里对应的位置进行修改。这个流程比重新生成整个组件要高效得多,而且不会引入不必要的改动。
注意:如果设计稿的改动比较大,比如布局结构变了,那还是重新生成组件比较稳妥。差异对比适合微调场景,结构性的改动还是走完整流程。
5. 常见问题与排查技巧实录
5.1 MCP 服务连接失败的排查思路
最常见的问题是 Cursor 启动之后 MCP 服务显示红色,连接不上。排查步骤是这样的:
第一,检查 Node.js 版本。MCP SDK 对 Node.js 版本有要求,建议用 18 以上的版本。在终端里跑node -v确认一下。
第二,检查脚本路径。Cursor 配置里的args必须是绝对路径,相对路径可能会因为工作目录的问题找不到文件。在终端里手动跑一下node /path/to/mcp-server.js,看能不能正常启动。
第三,检查环境变量。蓝湖的访问令牌如果没传进去,服务启动的时候可能不会报错,但调接口的时候会返回 401。在脚本里加一行console.error(process.env.LANHU_TOKEN)确认令牌有没有读到。
第四,看 Cursor 的日志。Cursor 的 MCP 日志在输出面板里可以找到,切换到 MCP 相关的频道,能看到服务启动的详细日志和报错信息。
5.2 标注数据解析出来对不上的情况
有时候拉下来的标注数据跟设计稿上看到的不一致,比如某个图层的尺寸对不上,或者颜色值有偏差。这种情况通常是以下几个原因:
一是设计稿有多个版本,你拉的是旧版本的数据。确认一下设计稿 ID 是不是最新的。
二是图层被锁定了或者隐藏了,蓝湖 API 默认可能不返回隐藏图层的数据。在设计稿里把图层解锁并显示出来。
三是标注数据的单位问题。蓝湖的标注数据默认是 px,但如果你在设计稿里用了其他单位,API 返回的值可能需要转换。检查一下设计稿的设置。
四是颜色值的格式问题。蓝湖返回的颜色可能是 RGB 格式,也可能是 HEX 格式,需要统一转换。我在 MCP 服务里做了一层处理,统一转成 HEX 格式再返回给 AI。
5.3 AI 生成的代码不符合项目规范怎么办
这个问题通常是因为.cursorrules文件写得不够具体。AI 对规范的理解依赖于你给的示例和约束条件。如果生成的代码命名风格不对,就在.cursorrules里明确写清楚命名规则,并给出正例和反例。如果变量引用不对,就把变量文件的路径和可用变量列表写进去。
另外,Cursor 的 AI 模型对上下文的理解能力有限,如果项目很大,它可能记不住所有的规范。我的做法是把最常用的规范放在.cursorrules的前面,确保 AI 优先读到。不常用的规范可以放到单独的文档里,需要的时候手动提醒 AI。
还有一个技巧是在对话里直接给 AI 反馈。比如它生成的代码用了#333而不是$text-primary,你就说“样式里不要硬编码色值,用 SCSS 变量”,AI 会记住这个反馈并在后续生成中调整。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| MCP 服务显示红色 | Node 版本过低 | 终端跑node -v | 升级到 Node 18+ |
| MCP 服务显示红色 | 脚本路径错误 | 手动跑node 脚本路径 | 改用绝对路径 |
| 调接口返回 401 | 令牌未传入 | 打印process.env.LANHU_TOKEN | 检查 Cursor 配置的 env 字段 |
| 标注数据对不上 | 设计稿版本旧 | 确认设计稿 ID | 用最新版本的设计稿 ID |
| 标注数据对不上 | 图层被隐藏 | 在设计稿里检查图层状态 | 解锁并显示图层 |
| 颜色值格式不对 | API 返回 RGB | 打印原始数据 | 在 MCP 服务里统一转 HEX |
| 代码命名不规范 | .cursorrules不具体 | 检查规则文件 | 补充命名规则和示例 |
| 变量引用错误 | 变量文件未告知 AI | 检查.cursorrules | 写入变量文件路径和变量列表 |
| 布局方式不对 | AI 理解偏差 | 先让 AI 分析布局 | 确认后再生成代码 |
| 样式精度不够 | 未做截图对比 | 开发者工具截图对比 | 用元素检查微调 |
5.5 几个踩过的坑和避坑建议
第一个坑是蓝湖 API 的调用频率限制。蓝湖开放平台对接口调用有频率限制,短时间内大量请求可能会被限流。我的做法是在 MCP 服务里加一个简单的缓存,同一个设计稿 ID 的标注数据缓存 5 分钟,避免重复请求。
第二个坑是 MCP 服务的日志输出。MCP 协议通过标准输入输出通信,如果你在脚本里用console.log打印调试信息,可能会干扰协议通信。调试信息应该用console.error输出到标准错误流,这样不会影响 MCP 的正常通信。
第三个坑是 AI 生成的代码里图片路径问题。设计稿里的图片在蓝湖上是一个 URL,但项目里通常会把图片下载到本地。AI 生成代码的时候可能会直接用蓝湖的 URL,你需要手动替换成本地路径。我的做法是在.cursorrules里约定图片路径的格式,让 AI 生成占位路径,然后手动替换。
第四个坑是 SCSS 变量的作用域问题。如果变量文件没有在组件里正确引入,编译的时候会报变量未定义的错误。Vue 3 项目里通常用@use或者@import引入变量文件,需要在vite.config.js里配置additionalData,让每个 SCSS 文件自动引入变量。
// vite.config.js export default defineConfig({ css: { preprocessorOptions: { scss: { additionalData: `@use "@/styles/variables" as *;`, }, }, }, });这样配置之后,组件里的 SCSS 就可以直接用变量,不需要每个文件都手动引入。
6. 这套方案的实际效果和后续扩展
用这套方案跑了大概两周,最直观的感受是还原一个中等复杂度页面的时间从原来的 3-4 小时压缩到了 1 小时左右。省下来的时间主要是在“量标注”和“写样式”这两个环节,AI 直接读数据生成代码,我只需要做校验和微调。设计师那边也不再频繁追问“还原了吗”,因为我在蓝湖上更新设计稿之后,重新拉一次数据就能同步,他们能看到进度。
后续我打算在这几个方向继续优化:一是把截图对比做成自动化,用 Playwright 定时跑截图任务,跟设计稿做像素级 diff,有偏差自动告警;二是把常用的组件模板沉淀到.cursorrules里,让 AI 生成的代码更贴近项目风格;三是探索把 MCP 服务部署到团队内部,让其他前端同事也能用上这套流程,减少重复配置的成本。
如果你也在做前端还原,建议先从一个小页面开始试,把 MCP 服务和 Cursor 配置跑通,感受一下整个流程。跑通之后再逐步把 SCSS 变量体系和代码规范补全,效果会越来越明显。遇到问题优先看 Cursor 的 MCP 日志和蓝湖 API 的返回数据,大部分问题都能从日志里找到线索。