Skip to content

A Vue 3 plugin for building dynamic UI from JSON Schema - declarative, reactive, and powerful

License

Notifications You must be signed in to change notification settings

maliang/vschema-ui

Repository files navigation

VSchema-UI

npm version License: MIT

一个强大的 Vue 3 插件,通过 JSON Schema 声明式构建动态 UI。支持响应式数据、计算属性、事件处理、条件渲染、循环渲染、API 调用、WebSocket 等完整功能。

English | 简体中文

✨ 特性

  • 🎯 声明式配置 - 通过 JSON Schema 定义组件结构,无需编写 Vue 模板
  • 🔄 响应式数据 - 完整支持 Vue 3 响应式系统
  • 📊 计算属性 - 支持表达式计算和派生状态
  • 🎪 事件处理 - 灵活的事件绑定和动作系统
  • 🔀 条件渲染 - 支持 ifshow 指令
  • 🔁 循环渲染 - 支持 for 指令遍历数组
  • 📝 表单绑定 - 支持 model 双向绑定
  • 🌐 API 调用 - 内置 fetch 动作和 initApi/uiApi 配置
  • 🔌 WebSocket - 支持长连接和实时通信
  • 📋 剪贴板 - 内置 copy 动作,兼容各种浏览器
  • 🎰 插槽支持 - 支持默认插槽、具名插槽和作用域插槽
  • 🔄 生命周期 - 支持 onMountedonUnmountedonUpdated 钩子
  • 👁️ 监听器 - 支持 watch 监听状态变化
  • 🧩 组件注册 - 支持注册自定义组件
  • 🔒 安全 - 内置表达式安全检查,防止 XSS 攻击

📦 安装

pnpm add vschema-ui
#
npm install vschema-ui
#
yarn add vschema-ui

🚀 快速开始

方式一:全局注册插件

import { 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>

3. 注入外部数据和方法

<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 结构

VSchema 组件 Props

属性 类型 说明
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[] 组件更新后执行

API 配置

属性 类型 说明
initApi string | ApiConfig 组件挂载时请求 API,返回数据与 data 合并
uiApi string | ApiConfig 组件挂载时请求 API,返回 JsonNode 替换 children

🎬 动作类型 (Actions)

Set 动作 - 修改状态

{ "set": "count", "value": "{{ count + 1 }}" }

Call 动作 - 调用方法

{ "call": "methodName", "args": ["{{ arg1 }}", "{{ arg2 }}"] }

Emit 动作 - 触发事件

{ "emit": "eventName", "payload": "{{ data }}" }

Fetch 动作 - API 调用

{
  "fetch": "https://api.example.com/data",
  "method": "POST",
  "body": { "key": "{{ value }}" },
  "then": { "set": "result", "value": "{{ $response }}" },
  "catch": { "set": "error", "value": "{{ $error.message }}" }
}

Copy 动作 - 复制到剪贴板

{
  "copy": "{{ shareUrl }}",
  "then": { "set": "copied", "value": true },
  "catch": { "set": "error", "value": "{{ $error.message }}" }
}

兼容性:优先使用 Clipboard API,自动降级到 execCommand

WebSocket 动作 - 长连接

{
  "ws": "wss://example.com/socket",
  "op": "connect",
  "id": "main",
  "onMessage": { "set": "lastMessage", "value": "{{ $response }}" }
}

If 动作 - 条件执行

{
  "if": "count > 10",
  "then": { "set": "message", "value": "大于10" },
  "else": { "set": "message", "value": "小于等于10" }
}

Script 动作 - 自定义脚本

{
  "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 }}"
  }
}

🌐 initApi 和 uiApi

initApi - 初始化数据

{
  "data": { "title": "加载中..." },
  "initApi": "/api/posts",
  "com": "div",
  "children": "{{ title }}"
}

uiApi - 动态 UI 加载

{
  "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 监听器

{
  "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

📄 License

MIT

About

A Vue 3 plugin for building dynamic UI from JSON Schema - declarative, reactive, and powerful

Resources

License

Stars

Watchers

Forks

Packages

No packages published