mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-05 23:56:42 +08:00
重构 PluginCard 组件,替换 FormRender 为 DynamicRender,优化动态渲染逻辑;删除不再使用的 FormRender 组件
This commit is contained in:
@@ -3,7 +3,7 @@ import { useToast } from 'vue-toast-notification'
|
|||||||
import { useConfirm } from 'vuetify-use-dialog'
|
import { useConfirm } from 'vuetify-use-dialog'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { Plugin } from '@/api/types'
|
import type { Plugin } from '@/api/types'
|
||||||
import FormRender from '@/components/render/FormRender.vue'
|
import DynamicRender from '@/components/render/DynamicRender.vue'
|
||||||
import PageRender from '@/components/render/PageRender.vue'
|
import PageRender from '@/components/render/PageRender.vue'
|
||||||
import VersionHistory from '@/components/misc/VersionHistory.vue'
|
import VersionHistory from '@/components/misc/VersionHistory.vue'
|
||||||
import { isNullOrEmptyObject } from '@core/utils'
|
import { isNullOrEmptyObject } from '@core/utils'
|
||||||
@@ -490,7 +490,12 @@ watch(
|
|||||||
<DialogCloseBtn v-model="pluginConfigDialog" />
|
<DialogCloseBtn v-model="pluginConfigDialog" />
|
||||||
<VDivider />
|
<VDivider />
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<FormRender v-for="(item, index) in pluginFormItems" :key="index" :config="item" :form="pluginConfigForm" />
|
<DynamicRender
|
||||||
|
v-for="(item, index) in pluginFormItems"
|
||||||
|
:key="index"
|
||||||
|
:config="item"
|
||||||
|
:model="pluginConfigForm"
|
||||||
|
/>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VCardActions class="pt-3">
|
<VCardActions class="pt-3">
|
||||||
<VBtn v-if="pluginPageItems.length > 0" @click="showPluginInfo" variant="outlined" color="info">
|
<VBtn v-if="pluginPageItems.length > 0" @click="showPluginInfo" variant="outlined" color="info">
|
||||||
@@ -509,7 +514,16 @@ watch(
|
|||||||
<VCardText class="min-h-40">
|
<VCardText class="min-h-40">
|
||||||
<PageRender @action="loadPluginPage" v-for="(item, index) in pluginPageItems" :key="index" :config="item" />
|
<PageRender @action="loadPluginPage" v-for="(item, index) in pluginPageItems" :key="index" :config="item" />
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<VFab icon="mdi-cog" location="bottom" size="x-large" fixed app appear @click="showPluginConfig" :class="{ 'mb-10': appMode }" />
|
<VFab
|
||||||
|
icon="mdi-cog"
|
||||||
|
location="bottom"
|
||||||
|
size="x-large"
|
||||||
|
fixed
|
||||||
|
app
|
||||||
|
appear
|
||||||
|
@click="showPluginConfig"
|
||||||
|
:class="{ 'mb-10': appMode }"
|
||||||
|
/>
|
||||||
</VCard>
|
</VCard>
|
||||||
</VDialog>
|
</VDialog>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
|
||||||
@@ -56,16 +56,6 @@ watchEffect(() => {
|
|||||||
<template>
|
<template>
|
||||||
<Component :is="config?.component" v-if="!config?.html" v-bind="config?.props" v-on="componentEvents">
|
<Component :is="config?.component" v-if="!config?.html" v-bind="config?.props" v-on="componentEvents">
|
||||||
{{ config?.text }}
|
{{ 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
|
<PageRender
|
||||||
v-for="(innerItem, innerIndex) in config?.content || []"
|
v-for="(innerItem, innerIndex) in config?.content || []"
|
||||||
:key="innerIndex"
|
:key="innerIndex"
|
||||||
|
|||||||
Reference in New Issue
Block a user