vue 版本:3.3
背景:我项目里面希望给第三方提供一个页面。第三方通过网络接口的方式返回 html 代码,我程序里面把别人的 html 代码嵌入到我的页面中。
目前想到的方案:
1 、使用 v-html 标签嵌入。问题:这种方式嵌入,对方页面中如何调用我 vue 页面的方法属性呢?比如我这里有一个$http 变量是 axios 的实例,这个里面封装的验签相关处理,他必须用我这个$http 属性才能正常调用接口,不然他过不去验签。
2 、使用 vue 的异步组件。目前还没研究明白怎么用
下面是 demo 代码
<template> <div id="main"> <el-tabs type="border-card"> <el-tab-pane v-for="(html,name) in pluginList" :label="name"> // 方案 1 <div v-html="html"> </div> // 方案 2 <AsyncComp /> // 这样写的话第二个 plugin 又叫啥名字呢? </el-tab-pane> </el-tabs> </div> </template> <script setup> import { reactive, ref, getCurrentInstance } from 'vue' const app = getCurrentInstance() const $http = app.appContext.config.globalProperties.$http import { defineAsyncComponent } from 'vue' const pluginList = reactive({}) $http.get('/api/plugin/list').then(res => { if (res.data != null && res.data.length > 0 ) { pluginList[res.data] = "" getPlugHtml(res.data) } }) const getPlugHtml = function(name){ // 方案 1 $http.post('/api/plugin/settings/'+name+"/index").then(res => { if (res.data != null && res.data!="" ) { pluginList[name] = res.data }else{ pluginList[name] = "Load Error!" } }) // 方案 2. 但是 AsyncComp 这个名字怎么处理呢?这里换成变量以后,我模板里面的代码该怎么调用异步组件呢? const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { $http.post('/api/plugin/settings/'+name+"/index").then(res => { if (res.data != null && res.data!="" ) { resolve(res.data) }else{ reject("Plugin Load Error!") } }) }) }) } </script>
1 saberlove 2024-07-23 15:16:43 +08:00 您是否在寻找 QianKun? |
![]() | 2 tog 2024-07-23 15:22:26 +08:00 为什么不用 iframe ? 发方案 1 按道理可行。 |
![]() | 3 weixind 2024-07-23 15:39:53 +08:00 需要定义个 bridge 。和 VUE 关系不大。 |
4 Jinnrry OP @saberlove #1 太重了,就这么简单一个功能,不想引入这么重的一个依赖。三四年前用过这玩意,当时留下了难以磨灭的记忆 |
5 Jinnrry OP @tog #2 主要是和我原来 vue 属性通讯的问题,使用 iframe 怎么把我的$http 之类的属性给到他的页面呢 |
![]() | 8 dumbass 2024-07-23 15:53:02 +08:00 听着有点像微前端的范畴。可以试试 micro-app ,接入很简单,就是主应用和子应用要按照框架约定好一些东西。 |
9 horizon 2024-07-23 15:57:37 +08:00 全部暴露到 window 上啊。。 |
![]() | 10 daysv 2024-07-23 16:03:54 +08:00 搞那么麻烦, 全挂全局 |
![]() | 11 lisia 2024-07-23 16:04:43 +08:00 把$http 写入到 window 对象中。 第三方 HTML 里面就可用在 HTML 里面插入 script 来获取了吧,不过这种安全风险有点大。 |
![]() | 12 murmur 2024-07-23 16:12:33 +08:00 这东西我在某个大型 OA 上见过,可以用 react 代码直接把内置组件换掉,还自带 babel |
14 Jinnrry OP @murmur #12 我其实也就是干这个事,我希望第三方可以通过某些方式,替换掉我本身的一些组件。第三方可以通过这种方式引入新的功能、或者替换我以前的功能模块 |
![]() | 15 94 2024-07-23 16:51:03 +08:00 @Jinnrry #4 ,但是你的这个需求就不是一个简单的需求啊,得有一整套的方案。就是前两年常常提到的微前端。 如果单纯为了临时解决就选择 iframe 。数据交互可以用 [postMessage]( https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage) |
![]() | 16 94 2024-07-23 16:53:00 +08:00 @dfkjgklfdjg #15 ,最近去年开始微前端慢慢被认为是“伪需求”,[Web Component]( https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components) 方案开始流行起来了。 |
![]() | 17 moxxun 2024-07-23 17:13:37 +08:00 via iPhone 看看 vue 文档动态组件,异步组件部分 |
![]() | 18 duanxianze 2024-07-23 17:35:12 +08:00 不想麻烦直接挂载全局就行了 稍微复杂一些就是提供一个 js 文件,里面封装你要提供的 API ,更复杂就是搞微前端,或者服务端渲染 |
19 Jinnrry OP @moxxun #17 我看了起码 10 遍了。官方文档上面只有一句 import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { // ...从服务器获取组件 resolve(/* 获取到的组件 */) }) }) // ... 像使用其他一般组件一样使用 `AsyncComp` 这个 AsyncComp 怎么能换成变量,怎么局部声明组件,实在是没找到地方介绍 |
21 ccSir 2024-07-23 18:06:02 +08:00 或者可以让第三方用 h() 创建组件,然后你封装一些方法供第三方调用就行。通过接口返回第三方写的 h() 在通过 defineComponent 创建组件,传入你自己定义的方法。 |
22 ccSir 2024-07-23 18:07:28 +08:00 虽然我自己也在用 v-html 。但是还是不太建议用这个, |
23 bladey 2024-07-23 18:10:26 +08:00 <el-tabs v-model="activeName"> <el-tab-pane v-for="(html, name) in pluginList" :label="name"> <component :is="asyncCpt" :http="$http" /> </el-tab-pane> </el-tabs> const activeName = ref(''); const asyncCpt = ref(null); watch(activeName, async (newVal) => { asyncCpt.value = defineAsyncComponent(() => { return new Promise((resolve, reject) => { $http.post('/api/plugin/settings/' + newVal + '/index').then((res) => { if (res.data != null && res.data != '') { resolve(res.data); } else { reject('Plugin Load Error!'); } }); }); }); }); |
![]() | 25 ipwx 2024-07-23 18:31:50 +08:00 你不如在这个组件里面 onMounted(() => window.pluginCOntext= {'$html': $html, ... 任何你想要传递的属性}); 然后在你的插件里面通过 window.pluginContext 拿到上下文。 |
26 Jinnrry OP @moxxun #20 不行 const AsyncComp = defineAsyncComponent(() => { return new Promise((resolve, reject) => { resolve("<template> <div> hello world!! </div> </template>") }) }) Uncaught (in promise) Error: Invalid async component load result: <template> <div> hello world!! </div> </template> 我理解,这玩意需要传一个组件的 js 对象?但是也没有哪里有说明,vue 组件格式的字符串,怎么能转对象 |
27 Jinnrry OP @bladey #23 感谢! 拿到的 html 是这样的 <template> <div> hello world </div> <script setup> console.log("test") </script> </template> 也可以是标准的 html 格式。但是无论是哪张格式,都会报:Uncaught (in promise) Error: Invalid async component load result 这个错误。始终无法成功创建组件。 这个 resolve 方法里面真的是传字符串吗? |
28 Jinnrry OP @ipwx #25 那通过什么方式展示插件的页面呢? v-html 不让执行 js ,这个异步组件我翻遍中英文资料,都没找到一篇关于网络加载的。我都怀疑这玩意根本不能加载网络组件了。所有能找到的示例,都是异步加载本地的组件,优化加载速度 |
![]() | 29 asdjgfr 2024-07-23 19:19:31 +08:00 用 CustomEvent 不就好了: https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent/CustomEvent |
31 LuckyLauncher 2024-07-23 19:36:07 +08:00 用 webcomponent ,直接用 esm 加载这个定义了 webcomponent 的 js 文件,然后 v-html 渲染这个 webcomponent 标签,不过这种方案三方的权限很大,如果你需要管控还是需要微前端 |
32 Jinnrry OP https://github.com/hacke2/vue-append/tree/master 找到一个插件,可以实现类似 v-html 的效果同时可以运行 js ,可惜是 vue2 的代码。看了下源码,思路是先加载 html ,然后找出 script 代码,使用 exec 运行一次 |
34 Jinnrry OP @asdjgfr #33 确实可以,https://github.com/hacke2/vue-append/tree/master 这个插件基本上也是这个思路。但是这样自己搞,总觉得不够优雅,很容易出问题。 |
![]() | 35 lisongeee 2024-07-23 20:02:24 +08:00 你这个是拿到的是 .vue 文件,不是编译后的 .js 代码,要不叫后端返回的时候给你编译为 js ,这样你直接用 import('/api/vue.js') 去引入 要不你就自己在前端使用 vue/compiler-sfc 将拿到的 .vue 字符串编译为组件对象 |
36 Jinnrry OP @lisongeee #35 编译为 js ?那 html 部分怎么处理呢? 我现在设计考虑的点是 1 、我这里能尽可能简单,同时能保证稳定性,不至于别人随便写点东西就崩了,或者别人崩了把我页面也搞崩了。 2 、别人尽可能多的复用我的样式、包等,比如我页面引入了 element-ui ,别人可以直接用 element-ui 的组件、样式等,不需要重复引入。这样才能保证最终别人嵌入的页面和我原来的页面样式差不多。 3 、对我和对别人,开发调试都简单。 |
![]() | 37 linglingling 2024-07-24 09:02:14 +08:00 多年前端,全栈都会一些。你这个要求,既要简单,又要安全,还要通讯等等,实现不了。符合你需求的是后端模板,如 Thymeleaf 。 |
38 bladey 2024-07-24 09:32:58 +08:00 @Jinnrry #27 这样的话试试这个写法,应该可以。这类需求还是第一次见,有一点一直没想明白,你想让第三方用你封装的 axios ,那在他的组件里调用时,拿到的 baseurl 、token 应该都是你的系统的,应该毫无意义吧,除非他的组件里请求的接口也在你的系统里,通过后端转发拿他系统的数据,不然这么搞我是没想明白到底要干什么 watch(activeName, async (newVal) => { $http.post('/api/plugin/settings/' + newVal + '/index').then((res) => { if (res.data != null && res.data !== '') { // 创建一个 Blob ,用于生成组件的 URL const blob = new Blob([res.data], { type: 'text/plain' }); const url = URL.createObjectURL(blob); // 动态加载组件 asyncCpt.value = defineAsyncComponent(() => { return import(url); }); } }); }); |
39 horizon 2024-07-24 09:55:38 +08:00 |
![]() | 40 ipwx 2024-07-24 10:43:48 +08:00 我觉得你想在页面上给一块区域,让服务器传来的 HTML 和 JS 能跑起来还是挺容易的。 拿到 DOM Element ,然后一边 xxx.innerHTML = 'HTML 部分'; 另一边 createElement('script') 然后把 JS 放进去跑。 但是感觉楼主你不会。 另一方面如果你要让 Vue 组件也跑起来,那大概得把整套 JS Module 都丢到页面上…… 算了这条路你还是自己趟吧。 |
41 Jinnrry OP @ipwx #40 在 vue 项目里面找到一个 issues ,https://github.com/vuejs/core/discussions/6939 准备按这个思路搞,拿到字符串,调用 vue 的相关编译方法,动态编译生成组件 |
43 Jinnrry OP @bladey #38 比如他需要请求我系统的用户信息接口,拿到用户相关资料。然后拿用户资料跟他系统的 id 关联,接着处理他直接的逻辑 <template> <div class="hello"> <MyComponent ></MyComponent> </div> </template> <script setup> import { defineAsyncComponent } from 'vue' // 创建一个 Blob ,用于生成组件的 URL const blob = new Blob([`<template> <div> hello world! </div> </template>`], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const MyCompOnent= defineAsyncComponent(() => { return import(url); }); </script> 安装你的思路写了要给 demo ,这个会报错 Cannot find module 'blob:http://localhost:8080/f795bf43-d4b9-4332-ac54-0139c19ce4e1' at http://localhost:8080/js/app.js:191:11 |
![]() | 45 unco020511 2024-07-24 18:00:46 +08:00 你把所有需要桥接的对象和方法都挂载到 window 上不行吗 |