@@ -46,6 +46,11 @@ const routes: Array<RouteRecordRaw> = [ | |||||
path: '/difficult', | path: '/difficult', | ||||
name: 'difficult', | name: 'difficult', | ||||
component: () => import('@/views/rescueCalculation/difficult.vue') | component: () => import('@/views/rescueCalculation/difficult.vue') | ||||
}, | |||||
{ | |||||
path: '/demo', | |||||
name: 'demo', | |||||
component: () => import('@/views/ddmDemo/Index.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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -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> |
@@ -0,0 +1,11 @@ | |||||
<script setup lang="ts"> | |||||
</script> | |||||
<template> | |||||
<div>这是PageOrDialog组件</div> | |||||
</template> | |||||
<style scoped lang="less"> | |||||
</style> |
@@ -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> |