Nuxt:基于 API 响应的复选框的条件 v 模型

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

在我的 nuxt 2 和 nuxt 2 组合 API 中,我有一个用户可以选择然后购买的项目列表。当用户之前购买过商品时,这些商品必须显示为对用户禁用,并且默认情况下必须选中复选框。我在 API 响应中有一个属性 has_purchase,如果用户之前购买过该商品,则该属性为 true 或 false。

如果该商品之前已购买过,我在尝试选中复选框时遇到问题。我在想有什么方法可以让 v 模型动态化,所以如果 has_purchased 为 true 则选中复选框,但如果没有取消选中。

这是我的单个文件组件:

<template>
    <div class="boost">
        <div class="container">
            <h1 class="page-title">
                {{ $t('upsell.boost.content.title') }}
            </h1>
            <h4 class="page-subtitle">
                {{ $t('upsell.boost.content.description') }}
            </h4>
            <p class="select-instructions">
                {{ $t('upsell.boost.content.select_options_instructions') }}
            </p>
            <LoadingSpinner v-if="isLoading" />
            <ul class="booster-list" v-else>
                <li
                    class="booster"
                    :class="{ 'booster-already-purchased': booster['has_purchased'] }"
                    v-if="isChecked"
                    v-for="(booster, index) in computedBoosters"
                    :style="{
                        background: isChecked[index] ? 'linear-gradient(82.38deg,#f5c33e,#fae36a)' : 'none',
                    }"
                >
                    <div class="checkbox">
                        <input type="checkbox" v-model="isChecked[index]" :disabled="booster['has_purchased']" />
                    </div>
                    <div class="content">
                        <div class="content-title">
                            <img :src="getBoosterIcon(booster['code'])" :alt="booster['name']" class="booster-image" />
                            <h4 class="booster-title">
                                {{ booster['name'] }}
                            </h4>
                        </div>
                        <p class="content-text">
                            {{ booster['benefits'][0] }}
                        </p>
                    </div>
                    <span class="price-booster">{{ booster['formatted_price'] }} </span>
                </li>
            </ul>
            <div v-if="allPurchased" class="purchased-everything">
                <span>
                    {{ $t('upsell.checkout.purchased_everything_message') }}
                </span>
                <NuxtLink
                    :to="localePath({ name: 'dashboard-castings' })"
                    class="button primary is-medium is-rounded upsell-button"
                    >{{ $t('upsell.checkout.purchased_everything_button_label') }}</NuxtLink
                >
            </div>
            <div class="totals-section" v-else>
                <ul class="selected-boosts">
                    <li v-for="(booster, index) in selectedBoosters" :key="index">
                        <span>{{ booster['name'] }}</span>
                        <span>{{ booster['formatted_price'] }}</span>
                    </li>
                </ul>
                <div class="total">
                    <span class="total-text">{{ $t('upsell.boost.content.total') }}</span>
                    <span class="total-price">{{ calculateTotalPrice }}</span>
                </div>
                <NuxtLink
                    @click.native="handleSelectedBoosters(selectedBoosters)"
                    :to="
                        localePath({
                            name: 'casting-id-boost-checkout',
                            params: { id: $route.params.id },
                        })
                    "
                    class="button primary is-medium is-rounded upsell-button"
                    rounded
                >
                    {{ $t('upsell.boost.content.buy_now_button_label') }}
                </NuxtLink>
                <p class="primary maybe-later-button">
                    <NuxtLink :to="localePath({ name: 'dashboard-castings' })">{{
                        $t('upsell.boost.content.maybe_later_button_label')
                    }}</NuxtLink>
                </p>
            </div>
        </div>
    </div>
</template>
<script lang="ts">
    import { computed, defineComponent, onMounted, ref, watch } from '@nuxtjs/composition-api'
    import { isManager } from '~/utils/user'
    import seoTitle from '../../../../utils/seoTitle'
    import useAuth from '~/utils/useAuth'
    import useRouter from '~/utils/useRouter'
    import useStore from '~/utils/useStore'
    import getBoosterIconById from '~/utils/useBoostersIcons'
    export default defineComponent({
        name: 'boost',
        head() {
            return seoTitle(this.$root.$i18n, 'upsell.boost.seo.title', 'upsell.boost.seo.description')
        },
        setup(_, context) {
            const { router, route } = useRouter(context)
            const {
                actions: { fetch: fetchBoosters, setSelectedBoosters: setSelectedBoosters },
                state: boostersState,
            } = useStore(context, 'boosters')
            const auth = useAuth(context)
            const boostersData = ref<BoostData | null>(boostersState.boostersData)
            const id = route.value.params.id
            const isLoading = ref(true)
            const paymentType = 'boost'
            const handleSelectedBoosters = (selectedBoosters: SelectedBooster[]) => {
                setSelectedBoosters(selectedBoosters)
            }
            const isChecked = ref<boolean[]>(boostersState.boostersIcons.map(() => false))
            const getBoosterDetails = async () => {
                isLoading.value = true
                try {
                    await fetchBoosters({ context, id, paymentType })
                } catch (error: any) {
                    if (!auth.loggedIn || !isManager(auth.user)) {
                        router.push(`/`)
                    }
                } finally {
                    setTimeout(() => {
                        isLoading.value = false
                    }, 100)
                }
            }
            onMounted(() => {
                getBoosterDetails()
            })
            const getBoosterIcon = (id: string): string => {
                return getBoosterIconById(context, id)
            }
            watch(
                () => boostersState.boostersData,
                (newBoostersData) => {
                    boostersData.value = newBoostersData
                },
                { deep: true }
            )
            const allPurchased = computed(() => {
                if (computedBoosters.value) {
                    return computedBoosters.value.every((booster) => booster.has_purchased)
                }
                return false
            })
            const computedBoosters = computed<UpsellItem[] | null>(() => {
                if (boostersData.value) {
                    return Object.values(boostersData.value) as UpsellItem[] | null
                } else {
                    return []
                }
            })
            const selectedBoosters = computed(() => {
                const selected: any[] = []
                isChecked.value.forEach((isSelected, index) => {
                    if (isSelected && computedBoosters.value) {
                        const booster = computedBoosters.value[index]
                        selected.push({ ...booster })
                    }
                })
                return selected
            })
            const calculateTotalPrice = computed(() => {
                let totalPrice = 0
                isChecked.value.forEach((isSelected, index) => {
                    if (isSelected && computedBoosters.value) {
                        const booster = computedBoosters.value[index]
                        totalPrice += parseFloat(booster.price)
                    }
                })
                const currencySymbol =
                    computedBoosters.value && computedBoosters.value.length > 0
                        ? computedBoosters.value[0].currency_symbol
                        : ''
                if (currencySymbol === '€' || currencySymbol === '$') {
                    return totalPrice.toFixed(2) + currencySymbol
                } else {
                    return currencySymbol + totalPrice.toFixed(2)
                }
            })
            return {
                allPurchased,
                boostersData,
                calculateTotalPrice,
                computedBoosters,
                getBoosterDetails,
                getBoosterIcon,
                handleSelectedBoosters,
                isChecked,
                isLoading,
                selectedBoosters,
            }
        },
    })
</script>

为了找到解决方案,代码的重要部分是:

如果 booster['has_purchased'] 和这里的 v-model 是问题所在,则复选框将被禁用,因为它们的复选框被禁用,并且在购买时具有正确的样式,但复选框未被选中。

<input type="checkbox" v-model="isChecked[index]" :disabled="booster['has_purchased']" />

然后是我原来的绑定(我知道这并不关心助推器['has_purchased'],但尝试了不同的方法,但无法让它工作:

const isChecked = ref<boolean[]>(boostersState.boostersIcons.map(() => false))

boostersState.boostersIcons 来自商店,这是形状:

boostersIcons: [
    {
        id: 'boost-launcher',
        icon: require('~/assets/images/upsell/boost/icons/launcher-icon.svg'),
        has_purchased: true,
    },
    {
        id: 'boost-outstanding',
        icon: require('~/assets/images/upsell/boost/icons/outstanding-icon.svg'),
        has_purchased: false,
    },
    {
        id: 'boost-urgent',
        icon: require('~/assets/images/upsell/boost/icons/urgent-icon.svg'),
        has_purchased: false,
    },
    {
        id: 'boost-agent-assistance',
        has_purchased: false,
        icon: require('~/assets/images/upsell/boost/icons/support.svg'),
        has_purchased: false,
    },
    {
        id: 'boost-improve-your-casting',
        icon: require('~/assets/images/upsell/boost/icons/improve-icon.svg'),
        has_purchased: false,
    },
]

最后,对于上下文,这是转换为组件内的计算属性的 api 响应:

[{
    "code": "boost-launcher",
    "name": "Launcher",
    "price": "12",
    "formatted_price": "£12",
    "currency_symbol": "£",
    "currency_iso": "GBP",
    "has_purchased": false,
    "benefits": ["Un 50% más modelos verán tu casting, por lo que conseguirás más candidatos"]
}, {
    "code": "boost-outstanding",
    "name": "Destacado",
    "price": "12",
    "formatted_price": "£12",
    "currency_symbol": "£",
    "currency_iso": "GBP",
    "has_purchased": false,
    "benefits": ["Recibirá candidatos de mayor calidad que se ajustan a tu brief"]
}, {
    "code": "boost-urgent",
    "name": "Urgente",
    "price": "19",
    "formatted_price": "£19",
    "currency_symbol": "£",
    "currency_iso": "GBP",
    "has_purchased": false,
    "benefits": ["Recibe una respuesta más rápida a tu casting, ya que el número de inscritos se disparará en tan sólo 24 horas"]
}, {
    "code": "boost-agent-assistance",
    "name": "Asistencia de un agente",
    "price": "29",
    "formatted_price": "£29",
    "currency_symbol": "£",
    "currency_iso": "GBP",
    "has_purchased": false,
    "benefits": ["Nuestro equipo te ayudará a conseguir los mejores candidatos para tu proyecto"]
}, {
    "code": "boost-improve-your-casting",
    "name": "Mejora tu casting",
    "price": "39",
    "formatted_price": "£39",
    "currency_symbol": "£",
    "currency_iso": "GBP",
    "has_purchased": false,
    "benefits": ["Haz que tu casting destaque con la ayuda de nuestro equipo de redactores y editores de imágenes"]
}]

任何帮助将不胜感激。

javascript vue.js nuxt.js
1个回答
0
投票

技巧是首先观察商店中的状态是否发生变化,然后评估数据的类型和来自商店的状态是否存在,然后根据“on_purchased”属性分配一个新的元素数组,以便复选框发生变化“基于此检查”状态。

    watch(
        () => boostersState.boostersData,
        (newBoostersData) => {
            boostersData.value = newBoostersData
            if (newBoostersData && typeof newBoostersData === 'object') {
                isChecked.value = Object.values(newBoostersData).map((booster: any) => booster['has_purchased'])
            }
        },
        { deep: true }
    )
© www.soinside.com 2019 - 2024. All rights reserved.