如何在嵌套的 v-for 循环中将计算出的 属性 与 bootstrap 弹出框匹配?

How do I match a computed property to a bootstrap popover inside a nested v-for loop?

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

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

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

<-- 每个按钮都包含一个这样的弹出窗口,我想将弹出窗口中的月份与正确的月份按钮匹配,并且只有那个

我现在遇到的问题是如何将我的计算 属性 连接到弹出窗口的嵌套 v-for 循环,以便仅显示任何月份的匹配日期。最初我尝试将 v-for 移动到按钮,但所做的只是为第一个 v-for 循环中每个月份名称的每个传入日期 创建了多个按钮。我在这里看到的嵌套 v-fors 的其他示例问题包含当数据似乎来自单个数组时的解决方案,但在我的例子中,我在单独的数组中有单独的数据,我想匹配,即(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)
      }
     }
    }
   }
  },

我想说的是如何 return 我想要的匹配月份到正确的 v-for 以便弹出窗口只显示每个月份的一个日期。我怀疑的一个问题是我的嵌套 for-in 循环,因为当我尝试 return 缩短的月份到 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 === 到传入日期的缩短月份名称,并希望获得有关如何获得所需解决方案的帮助。

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

我会说不要弄乱日期 (datetime) - 这个主题非常复杂,错误会出现在您最意想不到的地方。

使用合适的日期处理库,人们已经在其中解决了很多常见的用例(以及很多边缘案例):例如,我通常的选择是 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>