내가 진행했던 프로젝트는 교실 배치도면을 background로 사용하는 좌석배치도 였습니다.
<기존 관리시스템의 단점>
A4용지 안에 학생 이름과 경고나 예외사항등의 항목을 적어 놓고 매 시간마다 학생이 자리에 앉아있는지 확인하는 수기관리 시스템이었는데 이 부분이 넘 불편해 보였어요.
데이터가 100% 정확할 수 없다는 것을 수기관리의 단점으로 꼽았는데 종이가 사라지거나 물에 젖거나 하면 그 시간의 관리데이터는 사라지는것이나 마찬가지기 때문에 학생이 저는 자리에 앉아있었어요
하면 그냥 인정해줘야 하는것입니다.
<기존 관리시스템 단점을 어떻게 개선할것인가>
펜으로 체크하는것이 아닌 태블릿이나 스마트폰으로 콕콕콕 눌러가며 쉽게 관리하고 또 담당자가 바뀌더라도 좌석에 앉아있는 학생의 정보를 알 수 있도록 SPA 형태로 만들어 배포합니다.
로그인 정보를 기준으로 누가 언제 그 교실 공부시간을 관리했는지도 알 수 있게 만듭니다.
n년간 수기로 했던 부분을 바꾼다는것은 누군가에게는 오히려 불편한 부분이 될 수 있다는 생각을 했지만 그런 부분에 대해서는 내부에서 사용하는 것이기 때문에 수시로 피드백을 받아 수정하기로 했습니다.
학생 데이터 API는 만들어진 상태였고, 교실이 1-2개가 아니기 때문에 그 많은 데이터를 비교적 빠르게 불러오기 위해 프레임워크를 사용한 SPA 형태를 취했습니다.
1) leaflet.js 는 주로 지도를 불러올 때 사용하는 라이브러리인데, 이미지가 background로 깔려도 괜찮나..?
imageUrl
옵션을 프로젝트 내 이미지 경로로 설정하는데 이미지는 SVG로 export하여 라이브러리 안에서 확대가 되어도 깨지지 않도록 했습니다.2) 배치도 위 좌석을 어떻게 배치할까?
위와 같은 이유로 좌석배치를 할 때는 열과 행의 간격을 한번씩만 구하고 그 이후부터는 계산을 해서 좌표 데이터에 값을 넣어주었습니다. (노가다 결론..)
// 라이브러리의 클릭 이벤트를 사용하여 좌표 값 알아내기!
map.on('click', function(e) {
console.log("Lat, Lon : " + e.latlng.lat + ", " + e.latlng.lng)
});
background 경로
좌석 좌표 값
지정 범위
데이터는 하나의 js파일로 만들어 모듈처럼 사용했습니다.export default function () {
const seatCoordinate = {};
//지점 1
seatCoordinate.seoulCenter = {
room1: {
imageUrl: `/assets/images/seoul/room1.svg`,
coordinate : {
1: {x: 797, y: 26},
2: {x: 750, y: 26},
3: {x: 700, y: 26},
...
}
}
},
//지점 2
seatCoordinate.busanCenter = {
room5: {
imageUrl: `/assets/images/busan/room5.svg`,
coordinate : {
...
89: {x: 467, y: 330},
90: {x: 386, y: 330},
91: {x: 305, y: 330},
}
}
}
};
localStorage
를 사용하여 전체 데이터를 클라이언트의 localStorage에 담도록 되어있습니다. if(!localStorage.getItem('seatCoordinate')){
//값이 없어도 일단 지우고 다시 넣어주자.
localStorage.clear();
localStorage.setItem('seatCoordinate', JSON.stringify(seatCoordinate));
}
localStorage를 사용한 이유는 좌표값은 잘 바뀌지 않아 매번 새로 불러올 필요가 없고 돌아다니면서 관리하다 보니 네트워크 상태가 매 분 다르기 때문에 첫 접속시 받아오고 계속 저장해서 사용합니다.
3) 페이지에서 좌석 데이터를 어떻게 불러올까?
API에서 좌석 데이터를 호출할 때 요구하는 query string parameter를 컴포넌트에 작성하는데,
해당 교실 컴포넌트가 아니라 layout
폴더 안에 교실 컴포넌트용 roomLayout.vue
를 생성하여 해당 파일에 작성하게 됩니다.
roomLayout.vue
에서는 교실 페이지에 들어갔을 때 url parameter를 읽어 지점 이름(query string parameter)을 가져오는 코드를 넣어줍니다.
모듈로 만든 좌석 데이터는 middleware role을 할 수 있도록 roomLayout.vue
에 middleware로 지정합니다.
<script>
export default {
middleware: ['seatInitialize'],
data(){
return{
classRoom: '',
roomNum: ''
}
}
</script>
roomLayout.vue
에서 필요한 action을 dispatch 를 실행합니다.mounted(){
// url example : https://education.seat.kr/branch/seoul/room1
//url에서 branch 다음으로 / 기준으로 url 잘라 변수에 저장합니다.
const urlPath = $nuxt.$route.path.replace(/^.*?branch\/(.*)/, '$1').split('/'),
classRoom = urlPath[0],
roomNum = urlPath[1];
this.$store.dispatch('getSeatInfo', {
// 모듈에서 아래의 데이터를 가져와서 필요한 action을 disaptch 합니다.
classRoom: seatInitialize[classRoom][roomNum].floor,
standard: seatInitialize[classRoom][roomNum].coordinate,
bounds: seatInitialize[classRoom][roomNum].bounds,
imageUrl: seatInitialize[classRoom][roomNum].imageUrl,
});
}
getSeatInfo({commit, dispatch}, {classRoom, standard, imageUrl, bounds, ...}) {
/** leaflet.js UI */
const map = L.map('map', {crs: L.CRS.Simple, maxZoom: 1.2, minZoom : 0.5, doubleClickZoom: false});
const mapImage = L.imageOverlay(imageUrl, bounds).addTo(map);
/** leaflet map 그려주세요 */
map.fitBounds(bounds);
});
5) 좌석에 앉은 학생 정보 가져오려면?
bindTooltip
이라는 메소드를 사용하면 좌표 값에 따라 tooltip이 만들어 집니다.
tooltip이라고 하면 대부분 이런것을 떠올리시는데 맞습니다.
<이미지 출처 : https://www.codingfactory.net/10726>
openTooltip
이라는 메소드를 사용하면 tooltip이 열린 상태가 됩니다.getSeatInfo({commit, dispatch}, {classRoom, standard, imageUrl, bounds, ...}) {
axios.get(`/api/seat/${classRoom}`)
.then(res=> {
let arr = res.data.responseValue;
/** 배열 데이터 반복문 */
for (let i = 0; i < arr.length; i++) {
// L.circle이라는 Label이 있어야 tooltip 기능을 사용할 수 있고 좌표 지정도 가능합니다.
let label = L.circle([standard[arr[i].seatNum]['x'], standard[arr[i].seatNum]['y']], {
fillColor: 'transparent',
stroke: 0,
radius: 40
}).addTo(map).bindTooltip(`
<div class="seatWrap">
<dl>
<dd class="status">
<div class="seatNum">${arr[i].seatNum}</div>
<div class="${stdGender}">${stdGender}</div>
</dd>
<dt>${arr[i].student['stdName']}</dt>
</dl>
</div>
`, {
permanent: true,
direction: 'center',
autoPan: false,
}).openTooltip();
})
};
});
bindTooltip
메소드로 구현합니다.이렇게 하면 leaflet.js를 사용한 작고 귀여운 좌석배치도는 어느정도 구현이 됩니다. 제일 중요한 것은 라이브러리 옵션을 잘 이용해서 원하는대로 최대한 구현하는것입니다. imageUrl
L.circle
bindTooltip
openTooltip
이정도가 구현 시에 필요한 최소 옵션 or 메소드입니다.
프로젝트 베타 버전 배포 후 저와 백엔드 개발자는 무시무시한 피드백에 시달렸지만 피드백 반영을 하면서 또 많이 배우는 계기가 됐습니다.
생각나는 피드백으로는 프로덕트를 사용하고 있는 기기가 다양하고 해상도도 다양해서 "화면에서 전체 배치도를 보고싶어요" 정도가 있는데 bounds
나 setView
옵션을 디테일하게 설정하면 보완이 됩니다.
프로젝트 참고 페이지 : https://engineering.linecorp.com/ko/blog/floor-map-management-system-on-web-with-leaflet/
글을 이렇게 길게 쓴 적이 처음이라 오타나 오류가 있을 수 있습니다,, 부디 발견하시면 댓글을 남겨주시고 도움이 되셨다면 하트 부탁드려요!