21.03.24 lamplight서비스 프로젝트(평점 추가, 출력 구현, 리뷰 삭제 기능 구현, directorProfilePage 추가, orderListPage에서 진행단계별 필터링 기능 구현)
2021. 3. 24. 22:22ㆍVue.js/Spring & Vue APP 프로젝트(프론트엔드)
# NOTE
-리뷰[...]
-작성, 리스팅까진 성공[ㅇ]
-리스팅시 갯수제한..진행중[]
-평점[ㅇ]
typescript 기초
https://www.tutorialsteacher.com/typescript/typescript-number
vue 템플릿 모음
https://vuejsexamples.com/tag/rating/
# 주요 소스코드
<ReviewAddPage.vue>
<template>
<TitleBar>후기/평점 작성 페이지</TitleBar>
<section class="section section-review-write-form-box px-2">
<div class="container mx-auto">
<div class="px-6 py-6 bg-white rounded-lg shadow-md">
<form v-if="globalShare.isLogined" v-on:submit.prevent="checkAndAddReview">
<div title="평점" class="text-center h-24">
<div class="btn-success">평점</div>
<select ref="newRatingPointElRef" class="text-xl h-10 mt-2 w-1/2 border">
<option value="1">1점</option>
<option value="2">2점</option>
<option value="3">3점</option>
<option value="4">4점</option>
<option value="5">5점</option>
</select>
</div>
<div class="btn-success">후기 작성</div>
<FormRow>
<textarea ref="newReviewBodyElRef" class="form-row-input border" placeholder="내용을 입력해주세요."></textarea>
</FormRow>
<FormRow >
<div class="btns">
<input type="submit" value="작성" class="btn-primary" />
</div>
</FormRow>
</form>
<div v-else>
<router-link class="btn-link" to="/member/login">로그인</router-link> 후 이용해주세요.
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, getCurrentInstance, onMounted } from 'vue'
import { MainApi } from '../apis'
import { Router } from 'vue-router'
import * as Util from '../utils'
export default defineComponent({
name: 'ReviewAddPage',
props: {
globalShare: {
type: Object,
required: true
},
relId: {
type: Number,
required: true
},
relTypeCode: {
type: String,
required: true
}
},
setup(props){
const router:Router = getCurrentInstance()?.appContext.config.globalProperties.$router;
const mainApi:MainApi = getCurrentInstance()?.appContext.config.globalProperties.$mainApi;
const newReviewBodyElRef = ref<HTMLInputElement>();
const newRatingPointElRef = ref<HTMLInputElement>();
/* 공백 체크 */
function checkAndAddReview(){
//일반적으로 안해도 되지만 typescript에서는 해야됨
//제목
if(newReviewBodyElRef.value == null){
return;
}
const newReviewBodyEl = newReviewBodyElRef.value;
newReviewBodyEl.value = newReviewBodyEl.value.trim();
if(newReviewBodyEl.value.length == 0){
alert('내용을 입력해 주세요.')
newReviewBodyEl.focus();
return;
}
const raingAdd = (onSuccess:Function) => {
mainApi.rating_doAdd(props.relTypeCode, props.relId, Util.toIntOrNull(newRatingPointElRef.value?.value), props.globalShare.loginedMember.id)
.then(axiosResponse => {
if ( axiosResponse.data.fail ) {
alert(axiosResponse.data.msg);
return;
}
else{
onSuccess();
}
});
};
const startAddReview = () =>{
// 작성 함수로 보내기
addReview(props.relTypeCode, props.relId, newReviewBodyEl.value, props.globalShare.loginedMember.id);
}
raingAdd(startAddReview);
}
//typescript에서는 title:string, body:string 이런식으로 type을 적어주어야 한다
function addReview(relTypeCode:string, relId:number, body:string, memberId:number){
mainApi.review_doAdd(relTypeCode, relId, body, memberId)
.then(axiosResponse => {
alert(axiosResponse.data.msg);
// 로그인이 fail 상태이면 리턴
if ( axiosResponse.data.fail ) {
return;
}
//authKey가 있는 상태에서 가능
const newReviewId = axiosResponse.data.body.id;
//alert(newArticleId + "번 게시물 등록 완료!!");
router.replace("/director/list");
});
}
return{
newReviewBodyElRef,
newRatingPointElRef,
checkAndAddReview,
}
}
})
</script>
<style scoped>
</style>
<OrderListPage.vue>
<template>
<!-- 메인-요청 리스트 시작 -->
<section class="orderList-section bg-gray-500">
<div class="h-2 bg-gray-500"></div>
<!--리스트 search 시작-->
<div class="flex h-10 orderList-section-search m-2 text-right rounded-md">
<select ref="selectStepLevelElRef" class="h-full w-1/4 rounded-l-md" id="" @change="onChangeStepLevel">
<option value="0">진행단계 전체</option>
<option value="1">요청서 검토중</option>
<option value="2">장례준비중</option>
<option value="3">장례진행중</option>
<option value="4">장례종료(결제대기중)</option>
<option value="5">결제완료</option>
</select>
<select class="h-full w-1/4" id="" @change="onChangeSearchKeywordType($event)">
<option value="title">제목</option>
<option value="body">내용</option>
<option value="funeralHome">장례식장</option>
</select>
<input id="searchKeywordElRef" ref="searchKeywordElRef" class="h-full w-full pl-4" type="text" placeholder="검색어 입력" :value="searchKeyword" @keyup.enter="onInput($event)" >
<button class="w-20 text-white rounded-r-md bg-blue-500" type="button" @click="onClickInput">검색</button>
</div>
<!--리스트 search 끝-->
<!--리스트 grid 시작-->
<div class="orderList-section-grid grid grid-cols-1 md:grid-cols-3 gap-3 overflow-hidden">
<!--리스트 grid__body 시작-->
<div class="mt-6" v-bind:key="order.id" v-for="order in filteredOrders">
<div class="orderList-section-grid__body p-8 bg-white m-2 border rounded-xl">
<!--진행단계-->
<div class="btn-success">
진행단계: {{returnToString(order.stepLevel)}}
</div>
<!--제목-->
<div class="text-center m-4">
<router-link :to="'/order/detail?id=' + order.id" class="text-indigo-500 font-bold text-xl md:text-2xl hover:text-gray-700">
{{ order.title }}
</router-link>
</div>
<!--담당지도사-->
<h1 class="font-semibold text-gray-900 leading-none text-xl mt-1 mb-3 capitalize break-normal">
담당자회원번호: {{order.directorId}}
</h1>
<!--의뢰인명-->
<h1 class="font-semibold text-gray-900 leading-none text-xl mt-1 mb-3 capitalize break-normal">
의뢰인: {{order.extra__member}}
</h1>
<!--장례식장-->
<h1 class="font-semibold text-gray-900 leading-none text-xl mt-1 mb-3 capitalize break-normal">
장례식장: {{order.funeralHome}}
</h1>
<!--옵션-->
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
옵션1
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- 내용: {{ order.option1 }} / 수량: {{ order.option1qty }}
</p>
</div>
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
옵션2
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- 내용: {{ order.option2 }} / 수량: {{ order.option2qty }}
</p>
</div>
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
옵션3
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- 내용: {{ order.option3 }} / 수량: {{ order.option3qty }}
</p>
</div>
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
옵션4
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- 내용: {{ order.option4 }} / 수량: {{ order.option4qty }}
</p>
</div>
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
옵션5
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- 내용: {{ order.option5 }} / 수량: {{ order.option5qty }}
</p>
</div>
<!--추가 요청 내용-->
<div class="max-w-full border mt-2 p-1 pl-2 rounded-md">
추가 요청사항
<p class="text-base font-medium tracking-wide text-gray-600 mt-1">
- {{ order.body }}
</p>
</div>
<router-link :to="'/order/detail?id=' + order.id" class="block btn-primary mt-2 h-10 w-full rounded-md">
상세보기
</router-link>
<router-link :to="'/review/doAdd?relTypeCode=director&relId=' + order.directorId" class="block btn-secondary mt-2 h-10 w-full rounded-md">
후기/평점 작성
</router-link>
</div>
</div>
<!--리스트 grid__body 끝-->
</div>
<!--리스트 grid 끝-->
</section>
<!-- 메인-지도사 리스트 끝 -->
</template>
<script lang="ts">
import { defineComponent, reactive, ref, getCurrentInstance, onMounted, computed, watch } from 'vue'
import { IOrder } from '../types'
import { MainApi } from '../apis'
const searchKeywordElRef = ref<HTMLInputElement>();
const selectStepLevelElRef = ref<HTMLInputElement>();
export default defineComponent({
name: 'OrderListPage',
//props 속성은 컴포넌트 간에 데이터를 전달할 수 있는 컴포넌트 통신 방법입니다.
//props 속성을 기억할 때는 상위 컴포넌트에서 하위 컴포넌트로 내려보내는 데이터 속성으로 기억하면 쉽습니다
props: {
globalShare: {
type: Object,
required: true
},
memberId: {
type: Number,
required: true
}
},
setup(props){
const mainApi:MainApi = getCurrentInstance()?.appContext.config.globalProperties.$mainApi;
const state = reactive({
orders: [] as IOrder[],
searchKeyword: '' as string,
result:'' as string,
selectStepLevel: 0,
});
let searchKeywordType = "title";
function onInput(event:any){
state.searchKeyword = event.target.value;
return state.searchKeyword;
}
function onClickInput() {
if(searchKeywordElRef.value == undefined){
return;
}
state.searchKeyword = searchKeywordElRef.value.value;
return state.searchKeyword;
}
function onChangeSearchKeywordType(event:any){
searchKeywordType = event.target.value;
return searchKeywordType;
};
function onChangeStepLevel() {
if(selectStepLevelElRef.value == undefined){
return;
}
state.selectStepLevel = parseInt(selectStepLevelElRef.value?.value);
return state.selectStepLevel;
}
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;
}
//alert("1");
const filteredOrders = computed(() => {
//alert("2");
state.result = state.searchKeyword;
let filteredOrders = state.orders;
//filteredOrders = state.orders.filter((order:IOrder) => order.stepLevel === state.selectStepLevel)
if(state.selectStepLevel == 0){
if(searchKeywordType == "title"){
filteredOrders = state.orders.filter((order:IOrder) => order.title.includes(state.searchKeyword))
}
if(searchKeywordType == "body"){
filteredOrders = state.orders.filter((order:IOrder) => order.body.includes(state.searchKeyword))
}
if(searchKeywordType == "funeralHome"){
filteredOrders = state.orders.filter((order:IOrder) => order.funeralHome.includes(state.searchKeyword))
}
}
else{
if(searchKeywordType == "title"){
filteredOrders = state.orders.filter((order:IOrder) => order.title.includes(state.searchKeyword) && order.stepLevel === state.selectStepLevel)
}
if(searchKeywordType == "body"){
filteredOrders = state.orders.filter((order:IOrder) => order.body.includes(state.searchKeyword) && order.stepLevel === state.selectStepLevel)
}
if(searchKeywordType == "funeralHome"){
filteredOrders = state.orders.filter((order:IOrder) => order.funeralHome.includes(state.searchKeyword) && order.stepLevel === state.selectStepLevel)
}
}
return filteredOrders
})
const memberId = props.memberId;
function loadOrders(memberId:number){
//alert("4");
mainApi.order_list(memberId)
.then(axiosResponse => {
state.orders = axiosResponse.data.body.orders;
});
}
// onMounted 바로 실행하는 것이 아닌 모든 것이 준비되었을때 실행됨
onMounted(() => {
//alert("3");
loadOrders(memberId);
});
return{
state,
filteredOrders,
searchKeywordElRef,
onInput,
onClickInput,
returnToString,
onChangeSearchKeywordType,
onChangeStepLevel,
selectStepLevelElRef
}
}
})
</script>
<style scoped>
/* https://tailwindcomponents.com/component/blogs-posts-card */
.orderList-section-grid__body{
min-width: 400px;
}
</style>
'Vue.js > Spring & Vue APP 프로젝트(프론트엔드)' 카테고리의 다른 글
21.03.27 ionic 전역상태 세팅, 각 페이지 생성, 하단 메뉴 연결 등 (0) | 2021.03.27 |
---|---|
21.03.25 ionic 프레임워크 초기 셋팅 (0) | 2021.03.25 |
21.03.22 lamplight서비스 프로젝트(리뷰작성, 지도사별 리뷰리스팅까지 DB연계 완료) (0) | 2021.03.22 |
21.03.21 lamplight서비스 프로젝트(요청수정페이지,회원정보페이지,회원수정페이지까지 DB연계 완료) (0) | 2021.03.21 |
21.03.20 lamplight서비스 프로젝트(지도사 리스팅,요청페이지, 요청페이지 상세보기,요청페이지 리스팅까지 DB연계 완료) (0) | 2021.03.20 |