动态Vue组件
动态Vue组件允许您通过字符串形式的Vue单文件组件(SFC)内容来动态创建和渲染Vue组件。该组件支持完整的Vue SFC语法,包括template、script和style部分,并提供了样式隔离和错误处理机制。
基础使用
基本渲染
通过传入Vue单文件组件字符串来动态渲染组件:
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中使用
const rule = [
{
type: 'dynamic-render',
field: 'dynamicComponent',
props: {
vueContent: vueContent
}
}
]配置项
动态Vue组件提供了以下配置选项:
| 属性名 | 类型 | 默认值 | 必需 | 说明 |
|---|---|---|---|---|
| vueContent | String | - | 是 | Vue单文件组件字符串内容 |
事件
动态Vue组件提供了丰富的事件,方便您监听组件状态变化:
| 事件名 | 参数 | 说明 |
|---|---|---|
| mounted | element | 组件挂载完成事件,当动态组件渲染完成时触发 |
| error | error | 组件解析错误事件,当Vue内容解析失败时触发 |
| update:modelValue | value | 双向绑定值更新事件,当组件值变化时触发 |
事件使用示例
监听组件挂载:
function handleMounted(element) {
console.log('动态组件已挂载:', element);
// 可以在这里进行DOM操作或初始化
}监听解析错误:
function handleError(error) {
console.error('组件解析失败:', error);
// 显示错误提示给用户
showErrorMessage('组件内容解析失败,请检查语法');
}监听值变化:
function handleValueChange(value) {
console.log('组件值变化:', value);
// 可以实时保存数据或触发其他操作
}方法
动态Vue组件提供了多种方法,方便您进行程序化控制:
| 方法名 | 参数 | 说明 | 返回值 |
|---|---|---|---|
| reRender | - | 重新渲染组件 | - |
| removeStyle | id | 移除指定样式 | Boolean |
| clearAllStyles | - | 清除所有动态添加的样式 | Number |
方法使用示例
重新渲染组件:
function refreshComponent($inject) {
const dynamicRender = $inject.api.el('ref_dynamic_render');
dynamicRender.reRender();
}清除样式:
function clearStyles($inject) {
const dynamicRender = $inject.api.el('ref_dynamic_render');
const removedCount = dynamicRender.clearAllStyles();
console.log(`清除了 ${removedCount} 个样式`);
}支持的Vue语法
Template语法
支持完整的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
<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样式:
<style>
.component-wrapper {
padding: 20px;
background: #f5f5f5;
}
.component-wrapper h3 {
color: #333;
margin-bottom: 10px;
}
</style>导入外部依赖
虽然动态组件不支持 import 语法,但您可以通过以下方式实现相同的功能:
方案一:使用外部变量(推荐)
通过 formCreate.setData 设置外部变量,然后在动态组件中通过 api.getData 获取。这是最推荐的方式,详细说明请查看导入外部数据文档。
1. 在应用启动时设置外部变量:
// 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. 在动态组件中使用外部变量:
<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 方法。详细说明请查看依赖资源加载文档。
<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:
// 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:
<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> 标签加载到全局,可以直接使用:
<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>最佳实践
- 优先使用扩展 API:对于业务相关的通用方法(如消息提示、数据请求、工具函数等),建议通过
formCreate.extendApi扩展 API,这样在动态组件中可以直接通过api对象调用,代码更简洁统一 - 使用外部变量:对于需要共享的数据、实例对象等,通过
formCreate.setData设置,在动态组件中通过api.getData获取 - 清理资源:在组件卸载时,记得清理通过
formCreate.setData设置的数据,避免内存泄漏
示例
动态组件可以完美集成到FormCreate表单中:
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('自定义组件已挂载')
}
}
}
]动态更新组件内容
function updateComponentContent($inject, newContent) {
const dynamicRender = $inject.api.el('ref_dynamic_render');
// 更新vueContent属性会触发重新渲染
dynamicRender.vueContent = newContent;
}错误处理
组件内置了完善的错误处理机制:
const rule = [
{
type: 'dynamic-render',
field: 'errorHandling',
props: {
vueContent: invalidVueContent
},
on: {
error: (error) => {
console.error('组件解析错误:', error);
// 可以显示友好的错误提示
showErrorToast('组件内容有误,请检查语法');
}
}
}
]注意事项
注意
该组件需要导入完整的 Vue 版本才能正常工作,请确保在构建配置中正确设置 Vue 别名。
1. Vue 版本配置
动态组件需要完整的 Vue 版本支持,请在构建配置中添加以下别名设置:
Vue 3 配置:
// vite.config.js
export default {
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js'
}
}
}// webpack.config.js
module.exports = {
resolve: {
alias: {
vue: 'vue/dist/vue.esm-bundler.js'
}
}
}Vue 2 配置:
// vite.config.js
export default {
resolve: {
alias: {
vue: 'vue/dist/vue.esm.js'
}
}
}// webpack.config.js
module.exports = {
resolve: {
alias: {
vue: 'vue/dist/vue.esm.js'
}
}
}2. 外部资源限制
限制
动态组件内部不能通过 import 或 require 导入外部资源,包括:
- 外部 JavaScript 模块
- CSS 文件
- 图片资源
- 字体文件
不支持的用法:
<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 语法,但您可以通过多种方式实现相同的功能。详细说明和示例请查看导入外部依赖章节。


