PAMapp/assets/images/logo.pngPAMapp/assets/images/taiwan-logo.pngPAMapp/assets/scss/utilities/_heading.scss
@@ -22,10 +22,6 @@ font-weight: bold; } .lighter { font-weight: lighter; } .smTxt_bold { font-size: 16px; font-weight: bold; @@ -49,6 +45,11 @@ .text--regular { font-weight: normal !important; font-weight: lighter; } .text--lighter { font-weight: lighter; } .text--center { PAMapp/assets/scss/vendors/elementUI/_dialog.scss
@@ -35,3 +35,38 @@ } } } .pam-dialog-review { .review-content { display: flex; flex-direction: row; justify-content: space-evenly; } .review-text { width: 60%; line-height: 28px; @extend .p; @extend .text--lighter; } .review-score { display: flex; justify-content: center; margin-bottom: 30px; } .review-btn { display: flex; justify-content: center; } } .pam-dialog-reserved { .reserved-info { font-size: 20px; overflow-y:scroll; height: 400px; } .reserved-btn { display: flex; justify-content: center; } } PAMapp/assets/scss/vendors/elementUI/_rate.scss
@@ -1,6 +1,5 @@ .pam-quickFilter-rate { .pam-rate { height: auto; margin-top: 30px; display: flex; justify-content: center; @extend .fix-chrome-click--issue; PAMapp/components/Consultant/ConsultantCard.vue
@@ -33,17 +33,17 @@ v-if="showRemoveBtn" @click="removeAgent" >ç§»é¤</div> <div v-if="notScoreAppointmentYet" class="text--primary text--underline cursor--pointer xsTxt text--bold" @click="reviewsBtn = true">çµ¦äºæ»¿æåº¦è©å</div> </el-col> <el-col class="flex_column" :xs="9" :sm="6"> <el-button class="smTxt_bold outline_btn" @click="reserveCommunication" @click="reservedOrShowAppointmentInfo" :class="actionBtnStyle" >{{ actionBtnLabel }}</el-button> <div v-if="notScoreAppointmentYet" class="text--primary mt-10 text--center text--underline cursor--pointer" @click="reviewsBtn = true">çµ¦äºæ»¿æåº¦è©å</div> <div class="updateTime mt-10"> {{ agentInfo.updateTime | formatDate }} </div> @@ -53,12 +53,12 @@ <Ui-Dialog :isVisible.sync="isVisibleDialog" :width="width" class="pam-myDemand-dialog" class="pam-myDemand-dialog pam-dialog-reserved" > <div v-if="appointmentDetail"> <h5 class="subTitle text--center mb-30">é ç´æå</h5> <p class="smTxt">{{appointmentDetail.appointmentDate | formatDate}}</p> <div class="dialogInfo"> <div class="reserved-info"> <p>å§åï¼{{appointmentDetail.name}}</p> <p>é»è©±ï¼{{appointmentDetail.phone}}</p> <p>Emailï¼{{appointmentDetail.email}}</p> @@ -80,7 +80,7 @@ </div> </div> <div v-if="notScoreAppointmentYet" class="dialogInfo-btn"> <div v-if="notScoreAppointmentYet" class="reserved-btn"> <el-button type="primary" @click.native="reviewsBtn = true">çµ¦äºæ»¿æåº¦è©å</el-button> </div> @@ -91,14 +91,24 @@ </div> </div> </Ui-Dialog> <PopUpFrame :isOpen.sync="reviewsBtn"> <div class="mdTxt"> <PopUpFrame :isOpen.sync="reviewsBtn" class="reviewDialog-content"> <div class="mdTxt pam-dialog-review"> ä¿éªé¡§å滿æåº¦ <span class="hint">é¸åææ</span> <div class="dialogInfo-score"> <el-rate v-model="inputScore" class="pam-quickFilter-rate"></el-rate> <div class="mt-30 review-content"> <UiAvatar :size="80" :agentNo="agentInfo.agentNo"></UiAvatar> <div class="review-text">å°æ¼é¡§å <span class="text--primary">{{agentInfo.name}}</span> çæ´é«æåï¼æ¨çµ¦äºå¹¾é¡æè©å¹ï¼ </div> </div> <div class="dialogInfo-btn"> <div class="review-score"> <el-rate v-model="inputScore" class="pam-rate mt-30"></el-rate> </div> <div class="review-btn"> <el-button type="primary" :disabled="!inputScore" @@ -172,7 +182,6 @@ hideReviews = hideReviews; isConfirmPopup = false; appointmentDetail: any = { age : '', agentNo : '', @@ -203,7 +212,7 @@ } get isAppointment(): boolean { return !!this.agentInfo['appointmentStatus'];; return !!this.agentInfo['appointmentStatus']; } get latestReservedAppointment(): Appointment { @@ -290,7 +299,7 @@ @Action storeConsultantList!: () => void; reserveCommunication() { reservedOrShowAppointmentInfo() { const isAppointment = !!this.agentInfo['appointmentStatus']; const contactStatus = this.agentInfo.contactStatus; if (!isAppointment && (!contactStatus || contactStatus === 'picked')) { @@ -442,20 +451,4 @@ flex-direction: column; justify-content: space-between; } .dialogInfo { font-size: 20px; overflow-y:scroll; height: 400px; } .dialogInfo-btn{ display: flex; justify-content: center; } .dialogInfo-score{ display: flex; justify-content: center; margin-bottom: 50px; } </style> PAMapp/components/Consultant/ConsultantSwiper.vue
@@ -25,8 +25,12 @@ </div> </swiper-slide> <div class="swiper-button-prev" slot="button-prev"></div> <div class="swiper-button-next" slot="button-next"></div> <div class="swiper-button-prev" slot="button-prev"> <i class="icon-left"></i> </div> <div class="swiper-button-next" slot="button-next"> <i class="icon-right"></i> </div> </swiper> </div> </template> @@ -93,9 +97,12 @@ height: 100%; &:after { display: none; } .icon-right,.icon-left { font-size: 20px; font-weight: bold; color: #707A81; color: $CORAL; } &.swiper-button-disabled { PAMapp/components/NavBar.vue
@@ -216,11 +216,11 @@ height: $DESKTOP_NAV_BAR; .pam-header__logo { width: 180px; height: 100%; width: 160px; height: 70px; margin: 0; background-image: url('~/assets/images/logo.png'); background-size: cover; background-size: contain; background-repeat: no-repeat; background-position: center; } PAMapp/components/QuickFilter/QuickFilterSelector.vue
@@ -71,7 +71,7 @@ <div v-else> <el-rate v-if="!hideReviews" class="pam-quickFilter-rate" class="pam-rate mt-30" v-model="pickedItem.avgScore" ></el-rate> </div> PAMapp/middleware/errorRoute.ts
File was renamed from PAMapp/middleware/errorRouteMiddleware.ts @@ -1,6 +1,7 @@ import { Middleware } from '@nuxt/types'; const errorRouteMiddleware: Middleware = (context) => { const errorRoute: Middleware = (context) => { if (!context.route.name) { const isAdminLogin = context.store.getters['localStorage/isAdminLogin']; if (isAdminLogin) { @@ -11,4 +12,4 @@ } } export default errorRouteMiddleware export default errorRoute; PAMapp/middleware/getUrlQuery.ts
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,12 @@ import { Middleware } from '@nuxt/types'; const getUrlQuery: Middleware = (context) => { const currentRouteName = context.route.name; const satisfactionIdFromMsg = context.route.query.appointmentId; if (currentRouteName === 'index' && satisfactionIdFromMsg) { context.store.commit('localStorage/storageSatisfactionIdFromMsg', satisfactionIdFromMsg); } } export default getUrlQuery PAMapp/nuxt.config.js
@@ -70,6 +70,6 @@ }, router: { base: process.env.ENV === 'uat' ? '/pam/' : '', middleware: 'errorRouteMiddleware' middleware: ['getUrlQuery', 'errorRoute'] } } PAMapp/pages/index.vue
@@ -39,12 +39,82 @@ <ConsultantSwiper :agents="recommendList"></ConsultantSwiper> </div> </div> <Ui-Dialog :isVisible.sync="isVisibleDialog" :width="width" class="pam-myDemand-dialog pam-dialog-reserved" @closeDialog="clearSatisfactionId" > <div v-if="appointmentDetail"> <h5 class="subTitle text--center mb-30">é ç´æå</h5> <p class="smTxt">{{appointmentDetail.appointmentDate | formatDate}}</p> <div class="reserved-info"> <p>å§åï¼{{appointmentDetail.name}}</p> <p>é»è©±ï¼{{appointmentDetail.phone}}</p> <p>Emailï¼{{appointmentDetail.email}}</p> <p>æ§å¥ï¼{{gender}}</p> <p>年齡ï¼{{appointmentDetail.age | toAgeLabel }}</p> <p>è·æ¥ï¼{{appointmentDetail.job}}</p> <p>éæ±ï¼{{appointmentDetail.requirement.split(',').join('ã')}}</p> <p v-for="(item, index) in hopeContactTime" :key="index" >é£çµ¡ææ®µ{{index + 1 | formatNumber}}ï¼{{ item | formatHopeContactTime }}</p> <div v-if="appointmentDetail.satisfactionScore"> <div class="mdTxt mt-10 mb-10">滿æåº¦</div> <el-rate :value="appointmentDetail.satisfactionScore" class="pam-myDemand-dialog__rate" disabled> </el-rate> </div> </div> <div v-if="!appointmentDetail.satisfactionScore" class="reserved-btn"> <el-button type="primary" @click.native="reviewsBtn = true">çµ¦äºæ»¿æåº¦è©å</el-button> </div> </div> </Ui-Dialog> <PopUpFrame :isOpen.sync="reviewsBtn" @closePopUp="clearSatisfactionId" > <div class="mdTxt pam-dialog-review"> ä¿éªé¡§å滿æåº¦ <span class="hint">é¸åææ</span> <div class="mt-30 review-content" v-if="agentInfo"> <UiAvatar :size="80" :agentNo="agentInfo.agentNo"></UiAvatar> <div class="review-text">å°æ¼é¡§å <span class="text--primary">{{agentInfo.name}}</span> çæ´é«æåï¼æ¨çµ¦äºå¹¾é¡æè©å¹ï¼ </div> </div> <div class="review-score"> <el-rate v-model="inputScore" class="pam-rate mt-30"></el-rate> </div> <div class="review-btn"> <el-button type="primary" :disabled="!inputScore" @click="userReviewsConsultants">éåº</el-button> </div> </div> </PopUpFrame> </div> </template> <script lang="ts"> import { Vue, Component, State, Action, Watch, namespace } from 'nuxt-property-decorator'; import { Consultant } from '~/shared/models/consultant.model'; import { UserReviewsConsultantsParams } from '~/shared/models/reviews.model'; import appointmentService from '~/shared/services/appointment.service'; import reviewsService from '~/shared/services/reviews.service'; import UtilsService from '~/shared/services/utils.service'; const localStorage = namespace('localStorage'); @@ -70,7 +140,54 @@ @localStorage.Mutation storageClearRecommendConsultant!: () => void; @localStorage.Getter currentSatisfactionIdFromMsg!: string; @localStorage.Mutation storageClearSatisfactionIdFromMsg!: () => void; consultantList: Consultant[] = []; appointmentDetail: any = { age : '', agentNo : '', appointmentDate : '', communicateStatus : '', consultantReadTime: null, consultantViewTime: null, contactTime : '', contactType : '', customerId : 0, email : '', gender : '', hopeContactTime : "", id : 0, job : "", lastModifiedDate : '', name : '', otherRequirement : null, phone : "", requirement : '', satisfactionScore : 0, }; isVisibleDialog = false; width = ''; reviewsBtn = false; inputScore = 0; agentInfo: Consultant = { agentNo : '', name : '', img : '', expertise : [], avgScore : 0, contactStatus : '', createTime : '', updateTime : '', customerViewTime : '', role : '', seniority : '', appointments : [] }; ////////////////////////////////////////////////////////////////////// @@ -84,6 +201,10 @@ this.storageClearRecommendConsultant(); } destroyed() { this.clearSatisfactionId(); } ////////////////////////////////////////////////////////////////////// @Watch('myConsultantList') @@ -91,13 +212,71 @@ this.consultantList = (this.myConsultantList || []) .filter(item => item.contactStatus !== 'contacted') .map((item) => ({ ...item, formatDate: new Date(item.updateTime || item.createTime)})) .sort((preItem, nextItem) => +nextItem.formatDate - +preItem.formatDate) .sort((preItem, nextItem) => +nextItem.formatDate - +preItem.formatDate); if (this.currentSatisfactionIdFromMsg) { this.agentInfo = this.myConsultantList.filter(item => { const satisfactionIdIndex = item.appointments?.findIndex(i => i.id === +this.currentSatisfactionIdFromMsg); return satisfactionIdIndex !== undefined && satisfactionIdIndex > -1; })[0]; if (this.agentInfo) { this.openAppointmentInfo(); } } } private openAppointmentInfo() { appointmentService.getAppointmentDetail(+this.currentSatisfactionIdFromMsg).then(res => { this.appointmentDetail = res; this.width = UtilsService.isMobileDevice() ? '80%' : ''; this.isVisibleDialog = true; if (!this.appointmentDetail.satisfactionScore) { setTimeout(() => { this.reviewsBtn = true; }, 500) } }); } ////////////////////////////////////////////////////////////////////// navigateToRoute(path: string): void { this.$router.push(path); } userReviewsConsultants() { const reviewParams: UserReviewsConsultantsParams = { appointmentId: this.appointmentDetail.id, score: this.inputScore, } this.appointmentDetail.satisfactionScore = this.inputScore; reviewsService.userReviewsConsultants(reviewParams).then((res) => { this.reviewsBtn = false; }); } clearSatisfactionId() { console.log('close'); this.$router.push({query: {}}); this.storageClearSatisfactionIdFromMsg(); } /////////////////////////////////////////////////////////////////////////////// get gender() { if (this.appointmentDetail.gender) { return this.appointmentDetail.gender === 'male' ? 'ç·æ§' : '女æ§'; } return '' } get hopeContactTime() { const contactList = this.appointmentDetail.hopeContactTime .split("'").map((item: any) => item.slice(0, item.length)); return contactList.filter((item: any) => !!item && item !== ",") } } @@ -182,5 +361,4 @@ max-width: 335px; } } </style> PAMapp/pages/questionnaire/_agentNo.vue
@@ -155,10 +155,17 @@ const roleStorage = namespace('localStorage'); @Component export default class Questionnaire extends Vue { @State('myConsultantList') myConsultantList!: Consultant[]; @Action storeConsultantList!: () => Promise<number>; @roleStorage.Getter isUserLogin!:boolean; @roleStorage.State recommendConsultantItem!:string; @State('myConsultantList') myConsultantList!: Consultant[]; @Action storeConsultantList!: () => Promise<number>; @roleStorage.Getter isUserLogin!:boolean; @roleStorage.State recommendConsultantItem!:string; genderOptions=[ { @@ -279,6 +286,8 @@ appointmentId = 0; appointmentTime = ''; //////////////////////////////////////////////////////////////////////////// beforeRouteEnter(to: any, from: any, next: any) { next(vm => { const isUserLogin = authService.isUserLogin(); @@ -293,13 +302,10 @@ }) } async fetch() { if (authService.isUserLogin()) { await this.storeConsultantList(); }; } mounted(): void { if (authService.isUserLogin()) { this.storeConsultantList(); }; this.setMyRequest(); } @@ -328,6 +334,121 @@ } } //////////////////////////////////////////////////////////////////////////// @Watch('myConsultantList') onMyConsultantListChange() { if (authService.isUserLogin() && this.myConsultantList.length > 0) { const editAppointment = this.getLatestReserved(this.$route.params.agentNo); if (editAppointment && editAppointment.agentNo) { this.myRequest = JSON.parse(JSON.stringify(editAppointment)); if (!this.$route.query || this.$route.query.edit !== 'true') { this.isEditPopup = true; } this.isEditBtn = true; } } } private getLatestReserved(agentNo) { const agentInfo = this.myConsultantList.filter(item => item.agentNo === agentNo); const appointmentInfo = agentInfo.length > 0 && agentInfo[0].appointments ? agentInfo[0].appointments! .filter((appointment) => appointment.communicateStatus !== 'contacted') .map((reversedAppointment) => ( { ...reversedAppointment, sortDate: new Date(reversedAppointment.appointmentDate) })) .sort((preAppointment, nextAppointment) => +nextAppointment.sortDate - +preAppointment.sortDate)[0] : null; return this.getReservedData(appointmentInfo); } private getReservedData(appointmentInfo) { if (appointmentInfo) { const hopeContactTime = appointmentInfo!.hopeContactTime.split("'") .filter(item => item && item !== ','); this.getAppointmentId(appointmentInfo); return { ...appointmentInfo, hopeContactTime: hopeContactTime.map(item => { const info = item.split('ã'); return { selectWeekOptions: info[0].split(','), selectTimesOptions: info[1].split(',') } }), requirement: appointmentInfo.requirement.split(',') } } else { return null; } } private getAppointmentId(appointmentInfo) { this.appointmentId = appointmentInfo.id; this.appointmentTime = appointmentInfo.lastModifiedDate ? appointmentInfo.lastModifiedDate : appointmentInfo.appointmentDate; } //////////////////////////////////////////////////////////////////////////// sentDemand() { if (this.isEditBtn) { this.editAppointmentDemand(); } else { queryConsultantService.addFavoriteConsultant([this.$route.params.agentNo]).then(res => this.sentAppointmentDemand()); } } private editAppointmentDemand() { const info = { ...this.myRequest, requirement: _.map(this.myRequest.requirement,o=>o).toString(), hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'', id: this.appointmentId, otherRequirement: null } appointmentService.editAppointment(info).then(res => { this.sendReserve = true; this.myRequest.hopeContactTime = []; setRequestsToStorage(this.myRequest); }); } private sentAppointmentDemand() { const data: AppointmentParams = { ...this.myRequest, requirement: _.map(this.myRequest.requirement,o=>o).toString(), hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'', agentNo: this.$route.params.agentNo }; queryConsultantService.appointmentDemand(data).then(res => { this.sendReserve = true; this.myRequest.hopeContactTime = []; setRequestsToStorage(this.myRequest); }); } private getHopeContactTime() { const selectedHopeContactTime = this.myRequest.hopeContactTime.filter((i) => i.selectWeekOptions?.length && i.selectTimesOptions?.length); return selectedHopeContactTime.map(i => { return `'${i.selectWeekOptions}ã${i.selectTimesOptions}'`} ).toString(); } closeReservePopUp() { this.sendReserve = false; this.$router.push('/') } //////////////////////////////////////////////////////////////////////////// get phoneValid(): boolean { const rule = /^09[0-9]{8}$/; return this.myRequest.phone @@ -350,121 +471,6 @@ return this.myRequest.hopeContactTime[0]?.selectWeekOptions.length >0 && this.myRequest.hopeContactTime[0]?.selectTimesOptions.length >0; } sentDemand() { if (this.isEditBtn) { this.sentEditAppointmentDemand(); } else { queryConsultantService.addFavoriteConsultant([this.$route.params.agentNo]).then(res => this.sentAppointmentDemand()); } } private sentAppointmentDemand() { const data: AppointmentParams = { ...this.myRequest, requirement: _.map(this.myRequest.requirement,o=>o).toString(), hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'', agentNo: this.$route.params.agentNo }; queryConsultantService.appointmentDemand(data).then(res => { this.sendReserve = true; this.myRequest.hopeContactTime = []; setRequestsToStorage(this.myRequest); }); } private sentEditAppointmentDemand() { const info = { ...this.myRequest, requirement: _.map(this.myRequest.requirement,o=>o).toString(), hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'', id: this.appointmentId, otherRequirement: null } appointmentService.editAppointment(info).then(res => { this.sendReserve = true; this.myRequest.hopeContactTime = []; setRequestsToStorage(this.myRequest); }); } getHopeContactTime() { const selectedHopeContactTime = this.myRequest.hopeContactTime.filter((i) => i.selectWeekOptions?.length && i.selectTimesOptions?.length); return selectedHopeContactTime.map(i => { return `'${i.selectWeekOptions}ã${i.selectTimesOptions}'`} ).toString(); } closeReservePopUp() { this.sendReserve = false; this.$router.push('/') } private getLatestReserved(agentNo) { const agentInfo = this.myConsultantList.filter(item => item.agentNo === agentNo); const appointmentInfo = agentInfo.length > 0 && agentInfo[0].appointments ? agentInfo[0].appointments! .filter((appointment) => appointment.communicateStatus !== 'contacted') .map((reversedAppointment) => { return { ...reversedAppointment, sortDate: new Date(reversedAppointment.appointmentDate) } }) .sort((preAppointment, nextAppointment) => +nextAppointment.sortDate - +preAppointment.sortDate)[0] : null; return this.getReservedData(appointmentInfo); } private getReservedData(appointmentInfo) { if (appointmentInfo) { const hopeContactTime = appointmentInfo!.hopeContactTime.split("'") .filter(item => item && item !== ','); this.getAppointmentId(appointmentInfo); return { age: appointmentInfo.age, agentNo: appointmentInfo.agentNo, contactType: appointmentInfo.contactType, email: appointmentInfo.email || '', gender: appointmentInfo.gender, hopeContactTime: hopeContactTime.map(item => { const info = item.split('ã'); return { selectWeekOptions: info[0].split(','), selectTimesOptions: info[1].split(',') } }), job: appointmentInfo.job, phone: appointmentInfo.phone || '', requirement: appointmentInfo.requirement.split(',') } } else { return null; } } private getAppointmentId(appointmentInfo) { this.appointmentId = appointmentInfo.id; this.appointmentTime = appointmentInfo.lastModifiedDate ? appointmentInfo.lastModifiedDate : appointmentInfo.appointmentDate; } @Watch('myConsultantList') onMyConsultantListChange() { if (authService.isUserLogin() && this.myConsultantList.length > 0) { const editAppointment = this.getLatestReserved(this.$route.params.agentNo); if (editAppointment && editAppointment.agentNo) { this.myRequest = JSON.parse(JSON.stringify(editAppointment)); if (!this.$route.query || this.$route.query.edit !== 'true') { this.isEditPopup = true; } this.isEditBtn = true; return; } } } } </script> PAMapp/store/localStorage.ts
@@ -9,6 +9,7 @@ quickFilterSelectedItem = localStorage.getItem('quickFilter'); recommendConsultantItem = localStorage.getItem('recommendConsultantItem'); appointmentIdFromMsg = localStorage.getItem('appointmentIdFromMsg'); satisfactionIdFromMsg = localStorage.getItem('satisfactionIdFromMsg'); get idToken(): string|null { return this.id_token; @@ -36,6 +37,10 @@ get currentAppointmentIdFromMsg(): string|null { return this.appointmentIdFromMsg; } get currentSatisfactionIdFromMsg(): string|null { return this.satisfactionIdFromMsg; } @Mutation storageIdToken(token: string): void { @@ -68,6 +73,11 @@ this.appointmentIdFromMsg = localStorage.getItem('appointmentIdFromMsg'); } @Mutation storageSatisfactionIdFromMsg(id: string) { localStorage.setItem('satisfactionIdFromMsg', id); this.satisfactionIdFromMsg = localStorage.getItem('satisfactionIdFromMsg'); } @Mutation storageClear(): void { localStorage.removeItem('myRequests'); localStorage.removeItem('userInfo'); @@ -97,6 +107,11 @@ this.appointmentIdFromMsg = localStorage.getItem('appointmentIdFromMsg'); } @Mutation storageClearSatisfactionIdFromMsg() { localStorage.removeItem('satisfactionIdFromMsg'); this.appointmentIdFromMsg = localStorage.getItem('satisfactionIdFromMsg'); } @Action actionStorageClear(): void { this.context.commit("storageClear"); }