首页 > 解决方案 > 如何将计算属性与嵌套 v-for 循环内的引导弹出框匹配?

问题描述

在输入这个问题之前,我花了几天时间寻找一个足够接近但无济于事的例子,所以我将我的具体用例带到 SO

我正在使用 Bootstrap Vue 进行布局,我将日期加载到对应于月份的 12 个按钮中,然后在每个按钮上方显示一个弹出窗口,其中包括从 Firebase 加载的该月份的匹配日期。到目前为止,我几乎具有所需的行为,因为我使用 v-for 循环遍历包含月份名称的月份对象数组和用于保存该月份匹配日期的数组,该数组根据月份名称和这些按钮内的另一个 v-for 循环用于弹出窗口,我在其中加载来自 Firebase 的所有日期。

我还有一个计算属性,我将来自 Firebase 的传入日期缩短为其缩短的月份名称,然后在月份数组和我的日期数组上使用嵌套的 for-in 循环,以将缩短的月份与现有的月份匹配我几个月的对象数组中的数组。为了清楚起见,我将在下面附上我刚才所说的照片

在此处输入图像描述

在此处输入图像描述<--每个按钮都包含一个这样的弹出窗口,我想将弹出窗口中的月份与正确的月份按钮匹配,并且只有那个

我现在遇到的问题是如何将我的计算属性连接到弹出框的嵌套 v-for 循环,以便只显示任何月份的匹配日期。最初我尝试将 v-for 移动到按钮上,但所做的只是为每个传入日期第一个 v-for 循环中的每个月份名称创建多个按钮。我在这里看到的嵌套 v-fors 的其他示例问题包含当数据来自单个数组时的解决方案,但在我的情况下,我在单独的数组中有单独的数据,我想匹配 ie(Jan 按钮应该只有一月日期,二月按钮应该只有二月日期等等)。我将在下面粘贴我现在拥有的相关代码块

从模板:

<template>
  <b-container>
    <b-row>
        <div v-for="(month,index) in months" :key="index">
            <b-button 
                :id="`date-popover-${month.name}`"
                class="shadeCircleColor" 
                :to="{ name: 'PaymentsDetailPage', params: { id: id } }"
            >
                {{month.name}}
            </b-button>
            <b-popover
                :target="`date-popover-${month.name}`"
                triggers="hover"
                placement="top"
            >
              <div v-for="(payment, index) in payments" :key="index">
                {{payment.createdOn}}
              </div>
            </b-popover>
        </div>
        {{ matchedMonths }}
    </b-row>
  </b-container>
</template>

从数据():

data() {
    return {
      dates: [], <-- dates from firebase will show here in from a property  i.e(dates.createdOn)
      months: [ 
        {name: 'Jan', createdOn: [] }, 
        {name: 'Feb', createdOn: [] }, 
        {name: 'Mar', createdOn: [] }, 
        {name: 'Apr', createdOn: [] }, 
        {name: 'May', createdOn: [] }, 
        {name: 'Jun', createdOn: [] }, 
        {name: 'Jul', createdOn: [] }, 
        {name: 'Aug', createdOn: [] }, 
        {name: 'Sep', createdOn: [] }, 
        {name: 'Oct', createdOn: [] }, 
        {name: 'Nov', createdOn: [] }, 
        {name: 'Dec', createdOn: [] }, 
      ],
    };
  },

从计算的属性:

computed: {
   matchedMonths() {
    for(let i in this.months) {
     for(let j in this.dates) {
      var dates = new Date(this.dates[j].createdOn)
      const shortMonth = dates.toLocaleString('default', { month: 'short' });
      if(shortMonth === this.months[i].name) {
        this.months[i].createdOn.push(shortMonth)
        console.log(this.months[i].name, this.months[i].createdOn)
      }
     }
    }
   }
  },

我想说我坚持的是如何将我想要的匹配月份返回到正确的 v-for 以便弹出框仅显示每个月份的一个日期。我怀疑的一个问题是我的嵌套 for-in 循环,因为当我尝试将缩短的月份返回到 months.createdOn 数组时,它会使所有按钮消失。从那以后我尝试的是更改循环以在日期数组上使用 .map 函数,然后以某种方式将其与月份数组匹配,但是当我尝试将这些日期推入时遇到未定义的错误。

这是说明以下内容的代码:

const justDates = this.payments.map(payment => {
  return new Date(payment.createdOn).toLocaleString('default', { month: 'short'})
})
console.log(justDates)
const months= this.months.map(month => {
  if(justDates === month.name){
    return month.createdOn.push(justDates)
  }
})
console.log(months)

我不知道将matchedMonths计算属性放在哪里才能使其正常工作,或者我是否应该在任一v-for循环中使用v-if来检查month.name ===是否为缩短的月份名称即将到来的日期,并希望获得有关如何获得所需解决方案的帮助。

我还需要帮助是将缩短的月份日期改回完整的月份日期和年份字符串,一旦我得到与他们的按钮匹配的正确的缩短月份。该代码是否也包含在计算属性中?感谢您的时间。

标签: javascriptarraysvue.jsbootstrap-4bootstrap-vue

解决方案


我会说不要弄乱日期(日期时间)——这个话题是出了名的复杂,错误出现在你最不期望的地方。

使用适当的日期处理库,人们已经在其中解决了很多常见的用例(以及很多边缘案例):例如,我通常的选择是dayjs

但在您的情况下,这可能没有必要。如果您只想根据月份对日期字符串进行排序,那看起来很简单:

const dates = [
  '2021-10-04',
  '2021-09-08',
  '2021-08-06',
  '2021-07-02',
  '2021-05-04',
  '2021-01-20',
  '2021-02-11',
  '2021-03-14',
  '2021-04-10',
  '2021-06-15',
  '2021-11-16',
  '2021-12-28',
]

const getMonthFromDate = (s) => {
  // with Date.prototype.getMonth() January is 0!
  return new Date(s).getMonth()
}

const mappedMonths = dates.map(getMonthFromDate)

console.log(mappedMonths)

因此,如果您的输入数据(来自 Firebase)是“可解析的” new Date(),那么您就完成了第一部分:您有月份(从 0 到 11 的数字)。如果没有,那么您仍然可以去dayjs定义您希望日期到达的格式。

这是另一个可以帮助您将其付诸实践的片段:

Vue.component('ButtonWithPopover', {
  props: ['item'],
  computed: {
    targetId() {
      return `date-popover-${this.item.name}`
    },
  },
  template: `
    <div>
      <b-button
        :id="targetId"
      >
        {{ item.name }}
      </b-button>
      <b-popover
        :target="targetId"
        triggers="hover"
        placement="bottom"
      >
        <template #title>
          {{ item.name }}:
        </template>
        <div
          v-for="createdOnItem in item.createdOn"
          :key="createdOnItem"
        >
          {{ createdOnItem }}
        </div>
      </b-popover>
    </div>
  `
})

new Vue({
  el: "#app",
  data() {
    return {
      dates: [
        '2021-10-04',
        '2021-09-08',
        '2021-08-06',
        '2021-07-02',
        '2021-05-04',
        '2021-01-20',
        '2021-02-11',
        '2021-03-14',
        '2021-04-10',
        '2021-06-15',
        '2021-11-16',
        '2021-12-28',
        '2021-02-03', // I added this, to show that multiple lines can appear in a month popover
      ],
      months: [{
          name: 'Jan',
        },
        {
          name: 'Feb',
        },
        {
          name: 'Mar',
        },
        {
          name: 'Apr',
        },
        {
          name: 'May',
        },
        {
          name: 'Jun',
        },
        {
          name: 'Jul',
        },
        {
          name: 'Aug',
        },
        {
          name: 'Sep',
        },
        {
          name: 'Oct',
        },
        {
          name: 'Nov',
        },
        {
          name: 'Dec',
        },
      ],
    }
  },
  computed: {
    // this computed merges the months with the
    // available dates; as it's a computed, it
    // updates if the data it depends on updates
    monthItems() {
      return this.months.map((e, i) => {
        const createdOn = this.getFilteredDate(i, this.dates)
        return {
          ...e,
          createdOn,
        }
      })
    },
  },
  methods: {
    getMonthFromDate(date) {
      return new Date(date).getMonth()
    },
    getFilteredDate(idx, dates) {
      return dates.filter(date => {
        return this.getMonthFromDate(date) === idx
      }) || []
    },
  },
  template: `
    <b-container
      class="py-2"
    >
      <b-row>
        <b-col
          class="d-flex"
        >
          <button-with-popover
            v-for="item in monthItems"
            :key="item.name"
            :item="item"
          />
        </b-col>
      </b-row>
    </b-container>
  `
})
<!-- Add this to <head> -->

<!-- Load required Bootstrap and BootstrapVue CSS -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />

<!-- Load polyfills to support older browsers -->
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>

<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>

<!-- Load the following for BootstrapVueIcons support -->
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue-icons.min.js"></script>

<div id="app"></div>


推荐阅读