21.04.03~04.06 lamplight서비스 프로젝트(ionic이사 진행중-의뢰인로그인,회원가입,정보수정.....지도사리스팅....요청서crud, 리스팅 완료)

2021. 4. 6. 23:39Vue.js/Spring & Vue APP 프로젝트(프론트엔드)

# NOTE

Todo
-ionic으로 일단 이사[]
 -의뢰인로그인,로그아웃[ㅇ]
 -의뢰인회원가입후 로그인아이디[ㅇ]
 -의뢰인마이페이지[ㅇ]
 -의뢰인회원정보 수정[ㅇ]
 -지도사 리스팅[ㅇ]
 -요청서 작성[ㅇ]/상세보기[ㅇ]
 -요청서 수정[ㅇ]
 -요청서 삭제[ㅇ]
  -컨펌창 도입[ㅇ]
   -요청서 작성시[ㅇ]
   -요청서 수정시[ㅇ]
   -회원가입시[ㅇ]
   -회원정보 수정시[ㅇ]
 -요청서 리스팅[ㅇ]
-ionic 컨펌창 도입[ㅇ]
https://forum.ionicframework.com/t/return-value-from-alert-confirmation/75824/2
 -사이클 변경
의뢰인 요청서 등록 -> 해당 지역에 속한 지도사에게 푸시알림(or SMS)
-> 요청서 확인 후 수락 푸시알림(or SMS)+연락처 -> 장례 준비 진행(도우미 지원(2차 배포시 추가))
-> 장례 진행 -> 종료 -> 의뢰인 최종 확인(결제(2차 배포시 추가)) -> 평점/리뷰 작성

 -요청서 등록시 해당 지역 지도사에게 알림(연락처 전달?)[]
 -요청서 확인 후 수락 푸시알림(or SMS)+지도사프로필[]
 -....
NaN
//전역 NaN 속성은 Not-A-Number(숫자가 아님)를 나타냄
//isNaN() 함수로 NaN 여부를 확인

# 주요소스코드

<Client/Join.vue>

<template>
  <ion-page>
    <ion-custom-header>회원 - 가입</ion-custom-header>
    <ion-content :fullscreen="true">
      <ion-header collapse="condense">
        <ion-toolbar>
          <ion-title size="large">회원 - 가입</ion-title>
        </ion-toolbar>
      </ion-header>
      <ion-custom-body class="justify-center">
        <div class="logo-box text-center">
          <span>
            <span class="text-3xl">
              <font-awesome-icon icon="lemon" />
            </span>
            <span class="font-bold text-3xl">
              DESIGN LEMON
            </span>
          </span>
        </div>
        <form @submit.prevent="checkAndJoin">
          <div>
            <ion-item>
              <ion-label position="stacked">프로필 이미지</ion-label>
              <ion-input v-model="joinFormState.profileImg" type="file"></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">아이디</ion-label>
              <ion-input v-model="joinFormState.loginId" type="text" minlength="5" maxlength="12" placeholder="아이디를 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">비밀번호</ion-label>
              <ion-input v-model="joinFormState.loginPw" minlength="8" type="password" placeholder="비밀번호를 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">비밀번호 확인</ion-label>
              <ion-input v-model="joinFormState.loginPwConfirm" minlength="8" type="password" placeholder="비밀번호 확인을 해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">이름</ion-label>
              <ion-input v-model="joinFormState.name" minlength="2" placeholder="이름을 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">연락처</ion-label>
              <ion-input v-model="joinFormState.cellphoneNo" type="tel" maxlength="11" placeholder="연락처를 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">이메일</ion-label>
              <ion-input v-model="joinFormState.email" type="email" placeholder="이메일을 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div>
            <ion-item>
              <ion-label position="floating">지역</ion-label>
              <ion-input v-model="joinFormState.region" placeholder="시/도 주소를 입력해주세요."></ion-input>
            </ion-item>
          </div>
          <div class="py-2 px-4">
            <ion-button type="submit" expand="block">가입</ion-button>
          </div>
          <div class="px-4">
            <ion-button color="secondary" type="reset" expand="block">초기화</ion-button>
          </div>
        </form>
      </ion-custom-body>
    </ion-content>
  </ion-page>
</template>

<style>
</style>

<script lang="ts">
import { IonCustomBody, IonCustomHeader } from '@/components/';
import { 
  IonPage, 
  IonHeader, 
  IonToolbar, 
  IonTitle, 
  IonContent,
  IonLabel, 
  IonInput, 
  IonItem, 
  IonButton, 
} from '@ionic/vue';
import { useGlobalState } from '@/stores'
import { useMainService } from '@/services';
import { useRouter } from 'vue-router';
import * as util from '@/utils';
import { reactive } from 'vue';


const useJoinFormState = () => {
  return reactive({
    profileImg: [] as File[],
    loginId: '',
    loginPw: '',
    loginPwConfirm: '',
    name: '',
    cellphoneNo: '',
    email: '',
    region: '',
  })
}

export default {
  name: 'Join',

  components: { 
    IonHeader, 
    IonToolbar, 
    IonTitle,
    IonLabel, 
    IonInput, 
    IonItem, 
    IonButton, 
    IonContent, 
    IonPage, 
    IonCustomBody, 
    IonCustomHeader 
  },

  setup() {
    const globalState = useGlobalState();
    const joinFormState = useJoinFormState();
    const router = useRouter();
    const mainService = useMainService();

    // function confirmAlert(){
    //   const msg = '해당 내용으로 가입하시겠습니까?'
    //   util.showAlertConfirm(msg)
    // }

    function checkAndJoin() {
       // 아이디 체크
      const loginId = joinFormState.loginId.trim();
      
      if ( joinFormState.loginId.trim().length == 0 ) {
        alert('아이디를 입력해주세요.');
        return;
      }
      // 비번 체크
      const loginPw = joinFormState.loginPw.trim();
      
      if ( loginPw.length == 0 ) {
        alert('비밀번호를 입력해주세요.');
        return;
      }
      
      // 비번확인 체크
      const loginPwConfirm = joinFormState.loginPwConfirm.trim();
      
      if ( loginPw != loginPwConfirm ) {
        alert('비밀번호가 일치하지 않습니다.');
        return;
      }

      // 이름 체크
      const name = joinFormState.name.trim();

      if ( name.length == 0 ) {
        alert('이름을 입력해주세요.');
        return;
      }
      
      // 전화번호 체크
      const cellphoneNo = joinFormState.cellphoneNo.trim();
      
      if ( cellphoneNo.length == 0 ) {
        alert('연락처를 입력해주세요.');
        return;
      }

      // 이메일 체크
      const email = joinFormState.email.trim();
      
      if ( email.length == 0 ) {
        alert('이메일을 입력해주세요.');
        return;
      }

      // 시/도 주소 체크
      const region = joinFormState.region.trim();
      
      if ( region.length == 0 ) {
        alert('지역(시/도)을 입력해주세요.');
        return;
      }

      async function startFileUpload(onSuccess: Function){
        // ! => 반전
        // a = undefinded(or null) / !a = true / !!a = flase란 의미
        // ? => 만약 profileImgElRef.value?까지가 null이면 여기까지만 실행하겠다라는 의미
        // 즉, !!!profileImgElRef.value?.files의 의미는 해당 파일이 없는지 물어보는 것
        // 없으면 true
        if(joinFormState.profileImg == null){
          onSuccess("");  //파일이 없으면 다음 과정 생략하고 onSuccess() 즉시 실행
          alert("파일 업로드 안됨")
          return;
        }
        const axRes = await mainService.common_genFile_doUpload(joinFormState.profileImg[0])

        if ( axRes.data.fail ) {
          util.showAlert(axRes.data.msg);
          return;
        }
        else{
            onSuccess(axRes.data.body.genFileIdsStr);
        }
      }

      async function join(loginId: string, loginPw: string, name: string, cellphoneNo: string, email: string, region: string, genFileIdsStr: string) {
        const axRes = await  mainService.client_doJoin(loginId, loginPw, name, cellphoneNo, email, region, genFileIdsStr);
  
          util.showAlert(axRes.data.msg);
        
          if ( axRes.data.fail ) {
            return;
          }

          router.replace('/client/login?loginId=' + loginId)
      }

      const startJoin = (genFileIdsStr: string) =>{
          join(loginId, loginPw, name, cellphoneNo, email, region,  genFileIdsStr);
      }

      const msg = '해당 내용으로 가입하시겠습니까?'
      util.showAlertConfirm(msg).then(confirm => {
        if (confirm == false) {
          return
        } else{
          startFileUpload(startJoin);
        }
      })
      
    }

    

    return {
      globalState,
      //confirmAlert,
      joinFormState,
      checkAndJoin,
      
    }
  }
}
</script>

<Order/List.vue>

<template>
<ion-custom-header>의뢰 - 리스트</ion-custom-header>
<ion-content >
  <ion-list class="mb-12">
    <ion-item>
    <ion-select v-model="searchState.selectStepLevel">
      <ion-select-option value="0">진행단계 전체</ion-select-option>
      <ion-select-option value="1">요청서 검토중</ion-select-option>
      <ion-select-option value="2">장례준비중</ion-select-option>
      <ion-select-option value="3">장례진행중</ion-select-option>
      <ion-select-option value="4">장례종료(확인대기중)</ion-select-option>
      <ion-select-option value="5">장례종료(최종종료)</ion-select-option>
    </ion-select>
    </ion-item>
    <ion-item>
    <ion-select v-model="searchState.searchKeywordType">
      <ion-select-option value="deceasedName">고인명</ion-select-option>
      <ion-select-option value="bereavedName">상주명</ion-select-option>
      <ion-select-option value="extra__clientName">의뢰인명</ion-select-option>
      <ion-select-option value="body">내용</ion-select-option>
      <ion-select-option value="funeralHome">장례식장</ion-select-option>
    </ion-select>
    </ion-item>
    <ion-item>
      <ion-searchbar show-cancel-button="focus" animated inputmode="search" enterkeyhint="enter" placeholder="검색어를 입력해주세요." :value="searchState.searchKeyword" @keyup.enter="onInput($event)"></ion-searchbar>
    </ion-item>
    <ion-list-header>MyOrderList</ion-list-header>
    <template v-bind:key="order.id" v-for="order in returnFilteredOrders">
    <ion-item>
      <!--진행단계-->
        <div v-if="order.stepLevel==1" class="btn-success">
          진행단계: {{returnToString(order.stepLevel)}}
        </div>
        <div v-if="order.stepLevel==2" class="btn-secondary">
          진행단계: {{returnToString(order.stepLevel)}}
        </div>
        <div v-if="order.stepLevel==3" class="btn-warning">
          진행단계: {{returnToString(order.stepLevel)}}
        </div>
        <div v-if="order.stepLevel==4" class="btn-primary">
          진행단계: {{returnToString(order.stepLevel)}}
        </div>
        <div v-if="order.stepLevel==5" class="btn-primary">
          진행단계: {{returnToString(order.stepLevel)}}
        </div>
      <ion-label>
        <ion-grid>
          <ion-row>
            <ion-col size="12" class="bg-gray-300 border rounded-md">
              고인명: {{order.deceasedName}}
            </ion-col>
          </ion-row>
          <ion-row>
            <ion-col size="4">
              상주명: {{order.bereavedName}}
            </ion-col>
            <ion-col size="10">
              의뢰인: {{order.extra__clientName}}
            </ion-col>
            <ion-col size="10">
              담당지도사: {{order.extra__expertName}}
            </ion-col>
          </ion-row>
          <ion-row>
            <ion-col size="10">
              장례식장: {{order.funeralHome}}
            </ion-col>
            <ion-col size="10">
              인원: {{order.head}} 명
            </ion-col>
            <ion-col size="10">
              종교: {{order.religion}}
            </ion-col>
            <ion-col size="10">
              장례시작일: {{order.startDate}}
            </ion-col>
            <ion-col size="10">
              장례종료일: {{order.endDate}}
            </ion-col>
          </ion-row>
        </ion-grid>
      </ion-label>
      <div class="flex-col">
        <ion-item-divider class="mt-2">
          <ion-button color="" slot="end" :href="'/order/detail?id=' + order.id">
            상세보기
          </ion-button>
        </ion-item-divider>
        <ion-item-divider class="mt-2">
          <ion-button v-if="globalState.loginedClient.id == order.clientId" color="success" slot="end" :href="'/review/add?relTypeCode=expert&relId=' + order.expertId">
            후기/평점 작성
          </ion-button>
        </ion-item-divider>

        <ion-item-divider class="mt-2" v-if="globalState.loginedClient.id !== order.clientId">
          <ion-button v-if="order.stepLevel==1" color="success" slot="end" @click="changeStepLevel(order.id, order.stepLevel)">
            의뢰수락(장례준비)
          </ion-button>
          <ion-button v-if="order.stepLevel==2" color="success" slot="end" @click="changeStepLevel(order.id, order.stepLevel)">
            장례진행
          </ion-button>
          <ion-button v-if="order.stepLevel==3" color="success" slot="end" @click="changeStepLevel(order.id, order.stepLevel)">
            장례종료(확인요청)
          </ion-button>
        </ion-item-divider>

        <ion-item-divider class="mt-2" v-if="globalState.loginedClient.id == order.clientId">
          <ion-button v-if="order.stepLevel==2" color="success" slot="end">
            장례준비중
          </ion-button>
          <ion-button v-if="order.stepLevel==3" color="success" slot="end">
            장례진행중
          </ion-button>
          <ion-button v-if="order.stepLevel==4" color="success" slot="end" @click="changeStepLevel(order.id, order.stepLevel)">
            장례종료(확인)
          </ion-button>
        </ion-item-divider>
        

      </div>
      </ion-item>
    </template>
  </ion-list>
</ion-content>
</template>

<style>
</style>

<script lang="ts">
import { IonCustomHeader } from '@/components/';
import { 
  IonSelect, 
  IonSelectOption, 
  IonSearchbar, 
  IonLabel, 
  IonListHeader, 
  IonList, 
  IonItem, 
  IonContent,
  IonItemDivider,
  IonCol,
  IonRow,
  IonGrid,
  IonButton,
} from '@ionic/vue';
import { useGlobalState } from '@/stores'
import { useMainService } from '@/services';
import { reactive, computed, onMounted, watch } from 'vue';
import * as util from '@/utils';
import { useRoute } from 'vue-router';
import { Order } from '@/types';

const useSearchState = () => {
  return reactive({
    searchKeyword: '',
    searchKeywordType: 'deceasedName',
    selectStepLevel: '0',
  })
}

export default  {
  name: 'OrderList',
  
  components: { 
    IonSelect, 
    IonSelectOption, 
    IonSearchbar, 
    IonCustomHeader, 
    IonLabel, 
    IonListHeader, 
    IonList, 
    IonItem, 
    IonContent,
    IonItemDivider,
    IonCol,
    IonRow,
    IonGrid,
    IonButton,
  },
  
  setup() {
    const globalState = useGlobalState();
    const mainService = useMainService();
    const searchState = useSearchState();
    const route = useRoute();

    const state = reactive({
      orders: [] as Order[],
    });

    function returnToString(stepLevel: any) {
      let stepLevelToStr = ''; 
      if(stepLevel == 1){
        stepLevelToStr = '의뢰요청(의뢰검토중)';
      }
      if(stepLevel == 2){
        stepLevelToStr = '의뢰승인(장례준비중)';
      }
      if(stepLevel == 3){
        stepLevelToStr = '장례진행중';
      }
      if(stepLevel == 4){
        stepLevelToStr = '장례종료(종료확인요청)';
      }
      if(stepLevel == 5){
        stepLevelToStr = '종료확인(최종종료)';
      }
      
      return stepLevelToStr;
    }

    function onInput(event: any){
      searchState.searchKeyword = event.target.value;
      return searchState.searchKeyword;
    }


    const returnFilteredOrders = computed(() => {

      let filteredOrders = state.orders;
      
      if(searchState.selectStepLevel == '0'){
        if(searchState.searchKeywordType == "deceasedName"){
          filteredOrders = state.orders.filter((order: Order) => order.deceasedName.includes(searchState.searchKeyword))
        }
        if(searchState.searchKeywordType == "bereavedName"){
          filteredOrders = state.orders.filter((order: Order) => order.bereavedName.includes(searchState.searchKeyword))
        }
        if(searchState.searchKeywordType == "extra__clientName"){
          filteredOrders = state.orders.filter((order: Order) => order.extra__clientName.includes(searchState.searchKeyword))
        }
        if(searchState.searchKeywordType == "body"){
          filteredOrders = state.orders.filter((order: Order) => order.body.includes(searchState.searchKeyword))
        }
        if(searchState.searchKeywordType == "funeralHome"){
          filteredOrders = state.orders.filter((order: Order) => order.funeralHome.includes(searchState.searchKeyword))
        }
      }
      else{
        if(searchState.searchKeywordType == "deceasedName"){
          filteredOrders = state.orders.filter((order: Order) => order.deceasedName.includes(searchState.searchKeyword) && order.stepLevel === parseInt(searchState.selectStepLevel))
        }
        if(searchState.searchKeywordType == "bereavedName"){
          filteredOrders = state.orders.filter((order: Order) => order.bereavedName.includes(searchState.searchKeyword) && order.stepLevel === parseInt(searchState.selectStepLevel))
        }
        if(searchState.searchKeywordType == "extra__clientName"){
          filteredOrders = state.orders.filter((order: Order) => order.extra__clientName.includes(searchState.searchKeyword) && order.stepLevel === parseInt(searchState.selectStepLevel))
        }
        if(searchState.searchKeywordType == "body"){
          filteredOrders = state.orders.filter((order: Order) => order.body.includes(searchState.searchKeyword) && order.stepLevel === parseInt(searchState.selectStepLevel))
        }
        if(searchState.searchKeywordType == "funeralHome"){
          filteredOrders = state.orders.filter((order: Order) => order.funeralHome.includes(searchState.searchKeyword) && order.stepLevel === parseInt(searchState.selectStepLevel))
        }
      }
      return filteredOrders
    })

    async function loadOrders(memberId: number, memberType: string){
      const axRes = await mainService.order_list(memberId, memberType)
      state.orders = axRes.data.body.orders;
    }

    // function doDeleteReview(id: number) {
    //   if(confirm('정말 삭제하시겠습니까?') == false){
    //     return;
    //   }
    //   mainService.review_doDelete(id)
    //   .then(axiosResponse => {
    //       alert(axiosResponse.data.msg);
    //       if ( axiosResponse.data.fail ) {
    //         return;
    //       }
    //     window.location.reload();
    //   });
    // }

    async function doChangeStepLevel(id: number, stepLevel: number){
      const axRes = await mainService.order_changeStepLevel(id, stepLevel)
          util.showAlert(axRes.data.msg)

          if ( axRes.data.fail ) {
            return;
          }
          window.location.reload();
    }

    function changeStepLevel(id: number, stepLevel: number){

      const msg = '해당 의뢰를 수락하시겠습니까?'
      util.showAlertConfirm(msg).then(confirm => {
        if (confirm == false) {
          return
        } else{
          doChangeStepLevel(id, stepLevel)
        }
      })
      
    }

    let loginedMemberId = 0;
    let loginedMemberType = '';

    if(globalState.loginedClient.id != null){
        loginedMemberId = globalState.loginedClient.id
        loginedMemberType = 'client'
    }

    // onMounted 바로 실행하는 것이 아닌 모든 것이 준비되었을때 실행됨
    onMounted(() => {
      //alert("3");
      loadOrders(loginedMemberId, loginedMemberType);
     // loadReviews(relTypeCode);
    });


    return {
      globalState,
      mainService,
      state,
      searchState,
      returnFilteredOrders,
      //doDeleteReview,
      onInput,
      returnToString,
      changeStepLevel,
      //onClickInput,
    }
  }
}


</script>