张海 пре 1 месец
родитељ
комит
2a0d0cedde
8 измењених фајлова са 567 додато и 0 уклоњено
  1. +5
    -0
      src/router/index.ts
  2. +30
    -0
      src/views/ddmDemo/Center.vue
  3. +16
    -0
      src/views/ddmDemo/Footer.vue
  4. +17
    -0
      src/views/ddmDemo/Header.vue
  5. +299
    -0
      src/views/ddmDemo/Index.vue
  6. +34
    -0
      src/views/ddmDemo/Layout.vue
  7. +11
    -0
      src/views/ddmDemo/PageOrDialog.vue
  8. +155
    -0
      src/views/ddmDemo/Panel.vue

+ 5
- 0
src/router/index.ts Прегледај датотеку

@@ -46,6 +46,11 @@ const routes: Array<RouteRecordRaw> = [
path: '/difficult',
name: 'difficult',
component: () => import('@/views/rescueCalculation/difficult.vue')
},
{
path: '/demo',
name: 'demo',
component: () => import('@/views/ddmDemo/Index.vue')
}

]


+ 30
- 0
src/views/ddmDemo/Center.vue Прегледај датотеку

@@ -0,0 +1,30 @@

<template>
<div class="center">
<div>这是Center.vue组件</div>
<slot>center组件默认插槽</slot>
<div></div>
<!-- <slot name="footer" :val="'插槽传参'">center组件具名插槽</slot>-->
<!-- 父向子传参-->
<!-- {{msg}}-->
</div>
</template>

<script setup lang="ts">
import { ref, defineEmits, defineProps } from 'vue'
const emits = defineEmits(['onFoo'])
defineProps(['msg'])
setTimeout(() => {
console.log('hhhhh')
emits('onFoo', '子向父组件传参')
}, 2000)

</script>

<style scoped lang="less">
.center{
height: 500px;
border: 1px solid rebeccapurple;

}
</style>

+ 16
- 0
src/views/ddmDemo/Footer.vue Прегледај датотеку

@@ -0,0 +1,16 @@
<script setup lang="ts">

</script>

<template>
<div class="footer">
这是footer组件
</div>
</template>

<style scoped lang="less">
.footer{
border: 1px solid skyblue;
height: 200px;
}
</style>

+ 17
- 0
src/views/ddmDemo/Header.vue Прегледај датотеку

@@ -0,0 +1,17 @@
<script setup lang="ts">

</script>

<template>
<div class="header">
这是Header.vue组件
</div>
</template>

<style scoped lang="less">
.header{
//width: 100%;
height: 300px;
border: 1px solid salmon;
}
</style>

+ 299
- 0
src/views/ddmDemo/Index.vue Прегледај датотеку

@@ -0,0 +1,299 @@
<template>
<div>
<!-- <div>123</div>-->
<!-- <component :is="Comp"></component>-->
<!-- 直接以函数的形式渲染组件-->
<!-- <Comp :count="'展示props传参'">-->
<!-- <div>使用函数组件插槽</div>-->
<!-- <template #header>-->
<!-- <div>具名插槽</div>-->
<!-- </template>-->
<!-- </Comp>-->
<!-- <Comp2>-->
<!-- <div>使用center组件的插槽</div>-->
<!-- </Comp2>-->
<!-- <center @onFoo="(val)=>{console.log(val)}"></center>-->
<Comp3></Comp3>
<!-- <component :is="Comp3"></component>-->
<div>
动态渲染组件
<component
:is="dynamicComponent"
v-bind="jsonConfig.template.props"
></component>
</div>
</div>
</template>

<script setup lang="ts">
import { compile } from '@vue/compiler-dom'
import { defineComponent, resolveComponent, h, ref, type FunctionalComponent, onMounted } from 'vue'
import * as Vue from 'vue'
import Center from './Center.vue'
import PageOrDialog from './PageOrDialog.vue'
import Layout from './Layout.vue'
import Header from './Header.vue'
import Panel from './Panel.vue'
import Footer from './Footer.vue'

// 模拟 JSON 数据
const jsonConfig = {
template: {
type: 'custom',
name: 'CustomComponent',
code: `<div>
<el-button>这是一个json传入的按钮</el-button>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>`,
props: {
title: '动态标题',
description: '这是通过 JSON 动态渲染的 Vue 代码'
}
}
}

// 编译 JSON 中的 Vue 模板为渲染函数
const createDynamicComponent = (template: string) => {
const { code } = compile(template, { mode: 'function' })
// eslint-disable-next-line no-new-func
const renderFunction = new Function('Vue', code)(Vue)

// 返回一个动态组件
return defineComponent({
props: ['title', 'description'],
render: renderFunction
})
}

// 解析 JSON 并生成动态组件
const dynamicComponent = ref()
if (jsonConfig.template?.code) {
dynamicComponent.value = createDynamicComponent(jsonConfig.template.code)
}

// h函数基础使用
// const comp = h('div', { style: 'color: red' }, 'Hello World')
const components = { Layout, PageOrDialog, Header, Panel, Center, Footer }
// 使用响应式变量 和 使用 子节点的嵌套,事件,属性,插槽的使用
const msg = ref('使用变量')
// 注意这里是一个函数 FunctionalComponent:方法组件类型声明
// 这里是声明了一个函数组件,然后被使用
const Comp = ((props, { slots }) => {
return h('div', { style: 'color: red', onClick () { msg.value = '点击了' } },
[
h('span', {}, msg.value),
h('h1', {}, props.count),
h('h1', {}, [slots?.header?.(), slots?.default?.()])
])
}) as FunctionalComponent<{ count:string }>
// 这里是声明了一个函数组件,但是是使用的已经定义好的组件
const Comp2 = ((props, { slots }) => {
return h(Center, {
msg: '我是参数',
// 事件传参
onFoo (val:any) {
console.log(123123)
console.log('点击了' + val)
}
}, {
default: slots.default,
// 这里在footer插槽里面又嵌套了Center组件继续使用插槽,递归组件
footer: () => h(Center, null, {
default: () => h('div', {}, '我是footer插槽'),
footer: ({ val }) => h('div', {}, val + '使用插槽传参数')
})
})
}) as FunctionalComponent
// 递归解析模板的函数
const parseTemplate = (template: any, model: Record<string, any>) => {
if (!template) return null

const componentName =
template.type in components
? components[template.type]
: resolveComponent(template.type || 'div')

// 查找 model 数据并传递给组件
const modelData = model[template.name] || {}
console.log(modelData, 'modelData')
console.log(template.type, 'modelData')
const children = (template.children || []).map((child: any) =>
parseTemplate(child, model)
)

return h(
componentName,
{
style: { margin: '20px', padding: '20px' },
...template.props, // 组件自带的 props
data: modelData // 绑定的 model 数据
},
() => children
)
}

// const PageRenderer = defineComponent({
// props: ['config'],
// setup (props) {
// return () => parseTemplate(template)
// }
// })
// function bindModel (modelConfig, data) {
// const model = {}
// for (const [key, value] of Object.entries(modelConfig)) {
// model[key] = value.fields.map((field) => ({
// ...field,
// value: data[field.field.split('.')[0]][field.field.split('.')[1]]
// }))
// }
// return model
// }
const Comp3 = () => parseTemplate(json.form[0].template, json.form[0].model)
onMounted(() => {
// console.log(Comp2)
// console.log(Comp3())
// console.log(parseTemplate(json.form[0].template))
})
const json = {
form: [
{
page: {
name: '',
type: 'page/dialog',
title: '中文标题',
template: '模板文件,为空则读自定义模板'
},
template: {
type: 'Layout',
name: 'l1',
children: [
{
type: 'Header',
name: 'h1',
props: {
title: '标题',
subTitle: '副标题',
icon: 'icon'

}
},
{
type: 'Center',
name: 'c1',
children: [
{
type: 'Panel',
name: 'p1'
}

]

},
{
type: 'Footer',
name: 'Footer',
props: {
title: '标题',
subTitle: '副标题',
icon: 'icon'

}

}
]
},
model: {

p1: {
vueComponent: {
vueTemplate: `
<div style="background-color: #42b883;height: 180px;line-height: 30px;color: #fff;width: 400px;border-radius: 4px;padding:10px;margin: 0 auto">
<el-button type="primary" @click="log123">点击事件测试</el-button>

<h1>{{ title }}</h1>
<p >{{ description }}</p>
<p>点击次数:{{ clickCount }}</p>
<p>计算属性{{capitalizedTitle}}</p>

</div>`,
props: {
title: '动态标题',
description: '这是通过 JSON 动态渲染的 Vue 代码块'
},
methods: {
log123 () {
console.log('按钮点击事件触发!')
this.clickCount++
}
},
data: {
clickCount: 0
},
computed: {
capitalizedTitle () {
return this.clickCount + '00'
}
},
watch: {
clickCount (newValue) {
console.log('点击次数发生变化:', newValue)
}
}
},

dataset: [
{
d1: { f1: '我是数据集数据' }
},
{
d2: { f2: '我是f2' }
}
],
fields: [
{
name: 'f1',
field: 'd1.f1',
type: 'combo',
readonly: true
},
{
name: 'f1',
field: 'd1.f1',
type: 'vue',
content: { string: '自定义vue放这里' }
}
]
}

}
}
]
}

const template = {
type: 'div',
props: { class: 'container' },
children: [
{
type: 'span',
props: { class: 'text' },
children: ['Hello']
},
{
type: 'button',
props: { onClick: () => alert('Clicked!') },
children: ['Click me']
}
]
}

// const vNode = parseTemplate(template)
// console.log(PageRenderer)
// console.log(vNode)

</script>

<style scoped lang="less">

</style>

+ 34
- 0
src/views/ddmDemo/Layout.vue Прегледај датотеку

@@ -0,0 +1,34 @@
<script setup lang="ts">
import { ref, useAttrs, defineProps } from 'vue'

// 定义 props,并为 p1 添加类型
// defineProps<{
// p1: {
// name: string;
// value: any;
// };
// }>()

// 使用 useAttrs 获取未定义的属性
const attrs = useAttrs()
const count = ref(0)

// 调试输出
console.log(attrs, 'attrs') // 打印未定义的属性
// console.log(p1, 'p1') // 打印 props 中的 p1
</script>

<template>
<div v-bind="$attrs" class="layout">
这是Layout.vue组件
<slot></slot>
</div>

</template>

<style scoped lang="less">
.layout{
text-align: center;
border: 1px solid seagreen;
}
</style>

+ 11
- 0
src/views/ddmDemo/PageOrDialog.vue Прегледај датотеку

@@ -0,0 +1,11 @@
<script setup lang="ts">

</script>

<template>
<div>这是PageOrDialog组件</div>
</template>

<style scoped lang="less">

</style>

+ 155
- 0
src/views/ddmDemo/Panel.vue Прегледај датотеку

@@ -0,0 +1,155 @@
<template>
<div class="panel">
<p style="background-color: #fff;" >这是 Panel 组件</p>
<!-- 渲染 JSON 动态组件 -->
<div>{{data.dataset}}</div>
<component
v-if="dynamicComponent"
:is="dynamicComponent"
v-bind="data?.vueComponent.props"
></component>

<!-- 自定义渲染函数 -->
<!-- <div v-else-if="renderedContent">-->
<!-- <div v-html="renderedContent"></div>-->
<!-- </div>-->
<!-- 默认内容 -->
<div v-else>
<p>没有动态组件或渲染函数</p>
</div>
</div>

</template>
<script setup lang="ts">
import { compile } from '@vue/compiler-dom'
import { defineComponent, h, ref, shallowRef, reactive, computed, defineProps, useAttrs, watch, toRefs } from 'vue'
import * as Vue from 'vue'

const attrs = useAttrs()
const props = defineProps({
// title: String,
// description: String,
// vueComponent: Object,
data: Object
})
console.log(props, 'awdasdas')
// 解构 props
// const { customContent, render1 } = toRefs(props)

// 模拟 JSON 配置数据
const jsonConfig = {
template: {
type: 'custom',
name: 'CustomComponent',
code: `<div>
<el-button type="primary" @click="log123">这是一个json传入的按钮</el-button>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>`,
props: {
title: '动态标题',
description: '这是通过 JSON 动态渲染的 Vue 代码'
},
methods: {
log123 () {
console.log('按钮点击事件触发!')
}
}
}
}

// 动态组件解析器
const createDynamicComponent = (
template: string,
options:{
props?: Record<string, any> // 动态 props
methods?: Record<string, Function> // 动态方法
data?: Record<string, any> // 动态 data
computed?: Record<string, Function> // 动态计算属性
watch?: Record<string, Function> // 动态 watch
} = {}
) => {
try {
// 编译模板为渲染函数
const { code } = compile(template, { mode: 'function' })

// eslint-disable-next-line no-new-func
const renderFunction = new Function('Vue', code)(Vue)

// 返回动态组件定义
return defineComponent({
name: 'DynamicComponent',
props: options.props || {},
setup () {
// 响应式 data
const state = reactive({
...options.data
})

// 计算属性
const computedValues = {}
if (options.computed) {
for (const [key, fn] of Object.entries(options.computed)) {
computedValues[key] = computed(fn.bind(state))
}
}

// 监听器
if (options.watch) {
for (const [key, fn] of Object.entries(options.watch)) {
watch(
() => state[key],
fn.bind(state)
)
}
}

// 方法绑定
const methods = {}
if (options.methods) {
for (const [name, method] of Object.entries(options.methods)) {
methods[name] = method.bind(state)
}
}
return {
...toRefs(state),
...computedValues,
...methods,
...props // 保留 props
}
},
render: renderFunction
})
} catch (error) {
console.error('编译模板失败:', error)
return null
}
}

// 动态组件引用shallowRef
const dynamicComponent = ref<ReturnType<typeof defineComponent> | null>(null)
// data.vueComponent.vueTemplate
// 初始化动态组件
if (props?.data?.vueComponent?.vueTemplate) {
console.log(123123132)
dynamicComponent.value = createDynamicComponent(
props?.data?.vueComponent.vueTemplate,
{
props: props?.data?.vueComponent.props,
methods: props?.data?.vueComponent.methods,
data: props?.data?.vueComponent.data,
computed: props?.data?.vueComponent.computed,
watch: props?.data?.vueComponent.watch
}

)
console.log(dynamicComponent.value, 'dynamicComponent')
}
</script>

<style scoped lang="less">
.panel{
//height: 200px;
border: 1px solid gold;
}
</style>

Loading…
Откажи
Сачувај