vue-mod-page/src/components/DemiHelper.ts

137 lines
4.4 KiB
TypeScript

import * as Vue3 from "vue";
// @ts-ignore
import { ComponentInternalInstance, h, isVue2, isVue3, type VNode, type VNodeChildren, type VNodeData } from "vue-demi";
import { InvokerItem } from "./InvokerItem";
function splitAttrs(obj: object) : { attrs: Record<string, any>, on: Record<string, any> } {
const attrs: Record<string, any> = {};
const listeners: Record<string, any> = {};
for (const key in obj) {
if (key.indexOf('on') == 0) {
const newKey = key[2].toLowerCase() + key.substring(3);
listeners[newKey] = obj[key];
} else {
attrs[key] = obj[key];
}
}
console.log("Listener", listeners)
return { attrs, on: listeners };
}
/**
* 将扁平化的组件数据对象分离为Vue2形式的data对象
* @param rawData
* @returns
*/
function splitVue2Data(rawData?: Record<string, any> | null) {
// console.warn("Handling Vue2 data: ", rawData);
if (!rawData) return {};
if (isVue3) throw new Error("Vue3 data object is not supported in Vue2");
// Vue2分离式处理
else {
const on = {}; // 可能需要区分NativeOn
const { class: cls, style, attrs = {}, props = {}, ...rest } = rawData;
// 处理事件
for (const key in rest) {
if (key.indexOf('on') == 0 && key != 'on') {
const newKey = key[2].toLowerCase() + key.substring(3);
on[newKey] = rest[key];
}
}
const v2data = {
class: cls,
style,
attrs: attrs,
props: { ...props, ...rest }, // 未明确的属性放入props
on,
}
return v2data;
}
}
/**
* 渲染具名插槽
* @param slotFn
* @param args
* @returns
*/
function renderSlot(slotFn: Function, args: any, renderFunction?: Function) {
let child: string | Array<InvokerItem> | InvokerItem = slotFn(args);
// console.warn("Rendering Slot: ", child);
// 字符串情况
if (typeof child == 'string') {
return child;
}
// Array<InvokerItem>情况
else if (Array.isArray(child)) {
return child.map((c: InvokerItem) => hh(c.tag, c.data, c.children, renderFunction));
}
// InvokerItem情况
else {
return hh(child.tag, child.data, child.children, renderFunction);
}
}
export function hh(tag: string | object, data?: Record<string, any> | null, children?: string | Array<InvokerItem> | Record<string, Function>, renderFunction?: Function) {
// 适配Vue2渲染函数结构
// console.debug("Rendering", tag, data, children, `Vue Version: ${isVue2 ? 'Vue2' : 'Vue3'}`);
const render = renderFunction || h;
// 处理tag
let processedTag: string | object = tag;
if (isVue3 && typeof tag == 'string') {
// Vue 3 需要解析全局组件
// @ts-ignore - Vue 3 的 resolveComponent 需要特殊处理
const resolved = Vue3?.resolveComponent?.(tag);
processedTag = resolved || tag;
}
// 处理data
let processedData: any = null;
if (isVue2) {
processedData = splitVue2Data(data);
}
else processedData = data;
// 处理children
let processedChildren: any = {};
// 字符串则直接返回
if (typeof children == 'string') {
processedChildren = children
}
// 子节点列表则递归处理
else if (Array.isArray(children)) {
processedChildren = children.map(child => hh(child.tag, child.data, child.children, renderFunction));
// console.warn("Processed Children: ", processedChildren);
}
// 处理具名插槽对象
else if (children && typeof children == 'object') {
if (isVue3) {
processedChildren = Object.fromEntries(
Object.entries(children).map(([name, slotFn]) => [
name,
(args) => renderSlot(slotFn, args, renderFunction)
])
)
}
else {
// 兼容Vue2插槽
processedData['scopedSlots'] = Object.fromEntries(
Object.entries(children).map(([name, slotFn]) => [
name,
(args) => {
return renderSlot(slotFn, args, renderFunction);
}
])
);
}
}
// console.debug("Processed", {
// processedTag, processedData, processedChildren
// });
return render(processedTag, processedData, processedChildren);
}