Cursor插件开发:AI工作流下的沙盒化插件设计与实战

发布时间:2026/10/5 11:44:28
Cursor插件开发:AI工作流下的沙盒化插件设计与实战 1. 项目概述从“plugins”这个词开始我们到底在聊什么“plugins”不是个新词但最近半年它在开发者圈子里的热度几乎追平了“agent”和“TypeScript”。你刷技术社区、看GitHub trending、甚至翻国内开发群聊天记录十次里有七次会撞见这个词——但它从来不是孤立出现的。它总和Cursor绑定在一起和plugin.json文件形影不离和TypeScript SDK搭配使用最后又悄悄滑进AI Agent的底层架构里。这不是巧合而是一条正在成型的技术链路本地化、可扩展、面向AI工作流的插件化开发范式。我从去年底开始系统性地用 Cursor 做日常编码前两个月还在用它写 React 组件、调试 Node.js 接口到第三个月我发现自己写的 80% 代码其实是在围绕一个 plugin.json 文件打转——改 schema、调 activationEvents、试 runCommand 的返回值、反复 reload 插件看 console.log 是否输出。那时候我才意识到“plugins”根本不是“给编辑器加个语法高亮”那种小功能它是把整个开发环境变成一个可编程的 AI 协作沙盒。你写的不是传统意义上的“编辑器插件”而是一个个能被 Agent 调度、能主动触发 LLM 调用、能读取当前文件上下文并生成结构化响应的轻量级服务单元。举个最直白的例子你写一个叫linxin666/dsh-p的插件这名字来自热搜里的报错它可能只做一件事——当用户选中一段 SQL 语句右键点击“解释这段查询”插件就自动提取表名、字段、JOIN 条件拼成 prompt 发给本地部署的 Ollama 模型再把返回的自然语言解释插入到注释块里。整个过程不依赖网络请求除非你主动配置不打开新窗口不打断当前编辑节奏。这就是 plugins 的真实形态原子化、上下文感知、与 AI 深度耦合的执行单元。所以当你看到“failed to load plugins web boot: 2 entries did not activate”这类报错时别急着删 node_modules 或重装 Cursor——这背后大概率是你的插件没通过 harness 的沙盒校验或是 agent runtime 没正确加载 TypeScript 编译后的 dist 文件。它不是“插件没装好”而是“AI 工作流的调度链断了”。这也是为什么“harness 和 agent 区别”、“agent 安全”、“agent 怎么扛并发”这些词会高频出现在热搜里大家已经从“怎么写插件”快速进化到了“怎么让插件在 AI 驱动的复杂环境中稳定、安全、高效地跑起来”。适合谁来读这篇如果你是刚用 Cursor 写了第一个 Hello World 插件的新手这篇能帮你绕过 90% 的坑如果你已经在用hermes-agent或pi-agent搭建自己的 agent 项目这篇会告诉你 plugin.json 里那几行看似简单的字段如何决定整个 agent 的启动顺序和权限边界如果你正卡在“cursor 中文设置”或“cursor 设置中文回复”这种基础问题上——抱歉那不属于 plugins 的范畴那是 IDE 的 UI 层配置我们不碰。我们只聚焦一件事如何让一个 plugin 成为真正可调度、可编排、可审计的 AI 工作流组件。2. 插件系统设计逻辑为什么不是 VS Code 的那一套2.1 本质差异从“UI 扩展”到“AI 执行体”很多人第一次写 Cursor 插件时下意识去翻 VS Code 的 Extension API 文档结果越看越懵。不是因为文档写得差而是两者的设计哲学完全不同。VS Code 插件的核心目标是增强编辑器 UI 和编辑能力加个按钮、改个颜色、提供代码补全——所有行为都发生在用户界面上最终目的是提升人机交互效率。而 Cursor 的 plugins核心目标是成为 AI Agent 的可执行模块它不一定要有 UI很多 plugin 根本没有 view、webview 或 contribution point它的激活时机不是“用户点了菜单”而是“agent 判断此刻需要调用这个能力”。我拿一个真实案例对比VS Code 插件Prettier监听onCommand:prettier.format用户按快捷键或点菜单后读取当前文档文本 → 调用 prettier-core → 返回格式化后字符串 → 替换编辑器内容。整个流程是单向、同步、由人触发的。Cursor 插件huayu-yuan/sql-explainer参考热搜里那个 failed to load 的名字注册activationEvents: [onLanguage:sql]但真正激活是在 agent runtime 解析用户 query 时发现上下文含 SQL 片段 → 触发runCommand:sql.explain→ 插件从workspace.rootPath读取.env获取本地模型地址 → 构造 prompt → 调用fetch发送 POST → 解析 JSON 响应 → 返回{ explanation: 该查询先扫描 users 表..., complexity: O(n) }结构化对象 → agent 将其注入到下一步 prompt 中。看到区别了吗前者是“工具”后者是“协作者”。VS Code 插件是编辑器的仆人Cursor 插件是 agent 的队友。这就决定了它的 manifest 文件、生命周期、通信机制、错误处理方式全都得重来。2.2 plugin.json不是配置文件而是“能力契约书”plugin.json看似只是个 JSON 文件但它是整个插件系统的中枢神经。它不叫package.json也不叫manifest.json就叫plugin.json——这个命名本身就暗示了它的唯一性它定义的不是“怎么打包”而是“我能提供什么能力、在什么条件下可用、由谁来调用我”。我们拆解一个最小但完整的plugin.json{ name: myorg/translate-code, version: 0.1.0, displayName: Code Translator, description: Translate comments and docstrings between English and Chinese, publisher: myorg, engines: { cursor: ^0.45.0 }, activationEvents: [ onCommand:translate.code ], main: ./dist/extension.js, contributes: { commands: [ { command: translate.code, title: Translate Selected Code } ] }, ai: { capabilities: [translation, context-aware], sandbox: { allowedOrigins: [http://localhost:3000], allowedPermissions: [fs:read, network:fetch] } } }重点不在name或version而在ai这个顶层字段。这是 VS Code 里绝对没有的。它告诉 harness这个插件声明自己具备translation和context-aware两种能力标签agent 在规划 workflow 时会根据这些标签匹配任务它运行在沙盒中只允许访问本地fs:read读取当前项目中的 i18n 配置和network:fetch调用内部翻译 API其他如process.env、require、eval全部禁止allowedOrigins不是 CORS 白名单而是指明“只有来自http://localhost:3000的 agent 沙盒实例才能加载我”防止恶意网页注入。提示activationEvents里的onCommand:并非用户手动触发的入口。在 agent 场景下harness 会模拟 command 触发作为插件激活的信号。真正的调用链是agent → harness → plugin.json → activationEvents → 加载 main → 执行 runCommand。所以如果你的插件没写activationEventsharness 根本不会把它放进启动队列直接报 “did not activate”。2.3 TypeScript SDK不是类型定义而是“沙盒运行时接口”Cursor 官方提供的 TypeScript SDK名字叫cursor/sdk但它和types/node这类纯类型库有本质区别。它包含两层第一层是类型定义index.d.ts告诉你runCommand函数接收什么参数、返回什么 Promise第二层是沙盒内核的桩代码stub比如fetch函数的实现它不是浏览器原生 fetch而是 harness 注入的受控版本会检查allowedOrigins、记录请求日志、限制超时时间。我实测过如果你在插件里直接import { fetch } from node:undiciharness 启动时会直接报错Error: Module not allowed in sandbox。必须用 SDK 提供的fetch因为它已被 harness 劫持并注入了审计钩子。SDK 还封装了一个关键类AgentContext。它不是全局变量而是每次runCommand调用时harness 传入的上下文对象import { AgentContext, runCommand } from cursor/sdk; export async function activate(context: AgentContext) { // context.workspaceRoot: 当前打开的项目根路径沙盒内路径非真实 fs // context.selectedText: 用户当前选中的文本如果有的话 // context.fileUri: 当前活动文件的 URI沙盒内虚拟 URI // context.agentId: 调用此插件的 agent 实例 ID用于 trace 和 quota 控制 runCommand(translate.code, async (params) { const text params.text || context.selectedText; const targetLang params.lang || zh; // 注意这里不能直接 new URL()因为沙盒禁用构造函数 // 必须用 SDK 提供的 createUrl() const url context.createUrl(http://localhost:3000/api/translate); const res await context.fetch(url, { method: POST, body: JSON.stringify({ text, targetLang }), headers: { Content-Type: application/json } }); return res.json(); }); }看到没context.fetch、context.createUrl、context.selectedText——这些都不是 Node.js 或浏览器原生 API而是 harness 提供的、带权限控制和审计能力的代理接口。SDK 的价值就是让你在写业务逻辑时完全不用操心沙盒限制所有危险操作都被封装在context方法里且每个调用都会被记录到harness.log中。2.4 Harness不是加载器而是“AI 工作流的交通警察”“harness failed to load plugins” 这个报错90% 的人以为是插件代码错了。其实恰恰相反——harness 报错说明插件代码很可能没问题而是 harness 自己拒绝加载它。Harness 是 Cursor 插件系统的守门人它的职责不是“把插件跑起来”而是“确保插件在安全、合规、可追溯的前提下被 agent 调用”。Harness 的启动流程分三步静态校验读取plugin.json检查engines.cursor版本是否兼容、ai.sandbox.allowedPermissions是否在白名单内、main指向的文件是否存在动态沙盒初始化为每个插件创建独立 V8 Context不是 Node.js 的 vm 模块而是基于 QuickJS 的轻量沙盒注入 SDK stub并设置内存/ CPU 限额默认 128MB / 1s激活事件广播向所有通过校验的插件发送activationEvents等待它们调用runCommand注册 handler若超时默认 3s未注册则标记为 “did not activate”。所以当你看到web boot: 1 entry did not activate第一反应不应该是“我的 activate 函数写错了”而应该是检查plugin.json里的activationEvents是否匹配runCommand的命令名查harness.log看沙盒初始化阶段是否有PermissionDeniedError运行cursor --log-leveldebug捕获 harness 启动时的完整 trace。Harness 还有个隐藏机制插件优先级队列。它不是按文件顺序加载而是按plugin.json里的ai.capabilities标签排序。比如 agent 要执行code-review任务harness 会优先加载capabilities包含static-analysis的插件再加载test-generation最后才是documentation。这个顺序不可配置由 harness 内置策略决定。这也是为什么有些插件明明代码没问题却总在报错列表里排第一——它被排在了启动队列头部而前面某个插件失败导致整个队列中断。3. 核心细节解析从零写出一个可被 agent 调用的插件3.1 目录结构与构建链为什么必须用 TypeScript esbuildCursor 插件不支持直接运行.ts文件也不接受tsc编译出的 CommonJS。它强制要求源码用 TypeScript.ts构建产物必须是 ESM 格式.jstype: module输出目录必须是dist/且入口文件名必须与plugin.json的main字段一致默认./dist/extension.js不能有require()、__dirname、process.cwd()等 Node.js 特有 API。我踩过的最大坑用 Webpack 构建。Webpack 默认生成 IIFE 或 UMD即使配置output.libraryType: module也会注入 runtime 代码导致 harness 加载时报SyntaxError: Cannot use import statement outside a module。后来换成 esbuild一行命令搞定esbuild src/extension.ts \ --bundle \ --platformnode \ --targetes2020 \ --formatesm \ --outfiledist/extension.js \ --external:cursor/sdk \ --minify关键参数解释--platformnode告诉 esbuild 按 Node.js 环境解析模块--targetes2020Cursor harness 基于 QuickJS只支持到 ES2020不支持??、?.等新语法--formatesm强制输出 ESM这是 harness 唯一接受的格式--external:cursor/sdk把 SDK 作为外部依赖不打包进去harness 会自行注入--minify可选但建议开启减少沙盒加载时间。注意src/extension.ts里不能写import * as sdk from cursor/sdk必须用import { runCommand, AgentContext } from cursor/sdk。esbuild 的 tree-shaking 会剔除未使用的导出但如果用* as sdk它无法判断哪些方法实际被调用会导致 bundle 体积暴增甚至触发 harness 的内存限制。3.2 plugin.json 关键字段详解每一行都是生产环境的生死线plugin.json看似简单但每个字段都直接影响插件能否上线、能否被 agent 调用、能否通过安全审计。我们逐行深挖字段必填说明生产环境避坑点name是npm 包名格式必须以scope/name开头如myorg/translate。Scope 名称会被 harness 用作沙盒隔离标识。Scope 名称不能含大写字母或下划线否则 harness 启动时报Invalid scope nameScope 名称一旦发布不可更改否则已安装的插件无法更新。version是语义化版本号。harness 会比对engines.cursor和当前 Cursor 版本不匹配则拒绝加载。不要用0.0.1作为正式版harness 对0.x版本有额外校验如要求ai.sandbox必须显式声明建议从1.0.0起步。activationEvents是插件激活条件数组。支持onCommand:xxx、onLanguage:xxx、onUriScheme:xxx。注意onLanguage不是文件后缀而是 Language ID如typescript不是ts。多个 activationEvents 是 OR 关系不是 AND。如果写了[onCommand:a, onCommand:b]只要任一 command 被触发插件就激活。不要试图用它做权限控制。main是入口文件路径相对于 plugin.json。必须是 ESM 格式 JS 文件。路径必须用 Unix 风格斜杠/Windows 的\会导致 harness 读取失败路径不能以../开头harness 只允许读取插件目录内的文件。ai.capabilities否但强烈建议字符串数组声明插件能力标签。agent 用它做 workflow 规划。标准标签有code-generation,code-review,translation,test-generation,documentation,debugging。自定义标签如myorg:custom会被 harness 接受但 agent 可能无法识别。建议优先用标准标签再加自定义前缀。ai.sandbox.allowedPermissions否但生产环境必填数组声明插件需要的权限。合法值fs:read,fs:write,network:fetch,clipboard:read,clipboard:write,env:read。fs:write默认禁止需单独申请。env:read仅允许读取process.env.CURSOR_PLUGIN_*前缀的变量其他环境变量一律为空fs:write需在ai.sandbox下显式声明writePaths: [./logs/]且路径必须是相对路径。特别提醒ai.sandbox的两个隐藏规则allowedOrigins不是通配符[*]无效必须写具体域名如[https://api.myorg.com]如果插件需要调用本地服务如http://localhost:3000必须在allowedOrigins中显式列出且 harness 会验证该 origin 是否在localhost或127.0.0.1范围内否则拒绝加载。3.3 TypeScript SDK 实战如何写出既安全又高效的插件逻辑SDK 的核心是runCommand和AgentContext。但新手常犯的错误是把插件写成一个巨型函数所有逻辑堆在runCommand回调里。这样会导致三个问题单元测试困难无法 mock context错误边界模糊一个 try-catch 包不住所有异步分支agent 无法做细粒度的 timeout 控制harness 只能控制整个 command 的超时。正确的写法是分层 显式错误分类 上下文透传。// src/extension.ts import { runCommand, AgentContext, PermissionDeniedError, TimeoutError } from cursor/sdk; // 第一层领域逻辑纯函数无副作用可单元测试 export async function translateText( text: string, targetLang: string, context: AgentContext ): Promise{ translated: string; confidence: number } { if (!text.trim()) { throw new Error(Empty text cannot be translated); } // 沙盒内安全的 URL 构造 const url context.createUrl(http://localhost:3000/api/translate); try { const res await context.fetch(url, { method: POST, body: JSON.stringify({ text, targetLang }), headers: { Content-Type: application/json } }); if (!res.ok) { throw new Error(Translation API returned ${res.status}); } const data await res.json(); return { translated: data.translated, confidence: data.confidence || 0.8 }; } catch (err) { if (err instanceof PermissionDeniedError) { // harness 拒绝了 fetch 权限 throw new Error(Translation service unreachable: permission denied); } if (err instanceof TimeoutError) { // harness 主动中断了请求 throw new Error(Translation timed out); } throw err; // 其他错误原样抛出 } } // 第二层command handler只做参数校验、context 透传、错误包装 export async function activate(context: AgentContext) { runCommand(translate.code, async (params) { try { // 参数校验agent 可能传任意 JSON必须防御 const text typeof params.text string ? params.text : context.selectedText; const targetLang typeof params.lang string ? params.lang : zh; // 调用领域逻辑 const result await translateText(text, targetLang, context); // 返回结构化结果agent 会解析这个对象 return { success: true, ...result, timestamp: Date.now() }; } catch (err) { // 统一错误格式便于 agent 做分类处理 return { success: false, error: { message: err.message, code: err.name || UNKNOWN_ERROR } }; } }); }这个结构的优势translateText可以单独写 Jest 测试mockcontext.fetchrunCommand回调里只做薄包装错误分类清晰PermissionDeniedError是 harness 报的TimeoutError是 harness 主动中断的其他是业务错误返回值结构固定agent 无需解析不同插件的 error 格式统一用response.success判断成败。3.4 Agent 调用插件的完整链路从用户提问到插件返回很多人以为 agent 调用插件是黑盒其实它有明确的 HTTP-like 协议。我用curl抓包还原了整个流程harness 默认监听http://127.0.0.1:53217# 1. Agent 发起调用模拟 harness 的 internal RPC curl -X POST http://127.0.0.1:53217/v1/run-command \ -H Content-Type: application/json \ -d { command: translate.code, params: {text: Hello world, lang: zh}, context: { workspaceRoot: /home/user/myproject, selectedText: , fileUri: file:///home/user/myproject/src/index.ts, agentId: agent-abc123 } } # 2. Harness 收到请求查找已激活的插件 # - 匹配 plugin.json 的 contributes.commands.command translate.code # - 找到 myorg/translate 插件的 runCommand handler # 3. Harness 在沙盒中执行 handler传入 AgentContext 实例 # 4. 插件返回 JSONharness 封装成标准响应 { id: cmd-xyz789, success: true, result: { translated: 你好世界, confidence: 0.95, timestamp: 1715678901234 }, durationMs: 421 }关键点context字段是 harness 注入的不是 agent 传的。agent 只传command和paramscontext由 harness 根据当前编辑状态动态生成durationMs是 harness 计算的从收到请求到返回结果的毫秒数agent 用它做 timeout 控制和性能分析id是 harness 生成的唯一命令 ID可用于 trace 和 debugharness.log里会记录cmd-xyz789的完整执行栈。所以当你调试 “cursor 怎么设置中文回复” 这类问题时要明白插件本身不负责“设置中文”它只负责“把英文翻译成中文”。真正的“中文回复”是由 agent 的 system prompt 决定的比如你是一个专业的代码助手所有回复必须用简体中文专业术语保持英文如 React、TypeScript。插件只是 agent 的一个工具不是语言设置开关。4. 实操过程从创建到上线的全流程手把手4.1 初始化项目用官方脚手架还是手动搭建Cursor 官方提供了create-cursor-plugin脚手架但实测下来它生成的模板过于简单缺少生产环境必需的配置。我推荐手动搭建步骤如下# 1. 创建项目目录 mkdir my-cursor-plugin cd my-cursor-plugin # 2. 初始化 package.json注意不要用 npm init -y必须手动写 cat package.json EOF { name: myorg/translate-code, version: 1.0.0, description: Translate code comments and docstrings, main: ./dist/extension.js, types: ./src/extension.ts, scripts: { build: esbuild src/extension.ts --bundle --platformnode --targetes2020 --formatesm --outfiledist/extension.js --external:cursor/sdk --minify, watch: esbuild src/extension.ts --bundle --platformnode --targetes2020 --formatesm --outfiledist/extension.js --external:cursor/sdk --minify --watch, test: jest }, devDependencies: { cursor/sdk: ^0.45.0, types/jest: ^29.5.0, esbuild: ^0.18.0, jest: ^29.5.0, ts-jest: ^29.1.0, typescript: ^5.0.0 } } EOF # 3. 创建 tsconfig.json关键必须启用 esModuleInterop cat tsconfig.json EOF { compilerOptions: { target: ES2020, module: ESNext, lib: [ES2020, DOM], allowJs: false, skipLibCheck: true, esModuleInterop: true, allowSyntheticDefaultImports: true, strict: true, forceConsistentCasingInFileNames: true, moduleResolution: node, resolveJsonModule: true, isolatedModules: true, noEmit: false, outDir: ./dist, rootDir: ./src, declaration: false, sourceMap: false, removeComments: true, noUnusedLocals: true, noUnusedParameters: true, noImplicitReturns: true, noFallthroughCasesInSwitch: true }, include: [src/**/*], exclude: [node_modules] } EOF # 4. 创建 plugin.json按前文表格填好生产环境字段 cat plugin.json EOF { name: myorg/translate-code, version: 1.0.0, displayName: Code Translator, description: Translate comments and docstrings between English and Chinese, publisher: myorg, engines: { cursor: ^0.45.0 }, activationEvents: [ onCommand:translate.code ], main: ./dist/extension.js, contributes: { commands: [ { command: translate.code, title: Translate Selected Code } ] }, ai: { capabilities: [translation, context-aware], sandbox: { allowedOrigins: [http://localhost:3000], allowedPermissions: [fs:read, network:fetch] } } } EOF # 5. 创建 src/extension.ts用前文的分层代码 mkdir src curl -o src/extension.ts https://gist.githubusercontent.com/your-gist-id/raw/extension.ts注意engines.cursor的版本号必须和你本地 Cursor 版本严格匹配。查看方式打开 Cursor → Help → About → 记下版本号如0.45.2然后在plugin.json里写^0.45.0表示兼容0.45.0到0.45.999。如果写成^0.44.0而你用的是0.45.2harness 会拒绝加载。4.2 本地开发与调试如何让 harness 重新加载你的插件Cursor 不像 VS Code 那样支持 F5 启动调试。它的插件热重载靠的是 harness 的 watch 机制。正确流程确保 harness 正在运行打开 Cursor按CmdShiftPMac或CtrlShiftPWin输入Developer: Toggle Developer Tools在 Console 里能看到harness started日志修改代码后运行npm run buildharness 会监听dist/目录检测到文件变化自动 reload触发插件在代码里选中一段英文注释按CmdShiftP→ 输入Translate Selected Code→ 回车查看日志打开Developer Tools→ Console搜索translate.code能看到插件的console.log输出抓取 harness 日志在 Terminal 运行cursor --log-leveldebug 21 | grep harness能看到详细的加载和调用 trace。常见失败场景及解决harness failed to load plugins web boot: 0 entries activated说明 harness 根本没找到你的插件。检查plugin.json是否在项目根目录name字段是否和package.json一致Error: Cannot find module ./dist/extension.jsnpm run build没成功或者main字段路径写错了ReferenceError: fetch is not defined你在插件里用了原生fetch必须用context.fetchPermissionDeniedError: network:fetch not allowedplugin.json的ai.sandbox.allowedPermissions没加network:fetch。4.3 发布与版本管理如何让团队成员一键安装Cursor 插件不走 npm registry而是用本地文件系统或私有 Git 仓库。生产环境推荐 Git 方式# 1. 将插件推送到私有 Git 仓库如 GitLab git init git add . git commit -m feat: initial plugin git remote add origin https://gitlab.com/myorg/cursor-plugins.git git push -u origin main # 2. 在 Cursor 中安装 # - CmdShiftP → Plugins: Install Plugin from Git # - 输入仓库 URLhttps://gitlab.com/myorg/cursor-plugins.git # - 选择分支main # - 点击 Install版本管理的关键是plugin.json的version字段。每次发布新版本必须修改plugin.json的version如从1.0.0到1.0.1提交新 taggit tag v1.0.1 git push origin v1.0.1在 Cursor 中插件页面会显示 “Update available”用户点击即可升级。注意Cursor 不支持 semantic versioning 的~或^它只认精确版本号。所以engines.cursor写^0.45.0是为了向后兼容而插件自身的version必须是精确值。4.4 生产环境监控如何定位 “did not activate” 的真实原因harness failed to load plugins这个报错太笼统。要精准定位必须结合三处日志Cursor 主进程日志最外层路径~/Library/Application Support/Cursor/logs/main.logMac或%APPDATA%\Cursor\logs\main.logWin搜索关键词harness failed能看到哪几个插件被跳过Harness 沙盒日志核心路径~/Library/Application Support/Cursor/logs/harness.log搜索插件name如myorg/translate-code能看到沙盒初始化的每一步[INFO] Loading plugin myorg/translate-code1.0.0 [DEBUG] Checking ai.sandbox.allowedPermissions... [ERROR] Permission network:fetch not allowed in sandbox config [WARN] Plugin myorg/translate-code did not activate插件自身日志可选在runCommand回调里加console.log(Plugin activated)如果这行没输出说明 harness 根本没走到激活步骤问题在plugin.json或沙盒校验如果有输出但后续报错说明问题在插件逻辑里。我整理了一个快速排查表现象最可能原因检查位置解决方案web boot: X entries did not activateX0plugin.json格式

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询