一个强大的 Vue 3 插件,通过 JSON Schema 声明式构建动态 UI。支持响应式数据、计算属性、事件处理、条件渲染、循环渲染、API 调用、WebSocket 等完整功能。
English | 简体中文
- 🎯 声明式配置 - 通过 JSON Schema 定义组件结构,无需编写 Vue 模板
- 🔄 响应式数据 - 完整支持 Vue 3 响应式系统
- 📊 计算属性 - 支持表达式计算和派生状态
- 🎪 事件处理 - 灵活的事件绑定和动作系统
- 🔀 条件渲染 - 支持
if和show指令 - 🔁 循环渲染 - 支持
for指令遍历数组 - 📝 表单绑定 - 支持
model双向绑定 - 🌐 API 调用 - 内置
fetch动作和initApi/uiApi配置 - 🔌 WebSocket - 支持长连接和实时通信
- 📋 剪贴板 - 内置
copy动作,兼容各种浏览器 - 🎰 插槽支持 - 支持默认插槽、具名插槽和作用域插槽
- 🔄 生命周期 - 支持
onMounted、onUnmounted、onUpdated钩子 - 👁️ 监听器 - 支持
watch监听状态变化 - 🧩 组件注册 - 支持注册自定义组件
- 🔒 安全 - 内置表达式安全检查,防止 XSS 攻击
pnpm add vschema-ui
# 或
npm install vschema-ui
# 或
yarn add vschema-uiimport { createApp } from 'vue';
import { VSchemaPlugin } from 'vschema-ui';
import App from './App.vue';
const app = createApp(App);
app.use(VSchemaPlugin);
app.mount('#app');<template>
<VSchema :schema="schema" />
</template><template>
<VSchema :schema="schema" />
</template>
<script setup lang="ts">
import { VSchema } from 'vschema-ui';
import type { JsonNode } from 'vschema-ui';
const schema: JsonNode = {
data: { count: 0 },
com: 'div',
children: [
{ com: 'p', children: '计数: {{ count }}' },
{
com: 'button',
events: { click: { set: 'count', value: '{{ count + 1 }}' } },
children: '增加'
}
]
};
</script>import { createVSchema } from 'vschema-ui';
import MyButton from './components/MyButton.vue';
// 创建带配置的 VSchema 组件
const VSchema = createVSchema({
baseURL: 'https://api.example.com',
components: {
MyButton, // 注册自定义组件
},
defaultHeaders: {
'Authorization': 'Bearer token'
}
});
export default VSchema;<template>
<VSchema :schema="schema" />
</template>
<script setup lang="ts">
import type { JsonNode } from 'vschema-ui';
const schema: JsonNode = {
data: { count: 0 },
com: 'div',
children: [
{ com: 'p', children: '计数: {{ count }}' },
{
com: 'button',
events: { click: { set: 'count', value: '{{ count + 1 }}' } },
children: '增加'
}
]
};
</script><template>
<VSchema
:schema="schema"
:initial-data="initialData"
:methods="externalMethods"
/>
</template>
<script setup lang="ts">
import type { JsonNode } from 'vschema-ui';
const schema: JsonNode = {
data: { form: { username: '', password: '' } },
com: 'div',
children: [
{
com: 'input',
model: 'form.username',
props: { placeholder: '用户名' }
},
{
com: 'button',
events: {
click: {
script: 'await $methods.login(state.form.username, state.form.password);'
}
},
children: '登录'
}
]
};
const initialData = { appName: 'My App' };
const externalMethods = {
login: async (username: string, password: string) => {
console.log('登录:', username, password);
}
};
</script>| 属性 | 类型 | 说明 |
|---|---|---|
schema |
JsonNode | string |
JSON Schema 定义(对象或 JSON 字符串) |
config |
GlobalConfig |
组件级别的配置(覆盖全局配置) |
initialData |
object |
初始化数据,会与 schema.data 合并 |
methods |
object |
外部注入的方法,可在 script 动作中通过 $methods 访问 |
| 属性 | 类型 | 说明 |
|---|---|---|
com |
string |
组件类型(HTML 标签或注册的组件名) |
props |
object |
传递给组件的 props |
children |
JsonNode[] | string |
子节点或文本内容 |
events |
object |
事件处理器 |
slots |
object |
插槽定义 |
| 属性 | 类型 | 说明 |
|---|---|---|
if |
string |
条件渲染(v-if) |
show |
string |
显示/隐藏(v-show) |
for |
string |
循环渲染,格式: "item in items" 或 "(item, index) in items" |
key |
string |
循环项的 key |
model |
string |
双向绑定(v-model) |
ref |
string |
模板引用 |
| 属性 | 类型 | 说明 |
|---|---|---|
data |
object |
响应式数据定义(Schema 中声明) |
computed |
object |
计算属性(值为表达式字符串) |
watch |
object |
监听器 |
methods |
object |
方法定义 |
💡 data vs state:
data用于 Schema 中声明初始数据,运行时通过state访问当前状态
| 属性 | 类型 | 说明 |
|---|---|---|
onMounted |
Action | Action[] |
组件挂载后执行 |
onUnmounted |
Action | Action[] |
组件卸载前执行 |
onUpdated |
Action | Action[] |
组件更新后执行 |
| 属性 | 类型 | 说明 |
|---|---|---|
initApi |
string | ApiConfig |
组件挂载时请求 API,返回数据与 data 合并 |
uiApi |
string | ApiConfig |
组件挂载时请求 API,返回 JsonNode 替换 children |
{ "set": "count", "value": "{{ count + 1 }}" }{ "call": "methodName", "args": ["{{ arg1 }}", "{{ arg2 }}"] }{ "emit": "eventName", "payload": "{{ data }}" }{
"fetch": "https://api.example.com/data",
"method": "POST",
"body": { "key": "{{ value }}" },
"then": { "set": "result", "value": "{{ $response }}" },
"catch": { "set": "error", "value": "{{ $error.message }}" }
}{
"copy": "{{ shareUrl }}",
"then": { "set": "copied", "value": true },
"catch": { "set": "error", "value": "{{ $error.message }}" }
}兼容性:优先使用 Clipboard API,自动降级到 execCommand
{
"ws": "wss://example.com/socket",
"op": "connect",
"id": "main",
"onMessage": { "set": "lastMessage", "value": "{{ $response }}" }
}{
"if": "count > 10",
"then": { "set": "message", "value": "大于10" },
"else": { "set": "message", "value": "小于等于10" }
}{
"script": "await $methods.login(state.form.username, state.form.password);"
}可用变量: state, computed, $event, $response, $error, $methods
使用 {{ expression }} 语法在字符串中嵌入表达式:
{
"children": "你好,{{ user.name }}!",
"props": {
"class": "{{ isActive ? 'active' : 'inactive' }}",
"disabled": "{{ loading }}"
}
}{
"data": { "title": "加载中..." },
"initApi": "/api/posts",
"com": "div",
"children": "{{ title }}"
}{
"uiApi": "/api/page/{{ pageId }}",
"com": "div",
"children": "加载中..."
}{
"com": "MyCard",
"slots": {
"default": [{ "com": "p", "children": "内容" }],
"header": [{ "com": "h3", "children": "标题" }]
}
}{
"com": "MyList",
"slots": {
"item": {
"content": [{ "com": "span", "children": "{{ slotProps.item.name }}" }],
"slotProps": "slotProps"
}
}
}import { useComponentRegistry } from 'vschema-ui';
import MyButton from './MyButton.vue';
const registry = useComponentRegistry();
registry.register('MyButton', MyButton);import { createApp } from 'vue';
import { createVSchemaPlugin } from 'vschema-ui';
const app = createApp(App);
app.use(createVSchemaPlugin({
baseURL: 'https://api.example.com',
defaultHeaders: {
'Authorization': 'Bearer token'
},
responseDataPath: 'data',
// API 响应格式配置
responseFormat: {
codeField: 'code', // 业务状态码字段名,默认 'code'
msgField: 'msg', // 消息字段名,默认 'msg'
dataField: 'data', // 数据字段名,默认 'data'
successCode: 200, // 业务成功状态码,默认 200,支持数组如 [0, 200]
},
components: {
MyButton: MyButtonComponent
}
}));VSchema 支持自定义后端 API 返回格式,默认格式为 { code, msg, data }:
// 自定义响应格式
responseFormat: {
codeField: 'status', // 后端使用 status 字段
msgField: 'message', // 后端使用 message 字段
dataField: 'result', // 后端使用 result 字段
successCode: [0, 200], // 0 和 200 都表示成功
}当 API 返回的业务状态码不等于 successCode 时,会自动触发 catch 回调,错误信息从 msgField 字段提取。
支持 Vue 事件修饰符语法:
{
"events": {
"click.prevent.stop": { "call": "handleClick" },
"keyup.enter": { "call": "submit" },
"keyup.ctrl.s": { "call": "save" }
}
}支持的修饰符:.prevent, .stop, .capture, .self, .once, .passive, .enter, .tab, .esc, .space, .up, .down, .left, .right, .ctrl, .alt, .shift, .meta
{
"watch": {
"searchText": { "call": "doSearch" },
"user": {
"handler": { "call": "onUserChange" },
"immediate": true,
"deep": true
}
}
}表达式求值器内置安全检查,禁止以下危险操作:
eval(),Function()等代码执行window,document,globalThis等全局对象访问constructor,__proto__等原型链操作- 直接的
fetch,XMLHttpRequest调用(请使用 fetch 动作)
{
"data": { "count": 0 },
"computed": { "double": "count * 2" },
"com": "div",
"children": [
{ "com": "p", "children": "计数: {{ count }}, 双倍: {{ double }}" },
{
"com": "button",
"events": { "click": { "set": "count", "value": "{{ count + 1 }}" } },
"children": "增加"
}
]
}{
"data": { "todos": [], "newTodo": "" },
"methods": {
"addTodo": {
"if": "newTodo.trim()",
"then": [
{ "set": "todos", "value": "{{ [...todos, { id: Date.now(), text: newTodo }] }}" },
{ "set": "newTodo", "value": "" }
]
}
},
"com": "div",
"children": [
{
"com": "input",
"model": "newTodo",
"props": { "placeholder": "添加任务..." },
"events": { "keyup.enter": { "call": "addTodo" } }
},
{
"com": "ul",
"children": [
{
"for": "todo in todos",
"key": "{{ todo.id }}",
"com": "li",
"children": "{{ todo.text }}"
}
]
}
]
}# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 运行测试
pnpm test
# 构建
pnpm build