请教一下写前端的各位大佬, vue 动态组件如何动态定义名字呢? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Jinnrry
V2EX    Vue.js

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

  •  
  •   Jinnrry 2024-07-23 15:12:46 +08:00 3288 次点击
    这是一个创建于 443 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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> 
    45 条回复    2024-07-24 18:00:46 +08:00
    saberlove
        1
    saberlove  
       2024-07-23 15:16:43 +08:00
    您是否在寻找 QianKun?
    tog
        2
    tog  
       2024-07-23 15:22:26 +08:00
    为什么不用 iframe ?
    发方案 1 按道理可行。
    weixind
        3
    weixind  
       2024-07-23 15:39:53 +08:00
    需要定义个 bridge 。和 VUE 关系不大。
    Jinnrry
        4
    Jinnrry  
    OP
       2024-07-23 15:41:46 +08:00
    @saberlove #1 太重了,就这么简单一个功能,不想引入这么重的一个依赖。三四年前用过这玩意,当时留下了难以磨灭的记忆
    Jinnrry
        5
    Jinnrry  
    OP
       2024-07-23 15:42:48 +08:00
    @tog #2 主要是和我原来 vue 属性通讯的问题,使用 iframe 怎么把我的$http 之类的属性给到他的页面呢
    Jinnrry
        6
    Jinnrry  
    OP
       2024-07-23 15:49:10 +08:00
    @tog #2 方案 1 的主要问题是 v-html 里面不执行 js 代码,只能插入 html 内容
    Jinnrry
        7
    Jinnrry  
    OP
       2024-07-23 15:49:52 +08:00
    @weixind #3 大佬细说?这一句话理解不了啊
    dumbass
        8
    dumbass  
       2024-07-23 15:53:02 +08:00
    听着有点像微前端的范畴。可以试试 micro-app ,接入很简单,就是主应用和子应用要按照框架约定好一些东西。
    horizon
        9
    horizon  
       2024-07-23 15:57:37 +08:00
    全部暴露到 window 上啊。。
    daysv
        10
    daysv  
       2024-07-23 16:03:54 +08:00
    搞那么麻烦, 全挂全局
    lisia
        11
    lisia  
       2024-07-23 16:04:43 +08:00
    把$http 写入到 window 对象中。
    第三方 HTML 里面就可用在 HTML 里面插入 script 来获取了吧,不过这种安全风险有点大。
    murmur
        12
    murmur  
       2024-07-23 16:12:33 +08:00
    这东西我在某个大型 OA 上见过,可以用 react 代码直接把内置组件换掉,还自带 babel
    op351
        13
    op351  
       2024-07-23 16:23:49 +08:00
    @murmur 泛微的 ecode 吗?
    Jinnrry
        14
    Jinnrry  
    OP
       2024-07-23 16:30:36 +08:00
    @murmur #12 我其实也就是干这个事,我希望第三方可以通过某些方式,替换掉我本身的一些组件。第三方可以通过这种方式引入新的功能、或者替换我以前的功能模块
    94
        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)
    94
        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) 方案开始流行起来了。
    moxxun
        17
    moxxun  
       2024-07-23 17:13:37 +08:00 via iPhone
    看看 vue 文档动态组件,异步组件部分
    duanxianze
        18
    duanxianze  
       2024-07-23 17:35:12 +08:00
    不想麻烦直接挂载全局就行了 稍微复杂一些就是提供一个 js 文件,里面封装你要提供的 API ,更复杂就是搞微前端,或者服务端渲染
    Jinnrry
        19
    Jinnrry  
    OP
       2024-07-23 17:36:47 +08:00
    @moxxun #17 我看了起码 10 遍了。官方文档上面只有一句

    import { defineAsyncComponent } from 'vue'

    const AsyncComp = defineAsyncComponent(() => {
    return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
    })
    })
    // ... 像使用其他一般组件一样使用 `AsyncComp`

    这个 AsyncComp 怎么能换成变量,怎么局部声明组件,实在是没找到地方介绍
    moxxun
        20
    moxxun  
       2024-07-23 17:44:30 +08:00 via iPhone
    @Jinnrry resolve 里可以插入 template 吧
    ccSir
        21
    ccSir  
       2024-07-23 18:06:02 +08:00
    或者可以让第三方用 h() 创建组件,然后你封装一些方法供第三方调用就行。通过接口返回第三方写的 h() 在通过 defineComponent 创建组件,传入你自己定义的方法。
    ccSir
        22
    ccSir  
       2024-07-23 18:07:28 +08:00
    虽然我自己也在用 v-html 。但是还是不太建议用这个,
    bladey
        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!');
    }
    });
    });
    });
    });
    bladey
        24
    bladey  
       2024-07-23 18:12:05 +08:00
    @bladey 接口拿到的 html 不知道内容是什么样,不知道这样写行不行?
    ipwx
        25
    ipwx  
       2024-07-23 18:31:50 +08:00
    你不如在这个组件里面

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

    然后在你的插件里面通过 window.pluginContext 拿到上下文。
    Jinnrry
        26
    Jinnrry  
    OP
       2024-07-23 18:54:26 +08:00
    @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
        27
    Jinnrry  
    OP
       2024-07-23 19:11:28 +08:00
    @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
        28
    Jinnrry  
    OP
       2024-07-23 19:15:32 +08:00
    @ipwx #25 那通过什么方式展示插件的页面呢? v-html 不让执行 js ,这个异步组件我翻遍中英文资料,都没找到一篇关于网络加载的。我都怀疑这玩意根本不能加载网络组件了。所有能找到的示例,都是异步加载本地的组件,优化加载速度
    asdjgfr
        29
    asdjgfr  
       2024-07-23 19:19:31 +08:00
    Jinnrry
        30
    Jinnrry  
    OP
       2024-07-23 19:21:12 +08:00
    @asdjgfr #29 啊?这和 event 有啥关系?我也不需要处理 event 啊
    LuckyLauncher
        31
    LuckyLauncher  
       2024-07-23 19:36:07 +08:00
    用 webcomponent ,直接用 esm 加载这个定义了 webcomponent 的 js 文件,然后 v-html 渲染这个 webcomponent 标签,不过这种方案三方的权限很大,如果你需要管控还是需要微前端
    Jinnrry
        32
    Jinnrry  
    OP
       2024-07-23 19:37:07 +08:00
    https://github.com/hacke2/vue-append/tree/master

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

    要不你就自己在前端使用 vue/compiler-sfc 将拿到的 .vue 字符串编译为组件对象
    Jinnrry
        36
    Jinnrry  
    OP
       2024-07-23 20:09:39 +08:00
    @lisongeee #35 编译为 js ?那 html 部分怎么处理呢? 我现在设计考虑的点是

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

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

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

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

    但是感觉楼主你不会。

    另一方面如果你要让 Vue 组件也跑起来,那大概得把整套 JS Module 都丢到页面上…… 算了这条路你还是自己趟吧。
    Jinnrry
        41
    Jinnrry  
    OP
       2024-07-24 11:22:11 +08:00
    @ipwx #40 在 vue 项目里面找到一个 issues ,https://github.com/vuejs/core/discussions/6939

    准备按这个思路搞,拿到字符串,调用 vue 的相关编译方法,动态编译生成组件
    DesnLee
        42
    DesnLee  
       2024-07-24 16:07:28 +08:00
    @Jinnrry #27 你这个为什么 template 包着 setup ?
    Jinnrry
        43
    Jinnrry  
    OP
       2024-07-24 16:11:45 +08:00
    @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
    Jinnrry
        44
    Jinnrry  
    OP
       2024-07-24 16:12:32 +08:00
    @DesnLee #42 回复的时候手敲的,敲错了
    unco020511
        45
    unco020511  
       2024-07-24 18:00:46 +08:00
    你把所有需要桥接的对象和方法都挂载到 window 上不行吗
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1141 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 23:24 PVG 07:24 LAX 16:24 JFK 19:24
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86