重构 PluginCard 组件,替换 FormRender 为 DynamicRender,优化动态渲染逻辑;删除不再使用的 FormRender 组件

This commit is contained in:
jxxghp
2025-01-11 16:24:16 +08:00
parent 6ea6f89ab2
commit 3023214072
4 changed files with 115 additions and 89 deletions

View File

@@ -0,0 +1,98 @@
<script setup lang="ts">
import { h, resolveComponent, defineProps } from 'vue'
// 定义 props
const props = defineProps<{
config: Record<string, any> // JSON 配置
model: Record<string, any> // 数据模型
}>()
/**
* 解析属性,支持 v-model 和动态绑定
* @param rawProps 原始属性
* @param model 数据模型
* @returns 解析后的属性
*/
const parseProps = (rawProps: Record<string, any>, model: Record<string, any>) => {
const parsedProps: Record<string, any> = {}
for (const [key, value] of Object.entries(rawProps)) {
if (key === 'modelvalue') {
// 将 modelvalue 转换为 v-model:value 的形式
parsedProps['value'] = model[value]
parsedProps['onUpdate:value'] = (newValue: any) => {
model[value] = newValue
}
} else if (key === 'model') {
// 处理 v-model
parsedProps['modelValue'] = model[value]
parsedProps['onUpdate:modelValue'] = (newValue: any) => {
model[value] = newValue
}
} else if (key.startsWith('model:')) {
// 处理 v-model:<prop>
const propName = key.replace('model:', '')
parsedProps[propName] = model[value]
parsedProps[`onUpdate:${propName}`] = (newValue: any) => {
model[value] = newValue
}
} else {
// 普通属性直接赋值
parsedProps[key] = typeof value === 'string' && value in model ? model[value] : value
}
}
return parsedProps
}
/**
* 渲染插槽内容
* @param slotContent 插槽配置
* @param model 数据模型
* @param slotScope 插槽作用域
*/
const renderSlotContent = (slotContent: any, model: any, slotScope: any) => {
if (Array.isArray(slotContent)) {
// 如果插槽内容是数组,递归渲染
return slotContent.map(childConfig => renderComponent(childConfig, model, slotScope))
}
// 如果插槽内容是单个配置,递归渲染
return renderComponent(slotContent, model, slotScope)
}
/**
* 渲染组件函数(递归支持嵌套)
* @param config JSON 配置
* @param model 数据模型
* @param slotScope 插槽作用域
* @returns 渲染的组件 VNode
*/
const renderComponent = (config: any, model: any, slotScope: any = {}) => {
const { component, props: componentProps = {}, content = [], slots = {} } = config
// 动态解析组件
const Component = resolveComponent(component)
// 解析属性
const parsedProps = parseProps(componentProps, model)
// 动态插槽解析
const slotNodes: Record<string, any> = {}
for (const [slotName, slotContent] of Object.entries(slots)) {
slotNodes[slotName] = (slotScopeData: any) => renderSlotContent(slotContent, model, slotScopeData)
}
// 渲染组件
return h(Component, parsedProps, {
...slotNodes,
default: () => content.map((childConfig: any) => renderComponent(childConfig, model)),
})
}
</script>
<template>
<!-- 调用递归渲染函数 -->
<div>
<component :is="renderComponent(config, model)" />
</div>
</template>

View File

@@ -1,76 +0,0 @@
<script lang="ts" setup>
import { RenderProps } from '@/api/types'
import { type PropType, ref } from 'vue'
// 输入参数
defineProps({
config: Object as PropType<RenderProps>,
form: Object as PropType<any>,
})
</script>
<template>
<!-- 使用modelvalue -->
<Component
:is="config.component"
v-if="!config.html && !!config.props?.modelvalue"
v-bind="config.props"
v-model:value="form[config.props?.modelvalue]"
>
{{ config.text }}
<!-- slots -->
<template v-for="(slotContents, name) in config.slots || {}" :key="name" v-slot:[name]="{ _props }">
<slot :name="name" v-bind="_props">
<template v-for="(slotItem, slotIndex) in slotContents || []" :key="slotIndex">
<FormRender
v-if="!!slotItem.props?.modelvalue"
v-model:value="form[slotItem.props?.modelvalue]"
:config="slotItem"
:form="form"
/>
<FormRender v-else v-model="form[slotItem.props?.model]" :config="slotItem" :form="form" />
</template>
</slot>
</template>
<!-- content -->
<template v-for="(innerItem, innerIndex) in config.content || []" :key="innerIndex">
<FormRender
v-if="!!innerItem.props?.modelvalue"
v-model:value="form[innerItem.props?.modelvalue]"
:config="innerItem"
:form="form"
/>
<FormRender v-else v-model="form[innerItem.props?.model]" :config="innerItem" :form="form" />
</template>
</Component>
<!-- 使用html -->
<Component :is="config.component" v-else-if="config.html" v-bind="config.props" v-html="config.html" />
<!-- 使用model -->
<Component :is="config.component" v-else v-bind="config.props" v-model="form[config.props?.model]">
{{ config.text }}
<!-- slots -->
<template v-for="(slotContents, name) in config.slots || {}" :key="name" v-slot:[name]="{ _props }">
<slot :name="name" v-bind="_props">
<template v-for="(slotItem, slotIndex) in slotContents || []" :key="slotIndex">
<FormRender
v-if="!!slotItem.props?.modelvalue"
v-model:value="form[slotItem.props?.modelvalue]"
:config="slotItem"
:form="form"
/>
<FormRender v-else v-model="form[slotItem.props?.model]" :config="slotItem" :form="form" />
</template>
</slot>
</template>
<!-- content -->
<template v-for="(innerItem, innerIndex) in config.content || []" :key="innerIndex">
<FormRender
v-if="!!innerItem.props?.modelvalue"
v-model:value="form[innerItem.props?.modelvalue]"
:config="innerItem"
:form="form"
/>
<FormRender v-else v-model="form[innerItem.props?.model]" :config="innerItem" :form="form" />
</template>
</Component>
</template>

View File

@@ -56,16 +56,6 @@ watchEffect(() => {
<template>
<Component :is="config?.component" v-if="!config?.html" v-bind="config?.props" v-on="componentEvents">
{{ config?.text }}
<template v-for="(content, name) in config?.slots || {}" :key="name" v-slot:[name]="{ _props }">
<slot :name="name" v-bind="_props">
<PageRender
v-for="(slotItem, slotIndex) in content || []"
:key="slotIndex"
:config="slotItem"
@action="emit('action')"
/>
</slot>
</template>
<PageRender
v-for="(innerItem, innerIndex) in config?.content || []"
:key="innerIndex"