본문 바로가기

프로젝트/팀 프로젝트

230414~230426_Team project_Javascript 주식게임 제작

728x90

개요

 약 2주간의 팀 프로젝트 작업을 진행하게 되었다. 팀 프로젝트는 Html5, CSS3, Javascript를 이용하여 웹사이트를 제작하는 것이다. 사이트 자체를 제작해도 되고, 게임을 제작해도 되므로 주제는 자유였다. 단, 사이트를 제작할 때 몇가지 필수로 들어가야 하는 사항이 있었다. 

자유주제 프로젝트 필수조건
구분 웹 페이지 게임 페이지
로그인 페이지 로그인페이지를 제작.
로그인, 회원가입이 진행되어야 한다.
로그인페이지가 제작되어야 하며,
로그인, 회원가입이 진행되어야 한다.
게시판 페이지 고객문의 게시판을 제작.
로컬스토리지를 사용하고, 페이지 네이션을 추가.
등록기능이 들어가야 한다.(수정, 삭제는 자유)
자유 게시판을 제작.
로컬스토리지를 사용하고, 페이지 네이션을 추가.
등록기능이 들어가야 한다.(수정, 삭제는 자유)
어드민 페이지 어드민 페이지를 제작.
회원가입을 수락해야 한다.
X
마이 페이지 마이페이지 제작.
닉네임 변경이 가능해야 한다.
X

해당 내용을 고려하여 페이지 제작을 진행하고자 하였다. 

 

 


기획과정

 가장 먼저 어떤 페이지를 제작할지, 주제를 정해야 했다. 처음에는 웹 사이트를 제작하고자 하였고, 팀원들과 여러가지 의견을 추려 보았었는데 의견을 모아보던 중에 주식게임을 제작하고 싶다는 의견이 나왔고, 웹 사이트를 구상했던 것과 다르게 방향을 틀어 주식게임을 주제로 잡고 제작을 진행하게 되었다.

 우선 우리가 만들 주식게임은 '더 지니어스'에서 나온 주식게임을 모티브로 둔다. 게임의 진행사항은 아래와 같다.


< 주식게임 진행 사항 >

  1. 라운드마다 주가가 변경되는 시초가 2,000원인 증권 5개가 있다. 플레이어의 시드머니는 10,000원으로 시작한다.
  2. 임의의 시간 안에 속보, 상승률, 차트를 참고해 증권의 상승, 하락을 예측하여 매수, 매도를 진행한다.
  3. 시간이 지나 주가가 변경되면 상승률, 차트, 잔액 등의 증권가와 연결되어 있는 값이 변경된다.
  4. 플레이어는 라운드 안에 매수, 매도를 자유롭게 진행할 수 있다.
  5. 모든 라운드가 종료되었을 때 잔액이 가장 높은 플레이어가 승리한다.

해당 내용을 바탕으로 게임을 진행하고, 로그인 페이지, 게시판 페이지, 게임 메인페이지의 대략적인 스케치를 구상하였다.

게임페이지

상단에는 라운드 별 게임 시간이 표시되고 게임시간은 재생시킬수도, 멈출수도, 빠르게 진행할 수도 있다. 상승률을 표시시키는 영역이 있다.

가운데에는 주식별 금액을 확인할 수 있다. 게시판으로 이동할 수 있는 버튼이 있다.

하단에는 그래프로 주식의 가격 변동을 확인할 수 있고, 현재 내가 소지하고 있는 금액과, 주식현황을 확인할 수 있다. 주가 변동에 영향을 끼칠 수 있는 속보가 아래에서 올라온다

 

로그인페이지

게임을 로그인할 수 있는 로그인 창이 있고, 로그인 버튼과 회원가입 버튼이 있다.

회원가입 진행 시, 아이디와 닉네임은 중복체크를 진행하고, 패스워드는 다시 한번 입력하여 확인한다. 이 모든 과정이 완료되었을 경우에만 회원가입이 가능하다.

어드민 계정으로 로그인 시, 어드민 팝업이 뜬다. 해당 팝업에서는 회원가입 수락, 거절을 진행할 수 있다.

 

 

게시판페이지

게시판에는 현재 주가 변동내역이 함께 표시되며 게시판 리스트가 표시된다.

게시판 리스트는 글번호, 제목, 작성자, 조회수, 공감, 비공감란이 있으며 각 게시글마다 해당 정보들이 표기된다.

게시글을 등록 및 수정, 삭제를 진행할 수 있으며, 등록은 팝업을 이용하여 등록할 수 있다.

게시글 확인은 해당 게시글 제목을 누르면 확인이 가능하다. 수정, 삭제는 본인의 게시글만 작업이 가능하다.

 간단하게 페이지별로 스케치를 구상하였고, 역할 배분은 게임 페이지의 경우 구현해야 할 기능이 복잡하고 많으므로 게임 페이지에는 2명의 인원을, 로그인페이지 1명, 게시판페이지 1명을 담당하였다. 그 중 나는 로그인 페이지를 담당하여 작업을 진행하였다.

 

 

 


제작과정

 제작에 진행하기에 앞서 어떤 부분부터 제작을 진행해야 하는지 고민하는 시간이 있었다. 다른 조를 봤을 때, 보통 html과 css를 먼저 진행하여 디자인을 맞추고 자바스크립트로 기능을 구현하는 것을 보았다. 다만 우리 조의 경우에는 특별히 디자인 컨셉을 정하지 않고, 기능구현에 몰두하기로 얘기가 나온 상황이라 혼자서 디자인을 생각하기에도 무리가 있었기 때문에 로그인 페이지는 기능을 먼저 구현한 후, html과 css를 진행하고자 하였다.

 먼저, 회원가입 시, 유저가 입력한 계정 정보는 서버로 넘어가야 하지만, 현재의 나는 아직 백엔드를 아직 배우지 않아 해당 내용을 진행하기에는 한계가 있다. 때문에 해당 정보들을 서버로 넘기는 대신 브라우저를 닫아도 정보가 남아있는 로컬스토리지를 사용하기로 하였다.

 

JavaScript

 

정규식 구현

아이디와 패스워드가 정규식 형식에 맞게 입력이 되어야 하므로, 가장 먼저 아이디와 패스워드의 정규식을 설정해주었다. 정규식은 구글링해서 원하는 부분만 사용하였다.

function isId(asValue) {
    //5~8자, 영문 + 숫자 조합
    var regExp = /^[a-zA-Z][0-9a-zA-Z]{4,7}$/i;
    //test메소드로 검사해서 정규식 형식이 맞으면 true. 아니면 false 반환
    return regExp.test(asValue);
}

function isPw(asValue) {
    //최소 8자, 하나 이상의 대소문자 및 하나의 숫자, 하나의 특수문자
    var regExp = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
    return regExp.test(asValue); // 형식에 맞는 경우 true 리턴
}

 

회원가입

 

회원가입 oninput으로 형식에 맞지 않을 경우, 맞을 경우 멘트 출력

 회원가입 진행 시, 아이디 등의 계정정보를 입력할 때 각 사이트에서 권장하는 형식에 맞게 입력하는지 입력란 주위에 글씨를 본 적이 있을 것이다. 나도 해당 기능이 뜨도록 작업을 진행하고자 하였다. 

 내가 원하는 기능은 인풋란에서 입력이 진행될 때마다 인풋에 입력된 값을 확인하면서 해당 값이 정규식 조건에 맞을 경우 맞다는 내용이 파란 글씨로 뜨고, 조건에 맞지 않을 경우 정규식의 조건을 알려주는 멘트가 빨간 글씨로 뜨도록 제작하고자 하였다. 해당 기능을 구현하기 위한 이벤트 함수가 어떤 것이 있는지 구글링을 했고, 해당 기능은 oninput으로 구현이 가능한 것을 알게되었다. 

 해당 인풋에 입력된 값들이 아까 선언한 정규식에 조건과 맞는지 확인을 하려면 정규식 함수의 매개변수에 인풋으로 입력된 값을 넣어 반환하는 값을 받아야한다. true 값이 반환되면, 정규식에 맞게 입력되었다는 말이고, false 값이 반환되면 정규식에 맞지 않다는 말이므로, 반환받은 값으로 if문을 조건에 맞게 작성하였다.

 input의 값이 정규식에 부합하게 작성되었는지 결정짓는 변수가 필요하다. 왜냐하면, 최종적으로 회원가입을 진행할 때 정규식에 맞게 작성했는지를 확인해야하고, 중복확인을 진행할 때도 해당 값이 필요하기 때문이다. 따라서, 아이디가 정규식에 맞게 입력되었는지 값을 넣어줄 수 있는 userIdTnF와, 비밀번호 userPwTnF객체를 생성하여, 정규식에 맞게 입력되어 있으면 0의 값을, 정규식에 부합하지 않으면 1의 값을 가지도록 설정하였다.  

 닉네임의 oninput에서는 닉네임의 중복확인 값을 체크해서 'userNickOverlapTnF'에서 1의 값을 받고 다시 수정한 value값을 0으로 돌려줘야할 필요가 있었기 때문에 무조건 값을 입력하게 되면 다시 중복확인을 클릭할 때까지 userNickOverlapTnF 값이 0이 되도록 설정하였다.

<div class="sign-subdiv">
    <div class="sign-id">
        <input type="text" class="sign-idInput" placeholder="아이디를 입력하세요">
        <button class="sign-idbtn">중복확인</button>
        <div class="id-subtext"></div>
    </div>
    <div class="sign-pw">
        <input type="text" class="sign-pwInput" placeholder="비밀번호를 입력하세요">
        <div class="pw1-subtext"></div>
        <input type="text" class="sign-rePwInput" placeholder="비밀번호를 확인해주세요">
        <div class="pw2-subtext"></div>
    </div>
    <div class="sign-name">
        <input type="text" class="sign-nickname" placeholder="닉네임을 입력해주세요">
        <button class="sign-nickbtn">중복확인</button>
        <div class="nick-subtext"></div>
    </div>
</div>
let signIdSub = document.querySelector('.id-subtext');              //아이디 영역 결과노출 텍스트 영역
let signPWSub1 = document.querySelector('.pw1-subtext');            //패스워드 영역 결과노출 텍스트 영역
let signPWSub2 = document.querySelector('.pw2-subtext');            //패스워드 확인 영역 결과노출 텍스트 영역
let signNickSub = document.querySelector('.nick-subtext');          //닉네임 영역 결과노출 텍스트 영역

let signIdInput = document.querySelector('.sign-idInput');          //아이디 인풋
let signPwInput = document.querySelector('.sign-pwInput');          //패스워드 인풋
let signRePwInput = document.querySelector('.sign-rePwInput');      //패스워드 확인 인풋
let signNickname = document.querySelector('.sign-nickname');        //닉네임 인풋


let userIdTnF = 1;                  //유저 아이디가 정규식에 만족할 경우 0, 아닐경우 1.
let userPwTnF = 1;                  //유저 패스워드 확인이 첫 번째 패스워드 입력값과 일치할 경우 0, 아닐경우 1

let userIdOverlapTnF = 1;           //유저 아이디가 중복되지 않을 경우 0, 아닐경우 1.
let userNickOverlapTnF = 1;         //유저 닉네임이 중복되지 않을 경우 0, 아닐경우 1.


//------- 패스워드 입력 시 형식에 맞지 않을 경우, "대소문자+숫자+특수문자. 최소 8자로 입력해주세요." 출력 -------
signPwInput.oninput = function signID() {
    if (isPw(signPwInput.value)) {
        signPWSub1.innerHTML = "사용 가능한 패스워드 입니다.";
        signPWSub1.style.color = "blue";
    }
    else {
        signPWSub1.innerHTML = "대소문자+숫자+특수문자. 최소 8자로 입력해주세요.";
        signPWSub1.style.color = "red";
    }

}


//------- 패스워드 확인 입력 시 이전 입력값과 동일하지 않을 경우, "비밀번호가 일치하지 않습니다." 출력 ----------
signRePwInput.oninput = function () {
    // console.log(isPw(signPwInput.value));
    if (isPw(signPwInput.value)) {
        if (signRePwInput.value == signPwInput.value) {
            signPWSub2.innerHTML = "비밀번호가 일치합니다.";
            signPWSub2.style.color = "blue";
            userPwTnF = 0;
        }
        else {
            signPWSub2.innerHTML = "비밀번호가 일치하지 않습니다.";
            signPWSub2.style.color = "red";
            userPwTnF = 1;
        }
    }
    else{
        signPWSub2.innerHTML = "대소문자+숫자+특수문자. 최소 8자로 입력해주세요.";
        signPWSub2.style.color = "red";
        userPwTnF = 1;
    }
}

//----닉네임 인풋에 값을 입력하면 중복확인이 되지 않았음을 알려준다.-----------------------
signNickname.oninput = function () {
    userNickOverlapTnF = 1;
}

 

회원가입 아이디와 닉네임을 onclick으로 중복확인 진행

 아이디와 닉네임은 사이트에서 고유한 값을 가져야 하므로, 유저 정보를 담고있는 로컬스토리지의 값을 불러올 필요가 있었다. 하단의 코드로 로컬스토리지의 '유저'키의 데이터를 불러왔다.

userData = localStorage.getItem("유저");
console.log(JSON.parse(userData));

로컬스토리지의 '유저' 데이터 중, id키의 value가 아이디 인풋 값에서 입력한 값과 동일하지 않으면 중복되지 않으므로 회원가입이 되도록 설정을 할것이다. 우선 로컬스토리지 유저값이 담긴 배열에서 유저의 id와 인풋의 값이 같은지를 체크해야 한다. 배열에서 원하는 값을 뽑아내는 방법은 여러 방법이 있지만, 그 중에서 filter 메소드를 사용해서 로컬 배열에서 인풋 값과 동일한 값을 새로운 배열로 뽑아내고자 하였다.

let userIDSearch = JSON.parse(userData).filter(function (idvalue) {
      return idvalue.id === signIdInput.value;
  })

 아이디의 중복확인을 걸러줄 코드를 작성하였다. 아이디의 중복확인은 우선 아이디를 입력한 인풋의 값이 정규식에 충족해야하며, 로컬의 배열을 확인하였을 때 등록되어 있지 않은 값이어야 하고, 관리자 계정의 아이디 값과 동일할 수 없다.

// filter로 반환한 배열의 길이가 0, id가 정규식에 충족하며, 인풋에 입력된 값이 admin이 아닐경우
	if (userIDSearch.length == 0 && userIdTnF == 0 && signIdInput.value != "admin") {
        signIdSub.innerHTML = "사용 가능한 아이디 입니다.";
        signIdSub.style.color = "blue";
        userIdOverlapTnF = 0;
    }
// 인풋에 입력된 값이 admin일 경우	(관리자 계정이므로 해당 아이디로 회원가입 불가)
    else if (signIdInput.value == "admin") {
        signIdSub.innerHTML = "사용이 불가한 아이디 입니다.";
        signIdSub.style.color = "red";
        userIdOverlapTnF = 1;
    }
// filter로 반환한 배열의 길이가 0, id가 정규식에 충족하지 않을 경우
    else if (userIDSearch.length == 0 && userIdTnF != 0) {
        signIdSub.innerHTML = "대소문자+숫자. 5~8자로 입력해주세요.";
        signIdSub.style.color = "red";
        userIdOverlapTnF = 1;
    }
// filter로 반환한 배열의 길이가 1 이상일 경우 (회원으로 등록된 아이디일 경우)
    else {
        signIdSub.innerHTML = "이미 사용 중인 아이디 입니다.";
        signIdSub.style.color = "red";
        userIdOverlapTnF = 1;
    }

 

닉네임도 동일한 작업으로 진행되었다.

signNickBtn.onclick = function () {
    userData = window.localStorage.getItem("유저");

    let userNICKSearch = JSON.parse(userData).filter(function (idvalue) {
        return idvalue.nickname === signNickname.value;
    })
    console.log(userNICKSearch);
    if (userNICKSearch.length == 0) {
        signNickSub.innerHTML = "사용 가능한 닉네임 입니다.";
        signNickSub.style.color = "blue";
        userNickOverlapTnF = 0;
    }
    else {
        signNickSub.innerHTML = "이미 사용 중인 닉네임 입니다.";
        signNickSub.style.color = "red";
        userNickOverlapTnF = 1;
    }
}

 

정규식 조건, 중복확인이 완료된 경우 회원가입 진행

 정규식 조건과, 중복확인이 모두 완료되었을 경우에 회원가입을 진행한다. 회원가입 버튼을 클릭하면 유저가 인풋에 입력한 데이터들이 모두 로컬스토리지에 저장되어야 한다. 이 값들을 모두 로컬에 저장해주기 위해서는 'window.localStrage.setItem( )'으로 작성을 진행한다. 

//아이디와 패스워드가 정규식 조건에 맞을 경우, 아이디와 닉네임이 중복되지 않았을 경우
if (userIdTnF == 0 && userPwTnF == 0 && userIdOverlapTnF == 0 && userNickOverlapTnF == 0) {
	//tnf: 회원가입 승인여부. 승인이 안났을 경우 0, 승인완료된 경우 1
         
	window.localStorage.setItem("유저", JSON.stringify([...ass,  { id: signIdInput.value, pw: signPwInput.value, nickname: signNickname.value, tnf: 0 }]));
	// window.localStorage.setItem("유저", JSON.stringify([{ id: "admin", pw: "123123q!", nickname: "관리자" }]));
	alert("회원가입 신청이 완료되었습니다.\n승인이 완료되면 로그인이 가능합니다.");
}

 로컬스토리지에 담길 유저의 정보에는 id, pw, nickname 값과 회원가입 신청 시, 승인 여부를 가려주는 tnf 값이 들어가게 된다. 회원가입 신청을 했다고 로그인이 진행되지는 않으며, 관리자가 해당 회원을 가입승인 진행 해주어야 로그인이 가능하다. 

 상단의 내용으로 작성을 하게 되면 로컬스토리지 '유저'에 아무런 값이 없을 경우는 문제없이 새로운 값이 적용이 되지만, '유저'에 이미 값이 있는 경우에 회원가입을 진행하면 이미 저장된 값에 추가가 되어야 하지만, 기존의 값은 사라지고 새로운 값만이 저장되게 된다. 때문에 나는 로컬스토리지의 길이가 0일 경우와 0이 아닐 경우에 로컬스토리지에 값을 저장하는 방법을 나누고자 하였다.

 로컬스토리지의 길이가 0일 경우는 상단의 방법처럼 작성하면 되지만, 아닐 경우는 로컬에 저장되어 있는 값을 변수에 저장하여, JSON.stringify를 이용하여 저장해서 객체 형식으로 저장되어 있던 로컬의 데이터를 JSON.parse를 이용하여 배열형식으로 만들고, 새롭게 입력된 인풋 값으로 push메서드를 이용해서 추가하고자 한다. 이렇게 추가된 값을 가진 변수를 다시 'window.localStrage.setItem( )'으로 로컬스토리지에 저장한다.

❗이슈_회원가입 신청완료 후, 다시 가입신청을 할 때 중복확인을 하지 않아도 중복으로 가입신청이 된다.

 한 차례 회원가입 신청이 완료되면, 이전 회원가입 시 확인했던 중복확인 버튼으로 'userIdOverlapTnF'와 'userNickOverlapTnF'의 값이 0으로 저장되면서 중복확인 버튼을 눌러 해당 과정을 거치지 않더라도 회원가입의 조건을 충족시키게 된다. 때문에 같은 아이디와 닉네임으로 회원가입이 가능해지는 이슈가 발견되었었다. 

 해당 이슈로 인해, 회원가입 신청을 한 후, 회원가입 신청이 완료되면 'userIdOverlapTnF'와 'userNickOverlapTnF'값을 다시 1의 값을 저장하여, 새로 중복확인 버튼을  클릭하여 중복확인을 거치게 만들었다. 

회원가입 진행을 하는 코드는 하단의 내용을 진행이 마무리 되었다.

let signAdd = document.querySelector('.signup-add')

signAdd.onclick = function () {
    if (window.localStorage.length == 0) {        //로컬 스토리지에 아무 내용도 기입되지 않았을 경우
    	//아이디와 패스워드가 정규식 조건에 맞을 경우, 아이디와 닉네임이 중복되지 않았을 경우
        if (userIdTnF == 0 && userPwTnF == 0 && userIdOverlapTnF == 0 && userNickOverlapTnF == 0) {
            //tnf: 회원가입 승인여부. 승인이 안났을 경우 0, 승인완료된 경우 1
         
            window.localStorage.setItem("유저", JSON.stringify([...ass,  { id: signIdInput.value, pw: signPwInput.value, nickname: signNickname.value, tnf: 0 }]));
            // window.localStorage.setItem("유저", JSON.stringify([{ id: "admin", pw: "123123q!", nickname: "관리자" }]));
            alert("회원가입 신청이 완료되었습니다.\n승인이 완료되면 로그인이 가능합니다.");

            //회원가입 신청 완료 후, 다시 가입신청을 할 때 중복확인을 하지 않아도 중복으로 가입신청이 되는 이슈 발생
            userIdOverlapTnF = 1;
            userNickOverlapTnF = 1;

        }
        else {   //모든 조건이 들어맞지 않을 때
            alert("회원가입을 진행 할 수 없습니다. 입력한 사항을 다시 확인해주세요.1");
        }
    }
    else {       //로컬스토리지에 내용이 기입되어 있을 경우 추가로 기입되도록 함
        if (userIdTnF == 0 && userPwTnF == 0 && userIdOverlapTnF == 0 && userNickOverlapTnF == 0) {        //아이디와 패스워드가 정규식 조건에 맞을 경우.
            signUserData = JSON.parse(localStorage.getItem('유저'));
            signUserData.push({ id: signIdInput.value, pw: signPwInput.value, nickname: signNickname.value, tnf: 0 });
            window.localStorage.setItem("유저", JSON.stringify(signUserData));
            alert("회원가입 신청이 완료되었습니다.\n승인이 완료되면 로그인이 가능합니다.");
            userIdOverlapTnF = 1;
            userNickOverlapTnF = 1;
            
        }
        else {   //모든 조건이 들어맞지 않을 때
            alert("회원가입을 진행 할 수 없습니다. 입력한 사항을 다시 확인해주세요.2");
        }
    }
    // 회원가입이 완료되면, 어드민 팝업해서 새롭게 추가된 내용이 그려져야 한다.
    adminadd();
}

 

어드민

회원 가입 시, 어드민 팝업에 회원 정보가 그려져야 한다.

 회원가입 팝업에서 회워가입 버튼 클릭 시, 어드민 팝업에 해당 계정이 그려져야 한다. '가입승인'란에는 회원가입 신청을 한 계정이, '회원목록'에는 가입승인이 완료되어 로그인이 가능한 계정이 표시되어야 한다. 따라서 로컬의 '유저'의 tnf 키 값이 '0'인 계정은 가입승인에, '1'은 회원목록에 그려져야 한다.

function adminadd() {

.
.
.

       //추가 할 때 초기화 시켜주는 구문.
    adminAddNRemove.innerHTML = "";  
    adminRemove.innerHTML='';
    
    //----- 가입승인 리스트 목록 --------------------------------
    let _Aul = document.createElement("ul");
    let _Ali = document.createElement("li");
    _Ali.className = 'approve';
    let _Adiv1 = document.createElement("div");
    let _Adiv2 = document.createElement("div");
    let _Adiv3 = document.createElement("div");
    let _Adiv4 = document.createElement("div");
    let _Adiv5 = document.createElement("div");

    _Adiv1.innerHTML = "아이디";
    _Adiv2.innerHTML = "비밀번호";
    _Adiv3.innerHTML = "닉네임";
    _Adiv4.innerHTML = "수락";
    _Adiv5.innerHTML = "거절";
    
    _Ali.style.display = "flex";
    _Ali.append(_Adiv1,_Adiv2,_Adiv3,_Adiv4,_Adiv5);
    _Aul.append(_Ali);
    
    //---------------------------------------------------------



    //----- 회원목록 리스트 목록 --------------------------------
    let _Bul = document.createElement("ul");
    let _Bli = document.createElement("li");
    let _Bdiv1 = document.createElement("div");
    let _Bdiv2 = document.createElement("div");
    let _Bdiv3 = document.createElement("div");
    let _Bdiv4 = document.createElement("div");
    
    
    _Bdiv1.innerHTML = "아이디";
    _Bdiv2.innerHTML = "비밀번호";
    _Bdiv3.innerHTML = "닉네임";
    _Bdiv4.innerHTML = "삭제";
    
    _Bli.style.display = "flex";
    _Bli.append(_Bdiv1,_Bdiv2,_Bdiv3,_Bdiv4);
    _Bul.append(_Bli);

    //---------------------------------------------------------
    
    userData2.forEach(function(i,index){
        let _Ali = document.createElement('li');
        let _Bli = document.createElement('li');
  
        let _Adiv1 = document.createElement("div");
        let _Adiv2 = document.createElement("div");
        let _Adiv3 = document.createElement("div");
        let _Adiv4 = document.createElement("div");
        let _div5 = document.createElement("div");

        let _Bdiv1 = document.createElement("div");
        let _Bdiv2 = document.createElement("div");
        let _Bdiv3 = document.createElement("div");  
        let _Bdiv4 = document.createElement("div");
        
        let _addBtn = document.createElement("button");    
        let _removeBtn = document.createElement("button");
        let _removeBtn2 = document.createElement("button");

        
        //가입승인에 들어오는 계정 값
        _Adiv1.innerHTML = (i).id;
        _Adiv2.innerHTML = (i).pw;
        _Adiv3.innerHTML = (i).nickname;

        //회원목록에 들어오는 계정 값
        _Bdiv1.innerHTML = (i).id;
        _Bdiv2.innerHTML = (i).pw;
        _Bdiv3.innerHTML = (i).nickname;
        
        
        _Ali.style.display = "flex";                //가입승인 추가되는 계정들 정보 나열
        _Ali.append(_Adiv1,_Adiv2,_Adiv3,_Adiv4,_div5);


        _Bli.style.display = "flex";                //회원목록 추가되는 계정들 정보 나열
        // _topDiv2.append(_li.cloneNode(true));
        _Bli.append(_Bdiv1,_Bdiv2,_Bdiv3,_Bdiv4);
        
        
    });
    adminAddNRemove.append(_Aul);
    adminRemove.append(_Bul);
}
adminadd()

 

가입 승인에서는 수락, 거절 / 회원 목록에는 삭제 버튼이 구현 되어야 한다.

 가입 승인 목록에는 가입 대기 중인 계정이 '수락' 또는 '거절'되어야 하며, 회원 목록에서는 기존 회원을 '삭제'할 수 있어야 한다. 각 기능별로 'adminadd' 함수 안에 코드를 구현하였다.

// 가입 승인에서 '수락' 기능 구현
_addBtn.onclick = function(){
    console.log("수락될거임");
    console.log(userData2);
    console.log([...userData2]);
    console.log(userData2==[...userData2]);
    // i.tnf = 1;
    userData2[index].tnf = 1;
    window.localStorage.setItem('유저',JSON.stringify(userData2));
    adminadd();
    
}

// 가입 승인에서 '거절' 기능 구현
_removeBtn.onclick = function(){
    userData2.splice(index,1);
    // window.localStorage.removeItem
    console.log("나");
    console.log(userData2);
    console.log(...userData2);
    window.localStorage.setItem('유저',JSON.stringify(userData2));
    adminadd();
}

// 회원 목록에서 '삭제' 기능 구현
_removeBtn2.onclick = function(){
    userData2.splice(index,1);
    // window.localStorage.removeItem
    console.log("나");
    console.log(userData2);
    console.log(...userData2);
    window.localStorage.setItem('유저',JSON.stringify(userData2));
    adminadd();
}

 

 

팝업 스크롤 이동

로그인, 회원가입, 어드민 팝업은 마우스  드래그로 창이 이동되어야 한다.

 이 작업은 html, css를 모두 마친 후 추가로 구현한 기능이다. 해당 페이지의 모든 팝업을 드래그가 가능하도록 제작하였다. 마우스 이벤트를 사용하여, 마우스를 눌렀을 경우, 마우스를 클릭한 상태로 움직였을 경우, 마우스를 클릭했다가 뗐을 경우를 작성하였다. 이 모든 작업은 팝업창의 상단 바 영역을 클릭하였을 경우에만 동작하도록 영역을 설정하였다.

 마우스를 클릭했을 경우, 마우스 클릭이 일어난 X와 Y축의 좌표를 구하고 이벤트가 동작되는 중임을 나타나는 객체에 true 값을 넘긴다.

 마우스가 움직였을 클릭한 상태로 움직였을 경우에는 먼저 이벤트가 동작되는 중임을 나타내는 객체의 값이 false면 return되고, true면 이동 이벤트가 실행되도록 작성한다. 

 마우스를 눌렀다가 떼면 이벤트가 동작되는 중임을 나타내는 객체의 값을 false를 부여하여 mousemove 이벤트가 발생하지 않도록한다.

let loginTop = document.querySelector(".login-drag");       //로그인 상단 파란영역 (클릭될 영역)

let loginPress = false;      //마우스 클릭 여부
let loginStartX;             //마우스 클릭 시작지점 X좌표
let loginStartY;             //마우스 클릭 시작지점 Y좌표

loginTop.addEventListener('mousedown', function(e){         //로그인 상단 파란 바를 클릭 시 발생되는 이벤트
    loginStartX = e.clientX;             //loginStartX에 마우스 클릭이 시작되는 X축의 좌표를 구한다.
    loginStartY = e.clientY;             //loginStartY에 마우스 클릭이 시작되는 Y축의 좌표를 구한다.
    loginPress = true;                  //이벤트가 동작되는 중
})


loginTop.addEventListener('mousemove', function(e){         //로그인 상단 파란 바를 클릭한 상태로 움직이면 발생되는 이벤트
    if(!loginPress){                    //이벤트가 동작되지 않으면 값을 리턴한다.
        return;
    }                                   //이벤트가 동작중이면 하단의 코드들이 실행된다.
    let posX = loginStartX - e.clientX; //이전좌표와 현재좌표의 차이값을 저장한다.
    let posY = loginStartY - e.clientY;

    loginStartX = e.clientX;            //이전좌표에 현재 좌표 값을 대입한다.
    loginStartY = e.clientY;

    background.style.left = (background.offsetLeft - posX) + "px";      //X축 이동.
    background.style.top = (background.offsetTop - posY) + "px";        //Y축 이동.

})


loginTop.addEventListener('mouseup', function(){        //로그인 상단 파란 바를 클릭하다가 떼면 발생하는 이벤트
    loginPress = false;                 //이벤트가 더 이상 동작하지 않음을 알린다.
})

 

회원가입, 어드민 팝업도 상단과 동일하게 작동된다.

 

현재 시간

화면의 하단에는 현재 시간이 나타난다.

 현재 시간을 실시간으로 나타내는 코드를 작성해야 한다. 현재 시간은 'new Date()' 메소드를 사용하여 나타낼 수 있지만,  (연, 월, 일, 시, 분, 초, 밀리초)의 모든 데이터를 보여주므로, 다른 형식으로 노출되기는 원하는 나에게는 해당 정보를 다른 형식으로 출력되게할 필요가 있었다.

 내가 원하는 출력 형식은 예를 들어, '오후 09:07'의 형식으로 노출이 되어야 한다. 때문에 new Date() 에서 시간과 분의 값만 가져오고, 오전 오후 구분을 하기 위해 시간의 값이 12보다 큰지 작은지를 구별하여 노출하도록 하고, 분의 값이 10보다 작다면 분의 값 앞에 '0'을 추가하여 항상 두 자릿값이 되도록 한다.

이 코드를 한 번만 실행하면, 실행했을 당시의 시간만이 노출되고 흐르는 시간에 대해 업데이트가 진행되지 않으므로, 'setInterval'로 1초마다 업데이트하여 항상 현재시간이 나타나도록 한다.

let time = document.querySelector(".time");

function now() {
    const date = new Date();            //현재 시간 총 데이터
    let hour = date.getHours();
    let minutes = date.getMinutes();
    let SnM = "";

    if (hour < 12) {
        SnM = "오전"
        if (hour < 10) {
            hour = "0" + hour;
        }
    }
    else {
        SnM = "오후"
        hour -= 12;
        if (hour < 10) {
            hour = "0" + hour;
        }
    }

    if (minutes < 10) {
        minutes = "0" + minutes;
    }

    time.innerHTML = `${SnM} ${hour}:${minutes}`
}
now();
setInterval(now, 1000);

 

 

 

 

 

Html, CSS

 

컨셉

 우리조의 컨셉은 윈도우 XP를 웹페이지에 그대로 그려내는 방식을 선택하였다. 캡쳐본을 보면 확인 할 수 있겠지만, 윈도우 XP가 켜지는 화면부터, 게임을 시작하기 위해서 폴더에서 프로그램을 더블클릭하는 것까지 구현을 하고자 하였고 gif로 소리를 확인할 수는 없겠지만 윈도우가 시작할 때 나는 효과음도 주어 표현을 생생히 하고자 하였다.

 

 


결과물

인트로 화면

  • 인트로 화면에서 아무 값이나 입력값을 넣으면 로그인 화면으로 넘어간다.

 

회원가입 화면

  • 게임아이콘을 더블클릭하면 로그인을 할 수 있는 화면이 나오고, 회원가입을 클릭하면 회원가입을 할 수 있는 팝업이 뜬다. 
  • 회원가입 시 정규식에 맞게 입력하고, 중복확인을 거쳐야 회원가입이 가능하다.
  • 회원가입 시, 승인이 되지 않은 계정을 로그인을 진행할 수 없으므로 어드민에서 승인을 거쳐야 한다.

 

로그인 화면

  • 회원가입 시, 승인이 되지 않은 계정을 로그인을 진행할 수 없으므로 어드민에서 승인을 거쳐야 한다.
  • 정상적인 로그인이 완료되면 게임페이지로 넘어간다.

 

게임 화면

  • 게임은 총 10라운드로 진행되며, 시작 보유 금액은 10000원이다. 각 라운드 별로 일정 시간이 주어지며 해당 시간동안 자유롭게 주식을 매수, 매도할 수 있다.
  • 게임은 모든 라운드가 끝나거나, 보유금액이 마이너스가 되면 게임은 종료된다.

 

게시판 화면

  • 게시판 화면에서 게시글 등록, 수정, 삭제가 가능하며, 검색기능이 있어 게시글 제목으로 검색이 가능하다.
  • 페이지네이션이 구현되어 있다.

 

 

 

728x90