mcp server
es-plus
基于 Vue 3 + Element Plus 的企业级中后台 CRUD 组件库 — 配置化驱动表单、表格、弹窗全链路联动
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"es-plus": {
"command": "npx",
"args": [
"-y",
"@es-plus/mcp-server"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Nov 8, 2026ES-Plus
让 AI 生成的 CRUD 页面可以直接合进代码库:LLM 只写一份 JSON 配置,ES-Plus 负责 Schema 校验 + 确定性编译,一次产出 Vue 3 + Element Plus / Vue 2 + Element UI / Vue 3 + Ant Design Vue 三套可运行代码 —— 同一份配置、三端一致、可复现。
中文 | English
两个徽章都来自夜间 Layer-2 评估对 golden 语料留出集(few-shot 未见域、已排除近变体)的实测,不是营销数字。徽章 label 里标着测量所用的模型:读数随模型变化,不同模型之间不可横向比较,也不与历史读数拼成一条曲线。
第一个是门禁口径:in-schema intent 命中率 = 「零编辑 ∨ 3 轮内收敛」—— 模型首轮就给出可零改合并的配置,或在至多 3 轮自然语言修正内收敛到同一份配置,都算命中(定义)。
第二个是其中更严的一半:只算首轮零编辑,不给收敛机会,用来分辨「一次就对」和「改几轮才对」。
语料目前 227 例(留出 203 例),是回归哨兵而非统计显著的 benchmark,提升靠补语料而非调口径;门禁目标 ≥95%,当前实测以上方徽章为准(由夜间 eval 从 __tests__/golden/last-accuracy.json 生成)。
在线文档 · Playground · AI CRUD 生成器 · 更新日志 · v1.4 迁移指南
同一份配置的三个渲染端,各自一个线上站:Vue 3 + Element Plus · Vue 3 + Ant Design Vue · Vue 2 + Element UI —— 三个站都跑在线上,示例由各自站点的构建与守卫覆盖。
v1.4.0 起重命名:
es-plus-ui已重命名为@es-plus/vue3,同时新增 Vue 2 渲染器@es-plus/vue2与框架无关的@es-plus/core。原es-plus-ui包继续作为 stub 包工作,详见 迁移指南。
为什么选择 ES-Plus
2026 年用 AI 生成前端早不稀奇,稀奇的是敢不敢把生成结果直接合并。多数 AI codegen 是「凭感觉」:每次输出都不一样、对不上你的设计系统、没人替它校验 —— 于是你还得逐行 review、改、再 review。
ES-Plus 把这件事反过来做:让 LLM 只做它擅长的语义推理(需求 → 一份配置),把「生成正确代码」这件必须确定的事交给编译器。
- LLM 产出配置,不产出代码 —— 通过 MCP 把 Schema/约定交给宿主 LLM(Claude Code / Cursor),它只输出一份 JSON
- 库做校验 —— zod + JSON Schema 在编译前拦下非法配置,坏配置到不了产物
- 确定性编译 —— 同一份配置永远编译出字节级相同的三端代码,golden 用例语料 × 多类确定性不变量 + esbuild 真语法解析在 CI 守着
- 三端一致 —— 一份配置通吃 Vue 3 + Element Plus、Vue 2 + Element UI、Vue 3 + Ant Design Vue
而且它首先是个好用的 CRUD 库:中后台系统 80% 的页面是 CRUD —— 表单查询 → 表格展示 → 弹窗编辑。用原生 Element Plus 每个页面需要 200+ 行模板代码,用 ES-Plus 只需 30 行配置。
| 传统写法 | ES-Plus |
|---|---|
每个字段 5-8 行 <el-form-item> + <el-input> | 一行配置 { prop, label, formtype } |
手动 v-model 绑定每个字段 | 自动绑定到 model |
手动 @click 查询 + resetFields() 重置 | triggerEvent: true 全自动 |
模板声明 <el-dialog v-model="visible"> | 函数调用 dialog({ title, render }) |
| 手动编写分页事件处理 | 分页切换自动触发请求 |
同一页面、同一功能范围:模板与事件胶水代码减少 76.5%(115 → 27 行),整页非空行减少 40.4%(136 → 81 行);查询 / 重置 / 翻页 / 取数零事件代码。
数字由
npm run one-config:gen从docs/brand/one-config/{native,esplus}.vue实测得出, 由npm run check:site-claims守住院线。口径:模板区行数 +handle*/fetch*事件与取数函数行数。
核心特性
- 配置驱动 — JSON 配置生成表单、表格、弹窗,替代大量模板代码
- 全链路联动 — EsForm 嵌套在 EsTable 中,查询/重置/分页全自动联动
- 编程式弹窗 —
useDialog()命令式调用,JSX 渲染,表单验证集成 - 16 种表单控件 — Input、InputNumber、Select、DatePicker、TimePicker、Cascader、TreeSelect、Autocomplete、Radio、Checkbox、Switch、Slider、Rate、ColorPicker、Transfer、Upload
- 自适应高度 — ResizeObserver 自动重算表格高度
- 跨页选择 —
cachePageSelection分页切换保留勾选 - 任意后端适配 —
configTableOut配置化映射 API 响应字段 - 权限控制 —
permissionValue声明式按钮权限,无需 v-if - 国际化 —
labelKey+ 自定义翻译函数,兼容任意 i18n 方案 - TypeScript — 完整类型定义 + 44 个跨渲染器契约类型(CI 强制三端同构导出,数量由
check:readme校验) - AI 原生支持 — 配套 MCP Server 和 CLI:MCP 通过协议把 Schema/约定交给宿主 LLM(Claude Code/Cursor)做语义推理,es-plus 负责约束校验 + 确定性编译成可运行页面
快速开始
一行起步(推荐)
npm create es-plus@latest
交互式选渲染器(Vue 3 + Element Plus / Vue 2 + Element UI / Vue 3 + Ant Design Vue),可选一键写入 MCP(AI 生成)配置,产出一个装上就能跑的 CRUD 示例项目(查询表单 + 表格,零后端)。CI 用脚手架 smoke 守着三端 scaffold → install → build 全绿。详见 create-es-plus。
想手动集成到已有项目,继续往下看。
安装(Vue 3)
npm install @es-plus/vue3 element-plus @element-plus/icons-vue
注册插件(Vue 3)
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import ESPlus from '@es-plus/vue3'
import '@es-plus/vue3/dist/style.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.use(ESPlus)
app.mount('#app')
Vue 2 项目?
npm install @es-plus/vue2 element-ui
# Vue 2.6 还需要 @vue/composition-api
参见 @es-plus/vue2 README,配置 API 与 Vue 3 版本完全一致。
Ant Design Vue 项目?
npm install @es-plus/adapter-antdv ant-design-vue @ant-design/icons-vue dayjs
import { createApp } from 'vue'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
import ESPlus from '@es-plus/adapter-antdv'
import '@es-plus/adapter-antdv/dist/style.css'
const app = createApp(App)
app.use(Antd)
app.use(ESPlus)
app.mount('#app')
@es-plus/adapter-antdv 是 Ant Design Vue 4.x 渲染适配器,与 @es-plus/vue3 共享同一份 JSON 配置 Schema,配置 API 完全一致,仅底层 UI 组件不同。详见 adapter-antdv 文档。
第一个 CRUD 页面
<template>
<es-table
ref="tableRef"
:columns="columns"
:options="options"
v-model:data-source="tableData"
v-model:pagination="pagination"
>
<es-form :model="form" :form-item-list="formItems" :config-btn="btns" />
</es-table>
</template>
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({ name: '', status: '' })
const tableData = ref([])
const pagination = ref({ current: 1, pageSize: 10, total: 0 })
const formItems = [
{ prop: 'name', label: '姓名', formtype: 'Input', span: 6, attrs: { clearable: true } },
{ prop: 'status', label: '状态', formtype: 'Select', span: 6,
dataOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }] }
]
const btns = [
{ name: '查询', type: 'primary', key: 'query', triggerEvent: true },
{ name: '重置', key: 'rest', triggerEvent: true }
]
const columns = [
{ prop: 'name', label: '姓名' },
{ prop: 'status', label: '状态' },
{ prop: 'operate', label: '操作',
btns: [
{ name: '编辑', type: 'primary', clickEvent: (row) => edit(row) },
{ name: '删除', type: 'danger', clickEvent: (row) => del(row) }
] }
]
const options = {
border: true,
stripe: true,
httpRequest: async (params) => {
// 替换为你的 API 调用
const res = await fetch('/api/list', { method: 'POST', body: JSON.stringify(params.formParams) })
return res.json()
},
configTableOut: { total: 'total', tableData: 'data', pageSize: 'pageSize', current: 'pageIndex' }
}
</script>
EsForm 嵌套在 EsTable 中,查询/重置/分页全自动联动,零事件处理代码。
联动机制
┌───────────────────────────────────────────────┐
│ EsTable(通过 provide/inject 提供表格实例) │
│ ┌─────────────────────────────────────────┐ │
│ │ EsForm(自动发现父级 EsTable) │ │
│ │ [查询] → 自动调用 table.httpRequest() │ │
│ │ [重置] → 重置表单 + 重新查询 │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ 表格数据(自动分页) │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ 分页器(翻页自动触发 httpRequest) │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘
EsForm 放在 EsTable 的默认插槽中,通过 Vue 的 provide/inject 自动发现父级表格。按钮配置 triggerEvent: true 后,自动调用表格的数据请求方法,无需手动连接。
组件一览
| 组件 | 说明 | 典型场景 |
|---|---|---|
| EsForm | 配置化表单 | 查询表单、弹窗编辑表单、筛选条件 |
| EsTable | 配置化表格 | 数据列表、跨页多选、多级表头 |
| useDialog | 编程式弹窗 | 新增/编辑弹窗、详情查看、嵌套弹窗 |
| EsDialog | 增强弹窗组件 | 拖拽、全屏切换、自定义头部/底部 |
| EsCrudPage | CRUD 页面组件 | 传入 Schema 一键生成完整 CRUD 页面 |
| SvgIcon | SVG 图标 | 图标展示 |
EsForm 表单配置
const formItems = [
// 文本输入
{ prop: 'name', label: '姓名', formtype: 'Input', span: 6 },
// 下拉选择(静态选项)
{ prop: 'status', label: '状态', formtype: 'Select', span: 6,
dataOptions: [{ label: '启用', value: 1 }, { label: '禁用', value: 0 }] },
// 日期范围
{ prop: 'date', label: '日期', formtype: 'datePicker', span: 8,
attrs: { type: 'daterange', valueFormat: 'YYYY-MM-DD' } },
// 远程数据加载
{ prop: 'category', label: '分类', formtype: 'Select', span: 6,
apiParams: { url: '/api/categories' },
callOptionListFormat: (data) => data.map(i => ({ label: i.name, value: i.id })) },
// 条件显隐
{ prop: 'remark', label: '备注', formtype: 'Input', span: 12,
attrs: { type: 'textarea' },
isHidden: (model) => model.status !== 1 }
]
核心配置项
| 字段 | 类型 | 说明 |
|---|---|---|
prop | string | 字段名(必填) |
label | string | 标签(必填) |
formtype | string | 控件类型(16 种) |
span | number | 栅格列宽(1-24) |
attrs | object | 透传到 Element Plus 组件 |
dataOptions | array | Select/Radio/Checkbox 选项 |
isHidden | (model) => boolean | 条件显隐 |
render | (h, model) => VNode | 自定义渲染 |
apiParams | object | 远程加载选项数据 |
labelKey | string | i18n 翻译键 |
EsTable 表格配置
const columns = [
{ prop: 'name', label: '姓名' },
{ prop: 'amount', label: '金额', formatter: (row) => `¥${row.amount.toFixed(2)}` },
// 自定义渲染
{ prop: 'status', label: '状态',
render: (_, { row }) => h(ElTag,
{ type: row.status === 1 ? 'success' : 'danger' },
() => row.status === 1 ? '启用' : '禁用') },
// 操作按钮(prop 必须为 'operate')
{ prop: 'operate', label: '操作',
btns: [
{ name: '编辑', type: 'primary', clickEvent: (row) => openForm('编辑', row) },
{ name: '删除', type: 'danger', clickEvent: (row) => handleDelete(row) }
] }
]
const options = {
border: true,
stripe: true,
highlightCurrentRow: true,
httpRequest: fetchList,
configTableOut: { total: 'total', tableData: 'records', pageSize: 'size', current: 'page' },
rowkey: 'id',
cachePageSelection: true
}
后端响应映射
后端返回 { result: { items: [...], count: 50 } }?只需配置:
configTableOut: { total: 'count', tableData: 'items', pageSize: 'pageSize', current: 'pageIndex' }
组件会递归查找响应中的字段,适配任意嵌套结构。
请求/响应拦截
options: {
listenToCallBack: {
brcb: (params) => ({ ...params, token: getToken() }), // 请求前拦截
qrcb: (response) => transformResponse(response) // 响应后转换
}
}
useDialog 编程式弹窗
import { useDialog } from '@es-plus/vue3'
const dialog = useDialog()
function openEditForm(row) {
const formData = reactive({ ...row })
dialog({
title: '编辑用户',
width: '500px',
render: (h, { registerRef }) => (
<EsForm
ref={el => el && registerRef('form', el)}
model={formData}
formItemList={[
{ prop: 'name', label: '姓名', formtype: 'Input', span: 24 },
{ prop: 'email', label: '邮箱', formtype: 'Input', span: 24 }
]}
/>
),
configBtn: [
{ name: '取消', click: (_, { close }) => close() },
{ name: '确定', type: 'primary', click: async (_, { close, getRefs }) => {
try {
await getRefs('form')?.validate()
await api.updateUser(formData)
close()
} catch { /* 表单验证失败 */ }
}}
]
})
}
权限控制
安装时配置权限函数,按钮自动按权限显隐:
app.use(ESPlus, {
permission: (value) => userPermissions.includes(value)
})
// 按钮配置中声明权限标识
const btns = [
{ name: '新增', type: 'primary', permissionValue: 'user:add', click: () => add() },
{ name: '删除', type: 'danger', permissionValue: 'user:delete', click: (row) => del(row) }
]
// 无 'user:delete' 权限时,删除按钮自动隐藏,无需 v-if
国际化
安装时配置翻译函数,配合任意 i18n 库:
import { useI18n } from 'vue-i18n'
app.use(ESPlus, {
t: (key) => useI18n().t(key)
})
const formItems = [
{ prop: 'name', label: '姓名', labelKey: 'form.name', formtype: 'Input' }
]
// 有 labelKey 时使用 t(labelKey) 翻译,否则回退到 label
TypeScript 类型
import type {
FormItemOption, // 表单项配置
BtnConfig, // 按钮配置
LayoutFormProps, // 表单布局配置
TableColumn, // 表格列配置
TableOptions, // 表格选项
PaginationConfig, // 分页配置
DialogOptions, // 弹窗选项
ApiParams, // 接口参数
EsFormInstance, // 表单实例方法
EsTableInstance, // 表格实例方法
EsPlusOptions // 全局配置
} from '@es-plus/vue3'
跨 Vue 2 / Vue 3 / AntDV 共享类型时,从
@es-plus/core/types导入 — 同一份columns/formItemList在三个渲染器中通用。
AI 工具链
ES-Plus 从设计上拥抱 AI 编码,配套两个官方工具:
@es-plus/mcp-server — AI 编码工具集成
让 Claude Code、Cursor 等 AI 工具直接调用 CRUD 生成能力:
# Claude Code 一行配置
claude mcp add es-plus -- npx -y @es-plus/mcp-server
然后在 AI 对话中直接说:
"生成一个用户管理页面,查询姓名、手机号、状态,表格显示姓名、邮箱、状态、创建时间,支持新增编辑删除"
AI 自动调用 MCP Server 生成完整可运行的 .vue 文件。
工具面遵循「一个前门 + 确定性支撑」:generate_scene 是唯一前门(先声明要建什么:crud / table / form / dialog,再填该场景的意图表,路由与编译是确定性代码)。generate_composition 把多个独立片段装成一张能跑的页面。generate_crud 是 scene='crud' 的兼容入口(产物逐字节相同),generate_crud_from_config 标注 ADVANCED(已持有类型化配置时用),generate_crud_schema 标注 LEGACY FALLBACK(no-LLM 正则兜底)。ESPLUS_MCP_PROFILE=front-door 把 18 个工具收缩到 12 个 —— 隐藏标准不是「旧不旧」,而是 generate_scene 能否逐字节复现(四个 scene 复用同一组发射器),宿主每轮的 tools/list 因此从 ~339 KB 降到 ~21 KB。完整工具清单、选择决策树与分层依据见 packages/mcp-server/AI-NATIVE-ARCHITECTURE.md。
想先看效果再看原理:AI 案例导览 docs/ai/ai-showcase.md 逐个说明站点上每个 AI 页面证明了什么、证据在哪,以及 claim(measure / compile / execute)该怎么读 —— 跑过的才叫 execute。
@es-plus/cli — 命令行工具
终端直接生成 CRUD 页面:
# 交互式生成
npx @es-plus/cli create user-management
# 非交互式
npx @es-plus/cli create user-management \
-d "用户管理,查询姓名、手机号、状态,表格显示姓名、手机号、邮箱、状态、创建时间,支持新增编辑删除"
# 校验 JSON 配置
npx @es-plus/cli validate ./config.json --schema form-item
# 生成页面脚手架
npx @es-plus/cli scaffold dashboard --features query,table,dialog
全局配置
app.use(ESPlus, {
// 权限控制
permission: (value) => userPermissions.includes(value),
// 国际化
t: (key) => i18n.global.t(key),
// 表格全局默认配置
EsTable: {
httpRequest: async (params) => axios(params).then(r => r.data),
configQueryFieldOutput: {
total: 'total', tableData: 'data', pageSize: 'pageSize', current: 'pageIndex'
},
paginationLayout: {
layout: 'total, sizes, prev, pager, next, jumper',
pageSizes: [10, 20, 50, 100]
},
// 每张表的 options 默认值(实例 :options 优先)
defaults: { size: 'small', border: true }
}
})
项目结构
es-plus/
├── packages/
│ ├── vue3/ # Vue 3 + Element Plus 渲染器(npm: @es-plus/vue3)
│ ├── vue2/ # Vue 2 + Element UI 渲染器(npm: @es-plus/vue2)
│ ├── adapter-antdv/ # Vue 3 + Ant Design Vue 渲染器(npm: @es-plus/adapter-antdv)
│ ├── core/ # 框架无关核心层(npm: @es-plus/core)
│ ├── es-plus-legacy/ # 兼容 stub(npm: es-plus-ui,re-export @es-plus/vue3)
│ ├── shared/ # MCP/CLI 共享逻辑(npm: @es-plus/shared)
│ ├── mcp-server/ # MCP Server(npm: @es-plus/mcp-server)
│ └── cli/ # CLI 工具(npm: @es-plus/cli)
├── es-plus-docs/ # 主文档站(Vite + Vue 3 + Element Plus)
├── es-eui/ # Vue 2 + Element UI 文档站
├── es-pc/ # Ant Design Vue 文档站
├── scripts/ # 一致性/契约校验脚本(check:consistency 等)
├── __tests__/ # e2e 矩阵 + Playwright 运行时测试
└── docs/ # 设计文档与迁移指南
本地开发
# 根目录一次性装依赖(workspaces monorepo,无需逐包 install)
npm install --legacy-peer-deps
# 构建全部可发布包(按依赖顺序:shared → vue3 → vue2 → adapter-antdv → cli → mcp-server)
npm run build:packages
# 文档站点开发
cd es-plus-docs && npm run dev
# 运行全部包单测
npm test
# 跨包 e2e 矩阵(vue2/vue3/antdv × schema/sfc)
npm run test:e2e
# 类型检查(逐包)
npm run typecheck --workspaces --if-present
# 一致性校验(三端类型导出 / schema 单一真源)
npm run check:consistency
相关链接
| 包 | 说明 | 链接 |
|---|---|---|
@es-plus/vue3 | Vue 3 + Element Plus 渲染器 | npm |
@es-plus/vue2 | Vue 2 + Element UI 渲染器 | npm |
@es-plus/adapter-antdv | Vue 3 + Ant Design Vue 渲染器 | npm |
@es-plus/core | 框架无关核心层(类型/工具/算法) | npm |
@es-plus/mcp-server | AI 编码工具集成 | npm |
@es-plus/cli | 命令行工具 | npm |
es-plus-ui (deprecated) | 已迁移至 @es-plus/vue3 | 迁移指南 |
| 在线文档 | 完整 API 与示例 | 文档 |
贡献
欢迎提交 Issue 和 Pull Request!用法提问、集成讨论和功能设想请走 GitHub Discussions;能复现的缺陷与明确的功能请求请开 Issue。
- Fork 本仓库
- 创建功能分支:
git checkout -b feature/your-feature - 提交更改:
git commit -m 'feat: add your feature' - 推送分支:
git push origin feature/your-feature - 提交 Pull Request