Vue.js 2:可以使用带有“v-for”的多个“模板”吗?

问题描述 投票:0回答:1

对于客户项目,我需要使用 Vue.js 版本 2。

在组件中,我对一些使用

<template>
v-for
的字段定义 JSON 进行了 2 次迭代。

无论我首先放置哪一个迭代,都将被忽略 - 内容永远不会显示。如果我改变他们的顺序,它将是相应的另一个。

大量编辑:现在包括一个可重现的示例,希望

src/__tests__/ConcreteFormComponentFillingSlots.spec.js

// @vitest-environment jsdom

import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import ConcreteFormComponentFillingSlots from '@/__tests__/ConcreteFormComponentFillingSlots.vue'

describe('My bug with multiple template v-for iterations', () => {
    it('renders all form fields', () => {
        const wrapper = mount(ConcreteFormComponentFillingSlots)

        const renderedHtml = wrapper.html()

        console.log(`\n\n### + ${renderedHtml}\n\n####`)
        expect(renderedHtml).includes('Search | Key: |searchResultSlot1|')
        expect(renderedHtml).includes('Search | Key: |searchResultSlot2|')
        expect(renderedHtml).includes('Upload | Key: |uploadResultSlot1|')
        expect(renderedHtml).includes('Upload | Key: |uploadResultSlot2|')
    })
})

src/__tests__/GenericFormComponentWithSlots.vue

<script>

export default {
    name: 'GenericFormComponentWithSlots',
    props: {
        id: String,
        formConfig: Object
    },
    data () {
        return {
            pageSelectionIndex: 0,
            sectionSelectionIndex: 0
        }
    },
    methods: {
        getFieldLabel (field) {
            return field.label
        }
    },
    computed: {
        getSelectedSection () {
            return this.formConfig.sections[this.sectionSelectionIndex]
        }
    }
}
</script>

<template>
    <div v-bind:id="`c_smart-form-body_some_id`"
         class="c_smart-form-body">
        <transition-group name="show" tag="div">
            <template v-for="(field) in getSelectedSection.fields">
                <div v-bind:key="`${field.id}_${pageSelectionIndex}_outerForm`"
                     class="c_smart-form-field">
                    <div v-if="field.type === 'Slot'">
                        <p v-html="'Slot goes here: field(' + field.id + ')'"/>
                        <slot v-bind:name="`field(${field.id})`"
                              v-bind:field="field">
                        </slot>
                    </div>
                    <div v-else>
                        <p v-html="'Unknown field type: ' + field.type + ' for field ' + field.id"/>
                    </div>
                </div>
            </template>
        </transition-group>
    </div>
</template>

<style lang="less">
</style>

src/__tests__/ConcreteFormComponentFillingSlots.vue

<script>
import GenericFormComponentWithSlots from '@/__tests__/GenericFormComponentWithSlots.vue'
import theUserForm from '@/__tests__/the-user-form.json'
import _ from 'lodash'

export default {
    name: 'ConcreteFormComponentFillingSlots',
    mixins: [
    ],
    components: {
        GenericFormComponentWithSlots
    },
    props: {
    },
    data () {
        return {
            customForm: _.cloneDeep(theUserForm),
            uploadFieldConfigs: {
                uploadResultSlot1: {
                    isUploading: false,
                    slotName: 'field(uploadResultSlot1)'
                },
                uploadResultSlot2: {
                    isUploading: false,
                    slotName: 'field(uploadResultSlot2)'
                }
            },
            searchFieldConfigs: {
                searchResultSlot1: {
                    slotName: 'field(searchResultSlot1)'
                },
                searchResultSlot2: {
                    slotName: 'field(searchResultSlot2)'
                }
            }
        }
    }
}
</script>

<template>
    <div class="custom-task-content-container">
        <GenericFormComponentWithSlots v-if="customForm" id="customForm" v-bind:form-config=this.customForm
        >
            <!-- Iterates over uploadFieldConfigs; fieldConfig will be the child object, key will be the key under which it is stored as part of uploadFieldConfigs -->
            <!-- '#' is a shorthand for v-slot: (referencing the slot name dynamically is easier with the shorthand) https://vuejs.org/guide/components/slots.html#dynamic-slot-names -->
            <!-- '=data' passes in the field data, and we pass name and field in EssentialSmartForm, cf. https://vuejs.org/api/built-in-directives.html#v-slot -->
            <template v-for="(fieldConfig, key) in uploadFieldConfigs" #[fieldConfig.slotName]="data">
                <p :key="key + '_upload_debug1'" v-html="'Upload | Key: |' + key + '| Data: |' + JSON.stringify(data) + '|'"></p>
            </template>
            <template v-for="(fieldConfig, key) in searchFieldConfigs" #[fieldConfig.slotName]="slotProps">
                <p :key="key + '_upload_debug2'" v-html="'Search | Key: |' + key + '| Data: |' + JSON.stringify(slotProps) + '|'"></p>
            </template>
        </GenericFormComponentWithSlots>
    </div>
</template>

<style lang="less" scoped>
</style>

src/__tests__/the-user-form.json

{
  "id": "myForm",
  "version": 1,
  "headline": "My form",
  "sections": [
    {
      "id": "BaseInfo",
      "title": "Some Hints",
      "allowMultiple": false,
      "fields": [
        {
          "id": "searchResultSlot1",
          "type": "Slot",
          "label": "Search result slot 1",
          "keyLabel": "labelName",
          "isReadOnly": false
        },
        {
          "id": "searchResultSlot2",
          "type": "Slot",
          "label": "Search result slot 2",
          "keyLabel": "labelName",
          "isReadOnly": false
        },
        {
          "id": "uploadResultSlot1",
          "type": "Slot",
          "label": "Upload Result Slot 1",
          "upload": true,
          "required": true
        },
        {
          "id": "uploadResultSlot2",
          "type": "Slot",
          "label": "Upload Result Slot 2",
          "upload": true,
          "required": true
        }
      ],
      "defaultValues": {
      },
      "pages": [{}]
    }
  ]
}

vite.config.js

import vue from '@vitejs/plugin-vue2'
import { fileURLToPath, URL } from "node:url";

export default {
    plugins: [ vue() ],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    }
}

package.json

{
    "name": "My-App",
    "version": "1.0.0",
    "private": true,
    "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint",
        "test:unit": "vitest --environment jsdom --root src/",
        "coverage": "vitest --coverage --environment jsdom --root src/"
    },
    "dependencies": {
        "isomorphic-fetch": "^3.0.0",
        "register-service-worker": "^1.7.2",
        "vue": "^2.7.16",
        "vue-dompurify-html": "4.1",
        "vue-router": "^3.5.3"
    },
    "devDependencies": {
        "@vitejs/plugin-vue2": "^2.3.1",
        "@vitest/coverage-c8": "^0.29.2",
        "@vue/cli-plugin-eslint": "^5.0.8",
        "@vue/cli-plugin-pwa": "^5.0.4",
        "@vue/cli-plugin-router": "^5.0.4",
        "@vue/cli-service": "^5.0.8",
        "@vue/eslint-config-standard": "^6.1.0",
        "@vue/test-utils": "^1.3.6",
        "axios": "^1.6.5",
        "eslint": "^7.32.0",
        "eslint-plugin-import": "^2.26.0",
        "eslint-plugin-node": "^11.1.0",
        "eslint-plugin-promise": "^5.2.0",
        "eslint-plugin-vue": "^7.20.0",
        "jsdom": "^20.0.3",
        "keycloak-js": "^23.0.7",
        "less": "^4.1.2",
        "less-loader": "^10.2.0",
        "lodash": "^4.17.21",
        "msw": "^1.1.0",
        "portal-vue": "^2.1.7",
        "terser": "^5.30.4",
        "vitest": "^0.29.8",
        "vue-axios": "^3.4.1",
        "vue-i18n": "^8.27.1",
        "vue-sweetalert2": "^5.0.5",
        "vue-template-compiler": "^2.7.16",
        "vue-virtual-scroller": "^1.0.10",
        "workbox-webpack-plugin": "6.5.3"
    },
    "eslintConfig": {
        "root": true,
        "env": {
            "node": true
        },
        "extends": [
            "plugin:vue/essential",
            "@vue/standard"
        ],
        "parserOptions": {
            "ecmaVersion": 2021
        },
        "rules": {
            "indent": [
                "error",
                4
            ]
        }
    },
    "browserslist": [
        "> 1%",
        "last 2 versions",
        "not dead"
    ]
}

编辑:如果我在填充槽的同一组件中生成槽,问题仍然存在。如果我只有两次迭代并且不使用任何插槽,它就会消失。

vue.js vuejs2
1个回答
0
投票

这似乎是一个很奇怪的错误,这似乎是由两个

fieldConfig
的迭代器
v-for
的同名引起的。在我看来,无论出于何种原因,变量都是共享的。最简单的解决方法是为每个
v-for
循环使用不同的迭代器变量名称,例如:

            <template v-for="(fieldConfig, key) in uploadFieldConfigs" #[fieldConfig.slotName]="data">
                <p :key="key + '_upload_debug1'" v-html="'Upload | Key: |' + key + '| Data: |' + JSON.stringify(data) + '|'"></p>
            </template>
            <template v-for="(fieldConfig2, key) in searchFieldConfigs" #[fieldConfig2.slotName]="slotProps">
                <p :key="key + '_upload_debug2'" v-html="'Search | Key: |' + key + '| Data: |' + JSON.stringify(slotProps) + '|'"></p>
            </template>

注意我如何使用

fieldConfig2
作为
searchFieldConfigs
上的迭代器。

© www.soinside.com 2019 - 2024. All rights reserved.