【问题标题】:@click doesn't work when rendering it using v-html使用 v-html 渲染时 @click 不起作用
【发布时间】:2021-05-04 04:56:31
【问题描述】:

我有一个组件,我希望我可以使用父级的数据和列对象创建行和列。

有一种情况,我需要渲染一个 html 模板来创建一个可点击的链接,我让它想在 Actions 列中显示链接。 vue 版本:^2.5.17



下面是 parent.vue 的代码:
// parent.vue
<table-component :data="category" :column="column" class="bg-red-200 py-4 mt-8"/>
// parent.vue
data(){
  return {
    modalMode: '',
    isShowModalEdit: false,
    category: [
      { id: 1, name: 'Jasa Pre-Order', color: '#eb4034' },
      { id: 2, name: 'Jualan', color: '#fcba03' },
      { id: 3, name: 'Jasa Design', color: '#9f34eb' },
    ],
  }
}
// parent.vue
methods: {
  toggleModalEdit(){
    this.isShowModalEdit = !this.isShowModalEdit
    this.modalMode = 'EDIT'
  }
}
// parent.vue
computed: {
            column() {
                return [
                    {
                        dataField: 'name',
                        text: 'Name',
                    },
                    {
                        dataField: 'color',
                        text: 'Category Color',
                        formatter: (cell,row) => {
                            return `
                                <div style="background-color: ${cell};" class="rounded-full h-8 w-8 flex items-center justify-center mr-2"></div>
                                <div class="font-bold text-gray-500">${cell}</div>
                            `
                        },
                        classes: (cell, row, rowIndex, colIndex) => {
                            return 'flex';
                        }
                    },
                    {
                        dataField: 'actions',
                        text: 'Actions',
                        formatter: (cell,row) => {
                            return `
                                <a href="#" @click="${this.toggleModalEdit}" class="text-indigo-600 hover:text-indigo-900">Edit</a>
                            `
                        },
                    },
                ]
            },
}
这是来自 component.vue 的示例代码:
// component.vue
<tbody class="bg-white divide-y divide-gray-200">
  <tr v-for="(row, rowIndex) in data" :key="rowIndex">
    <td v-for="(col, colIndex) in column" :key="col.dataField" :class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' " v-html=" col.formatter ? col.formatter(row[col.dataField],row) : row[col.dataField] " class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"></td>
  <tr>
</tbody>
// component.vue
props: {
            data: {
                type: Array,
                required: true
            },
            column: {
                type: Array,
                required: true
            },
}
结果是这样的:

但是Actions 列中的链接无法正常工作,我希望单击此链接时将运行相关的方法,即toggleModalEdit。这就是我检查链接时的样子:


我还是vue的新手,我不确定我做得最好还是没有,希望你们能提供帮助。

【问题讨论】:

  • 如果您可以使用标记,请使用 templateslot。或者使用new Vue动态创建vue实例。

标签: javascript vue.js


【解决方案1】:

感谢 @SimplyComple0x78 的回答,我标记了您的建议:

为此,您需要遍历所有列并初始化一个新组件,该组件直接在 HTML 中处理单元格内部的内容(而不是在稍后将呈现的某些字符串中)。

所以我尝试创建和初始化一个新组件,我称之为element-generatorreference from here。这是代码:

// element-generator.vue
<script>
    export default {
        render: function (createElement) {
            const generatedChildren = (child) => {
                if(!child) return // when child of undefined
                if(typeof child === 'string') return child // when children is String
                return child.map((e,i,a)=>{
                    if(typeof child[i] == 'string'){
                        return child[i]
                    }else{
                        return createElement(
                            child[i].tag,
                            child[i].attributes,
                            generatedChildren(child[i].children) // javascript recursive
                        )
                    }
                })
            }
            return createElement(
                this.formatter.html.tag,
                this.formatter.html.attributes,
                generatedChildren(this.formatter.html.children)
            )
        },
        props: {
            formatter: {
                type: Object,
                required: true
            },
        },
    }
</script>

我不再在component.vue 中使用v-html,而是在&lt;td&gt; 内部进行检查并调用element-generator 来处理单元格内的内容:

// component.vue
<tbody class="bg-white divide-y divide-gray-200">
    <tr v-for="(row, rowIndex) in data" :key="rowIndex">
        <td v-for="(col, colIndex) in column" :key="col.dataField"
            :class=" col.classes ? col.classes(row[col.dataField],row,rowIndex,colIndex) : '' "
            class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
        >
            <element-generator v-if="col.formatter" :formatter="col.formatter(row[col.dataField],row)"></element-generator>
            <div v-else>{{row[col.dataField]}}</div>
        </td>
    </tr>
</tbody>

parent.vue 中,我将String 替换为稍后将传递给element-generator 的Object,如下所示:

// parent.vue
computed: {
    column() {
        return [
            {
                dataField: 'name',
                text: 'Name',
            },
            {
                dataField: 'color',
                text: 'Category Color',
                formatter: (cell,row) => {
                    return {
                        html: {
                            tag: 'div',
                            attributes: {
                                class: 'flex'
                            },
                            children:[
                                {
                                    tag: 'div',
                                    attributes: {
                                        style: `background-color: ${cell};`,
                                        class: 'rounded-full h-8 w-8 flex items-center justify-center mr-2',
                                    },
                                },
                                {
                                    tag: 'div',
                                    attributes: {
                                        class: 'font-bold text-gray-500',
                                    },
                                    children: cell
                                },
                            ]
                        }
                    }
                },
            },
            {
                dataField: 'actions',
                text: 'Actions',
                formatter: (cell,row) => {
                    return {
                        html: {
                            tag: 'a',
                            attributes: {
                                class: 'text-indigo-600 hover:text-indigo-900',
                                on: {
                                    click: this.toggleModalEdit
                                },
                                attrs: {
                                    href: "#"
                                },
                            },
                            children: 'Edit'
                        },
                    }
                },
            },
        ]
    },
},

然后当我在浏览器中检查它时,结果是这样的(这与上一个不同):

最后我想在点击 Edit 时显示的内容现在显示:


非常感谢大家。

【讨论】:

    【解决方案2】:

    您的问题是,v-html 指令中的 HTML 根本就是 not processed by Vue's template compiler

    由于编译器不编译此 HTML,@click 不会被解释(而只是在不触发任何操作的情况下呈现)。

    为此,您需要遍历所有列并初始化一个新组件,该组件直接在 HTML 中处理单元格内部的内容(而不是在稍后将呈现的某些字符串中)。

    我想这已经足够了——如果你仍然需要解释字符串中的内容,你可以use Vue.compile to interpret the content。但要小心,因为它不安全,以防其中包含一些恶意代码 - 但由于默认的指令 has no sanitizing at all,我猜这就是 Vue.js 的工作方式。

    【讨论】:

    • “出于安全原因”我不这么认为。它只是在概念上没有任何意义。从安全的角度来看,我仍然可以执行任意 XSS,无论代码是否首先由 Vue 的编译器编译。
    • 感谢您指出这一点,我在我的帖子中澄清了这一点。尽管如此,Vue.compile 的使用仍然是出于安全考虑,因为攻击者可以使用它获得更多选择。
    • 请记住,我仍然可以注入&lt;script&gt;Vue.compile(/* ... whatever ... */)&lt;/script&gt;,所以这并不重要。这是一个概念问题。
    • 哇,习惯了 Angular 的 Vue 真的很可怕 doesn't do any sanitizing by default,不知道这一点。那么你可能是对的背景。我不同意你的说法,有这样的东西没有意义,因为这个问题有一些用例,但这不是讨论的重点。
    • 感谢@idmean 的解答
    猜你喜欢
    • 2016-02-16
    • 2020-03-31
    • 1970-01-01
    • 1970-01-01
    • 2020-11-15
    • 1970-01-01
    • 2020-07-11
    • 1970-01-01
    • 2017-09-09
    相关资源
    最近更新 更多