请教一下写前端的各位大佬, vue 动态组件如何动态定义名字呢?

152 天前
 Jinnrry

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>

2686 次点击
所在节点    Vue.js
45 条回复
ccSir
152 天前
或者可以让第三方用 h() 创建组件,然后你封装一些方法供第三方调用就行。通过接口返回第三方写的 h() 在通过 defineComponent 创建组件,传入你自己定义的方法。
ccSir
152 天前
虽然我自己也在用 v-html 。但是还是不太建议用这个,🐶
bladey
152 天前
<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!');
}
});
});
});
});
bladey
152 天前
@bladey 接口拿到的 html 不知道内容是什么样,不知道这样写行不行?
ipwx
152 天前
你不如在这个组件里面

onMounted(() => window.pluginContext = {'$html': $html, ... 任何你想要传递的属性});

然后在你的插件里面通过 window.pluginContext 拿到上下文。
Jinnrry
152 天前
@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 组件格式的字符串,怎么能转对象
Jinnrry
152 天前
@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 方法里面真的是传字符串吗?
Jinnrry
152 天前
@ipwx #25 那通过什么方式展示插件的页面呢? v-html 不让执行 js ,这个异步组件我翻遍中英文资料,都没找到一篇关于网络加载的。我都怀疑这玩意根本不能加载网络组件了。所有能找到的示例,都是异步加载本地的组件,优化加载速度
asdjgfr
152 天前
Jinnrry
152 天前
@asdjgfr #29 啊?这和 event 有啥关系?我也不需要处理 event 啊
LuckyLauncher
152 天前
用 webcomponent ,直接用 esm 加载这个定义了 webcomponent 的 js 文件,然后 v-html 渲染这个 webcomponent 标签,不过这种方案三方的权限很大,如果你需要管控还是需要微前端
Jinnrry
152 天前
https://github.com/hacke2/vue-append/tree/master

找到一个插件,可以实现类似 v-html 的效果同时可以运行 js ,可惜是 vue2 的代码。看了下源码,思路是先加载 html ,然后找出 script 代码,使用 exec 运行一次
asdjgfr
152 天前
Jinnrry
152 天前
@asdjgfr #33 确实可以,https://github.com/hacke2/vue-append/tree/master 这个插件基本上也是这个思路。但是这样自己搞,总觉得不够优雅,很容易出问题。
lisongeee
152 天前
你这个是拿到的是 .vue 文件,不是编译后的 .js 代码,要不叫后端返回的时候给你编译为 js ,这样你直接用 import('/api/vue.js') 去引入

要不你就自己在前端使用 vue/compiler-sfc 将拿到的 .vue 字符串编译为组件对象
Jinnrry
152 天前
@lisongeee #35 编译为 js ?那 html 部分怎么处理呢? 我现在设计考虑的点是

1 、我这里能尽可能简单,同时能保证稳定性,不至于别人随便写点东西就崩了,或者别人崩了把我页面也搞崩了。

2 、别人尽可能多的复用我的样式、包等,比如我页面引入了 element-ui ,别人可以直接用 element-ui 的组件、样式等,不需要重复引入。这样才能保证最终别人嵌入的页面和我原来的页面样式差不多。

3 、对我和对别人,开发调试都简单。
linglingling
151 天前
多年前端,全栈都会一些。你这个要求,既要简单,又要安全,还要通讯等等,实现不了。符合你需求的是后端模板,如 Thymeleaf 。
bladey
151 天前
@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);
});
}
});
});
horizon
151 天前
@Jinnrry #36
你需要的是一个插件系统
如果还需要保证稳定性的话,那你需要自己开发一个沙盒
最简单还是 iframe 吧。。
ipwx
151 天前
我觉得你想在页面上给一块区域,让服务器传来的 HTML 和 JS 能跑起来还是挺容易的。

拿到 DOM Element ,然后一边 xxx.innerHTML = 'HTML 部分'; 另一边 createElement('script') 然后把 JS 放进去跑。

但是感觉楼主你不会。

另一方面如果你要让 Vue 组件也跑起来,那大概得把整套 JS Module 都丢到页面上…… 算了这条路你还是自己趟吧。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1059455

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX