☰
Cursor + 蓝湖 + MCP:Vue 3 设计稿自动还原实战
2026/11/2 9:03:27 网站建设 项目流程

1. 这套方案到底解决了什么问题

设计师追着问“还原了吗”,大概是每个前端都经历过的日常。页面刚联调完,设计稿改了一版,颜色从#3B82F6调成了#2563EB,间距从 16px 改成了 20px,圆角从 8px 变成了 12px。这些改动散落在蓝湖的标注里,如果没人主动同步,前端根本不知道。等设计师来问的时候,往往已经过去了两三天,返工成本极高。

我这次做的事情,本质上是把Cursor这个 AI 编程工具和蓝湖这个设计协作平台打通,让 AI 能够直接读取设计稿的标注信息,自动生成或修正 Vue 3 + SCSS 的样式代码。核心桥梁是MCP(Model Context Protocol),一个让 AI 工具能够调用外部服务的协议层。说白了,就是给 Cursor 装了一双能看蓝湖的眼睛。

这套方案适合几类人:一是中小团队的前端,没有专职的 Design Token 维护流程,设计稿和代码之间靠人肉同步;二是正在用 Vue 3 + SCSS 技术栈,希望把设计还原这件事半自动化的开发者;三是对 MCP 协议感兴趣,想找一个真实场景练手的工程师。如果你用的是 React 或者 Tailwind,思路完全一样,只是生成的代码模板需要调整。

我踩过的坑不少,从 MCP 服务部署到 Cursor 配置,再到 SCSS 变量命名冲突,前后折腾了大概一周。下面把完整的思路、操作步骤和避坑经验都摊开讲。

2. 整体方案设计与技术选型思路

2.1 为什么是 MCP 而不是直接调 API

蓝湖本身有 Open API,理论上你可以写一个脚本,定时拉取设计稿的标注数据,然后手动粘贴到代码里。但这样做的问题是:AI 不知道你拉了什么。你还是在用传统的方式做数据搬运,Cursor 只是一个被动的代码编辑器。

MCP 的价值在于,它让 Cursor 的 AI Agent 能够主动调用外部工具。当我在 Cursor 里说“帮我把这个页面的样式和蓝湖设计稿对齐”,AI 会自己去调用蓝湖 MCP 服务,拉取对应画板的标注数据,然后对比当前代码,生成修改建议。整个过程不需要我手动复制任何数据。

从技术架构上看,链路是这样的:

Cursor (AI Agent) → MCP Client → MCP Server (蓝湖适配层) → 蓝湖 Open API → 设计稿数据

MCP Server 是我自己写的一个中间层,负责把蓝湖 API 返回的数据转换成 MCP 协议规定的格式。这样做的好处是,Cursor 不需要知道蓝湖 API 的具体细节,只需要按照 MCP 协议调用工具即可。

2.2 技术栈选型与版本锁定

这套方案涉及的技术栈不算复杂,但版本兼容性需要特别注意:

技术组件版本选型理由
Cursor0.45+需要支持 MCP 协议的版本,早期版本没有这个功能
Node.js20 LTSMCP Server 的运行环境,18 也可以但 20 更稳
Vue 33.4+Composition API 和<script setup>是标配
SCSS1.70+需要支持@use语法,避免@import的全局污染
蓝湖 Open APIv2需要申请开发者权限,拿到 App Key 和 Secret
MCP SDK最新官方提供的 TypeScript SDK,用来快速搭建 Server

这里重点说一下 SCSS 的版本。很多老项目还在用@import,但@import的问题是所有变量都是全局的,AI 生成的代码很容易和现有变量冲突。我强烈建议迁移到@use和@forward,每个模块的变量作用域是独立的,AI 生成代码时不容易搞乱。

2.3 蓝湖 MCP 服务的部署方式选择

蓝湖 MCP 服务有两种部署方式:本地部署和远程部署。

本地部署就是把 MCP Server 跑在你自己的开发机上,Cursor 通过 stdio 和它通信。优点是数据不经过第三方服务器,安全性高;缺点是每台开发机都要配一遍,团队协作时比较麻烦。

远程部署是把 MCP Server 部署到一台内网服务器上,Cursor 通过 SSE 或 WebSocket 连接。优点是团队共用一套服务,配置一次就行;缺点是需要处理鉴权和网络问题。

我最终选了本地部署 + 团队共享配置模板的方案。原因是:蓝湖的 App Secret 权限很高,放在服务器上需要额外的密钥管理方案,小团队没必要搞这么复杂。本地部署虽然每台机器要配,但我把配置写成了一个脚本,新同事入职跑一下就行。

注意:蓝湖 Open API 的调用频率有限制,免费版大概是每分钟 60 次。如果团队人多,建议在 MCP Server 里加一层缓存,同一个画板的标注数据 5 分钟内不重复请求。

3. 核心细节解析与实操要点

3.1 蓝湖 Open API 的鉴权与数据获取

蓝湖 Open API 的鉴权流程分两步:先用 App Key 和 Secret 换 access token,再用 token 调具体接口。token 的有效期是 2 小时,过期需要重新获取。

获取 token 的请求大概长这样:

const response = await fetch('https://openapi.lanhuapp.com/v1/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ client_id: process.env.LANHU_APP_KEY, client_secret: process.env.LANHU_APP_SECRET, grant_type: 'client_credentials' }) }); const { access_token } = await response.json();

拿到 token 之后,调设计稿标注接口:

const designData = await fetch( `https://openapi.lanhuapp.com/v1/projects/${projectId}/boards/${boardId}/layers`, { headers: { Authorization: `Bearer ${access_token}` } } );

返回的数据结构是一个嵌套的图层树,每个图层包含name、type、style等字段。style里面就是我们要的颜色、字号、间距、圆角这些信息。

这里有个坑:蓝湖返回的颜色是 RGBA 格式,而且 alpha 通道经常是 1。比如rgba(59, 130, 246, 1),你需要自己转成十六进制#3B82F6。我写了一个转换函数,处理了各种边界情况:

function rgbaToHex(rgba) { const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); if (!match) return rgba; const [, r, g, b, a] = match; const hex = [r, g, b] .map(x => parseInt(x).toString(16).padStart(2, '0')) .join(''); return `#${hex.toUpperCase()}`; }

3.2 MCP Server 的工具定义与参数设计

MCP 协议的核心是工具(Tool)。每个工具就是一个函数,AI 可以根据你的自然语言描述,决定调用哪个工具、传什么参数。

我为蓝湖 MCP Server 定义了三个工具:

工具一:get_board_layers

获取指定画板的所有图层信息。参数包括projectId、boardId,可选layerName用于过滤特定图层。

工具二:get_layer_style

获取单个图层的详细样式。参数是layerId,返回该图层的颜色、字体、间距、圆角等完整样式。

工具三:compare_with_code

对比设计稿样式和当前代码中的样式,返回差异列表。参数是layerId和codeSnippet,这个工具需要 AI 先把当前代码传进来。

工具定义用 MCP SDK 写起来很简洁:

server.tool( 'get_board_layers', '获取蓝湖画板的所有图层信息', { projectId: z.string().describe('蓝湖项目 ID'), boardId: z.string().describe('画板 ID'), layerName: z.string().optional().describe('可选,过滤特定图层名称') }, async ({ projectId, boardId, layerName }) => { const layers = await fetchLayers(projectId, boardId); const filtered = layerName ? layers.filter(l => l.name.includes(layerName)) : layers; return { content: [{ type: 'text', text: JSON.stringify(filtered) }] }; } );

提示:工具的描述文字很关键。AI 是根据描述来决定调用哪个工具的。描述要写清楚“这个工具做什么”、“什么时候用”、“参数是什么意思”。我一开始描述写得太简略,AI 经常调错工具。

3.3 Vue 3 + SCSS 的代码生成模板设计

AI 生成的代码质量,很大程度上取决于你给的模板。我设计了一套 SCSS 变量命名规范,让 AI 按照这个规范生成代码。

命名规范是这样的:

// 颜色变量 $color-primary: #3B82F6; $color-text-primary: #1F2937; $color-text-secondary: #6B7280; $color-bg-page: #F9FAFB; // 间距变量 $spacing-xs: 4px; $spacing-sm: 8px; $spacing-md: 16px; $spacing-lg: 24px; $spacing-xl: 32px; // 圆角变量 $radius-sm: 4px; $radius-md: 8px; $radius-lg: 12px; $radius-full: 9999px;

然后在 Cursor 的提示词里,我把这套规范写进去:

生成 SCSS 样式时,请遵循以下规则: 1. 颜色使用 $color- 前缀的变量,不要直接写十六进制值 2. 间距使用 $spacing- 前缀的变量,优先使用 md 和 lg 3. 圆角使用 $radius- 前缀的变量 4. 如果设计稿中的值没有对应的变量,先生成一个新变量,命名格式为 $color-{语义名} 5. 使用 @use 而不是 @import

这样 AI 生成的代码就是这样的:

<template> <div class="card"> <h3 class="card__title">{{ title }}</h3> <p class="card__desc">{{ description }}</p> </div> </template> <style lang="scss" scoped> @use '@/styles/variables' as *; .card { padding: $spacing-lg; background: $color-bg-page; border-radius: $radius-md; &__title { color: $color-text-primary; font-size: 18px; margin-bottom: $spacing-sm; } &__desc { color: $color-text-secondary; font-size: 14px; line-height: 1.6; } } </style>

这套模板不是一次成型的。我改了大概五六版,才让 AI 稳定输出符合团队规范的代码。关键是要把“什么情况下用什么变量”的决策逻辑写清楚,而不是只给一堆变量名。

4. 完整实操流程与关键环节实现

4.1 环境准备与蓝湖 API 权限申请

第一步是申请蓝湖 Open API 权限。登录蓝湖后,进入「团队设置」→「开放平台」→「创建应用」,填写应用名称和回调地址(本地开发填http://localhost:3000/callback就行)。创建完成后会拿到App Key和App Secret,这两个值只显示一次,务必保存好。

第二步是初始化项目:

mkdir lanhu-mcp-server && cd lanhu-mcp-server npm init -y npm install @modelcontextprotocol/sdk zod dotenv npm install -D typescript @types/node tsx

第三步是配置环境变量,创建.env文件:

LANHU_APP_KEY=your_app_key_here LANHU_APP_SECRET=your_app_secret_here LANHU_API_BASE=https://openapi.lanhuapp.com/v1

注意:.env文件一定要加到.gitignore里。我见过有人把 App Secret 提交到公开仓库,结果被人刷了几万次 API 调用。

4.2 MCP Server 的完整实现

MCP Server 的入口文件src/index.ts大概长这样:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { getAccessToken, fetchLayers, fetchLayerDetail } from './lanhu-api.js'; const server = new McpServer({ name: 'lanhu-mcp', version: '1.0.0' }); // 工具一:获取画板图层 server.tool( 'get_board_layers', '获取蓝湖画板的所有图层信息,返回图层名称、类型和 ID 列表', { projectId: z.string().describe('蓝湖项目 ID,从项目 URL 中获取'), boardId: z.string().describe('画板 ID,从画板 URL 中获取') }, async ({ projectId, boardId }) => { const token = await getAccessToken(); const layers = await fetchLayers(token, projectId, boardId); const simplified = layers.map(l => ({ id: l.id, name: l.name, type: l.type })); return { content: [{ type: 'text', text: JSON.stringify(simplified, null, 2) }] }; } ); // 工具二:获取图层详细样式 server.tool( 'get_layer_style', '获取指定图层的详细样式信息,包括颜色、字体、间距、圆角等', { projectId: z.string().describe('蓝湖项目 ID'), layerId: z.string().describe('图层 ID') }, async ({ projectId, layerId }) => { const token = await getAccessToken(); const detail = await fetchLayerDetail(token, projectId, layerId); const style = { color: rgbaToHex(detail.style.color), fontSize: `${detail.style.fontSize}px`, fontWeight: detail.style.fontWeight, padding: detail.style.padding, margin: detail.style.margin, borderRadius: `${detail.style.borderRadius}px`, backgroundColor: rgbaToHex(detail.style.backgroundColor) }; return { content: [{ type: 'text', text: JSON.stringify(style, null, 2) }] }; } ); // 工具三:对比设计稿与代码 server.tool( 'compare_with_code', '对比蓝湖设计稿样式和当前代码中的样式,返回差异列表', { projectId: z.string().describe('蓝湖项目 ID'), layerId: z.string().describe('图层 ID'), codeSnippet: z.string().describe('当前代码中的样式片段') }, async ({ projectId, layerId, codeSnippet }) => { const token = await getAccessToken(); const designStyle = await fetchLayerDetail(token, projectId, layerId); const diffs = compareStyles(designStyle, codeSnippet); return { content: [{ type: 'text', text: JSON.stringify(diffs, null, 2) }] }; } ); const transport = new StdioServerTransport(); await server.connect(transport);

compareStyles函数是核心逻辑,它把设计稿的样式值和代码中的样式值做对比,返回不一致的项:

function compareStyles(designStyle, codeSnippet) { const diffs = []; const codeColor = extractColor(codeSnippet); const designColor = rgbaToHex(designStyle.style.color); if (codeColor && codeColor.toUpperCase() !== designColor.toUpperCase()) { diffs.push({ property: 'color', design: designColor, code: codeColor, suggestion: `将 color 改为 ${designColor}` }); } const codePadding = extractPadding(codeSnippet); const designPadding = designStyle.style.padding; if (codePadding && codePadding !== designPadding) { diffs.push({ property: 'padding', design: designPadding, code: codePadding, suggestion: `将 padding 改为 ${designPadding}` }); } return diffs; }

4.3 Cursor 的 MCP 配置与提示词调优

Cursor 的 MCP 配置在~/.cursor/mcp.json(全局)或项目根目录的.cursor/mcp.json(项目级)。我建议用项目级配置,这样团队每个人拉代码后自动生效。

配置内容:

{ "mcpServers": { "lanhu": { "command": "npx", "args": ["tsx", "/path/to/lanhu-mcp-server/src/index.ts"], "env": { "LANHU_APP_KEY": "your_app_key", "LANHU_APP_SECRET": "your_app_secret" } } } }

配好之后重启 Cursor,在聊天窗口输入/mcp应该能看到lanhu服务已经连接。如果显示红色,说明启动失败,检查 Node.js 版本和路径是否正确。

接下来是提示词调优。我在项目根目录建了一个.cursorrules文件,把代码规范写进去:

你是一个 Vue 3 + SCSS 的前端开发助手。 当用户要求还原设计稿时: 1. 先调用 get_board_layers 获取画板图层列表 2. 根据图层名称找到对应的元素 3. 调用 get_layer_style 获取详细样式 4. 对比当前代码,生成修改建议 5. 修改代码时使用 SCSS 变量,不要硬编码颜色和间距 SCSS 变量规范: - 颜色:$color-{语义},如 $color-primary、$color-text-primary - 间距:$spacing-{尺寸},如 $spacing-sm、$spacing-md、$spacing-lg - 圆角:$radius-{尺寸},如 $radius-sm、$radius-md、$radius-lg 代码风格: - 使用 <script setup> 语法 - 样式使用 scoped - 使用 @use 引入变量文件 - BEM 命名规范

这套提示词我调了大概十几次。最关键的一条是“先调用 get_board_layers 获取画板图层列表”,如果不写这句,AI 有时候会跳过这一步,直接凭猜测生成代码。

4.4 实际还原流程演示

假设设计师在蓝湖上更新了一个卡片组件的样式,我需要把代码同步过来。操作流程是这样的:

第一步,在 Cursor 聊天窗口输入:

帮我还原蓝湖项目 12345 中画板 67890 的卡片样式, 当前代码在 src/components/Card.vue

第二步,Cursor 的 AI 会自动调用get_board_layers,返回图层列表:

[ { "id": "layer_001", "name": "卡片容器", "type": "FRAME" }, { "id": "layer_002", "name": "标题", "type": "TEXT" }, { "id": "layer_003", "name": "描述文字", "type": "TEXT" } ]

第三步,AI 根据图层名称匹配到Card.vue中的元素,然后调用get_layer_style获取每个图层的样式。

第四步,AI 对比当前代码和设计稿样式,生成修改建议:

检测到以下差异: 1. 卡片容器背景色:当前 #FFFFFF,设计稿 #F9FAFB 2. 卡片容器圆角:当前 8px,设计稿 12px 3. 标题颜色:当前 #111827,设计稿 #1F2937 4. 描述文字行高:当前 1.4,设计稿 1.6

第五步,我确认后,AI 自动修改代码。整个过程大概 30 秒,比手动对照标注快得多。

实操心得:AI 匹配图层和元素时偶尔会出错,特别是图层命名不规范的时候。建议设计师在蓝湖上把图层命名规范化,比如“卡片容器”、“标题”、“描述文字”,而不是“Frame 1”、“Text 2”。这个沟通成本值得花。

5. 常见问题与排查技巧实录

5.1 MCP 服务连接失败的排查思路

这是最常见的问题。Cursor 里/mcp显示红色,或者调用工具时报错“tool not found”。

排查顺序是这样的:

第一,检查 Node.js 版本。MCP SDK 要求 Node.js 18 以上,我遇到过有人用 16 版本,启动直接报错。在终端跑node -v确认。

第二,检查路径。mcp.json里的args路径必须是绝对路径,不能用~或相对路径。我一开始写了./src/index.ts,Cursor 找不到文件。

第三,检查环境变量。如果LANHU_APP_KEY没配好,MCP Server 启动时会报错,但 Cursor 只显示连接失败,不显示具体错误。建议先在终端手动跑一遍:

LANHU_APP_KEY=xxx LANHU_APP_SECRET=xxx npx tsx src/index.ts

看有没有报错信息。

第四,检查端口冲突。如果用 SSE 模式,默认端口是 3000,可能被其他服务占用。换成 3001 或 8080 试试。

5.2 蓝湖 API 返回数据异常的常见原因

蓝湖 API 返回的数据有时候不太规范,我遇到过几种情况:

问题现象可能原因解决方法
颜色返回rgba(0,0,0,0)图层没有设置背景色跳过该属性,不要生成样式
间距返回"auto"使用了自动布局读取父容器的间距设置
字体大小返回0图层是容器类型只对 TEXT 类型图层读取字体
图层列表为空画板 ID 错误或权限不足检查 token 是否过期,确认应用有权限
返回数据乱码编码问题确保请求头带Accept: application/json

其中“间距返回 auto”这个坑我踩得最惨。蓝湖的自动布局(Auto Layout)会把子元素的间距交给父容器管理,子元素的padding和margin返回的是auto。这时候需要往上找父容器的itemSpacing属性。

5.3 AI 生成代码不符合预期的调优方法

AI 生成的代码有时候会跑偏,比如硬编码颜色、用错变量名、忘记加 scoped。我总结了几个调优方法:

方法一:在.cursorrules里加负面示例。告诉 AI 什么不要做,比告诉它什么要做更有效。比如:

不要这样写: .card { color: #3B82F6; } 要这样写: .card { color: $color-primary; }

方法二:分步执行,不要一次性让 AI 做太多。我一开始让 AI“还原整个页面的所有样式”,结果它生成的代码质量很差。后来改成“先还原卡片容器,再还原标题,再还原描述文字”,每一步都确认后再继续,质量明显提升。

方法三:给 AI 看现有代码的风格。在提示词里加上“参考src/components/Button.vue的代码风格”,AI 会模仿现有代码的写法,一致性更好。

方法四:用compare_with_code工具做校验。生成代码后,让 AI 再调用一次对比工具,检查是否还有遗漏的差异。这个步骤相当于自动化的 code review。

5.4 团队协作时的配置同步问题

本地部署 MCP Server 的方案,在团队协作时有一个问题:每个人的路径不一样,mcp.json里的绝对路径没法直接提交到仓库。

我的解决方案是:在仓库里放一个mcp.json.example,路径部分用占位符:

{ "mcpServers": { "lanhu": { "command": "npx", "args": ["tsx", "{{PROJECT_ROOT}}/tools/lanhu-mcp/src/index.ts"], "env": { "LANHU_APP_KEY": "{{LANHU_APP_KEY}}", "LANHU_APP_SECRET": "{{LANHU_APP_SECRET}}" } } } }

然后写一个setup.sh脚本,自动替换占位符并生成mcp.json:

#!/bin/bash PROJECT_ROOT=$(pwd) sed "s|{{PROJECT_ROOT}}|$PROJECT_ROOT|g" mcp.json.example > .cursor/mcp.json echo "请手动填写 LANHU_APP_KEY 和 LANHU_APP_SECRET"

新同事入职,跑一下./setup.sh,填两个密钥就行。这个脚本我放在tools/目录下,和 MCP Server 代码在一起。

避坑技巧:不要把mcp.json提交到仓库,只提交mcp.json.example。在.gitignore里加上.cursor/mcp.json。我见过有人不小心把密钥提交上去,虽然马上删了,但 Git 历史里还能查到,只能重新申请密钥。

6. 几个值得单独说的经验

6.1 缓存策略的设计

蓝湖 API 有频率限制,而且同一个画板的标注数据在短时间内不会变。我在 MCP Server 里加了一层内存缓存,key 是projectId:boardId,过期时间 5 分钟。

const cache = new Map(); async function fetchLayersWithCache(token, projectId, boardId) { const key = `${projectId}:${boardId}`; const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < 5 * 60 * 1000) { return cached.data; } const data = await fetchLayers(token, projectId, boardId); cache.set(key, { data, timestamp: Date.now() }); return data; }

这个缓存策略让 API 调用量降低了大概 70%。因为 AI 在还原一个页面时,会多次调用get_board_layers和get_layer_style,没有缓存的话很容易触发频率限制。

6.2 设计稿变更的主动通知

这套方案有一个局限:它是被动的。只有我在 Cursor 里主动问“还原了吗”,AI 才会去拉取设计稿数据。如果设计师改了稿但没通知我,我还是不知道。

我后来加了一个简单的主动通知机制:写了一个定时脚本,每小时调一次蓝湖 API,对比画板的updated_at时间戳。如果有变化,就在团队的飞书群里发一条消息:“蓝湖画板 XXX 有更新,请相关同学注意。”

这个脚本很简单,但效果很好。设计师改稿后,前端能第一时间知道,不用等设计师来追。

6.3 什么情况下这套方案不适用

不是所有项目都适合接 MCP。我总结了几种不适合的情况:

第一,设计稿变动极其频繁的项目。如果设计师一天改八版,AI 还原的速度跟不上改的速度,还不如等设计定稿了再一次性还原。

第二,样式高度定制化的项目。如果页面大量使用渐变、阴影、动画这些蓝湖标注不完整的属性,AI 能帮的忙有限,还是得手动调。

第三,团队没有统一的 SCSS 变量规范。如果项目里颜色值是散落的硬编码,AI 生成的代码也会跟着乱。这种情况下,先花时间把变量规范建立起来,再接 MCP 效果更好。

第四,对代码质量要求极高、不接受 AI 生成代码的团队。这套方案的定位是“辅助”而不是“替代”。AI 生成的代码需要人工 review,如果团队流程不允许 AI 参与编码,那就不适合。

6.4 后续可以扩展的方向

这套方案跑通之后,我还在想几个扩展方向。一是把 Figma 也接进来,蓝湖和 Figma 双源支持,设计师用哪个平台都能覆盖。二是加一个“设计稿变更影响分析”工具,当设计稿改动时,自动分析哪些组件受影响,生成影响范围报告。三是把 MCP Server 部署到内网服务器,用 SSE 模式让团队共用,省去每人配置的麻烦。

不过这些都是后话了。当前这套本地部署的方案,已经足够解决“设计师追着问还原了吗”这个问题。从实际使用效果看,设计还原的沟通成本降低了大概 60%,返工率也明显下降。最直观的感受是,设计师在群里 @ 我的次数少了很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询