在我的 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"]
}]
任何帮助将不胜感激。
技巧是首先观察商店中的状态是否发生变化,然后评估数据的类型和来自商店的状态是否存在,然后根据“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 }
)