博客 / 詳情

返回

ant design vue Table根據數據合併單元格

之前在外包做項目的時候,甲方提出一個想要合併單元格的需求,表格裏展示的內容是領導們的一週行程,因為不想出現重複內容的單元格,實際場景中領導可能連續幾天參加某個會議或者某個其他行程,本來 系統中對會議時間衝突是做了限制,也就是不能創建時間衝突的會議,那麼對重複行程的單元格直接進行合併是沒有問題的;但是後來又放開了限制、又允許存在會議時間衝突的情況了,因為實際中可能存在連續幾天的大會行程中,又安排了幾個小會,所以在後續的溝通中確定的方案是:單獨的連續行程進行合併,如果中間出現多個行程就不合並,如果單獨的長行程還沒結束,後面連續的排期還是合併。最終的效果參考下圖中的“會議111”。

根據表格的數據合併單元格,因為用的是ant design vue這個UI庫,所以我第一時間想的就是去翻文檔,查到的用法如下:

可是把這段代碼寫到項目裏並沒有生效,才發現最新已經是"ant-design-vue": "^4.2.6",而項目裏用的版本是"ant-design-vue": "^1.6.3",看懵了🤧🤧🤧,查了之後才發現這個版本的使用方法是這樣的:

於是我就按着這麼寫:

結果發現rowSpan的設置不管用,在網上搜索了一番,又自己試了幾次,發現加上style的設置才實現了合併單元格。

很煩接手這種項目,總是用一套模板開發新項目,永遠不更新三方庫,大量公司的“降本增效”以後這種情況會越來越多吧,反正當下能用就行,以後維護不了了再去考慮更新三方庫不知道會爆出什麼問題呢😅

具體的單元格是否合併就是按照業務邏輯來判斷了。在這個項目裏,每日行程的原始數據結構類似如下,就是把每個領導本週內的行程給查詢出來。

{
    staff1: [
      {
        event: '會議111',
        startTime: '2025-01-01 9:00',
        endTime: '2025-01-04 18: 00'
      },
      {
        event: '這是一個測試會議22',
        startTime: '2025-01-02 13:00',
        endTime: '2025-01-02 16: 00'
      },
      {
        event: '這是一個測試會議33',
        startTime: '2025-01-05 09:00',
        endTime: '2025-01-05 17: 00'
      }
    ],
    staff2: [
      {
        event: '會議q',
        startTime: '2025-01-01 9:00',
        endTime: '2025-01-01 18: 00'
      },
      {
        event: '這是一個測試會議ww',
        startTime: '2025-01-02 13:00',
        endTime: '2025-01-07 16: 00'
      },
    ],
    staff3: [
      {
        event: '待辦事項x',
        startTime: '2025-01-01 9:00',
        endTime: '2025-01-01 18: 00'
      },
      {
        event: '這是一個待辦事項ww',
        startTime: '2025-01-05 13:00',
        endTime: '2025-01-07 16: 00'
      },
    ]
 }

後端會做簡單的處理,把日程按單日分組,返回給前端的數據結構類似如下(項目裏原本是week0~week6,本文簡單演示就直接使用日期了):

{
    staff1: {
      '2025-01-01': [
        {
          event: '會議111',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-04 18: 00'
        },
      ],
      '2025-01-02': [
        {
          event: '會議111',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-04 18: 00'
        },
        {
          event: '這是一個測試會議22',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-02 16: 00'
        },
      ],
      '2025-01-03': [
        {
          event: '會議111',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-04 18: 00'
        },
      ],
      '2025-01-04': [
        {
          event: '會議111',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-04 18: 00'
        },
      ],
      '2025-01-05': [
        {
          event: '這是一個測試會議33',
          startTime: '2025-01-05 09:00',
          endTime: '2025-01-05 17: 00'
        }
      ],
      '2025-01-06': [],
      '2025-01-07': [],
    },
    staff2: {
      '2025-01-01': [
        {
          event: '會議q',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-01 18: 00'
        },
      ],
      '2025-01-02': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-03': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-04': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-05': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-06': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-07': [
        {
          event: '這是一個測試會議ww',
          startTime: '2025-01-02 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
    },
    staff3: {
      '2025-01-01': [
        {
          event: '待辦事項x',
          startTime: '2025-01-01 9:00',
          endTime: '2025-01-01 18: 00'
        },
      ],
      '2025-01-02': [],
      '2025-01-03': [],
      '2025-01-04': [],
      '2025-01-05': [
        {
          event: '這是一個待辦事項ww',
          startTime: '2025-01-05 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-06': [
        {
          event: '這是一個待辦事項ww',
          startTime: '2025-01-05 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
      '2025-01-07': [
        {
          event: '這是一個待辦事項ww',
          startTime: '2025-01-05 13:00',
          endTime: '2025-01-07 16: 00'
        },
      ],
    },
}

前端就在以上的結構基礎上進行遍歷處理。

第一步準備工作,先簡單判斷當前處理的行程是否在一天內結束,並且判斷是否跨時段(上下午),把這個兩個判斷結果存儲起來用於後續操作。

const inOneDay =
    moment(schedule.endTime).format('YYYY-MM-DD') ===
    moment(schedule.startTime).format('YYYY-MM-DD') // 是否在一天內完成(開始日期和結束日期一致)
let inOneRange = false // 是否在同個時段(上下午),判斷一天內的日程是否跨時段
if (inOneDay) {
  const startMorning = moment(schedule.startTime).isSameOrBefore(
      weekData[weekIndex].dateStr + ' ' + MORNING_END
  )
  const endAfternoon = moment(schedule.endTime).isSameOrAfter(
      weekData[weekIndex].dateStr + ' ' + AFTERNOON_START
  )
  if ((startMorning && !endAfternoon) || (!startMorning && endAfternoon)) inOneRange = true
}

第二步就在第一步的基礎上先做第一輪簡單的篩選,如果滿足以下條件之一,則當前處理的行程不用跨行處理。

  1. 當前行程所在時段存在多個行程
  2. 當前行程本身不跨時段
  3. 當前行程跨上下午時段,當前處理的是下午,但是上午存在多個行程
if (
      weekData[weekIndex][account].length > 1 || // 當前員工單個時段有多個行程
      (inOneDay && inOneRange) || // 某行程不跨時段
      (inOneDay &&
          !inOneRange &&
          weekIndex % 2 === 1 &&
          weekData[weekIndex - 1][account].length > 1) // 當前行程跨上下午時段,當前處理的是下午,但是上午存在多個行程
  ) {
    // 不做跨行處理
    result.isCross = false
    return result
}

第三步做第二輪篩選,首先做兩個判斷並保存判斷結果。

  1. 當前是否為跨行的開始行

    // 判斷是否是跨行的開始(滿足條件之一):
    // 1. 行程的開始日期等於當前行的日期,行程的開始時間晚於等於當前行的startTime
    // 2. 行程的開始日期等於當前行的日期,行程的結束日期晚於當前行的日期
    // 3. 行程的開始日期早於當前行的日期,且前一行的行程數量大於1
    // 4. 當前行程在第一行
    const isStart =
        (scheduleStartDate === weekData[weekIndex].dateStr &&
            scheduleStartTime >= weekData[weekIndex].startTime) ||
        (weekIndex % 2 === 1 &&
            weekData[weekIndex - 1][account].length > 1 &&
            scheduleStartDate === weekData[weekIndex].dateStr &&
            scheduleEndDate >= weekData[weekIndex].dateStr) ||
        (scheduleStartDate < weekData[weekIndex].dateStr &&
            weekIndex > 0 &&
            weekData[weekIndex - 1][account].length > 1) ||
        weekIndex === 0
  2. 當前是否為跨行的結束行

    // 判斷是否是跨行的結束(滿足條件之一):
    // 1. 當前行程在最後一行
    // 2. 行程的結束日期等於當前行的日期,行程的結束時間晚於當前行的startTime且早於等於當前行的endTime
    // 3. 下一行的日程數量大於1
    const isEnd =
        weekIndex === 13 ||
        (scheduleEndDate === weekData[weekIndex].dateStr &&
            scheduleEndTime >= weekData[weekIndex].startTime &&
            scheduleEndTime <= weekData[weekIndex].endTime) ||
        weekData[weekIndex + 1][account].length > 1

如果兩個判斷結果都為true,則説明既是開始行,同是又是結束行,那就不用做跨行處理。

最後篩出來的就是要跨行的單元格了,就要計算跨的行數了,也就是起始行的rowSpan值,非起始行的rowSpan就是0了。

起始行的rowSpan就是計算具體這個行程在表格裏跨的行數。

首先計算單個行程自身原本跨了幾個時段。

const diffScheduleEnd = moment(scheduleEndDate).diff(
    moment(weekData[weekIndex].dateStr),
    'days'
) // 與行程結束日期的天數差值
const diffWeekEnd = moment(weekData[13].dateStr).diff(
    moment(weekData[weekIndex].dateStr),
    'days'
) // 與周最後一天的天數差值
const dayOff = Math.min(diffScheduleEnd, diffWeekEnd) // 跨的天數
const timeOff = scheduleEndTime <= MORNING_END ? 1 : 2 // 跨的時段
let offRows = 0
// 行位移 = (天數-1)*2 + 跨的時段
if (dayOff > 0) offRows = (dayOff - 1) * 2 + timeOff
// 如果當前行程是上午開始的,再加一個行跨
if (weekIndex % 2 === 0) offRows++

再向後遍歷碰到存在多個行程的單元格就表示跨行結束,得到了rowSpan的值。

const len = weekIndex + 1 + offRows
let rowSpan = 1
for (let i = weekIndex + 1; i < len; i++) {
  if (weekData[i][account].length > 1) {
    break
  } else {
    rowSpan++
  }
}

最後我們就可以得到合併的單元格。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.