Vue.js - 동적으로 테이블 행 병합하기, 동적 rowspan(2)

Gyu·2022년 4월 12일
0

이전 글과 이어집니다...

내가 구현해야 할 화면

  • 이전 글에서 내가 구현한 화면은 백엔드에서 데이터를 받아 같은 날짜끼리는 행이 병합되는 테이블이었다.
  • 이번에는 아래의 조건을 추가된 화면을 작업해야했다.
1. 사용자는 검색 조건에 시간대별, 문의유형, 상담원을 선택할 수 있다.
2. 사용자는 검색 조건은 모두 선택 안 할수도, 모두 선택 할 수도 있다.
3. 같은 날짜의 같은 시간은 행이 병합되어야 한다.
4. 같은 날짜의 같은 시간의 같은 문의 유형은 행이 병합되어야 한다.

데이터 형식

{
	table_data: [
      {
        standard_at: '2022-01-01',
        standard_time: '9-10', // 시간
        inquire_type: '문의유형1', // 문의유형
        counselor_nm: '상담원1', // 상담원
        ...
      },
        ...
    ]
}
  • api 요청 시 백엔드에서 프론트로 위와 같은 형식의 데이터를 내려준다.
  • 사용자가 해당 검색 조건을 선택했을 때만 백엔드에서 standard_time, inquire_type, counselor_nm 프로퍼티를 내려준다.

어떻게 해결해야할까...

  • 처음에는 이전 글처럼 v-if 디렉티브와 v-bind 디렉티브에 메소드를 연결하여 구현하려고 했으나, 사용자 검색 조건 때문에 v-if 조건문이 점점 길어지고 복잡해졌다. 또한 v-for로 반복문을 돌면서 v-if 연산을하고, v-bind와 연결된 메소드를 계속 호출하는 방식이 비효율적인 것 같아 보였다.
  • 그래서 api 응답 데이터를 data에 할당하기 전에 렌더링 여부와 rowspan에 할당할 값을 계산하는 방식으로 작업을 했다.
// template 영역
...
<table>
  <thead>
    <tr>
      <th>날짜</th>
      <th v-if="tableData[0].standard_time">시간</th>
      <th v-if="tableData[0].inquire_type">문의유형</th>
      <th v-if="tableData[0].counselor_nm">상담원</th>
      <th>데이터1</th>
      <th>데이터2</th>
      <th>데이터3</th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="(item, index) in tableData">
      <td
      	v-if="item.isDateVisible"
      	:rowSpan="item.dateRowSpan"
      >
      	{{ item.standard_at }} 
      </td>
      <td
      	v-if="item.standard_time && item.isTimeVisible"
      	:rowSpan="item.timeRowSpan"
      >
      	{{ item.standard_time }} 
      </td>
      <td
      	v-if="item.inquire_type && item.isImquireTypeVisible"
      	:rowSpan="item.inquireTypeRowSpan"
      >
      	{{ item.inquire_type }} 
      </td>
      <td
      	v-if="item.counselor_nm"
      >
      	{{ item.counselor_nm }} 
      </td>
      <td> {{ item.data1 }} </td>
      <td> {{ item.data2 }} </td>
      <td> {{ item.data3 }} </td>
    </tr>
  </tbody>
</table>
...

// script 영역 - methods
...
getData() {
	axios.get(requestUrl, {config})
  	.then(res => {
      this.tableData = res.data.table_data.map((item, index, arr) => {
        const param = {item, index, arr};
        this.setDateProp(param);
        if(item.standard_time) this.setTimeProp(param);
        if(item.inquire_type) this.setInquireTypeProp(param);
        return item;
      });
    })
},
// 날짜 데이터 프로퍼티 설정 메소드
setDateProp({item, index, arr}) {
    item.isDateVisible = false;
    item.dateRowSpan = 0
    // 0번째 인덱스 요소 처리
    if(index == 0) {
      item.isDateVisible = true;
      item.dateRowSpan = this.getRowSpan(arr, item, 'standard_at');
      return;
    }
    // 이전 인덱스 요소와 시간 비교
    if(item.standard_at != arr[index-1].standard_at) {
      item.isDateVisible = true;
      item.dateRowSpan = this.getRowSpan(arr, item, 'standard_at');
    }
  },
// 시간 데이터 프로퍼티 설정 메소드
setTimeProp({item, index, arr}) {
  // 초깃값 세팅
  item.isTimeVisible = false;
  item.timeRowSpan = 0;
  // 0번째 인덱스 요소 처리
  if(index == 0) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, 'standard_time');
    return;
  }
  // 이전 인덱스와 시간, 날짜 비교
  if(item.standard_time != arr[index-1].standard_time) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, 'standard_time');
  } else if(item.standard_at != arr[index-1].standard_at) {
    item.isTimeVisible = true;
    item.timeRowSpan = this.getRowSpan(arr, item, 'standard_time');
  }
},
// 문의유형 데이터 프로퍼티 설정 메소드
setInquireTypeProp({item, index, arr}) {
  // 초깃값 세팅
  item.isSkillVisible = false;
  item.skillRowSpan = 0;
  // 0번째 인덱스 요소 처리
  if(index == 0) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, 'skill_nm');
    return;
  }
  // 이전 데이터와 문의 유형이 다른 경우
  if (arr[index-1].skill_nm != item.skill_nm) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, 'skill_nm');
    return;
  }

  // 문의 유형이 같은데 날짜 혹은 시간이 다른 경우
  if (item.standard_time
      && item.standard_time != arr[index-1].standard_time
     ) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, 'skill_nm');
  } else if (item.standard_at != arr[index-1].standard_at) {
    item.isSkillVisible = true;
    item.skillRowSpan = this.getRowSpan(arr, item, 'skill_nm');
  }
},
getRowSpan(arr, data, prop) {
  let rowSpan = 0;
  
  if (data.standard_time && prop != 'standard_at') {
    arr.forEach(item => {
      if(item[prop] == data[prop] 
         && item.standard_at == data.standard_at 
         && item.standard_time == data.standard_time) rowSpan++;
    })
    return rowSpan;
  }
  
  arr.forEach(item => {
    if(item[prop] == data[prop] 
       && item.standard_at == data.standard_at) rowSpan++;
  })
  
  return rowSpan;
}
...
  • 지금 다시 보니 setDateProp, setTimeProp, setInquireTypeProp 메소드 형태가 비슷해서 하나의 메소드로 합칠 수 있었는데, 당시에는 정신 없이 바빠서 그런 고민할 틈이 없었나 보다...
  • 이 방식보다 더 좋은 방식도 있겠지만 아무리 검색해봐도 나처럼 많은 조건이 있는 동적 행 병합 예제가 없어서 굴러가지 않은 머리를 굴려가며 코드를 어찌저찌 작성했다. 다음에는 더 좋은 방식을 고민해봐야겠다🤔
profile
애기 프론트 엔드 개발자

0개의 댓글