137 lines
4.4 KiB
TypeScript
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);
|
|
}
|