319 lines
8.4 KiB
Vue
319 lines
8.4 KiB
Vue
<script setup lang="ts">
|
||
import type { AxiosInterceptorManager, AxiosResponse } from 'axios';
|
||
|
||
import { getCurrentInstance, onMounted, ref, watch } from 'vue';
|
||
|
||
import { useQuery } from '@tanstack/vue-query';
|
||
import { jsonp, request } from '@vtj/utils';
|
||
import { createProvider } from '@vtj/web';
|
||
import { ElLoading, ElMessage } from 'element-plus';
|
||
import { isFn } from 'licia-es';
|
||
|
||
import { LowCodeService } from './service';
|
||
|
||
interface WujieProps {
|
||
interceptors?: {
|
||
request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
|
||
response: AxiosInterceptorManager<AxiosResponse>;
|
||
};
|
||
// 必填参数
|
||
fileId: string;
|
||
projectId: number | string;
|
||
// 可选参数
|
||
applicationId?: number | string;
|
||
name?: string;
|
||
[key: string]: any;
|
||
}
|
||
|
||
declare global {
|
||
interface Window {
|
||
$wujie?: {
|
||
bus: {
|
||
$emit: (event: string, data?: any) => void;
|
||
};
|
||
props: WujieProps;
|
||
};
|
||
}
|
||
}
|
||
|
||
// 定义必要的初始化参数
|
||
interface InitParams {
|
||
// 必填参数
|
||
fileId: string;
|
||
projectId: number | string;
|
||
// 可选参数
|
||
applicationId?: number | string;
|
||
[key: string]: any;
|
||
}
|
||
|
||
// 响应式状态
|
||
const renderer = ref();
|
||
const lowCodeService = new LowCodeService();
|
||
const isLoading = ref(false);
|
||
const provider = ref(null);
|
||
const loadingInstance = ref(null);
|
||
const errorMessage = ref('');
|
||
const initParams = ref<InitParams | null | WujieProps>(null);
|
||
|
||
// 判断是否为wujie子应用
|
||
const isWujieSubApp = window.$wujie !== undefined;
|
||
|
||
// 从URL解析查询参数
|
||
const getParamsFromUrl = (): Partial<InitParams> => {
|
||
const params = new URLSearchParams(window.location.search);
|
||
return {
|
||
fileId: params.get('fileId') || undefined,
|
||
projectId: params.get('projectId') || undefined,
|
||
applicationId: params.get('applicationId') || undefined,
|
||
};
|
||
};
|
||
|
||
// 按优先级获取初始化参数
|
||
const getInitParams = (): InitParams | null => {
|
||
if (isWujieSubApp && window.$wujie?.props) {
|
||
const props: WujieProps = window.$wujie.props;
|
||
console.log('WujieProps', props);
|
||
if (props.fileId && props.projectId) {
|
||
console.log('使用无界初始化渲染器');
|
||
return props as WujieProps;
|
||
}
|
||
}
|
||
|
||
// 2. 其次从URL参数获取
|
||
const urlParams = getParamsFromUrl();
|
||
if (urlParams.fileId && urlParams.projectId) {
|
||
console.log('使用URL参数初始化渲染器:', urlParams);
|
||
return urlParams as InitParams;
|
||
}
|
||
|
||
// 3. 都不满足,返回null
|
||
console.error('无法获取初始化参数');
|
||
errorMessage.value = '无法获取初始化参数,请检查无界配置或URL参数';
|
||
return null;
|
||
};
|
||
|
||
// 检查参数是否有效
|
||
const isValidParams = (params: InitParams | null): params is InitParams => {
|
||
return !!params && !!params.fileId && !!params.projectId;
|
||
};
|
||
|
||
// 初始化请求配置
|
||
const initRequestConfig = () => {
|
||
if (!isWujieSubApp) return console.log('不是无界渲染模式,不进行拦截器合并');
|
||
const props = window.$wujie.props;
|
||
const mergeRequestInterceptors = () => {
|
||
const requestHandlers = props.interceptors?.request?.handlers || [];
|
||
requestHandlers.forEach((handler) => {
|
||
if (isFn(handler?.fulfilled)) {
|
||
request.useRequest(
|
||
handler.fulfilled,
|
||
handler.rejected,
|
||
handler.options,
|
||
);
|
||
}
|
||
});
|
||
};
|
||
|
||
mergeRequestInterceptors();
|
||
};
|
||
|
||
// 显示加载中
|
||
const showLoading = (text = '低代码文件加载中...') => {
|
||
if (loadingInstance.value) return;
|
||
loadingInstance.value = ElLoading.service({ text });
|
||
};
|
||
|
||
// 隐藏加载中
|
||
const hideLoading = () => {
|
||
if (loadingInstance.value) {
|
||
loadingInstance.value.close();
|
||
loadingInstance.value = null;
|
||
}
|
||
};
|
||
|
||
// 初始化低代码引擎 - 只初始化一次
|
||
const initLowCodeEngine = async () => {
|
||
// 如果已经初始化过,直接返回
|
||
if (provider.value) return provider.value;
|
||
|
||
// 检查参数是否有效
|
||
if (!isValidParams(initParams.value)) {
|
||
const error = new Error('缺少必要参数:fileId 和 projectId');
|
||
errorMessage.value = error.message;
|
||
throw error;
|
||
}
|
||
|
||
// 初始化请求配置
|
||
initRequestConfig();
|
||
|
||
try {
|
||
const { provider: lowCodeProvider, onReady } = createProvider({
|
||
nodeEnv: import.meta.env.NODE_ENV,
|
||
service: lowCodeService,
|
||
project: { id: Number(initParams.value.projectId) },
|
||
adapter: {
|
||
request,
|
||
jsonp,
|
||
},
|
||
});
|
||
|
||
provider.value = { provider: lowCodeProvider, onReady };
|
||
return { provider: lowCodeProvider, onReady };
|
||
} catch (error) {
|
||
console.error('初始化低代码引擎失败:', error);
|
||
errorMessage.value = '初始化低代码引擎失败';
|
||
ElMessage.error('初始化低代码引擎失败');
|
||
return Promise.reject(error);
|
||
}
|
||
};
|
||
|
||
// 获取渲染组件
|
||
const getRenderComponent = async () => {
|
||
if (!isValidParams(initParams.value)) {
|
||
errorMessage.value = '缺少必要参数:fileId 和 projectId';
|
||
return null;
|
||
}
|
||
|
||
isLoading.value = true;
|
||
showLoading();
|
||
|
||
try {
|
||
// 1. 确保低代码引擎已初始化
|
||
const { provider: lowCodeProvider, onReady } = await initLowCodeEngine();
|
||
|
||
// 2. 获取渲染组件
|
||
return new Promise<any>((resolve) => {
|
||
onReady(async () => {
|
||
const instance = getCurrentInstance();
|
||
instance?.appContext.app.use(lowCodeProvider);
|
||
try {
|
||
const renderComponent = await lowCodeProvider.getRenderComponent(
|
||
initParams.value!.fileId,
|
||
);
|
||
console.log('渲染组件获取成功');
|
||
resolve(renderComponent);
|
||
} catch (error) {
|
||
console.error('获取渲染组件失败:', error);
|
||
errorMessage.value = '获取渲染组件失败';
|
||
ElMessage.error('获取渲染组件失败');
|
||
resolve(null);
|
||
}
|
||
});
|
||
});
|
||
} catch (error) {
|
||
console.error('获取渲染组件过程出错:', error);
|
||
return null;
|
||
} finally {
|
||
isLoading.value = false;
|
||
hideLoading();
|
||
}
|
||
};
|
||
|
||
// 使用 useQuery 管理渲染组件
|
||
const { data: rendererComponent, isError } = useQuery({
|
||
queryKey: [
|
||
'getRenderer',
|
||
initParams.value?.fileId,
|
||
initParams.value?.projectId,
|
||
],
|
||
queryFn: getRenderComponent,
|
||
enabled: false, // 初始不自动执行,改为手动控制
|
||
retry: 1, // 失败后重试一次
|
||
staleTime: 1000 * 60 * 5, // 5分钟内不重新获取
|
||
});
|
||
|
||
// 当组件挂载时,将渲染器组件赋值给 renderer
|
||
watch(rendererComponent, (newVal) => {
|
||
if (newVal) {
|
||
renderer.value = newVal;
|
||
console.log('渲染器组件已更新');
|
||
}
|
||
});
|
||
|
||
// 向父应用发送状态消息
|
||
const notifyParent = (event: string, data?: any) => {
|
||
if (isWujieSubApp && window.$wujie?.bus) {
|
||
window.$wujie.bus.$emit(event, data);
|
||
}
|
||
};
|
||
|
||
// 组件挂载后执行初始化
|
||
onMounted(async () => {
|
||
// 通知父应用已准备就绪(如果是wujie子应用)
|
||
if (isWujieSubApp) {
|
||
notifyParent('ready', 'y-code-renderer is ready');
|
||
}
|
||
|
||
// 获取初始化参数 - 在挂载时执行一次
|
||
initParams.value = getInitParams();
|
||
const paramsValid = isValidParams(initParams.value);
|
||
|
||
if (!paramsValid) {
|
||
errorMessage.value = '缺少必要参数:fileId 和 projectId';
|
||
if (isWujieSubApp) {
|
||
notifyParent('render-fail', errorMessage.value);
|
||
}
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await initLowCodeEngine();
|
||
|
||
const component = await getRenderComponent();
|
||
if (component) {
|
||
renderer.value = component;
|
||
if (isWujieSubApp) {
|
||
notifyParent('render-success');
|
||
}
|
||
} else {
|
||
if (isWujieSubApp) {
|
||
notifyParent('render-fail', 'Failed to get component');
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('初始化过程出错:', error);
|
||
if (isWujieSubApp) {
|
||
notifyParent('render-fail', error);
|
||
}
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="renderer-container">
|
||
<component
|
||
:is="renderer"
|
||
v-if="renderer"
|
||
:ctx-props="initParams"
|
||
v-bind="initParams"
|
||
/>
|
||
|
||
<div v-else-if="!isValidParams(initParams)" class="error-message">
|
||
{{ errorMessage || '缺少必要参数:fileId 和 projectId' }}
|
||
</div>
|
||
|
||
<div v-else-if="!isLoading && isError" class="error-message">
|
||
{{ errorMessage || '组件加载失败,请检查参数和网络连接' }}
|
||
</div>
|
||
|
||
<div v-else-if="!isLoading" class="error-message">
|
||
{{ errorMessage || '组件加载失败,请检查控制台日志' }}
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.renderer-container {
|
||
box-sizing: border-box;
|
||
padding: 20px;
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
.error-message {
|
||
margin-top: 50px;
|
||
color: red;
|
||
text-align: center;
|
||
}
|
||
</style>
|