初始创建!

This commit is contained in:
2024-12-19 13:51:07 +08:00
commit fa8b468c32
23 changed files with 3004 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
import * as Vue3 from "vue";
import { h, isVue2, isVue3, type VNode, type VNodeChildren, type VNodeData } from "vue-demi";
import { InvokerItem } from "./InvokerItem";
import { ComponentInternalInstance } from "vue-demi/lib/v2/index.js";
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, ctx?: ComponentInternalInstance) {
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, ctx));
}
// InvokerItem情况
else {
return hh(child.tag, child.data, child.children, ctx);
}
}
export function hh(tag: string, data?: Record<string, any> | null, children?: string | Array<InvokerItem> | Record<string, Function>, ctx?: ComponentInternalInstance) {
// 适配Vue2渲染函数结构
// console.debug("Rendering", tag, data, children, `Vue Version: ${isVue2 ? 'Vue2' : 'Vue3'}`);
// 处理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, ctx));
}
// 处理具名插槽对象
else if (children && typeof children == 'object') {
if (isVue3) {
processedChildren = Object.fromEntries(
Object.entries(children).map(([name, slotFn]) => [
name,
(args) => renderSlot(slotFn, args, ctx)
])
)
}
else {
// 兼容Vue2插槽
processedData['scopedSlots'] = Object.fromEntries(
Object.entries(children).map(([name, slotFn]) => [
name,
(args) => {
return renderSlot(slotFn, args);
}
])
);
}
}
console.debug("Processed", processedTag, processedData, processedChildren);
return h(processedTag, processedData, processedChildren);
}

152
src/components/Invoker.tsx Normal file
View File

@@ -0,0 +1,152 @@
import {
defineComponent,
onBeforeUpdate,
onUnmounted,
watch,
} from "vue-demi";
let idCount = 0 ;
export default defineComponent({
props: {
name: {
type: String,
default: () => null,
},
height: {
type: String,
default: () => "auto",
},
scroll: {
type: Boolean,
default: () => false,
},
items: {
type: Array ,
default: () => undefined,
},
url: {
type: String,
default: () => null,
},
},
setup(props, { slots, expose }) {
// const { proxy } = getCurrentInstance();
//
// const id = modService.addInvoker(proxy);
const id = ++idCount;
const isLocalInvoker = false;
let modContext: any = null;
const invokeMethods: { method: string; args: any[] }[] = [];
let isTemplate = false;
window["MES_MOD_INVOKERS"] ||= {};
window["MES_MOD_INVOKERS"][id] = {
getRenderContext,
initFinish,
modContext,
};
function updateComponent() {
if (modContext) {
// 判断子页面vue对象是否存在
modContext.renderVersion += 1;
modContext.$forceUpdate(); // 强制渲染
// 重新计算el-button的computed
const list = [modContext];
while (list.length != 0) {
const item = list.pop();
list.push(...item.$children);
const tag = item.$options._componentTag;
if (tag == "el-button" || tag == "ElButton") {
if (item.disabled) item._computedWatchers.buttonDisabled.run();
console.log("[Invoker]: force recalculate computed property...");
}
}
}
}
// 子组件调用
function eventCallback(type: string, ...args: any[]) {
switch (type) {
case "created":
initFinish(args[0]);
break;
}
}
function initFinish(context) {
modContext = context;
let m: { method: string; args: any[] } | undefined;
while ((m = invokeMethods.pop())) {
invokeMethod(m.method, m.args);
}
console.log("invoker init finish");
}
function invokeMethod(
method: string,
args: any[] = [],
callback: ((result: any) => void) | null = null
) {
// if(!this.component) throw new Error('子组件不支持此方法调用');
if (modContext) {
console.log("method", modContext.$children);
const result = modContext.$children[0][method](...args);
if (callback) {
callback(result);
}
} else {
invokeMethods.push({ method, args });
}
}
function getRefs() {
return modContext.$refs;
}
// vue3 渲染上下文
function getRenderContext() {
return props.items;
}
expose({
getRefs,
getRenderContext,
eventCallback,
initFinish,
});
watch(
() => props.items,
() => {
updateComponent();
}
);
onBeforeUpdate(() => {
console.log("invoker-beforeUpdate", slots.default);
if (isTemplate) {
// 只更新slots变更
updateComponent();
}
});
onUnmounted(() => {
console.log("invoker-destroyed");
modContext && modContext.$destroy();
});
return (h) => {
if (isLocalInvoker && slots.default) {
return <div>{slots.default()}</div>;
}
return (
<iframe
src={props.url.replace('{id}',id.toString())}
scrolling={props.scroll ? "yes" : "no"}
style={`border: none; width: 100%; height: ${props.height}; `}
></iframe>
);
};
},
});

View File

@@ -0,0 +1,8 @@
export interface InvokerItem {
/** 组件的标签或是Component对象 */
tag: string,
/** 组件数据对象包含attr, props, class, style, 事件等对象 */
data: Record<string, any> | null,
/** 子节点和插槽 */
children: string | Array<InvokerItem> | Record<string, Function>
}

136
src/components/Receiver.ts Normal file
View File

@@ -0,0 +1,136 @@
import { defineComponent, nextTick, onMounted, ref, getCurrentInstance, isVue3, h } from 'vue-demi';
import { hh } from './DemiHelper';
import SlotTester from '../SlotTester.vue';
console.log('receiver-loaded');
function renderContext(item: object | Array<any>, toSlot: boolean = false) {
/**
* 对象为字符串的情况
* # { tag: 'div', attrs: undefined, children: 'hhh' } => h('span', undefined, 'hhh')
*/
if (typeof item === 'string')
return item;
/**
* 对象为函数的情况
* # { tag: 'div', attrs: undefined, children: () => 'hhh' } => h('span', undefined, 'hhh')
*/
if (typeof (item) === "function") {
// return item;
return item();
}
/**
* 对象为数组的情况
* # { tag: 'div', attrs: undefined, [ { tag: 'span', attrs: undefined, 'hhh' } ] } => h('div', undefined, [ h('span', undefined, 'hhh') ])
*/
if (Array.isArray(item)) {
const list = item.map(i => {
if (isVue3) {
// const comp = resolveComponent(i.tag);
return hh(i.tag, i.attrs, i.children);
// return hDemi(comp, i.attrs, renderContext(i.children, typeof (comp) !== 'string') as any); // vue3
}
// const { attrs, listeners, ref } = splitAttrs(i.attrs);
const slots = renderContext(i.children);
if (typeof (slots) === "object") {
return hh(i.tag, {
attrs: i.attrs
// attrs,
// on: listeners,
// scopedSlots: slots,
// ref
});
}
return hh(i.tag, {
attrs: i.attrs,
}, slots);
});
if (toSlot) {
return () => list;
}
return list;
}
/**
* 复合对象的情况
* #
*/
const children: Record<string, any> = {};
for (const key in item) {
children[key] = function (scope) {
const child = item[key](scope);
if (isVue3) {
return hh(child.tag, child.attrs, renderContext(child.children, true));
}
if (Array.isArray(child)) {
const slots = renderContext(child);
return slots;
} else {
// const { attrs, listeners, ref } = splitAttrs(child.attrs);
const slots = renderContext(child.children);
return hh(child.tag, {
attrs: child.attrs
// attrs,
// on: listeners,
// ref
}, slots);
}
};
}
return children;
}
export default defineComponent({
name: 'Receiver',
props: {
parentId: {
type: [Number, String],
default: () => 0
}
},
setup(props) {
const instance = getCurrentInstance();
let invokerContext: any = null;
const renderVersion = ref(0);
if (window.parent != window) {
const invoker = window.parent['MES_MOD_INVOKERS'][props.parentId];
invokerContext = invoker;
}
onMounted(() => {
nextTick().then(() => {
console.log('mounted-finish');
if (!invokerContext['modContext']) {
invokerContext.initFinish(instance?.proxy);
}
});
});
return () => {
try {
console.log('receiver-update', renderVersion.value);
if (renderVersion.value < 0) return;
if (invokerContext.getRenderContext) {
const itemList = invokerContext.getRenderContext();
// const list = renderContext(itemList);
// console.log('item-list', list);
return hh('div', undefined, itemList);
}
return hh('div');
}
catch (e) {
console.error("[Receiver] 渲染节点过程中出现错误", e);
}
};
}
});