Skip to content

动态Vue组件

动态Vue组件允许您通过字符串形式的Vue单文件组件(SFC)内容来动态创建和渲染Vue组件。该组件支持完整的Vue SFC语法,包括template、script和style部分,并提供了样式隔离和错误处理机制。

基础使用

基本渲染

通过传入Vue单文件组件字符串来动态渲染组件:

js
const vueContent = `
<template>
  <div class="dynamic-component">
    <h3>{{ title }}</h3>
    <p>{{ message }}</p>
    <button @click="handleClick">点击我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '动态组件',
      message: '这是一个动态渲染的Vue组件'
    }
  },
  methods: {
    handleClick() {
      this.message = '按钮被点击了!'
    }
  }
}
</script>

<style scoped>
.dynamic-component {
  padding: 20px;
  border: 1px solid #ddd;
  border-radius: 8px;
}
</style>
`

在FormCreate中使用

js
const rule = [
  {
    type: 'dynamic-render',
    field: 'dynamicComponent',
    props: {
      vueContent: vueContent
    }
  }
]

配置项

动态Vue组件提供了以下配置选项:

属性名类型默认值必需说明
vueContentString-Vue单文件组件字符串内容

事件

动态Vue组件提供了丰富的事件,方便您监听组件状态变化:

事件名参数说明
mountedelement组件挂载完成事件,当动态组件渲染完成时触发
errorerror组件解析错误事件,当Vue内容解析失败时触发
update:modelValuevalue双向绑定值更新事件,当组件值变化时触发

事件使用示例

监听组件挂载

js
function handleMounted(element) {
    console.log('动态组件已挂载:', element);
    // 可以在这里进行DOM操作或初始化
}

监听解析错误

js
function handleError(error) {
    console.error('组件解析失败:', error);
    // 显示错误提示给用户
    showErrorMessage('组件内容解析失败,请检查语法');
}

监听值变化

js
function handleValueChange(value) {
    console.log('组件值变化:', value);
    // 可以实时保存数据或触发其他操作
}

方法

动态Vue组件提供了多种方法,方便您进行程序化控制:

方法名参数说明返回值
reRender-重新渲染组件-
removeStyleid移除指定样式Boolean
clearAllStyles-清除所有动态添加的样式Number

方法使用示例

重新渲染组件

js
function refreshComponent($inject) {
    const dynamicRender = $inject.api.el('ref_dynamic_render');
    dynamicRender.reRender();
}

清除样式

js
function clearStyles($inject) {
    const dynamicRender = $inject.api.el('ref_dynamic_render');
    const removedCount = dynamicRender.clearAllStyles();
    console.log(`清除了 ${removedCount} 个样式`);
}

支持的Vue语法

Template语法

支持完整的Vue模板语法:

vue
<template>
  <!-- 条件渲染 -->
  <div v-if="showContent">内容显示</div>

  <!-- 列表渲染 -->
  <ul>
    <li v-for="item in list" :key="item.id">
      {{ item.name }}
    </li>
  </ul>

  <!-- 事件处理 -->
  <button @click="handleClick">点击</button>

  <!-- 属性绑定 -->
  <input v-model="inputValue" :placeholder="placeholder" />
</template>

Script语法

Vue 2 Options API

vue
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  watch: {
    count(newVal) {
      console.log('count changed:', newVal)
    }
  }
}
</script>

Style语法

支持CSS样式:

vue
<style>
.component-wrapper {
  padding: 20px;
  background: #f5f5f5;
}

.component-wrapper h3 {
  color: #333;
  margin-bottom: 10px;
}
</style>

导入外部依赖

虽然动态组件不支持 import 语法,但您可以通过以下方式实现相同的功能:

方案一:使用外部变量(推荐)

通过 formCreate.setData 设置外部变量,然后在动态组件中通过 api.getData 获取。这是最推荐的方式,详细说明请查看导入外部数据文档

1. 在应用启动时设置外部变量

js
// main.js 或应用入口文件
import { formCreate } from 'path/to/fcDesignerPro';
import axios from 'axios';
import { formatDate, formatCurrency } from '@/utils/format';

// 设置工具函数
formCreate.setData('formatDate', formatDate);
formCreate.setData('formatCurrency', formatCurrency);

// 设置 API 实例
formCreate.setData('axios', axios);

// 设置工具对象
formCreate.setData('utils', {
  formatDate,
  formatCurrency,
  debounce: (fn, delay) => {
    let timer = null;
    return function(...args) {
      clearTimeout(timer);
      timer = setTimeout(() => fn.apply(this, args), delay);
    };
  }
});

2. 在动态组件中使用外部变量

vue
<template>
  <div class="dynamic-component">
    <p>格式化日期:{{ formattedDate }}</p>
    <p>格式化金额:{{ formattedAmount }}</p>
    <button @click="fetchData">获取数据</button>
  </div>
</template>

<script>
export default {
  props: {
    formCreateInject: Object
  },
  data() {
    return {
      date: new Date(),
      amount: 1234.56,
      formattedDate: '',
      formattedAmount: ''
    }
  },
  mounted() {
    // 通过 $inject.api.getData 获取外部变量
    const formatDate = this.formCreateInject.api.getData('formatDate');
    const formatCurrency = this.formCreateInject.api.getData('formatCurrency');

    if (formatDate) {
      this.formattedDate = formatDate(this.date);
    }
    if (formatCurrency) {
      this.formattedAmount = formatCurrency(this.amount);
    }
  },
  methods: {
    async fetchData() {
      // 获取 axios 实例
      const axios = this.formCreateInject.api.getData('axios');
      if (axios) {
        try {
          const response = await axios.get('/api/data');
          console.log('获取到的数据:', response.data);
        } catch (error) {
          console.error('请求失败:', error);
        }
      }
    }
  }
}
</script>

方案二:使用 loadjs 动态加载库

对于需要动态加载的第三方库,可以使用 loadjs.loadDepend 方法。详细说明请查看依赖资源加载文档

vue
<template>
  <div class="dynamic-component">
    <p>处理后的数据:{{ processedData }}</p>
    <button @click="processData">处理数据</button>
  </div>
</template>

<script>
export default {
  props: {
    formCreateInject: Object
  },
  data() {
    return {
      rawData: [1, 2, 3, 4, 5],
      processedData: []
    }
  },
  methods: {
    async processData() {
      // 动态加载 lodash 库
      this.formCreateInject.form.loadjs('https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js', () => {
          // 使用加载的库(通常会挂载到 window 对象)
          if (window._) {
              this.processedData = window._.map(this.rawData, n => n * 2);
          }
      });
    }
  }
}
</script>

方案三:扩展 API(推荐用于业务方法)

通过 formCreate.extendApi 扩展 API,将自定义方法添加到 API 对象上,这样在动态组件中就可以直接通过 api 对象调用这些方法。

1. 在应用启动时扩展 API

js
// main.js
import { formCreate } from 'path/to/fcDesignerPro';
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';

// 扩展 API,添加自定义方法
formCreate.extendApi(api => {
  return {
    // 自定义 HTTP 请求方法
    async request(url, options = {}) {
      try {
        const response = await axios({
          url,
          method: options.method || 'GET',
          data: options.data,
          headers: options.headers || {}
        });
        return response.data;
      } catch (error) {
        console.error('请求失败:', error);
        throw error;
      }
    },

    // 自定义消息提示方法
    showMessage(message, type = 'success') {
      return ElMessage({
        message,
        type,
        duration: 3000
      });
    },

    // 自定义确认对话框方法
    showConfirm(message, title = '提示') {
      return ElMessageBox.confirm(message, title, {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      });
    },

    // 自定义工具方法
    formatDate(date, format = 'YYYY-MM-DD') {
      // 使用 dayjs 或其他日期库
      if (window.dayjs) {
        return window.dayjs(date).format(format);
      }
      return new Date(date).toLocaleDateString();
    },

    // 自定义数据处理方法
    processData(data) {
      // 自定义数据处理逻辑
      return data.map(item => ({
        ...item,
        processed: true
      }));
    }
  };
});

2. 在动态组件中使用扩展的 API

vue
<template>
  <div class="dynamic-component">
    <p>格式化日期:{{ formattedDate }}</p>
    <button @click="fetchData">获取数据</button>
    <button @click="handleSubmit">提交</button>
  </div>
</template>

<script>
export default {
  props: {
    formCreateInject: Object
  },
  data() {
    return {
      formattedDate: '',
      data: []
    }
  },
  mounted() {
    // 通过 $inject.api 访问扩展的 API
    const api = this.formCreateInject.api;
    if (api.formatDate) {
      this.formattedDate = api.formatDate(new Date());
    }
  },
  methods: {
    async fetchData() {
      const api = this.formCreateInject.api;
      if (api) {
        try {
          // 使用扩展的 request 方法
          if (api.request) {
            const result = await api.request('/api/data', {
              method: 'GET'
            });
            this.data = api.processData ? api.processData(result) : result;

            // 使用扩展的 showMessage 方法
            if (api.showMessage) {
              api.showMessage('数据加载成功', 'success');
            }
          }
        } catch (error) {
          if (api.showMessage) {
            api.showMessage('数据加载失败', 'error');
          }
        }
      }
    },

    async handleSubmit() {
      const api = this.formCreateInject.api;
      if (api.showConfirm) {
        try {
          // 使用扩展的 confirm 方法
          await api.showConfirm('确定要提交吗?', '提交确认');

          // 确认后执行提交
          if (api.request) {
            await api.request('/api/submit', {
              method: 'POST',
              data: this.data
            });

            if (api.showMessage) {
              api.showMessage('提交成功', 'success');
            }
          }
        } catch (error) {
          // 用户取消或请求失败
          if (error !== 'cancel' && api.showMessage) {
            api.showMessage('提交失败', 'error');
          }
        }
      }
    }
  }
}
</script>

方案四:使用全局变量

如果库已经通过 <script> 标签加载到全局,可以直接使用:

vue
<script>
export default {
  methods: {
    // ✅ 使用全局变量(需要确保库已通过 script 标签加载)
    useGlobalLibrary() {
      if (window.axios) {
        window.axios.get('/api/data').then(res => {
          console.log('数据:', res.data);
        });
      }

      if (window._) {
        const result = window._.chunk([1, 2, 3, 4, 5], 2);
        console.log('分块结果:', result);
      }
    }
  }
}
</script>

最佳实践

  1. 优先使用扩展 API:对于业务相关的通用方法(如消息提示、数据请求、工具函数等),建议通过 formCreate.extendApi 扩展 API,这样在动态组件中可以直接通过 api 对象调用,代码更简洁统一
  2. 使用外部变量:对于需要共享的数据、实例对象等,通过 formCreate.setData 设置,在动态组件中通过 api.getData 获取
  3. 清理资源:在组件卸载时,记得清理通过 formCreate.setData 设置的数据,避免内存泄漏

示例

动态组件可以完美集成到FormCreate表单中:

js
const rule = [
  {
    type: 'dynamic-render',
    field: 'customComponent',
    props: {
      vueContent: `
        <template>
          <div>
            <el-input v-model="inputValue" placeholder="请输入内容" />
            <el-button @click="submit">提交</el-button>
          </div>
        </template>
        <script>
        export default {
          data() {
            return {
              inputValue: ''
            }
          },
          methods: {
            submit() {
              console.log('提交值:', this.inputValue)
            }
          }
        }
        </script>
      `
    },
    on: {
      mounted: (element) => {
        console.log('自定义组件已挂载')
      }
    }
  }
]

动态更新组件内容

js
function updateComponentContent($inject, newContent) {
    const dynamicRender = $inject.api.el('ref_dynamic_render');
    // 更新vueContent属性会触发重新渲染
    dynamicRender.vueContent = newContent;
}

错误处理

组件内置了完善的错误处理机制:

js
const rule = [
  {
    type: 'dynamic-render',
    field: 'errorHandling',
    props: {
      vueContent: invalidVueContent
    },
    on: {
      error: (error) => {
        console.error('组件解析错误:', error);
        // 可以显示友好的错误提示
        showErrorToast('组件内容有误,请检查语法');
      }
    }
  }
]

注意事项

注意

该组件需要导入完整的 Vue 版本才能正常工作,请确保在构建配置中正确设置 Vue 别名。

1. Vue 版本配置

动态组件需要完整的 Vue 版本支持,请在构建配置中添加以下别名设置:

Vue 3 配置

js
// vite.config.js
export default {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js'
    }
  }
}
js
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm-bundler.js'
    }
  }
}

Vue 2 配置

js
// vite.config.js
export default {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm.js'
    }
  }
}
js
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      vue: 'vue/dist/vue.esm.js'
    }
  }
}

2. 外部资源限制

限制

动态组件内部不能通过 importrequire 导入外部资源,包括:

  • 外部 JavaScript 模块
  • CSS 文件
  • 图片资源
  • 字体文件

不支持的用法

vue
<script>
// ❌ 不支持外部模块导入
import { someFunction } from './utils'
import axios from 'axios'

// ❌ 不支持外部样式导入
import './styles.css'

export default {
  data() {
    return {}
  }
}
</script>

<style>
/* ❌ 不支持外部资源引用 */
@import url('https://fonts.googleapis.com/css2?family=Roboto');
</style>

替代方案

动态组件虽然不支持 import 语法,但您可以通过多种方式实现相同的功能。详细说明和示例请查看导入外部依赖章节。