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 46 天前
您是否在寻找 QianKun?
|
![]() |
2
tog 46 天前
为什么不用 iframe ?
发方案 1 按道理可行。 |
![]() |
3
weixind 46 天前
需要定义个 bridge 。和 VUE 关系不大。
|
![]() |
8
bojackhorseman 46 天前
听着有点像微前端的范畴。可以试试 micro-app ,接入很简单,就是主应用和子应用要按照框架约定好一些东西。
|
9
horizon 46 天前
全部暴露到 window 上啊。。
|
![]() |
10
daysv 46 天前
搞那么麻烦, 全挂全局
|
![]() |
11
lisia 46 天前
把$http 写入到 window 对象中。
第三方 HTML 里面就可用在 HTML 里面插入 script 来获取了吧,不过这种安全风险有点大。 |
![]() |
12
murmur 46 天前
这东西我在某个大型 OA 上见过,可以用 react 代码直接把内置组件换掉,还自带 babel
|
14
Jinnrry OP @murmur #12 我其实也就是干这个事,我希望第三方可以通过某些方式,替换掉我本身的一些组件。第三方可以通过这种方式引入新的功能、或者替换我以前的功能模块
|
![]() |
15
dfkjgklfdjg 46 天前
@Jinnrry #4 ,但是你的这个需求就不是一个简单的需求啊,得有一整套的方案。就是前两年常常提到的微前端。
如果单纯为了临时解决就选择 iframe 。数据交互可以用 [postMessage]( https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage) |
![]() |
16
dfkjgklfdjg 46 天前
@dfkjgklfdjg #15 ,最近去年开始微前端慢慢被认为是“伪需求”,[Web Component]( https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components) 方案开始流行起来了。
|
![]() |
17
moxxun 46 天前 via iPhone
看看 vue 文档动态组件,异步组件部分
|
![]() |
18
duanxianze 46 天前
不想麻烦直接挂载全局就行了 稍微复杂一些就是提供一个 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 46 天前
或者可以让第三方用 h() 创建组件,然后你封装一些方法供第三方调用就行。通过接口返回第三方写的 h() 在通过 defineComponent 创建组件,传入你自己定义的方法。
|
22
ccSir 46 天前
虽然我自己也在用 v-html 。但是还是不太建议用这个,🐶
|
23
bladey 46 天前
<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 46 天前
你不如在这个组件里面
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 46 天前
用 CustomEvent 不就好了: https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent/CustomEvent
|
31
LuckyLauncher 46 天前
用 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 46 天前
你这个是拿到的是 .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 45 天前
多年前端,全栈都会一些。你这个要求,既要简单,又要安全,还要通讯等等,实现不了。符合你需求的是后端模板,如 Thymeleaf 。
|
38
bladey 45 天前
@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); }); } }); }); |
![]() |
40
ipwx 45 天前
我觉得你想在页面上给一块区域,让服务器传来的 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 45 天前
你把所有需要桥接的对象和方法都挂载到 window 上不行吗
|