From 6fa4bba623713c396432ba8b863846883d6a1906 Mon Sep 17 00:00:00 2001 From: wayne <wayne8692wayne8692@gmail.com> Date: 星期三, 26 一月 2022 10:52:23 +0800 Subject: [PATCH] Merge branch 'pollex-dev' into sit --- PAMapp/components/Client/ClientCard.vue | 550 +++++++++++++++++++++++++++++++++++++++++++----------- 1 files changed, 437 insertions(+), 113 deletions(-) diff --git a/PAMapp/components/Client/ClientCard.vue b/PAMapp/components/Client/ClientCard.vue index 6bfcef1..70510bf 100644 --- a/PAMapp/components/Client/ClientCard.vue +++ b/PAMapp/components/Client/ClientCard.vue @@ -1,21 +1,36 @@ <template> <div> - <el-row type="flex" class="rowStyle" @click.native="openDetail"> - <el-col :xs="5" :sm="3"> - <el-avatar - :size="50" - src="" - class="cursor--pointer fix-chrome-click--issue" - ></el-avatar> - <!-- <div class="satisfaction"> - <i class="icon-star pam-icon icon--yellow satisfaction"></i> - <span>{{'1'}}</span> - </div> --> - </el-col> - <el-col :xs="14" :sm="15"> - <div class="smTxt_bold name">{{client.name}}</div> - <div class="message">������</div> - <div class="professionals"> + <el-row + type="flex" + ref="clientCardRef" + class="rowStyle cursor--pointer" + justify="space-between" + :class="{'new': newAppointment }" + @click.native="viewAppointmentDetail" + > + <div class="test"> + <div class="unread" v-if="isReserved"> + <div class="circle" v-if="!isRead"></div> + </div> + + <div class="pl-10"> + <div class="smTxt_bold name">{{ client.name || 'NO NAME' }}</div> + <div v-if="client.communicateStatus === contactStatus.RESERVED" class="my-10 xsTxt">������</div> + <div + class="xsTxt mb-10 mt-10" + v-else-if="client.communicateStatus === contactStatus.CONTACTED"> + 蝝赤蝝���� + </div> + <div + class="xsTxt mb-10 mt-10" + v-else> + 皛踵�漲 + <span v-if="client.satisfactionScore" class="xsTxt text--primary"> + <UiReviewScore :score="client.satisfactionScore"></UiReviewScore> + </span> + <span v-else class="xsTxt text--mid_grey">�憛�</span> + </div> + <div class="professionals mb-10" v-if="client.communicateStatus === contactStatus.RESERVED"> <template v-if="client.requirement"> <span v-for="(item, index) in requirements" @@ -28,46 +43,146 @@ >(摰X�����瘙�)</span> </template> </div> - </el-col> - <el-col class="flex-column contactInfo" :xs="5" :sm="6"> - <div class="smTxt_bold cursor--pointer fix-chrome-click--issue" - :class="client.communicateStatus" - >{{isReserved ? '撌脤���' : '撌脰蝯�'}} + <AppointmentProgress + :currentStep="client.communicateStatus" + ></AppointmentProgress> + </div> + </div> + <!-- <el-col :xs="5" :sm="3" align="middle"> + <el-avatar + :size="50" + ></el-avatar> + <div class="satisfaction" v-if="!hideReviews"> + <template v-if="client.satisfactionScore"> + TODO:���遛��漲 + <i class="icon-star pam-icon icon--yellow satisfaction"></i> + <span>{{client.satisfactionScore}}</span> + </template> + <template v-else> + <div class="unfilled">�憛急遛��漲</div> + </template> </div> - <div class="date xsTxt text--mid_grey">{{date}}</div> - <div class="xsTxt text--mid_grey">{{time}}</div> - </el-col> + </el-col> --> + + <div class="flex-column contactInfo" :xs="4" :sm="6"> + + <div + class="invite-msg smTxt_bold" + @click.stop="showAddInterviewDialog" + v-if="client.communicateStatus === contactStatus.RESERVED"> + ���赤� + </div> + <div + class="invite-msg smTxt_bold" + @click.stop="navigateToCloseAppointment" + v-else-if="client.communicateStatus === contactStatus.CONTACTED"> + 蝯�� + </div> + <div + class="invite-msg smTxt_bold" + @click.stop="inviteReview" + v-else-if="!client.satisfactionScore"> + ��遛��漲 + </div> + + <div + class="date xsTxt text--black" + >{{ date }}</div> + <div + class="xsTxt text--mid_grey" + >{{ time }}</div> + </div> </el-row> <Ui-Dialog - :isVisible.sync="isVisibleDialog" - :width="width" + :isVisible.sync="isShowInformDialog" + :width="dialogWidth" + @closeDialog="closeInformDialog" class="pam-myDemand-dialog" > <h5 class="subTitle text--center mb-30" - >{{isReserved ? '������' : '撌脰蝯∟���'}}</h5> - <p class="smTxt text-right">{{client.appointmentDate | formatDate}}</p> + >{{isReserved ? '������' : '撌脰蝯∟���'}}</h5> + + <p v-if='isReserved' + class="smTxt text--right" + ><span v-if="isRead">{{client.consultantReadTime | formatDate}}</span> 撌脰�</p> + <p + v-if="!isReserved" + class="smTxt text--right" + >{{client.contactTime | formatDate}} �蝯�</p> + <p class="smTxt">{{client.appointmentDate | formatDate}} ����</p> + <div class="dialogTxt"> - <p>憪��{client.name}}</p> - <p>�閰梧�{client.phone}}</p> - <p>Email嚗{client.email}}</p> - <p>�批嚗{gender}}</p> - <p>撟湧翩嚗{client.age}}</p> - <p>�璆哨�{client.job}}</p> - <p>��瘙�{client.requirement.replace(',', '��')}}</p> - <p v-for="(item, index) in hopeContactTime" :key="index">��蝯⊥�挾{{index + 1 | formatNumber}}嚗{item}}</p> + <p>憪���<span>{{client.name}}</span></p> + <p>�閰梧��<span>{{client.phone}}</span></p> + <p>Email嚗�<span>{{client.email}}</span></p> + <p>�批嚗�<span>{{gender}}</span></p> + <p>撟湧翩嚗�<span>{{client.age | toAgeLabel }}</span></p> + <p>�璆哨��<span>{{client.job}}</span></p> + <p>��瘙��<span>{{client.requirement.split(',').join('��')}}</span></p> + <p v-for="(item, index) in hopeContactTime" + :key="index" + >��蝯⊥�挾{{index + 1 | formatNumber}}嚗�<span>{{ item | formatHopeContactTime}}</span></p> + </div> + <div class="mt-30"> + <div class="memoTitleStyle"> + <div class="mdTxt">�摰寞�膩</div> + <div + class="smTxt text--bold text--primary cursor--pointer text--underline edit" + @click='editMemo' + >蝺刻摩</div> </div> - <div class="mt-30 text--center" v-if="isReserved"> - <el-button @click="markAppointment">璅酉�撌脤��蝯�</el-button> + + <el-input + class="mt-10 pam-appointment-textarea" + type="textarea" + :rows="3" + maxlength="100" + placeholder="隢撓�嚗��100摮��" + :disabled="!isEdit" + v-model="memo" + > + </el-input> + + <div class="mt-10 smTxt text--bold text--primary text--right fixed-Height"> + <template v-if="isEdit"> + <span class="cursor--pointer" @click="cancelEditMemo">����</span> + <span class="pl-20 cursor--pointer" @click="saveMemo">�摮�</span> + </template> </div> + </div> + <div class="mt-30 text--center" v-if="isReserved"> + <el-button @click="markAppointment">璅酉�撌脤��蝯�</el-button> + </div> </Ui-Dialog> + + <InterviewMsg + :client="client" + :isVisible.sync="isShowAddInterviewDialog"> + </InterviewMsg> + <PopUpFrame :isOpen.sync="isShowInviteReviewDialog"> + <div class="text--middle invite-review"> + <div class="mb-30 mt-10">撌脩�遛��漲</div> + <div class="text--primary text--middle cursor--pointer text--underline" @click="isShowInviteReviewDialog = false" :size="'250px'">������</div> + </div> + </PopUpFrame> </div> </template> <script lang="ts"> -import { Vue, Component, Prop, Mutation, Action } from 'nuxt-property-decorator'; -import { isMobileDevice } from '~/assets/ts/device'; -import { ClientInfo, markAsContact } from '~/assets/ts/api/appointment'; +import { Vue, Component, Prop, namespace, Watch } from 'nuxt-property-decorator'; + +import appointmentService from '~/shared/services/appointment.service'; +import myConsultantService from '~/shared/services/my-consultant.service'; +import UtilsService from '~/shared/services/utils.service'; +import { hideReviews } from '~/shared/const/hide-reviews'; +import { ElRow } from 'element-ui/types/row'; +import { Appointment, AppointmentMemoInfo } from '~/shared/models/appointment.model'; +import { ContactStatus } from '~/shared/models/enum/contact-status'; +import reviewsService from '~/shared/services/reviews.service'; + +const appointmentStore = namespace('appointment.store'); +const localStorage = namespace('localStorage'); @Component({ filters: { @@ -76,15 +191,196 @@ const upperNumber = ['�', '銝�', '鈭�', '銝�', '���', '鈭�', '�', '銝�', '�', '銋�', '���'] return upperNumber[index]; } + }, + formatHopeContactTime(item: string): string { + if (item) { + const [hopeDay, hopeTime] = item.split('��'); + const day = hopeDay.split(',').length > 6 ? '銝����' : hopeDay; + const time = hopeTime.split(',').length > 3 ? '銝����' : hopeTime; + return `${day}��${time}`; + } + return ''; } } }) export default class ClientList extends Vue { - @Action updateMyAppointment!: (data: ClientInfo) => void + @Prop() + client!: Appointment; - @Prop() client!: ClientInfo; - isVisibleDialog = false; - width = ''; + @appointmentStore.Action + updateMyAppointmentList!: (data: Appointment) => void; + + @appointmentStore.Action + getAppointmentDetail!: (appointmentId: number) => Promise<Appointment>; + + @appointmentStore.Action + updateAppointmentDetail!: (id: number) => Appointment; + + @appointmentStore.Getter + appointmentProgress!: ContactStatus; + + @localStorage.Mutation + storageClearAppointmentIdFromMsg!: () => void; + + contactStatus = ContactStatus; + + dialogWidth = ''; + hideReviews = hideReviews; + isEdit = false; + isShowAddInterviewDialog = false; + isShowInformDialog = false; + isShowInviteReviewDialog = false; + memo = ''; + + memoInfo: AppointmentMemoInfo = { + appointmentId: 0, + content : '', + id : 0 + }; + + ////////////////////////////////////////////////////////////////////// + + @Watch('$route', {immediate: true}) + onRouteChange() { + const appointmentIdFromMsg = this.$route.query.appointmentId; + if (appointmentIdFromMsg && +appointmentIdFromMsg === this.client.id) { + this.openDetail(); + } + } + + ////////////////////////////////////////////////////////////////////// + + mounted() { + this.memoInfo = this.client.appointmentMemoList.length > 0 + ? JSON.parse(JSON.stringify(this.client.appointmentMemoList[0])) + : {appointmentId: 0, content: '', id: 0}; + this.memo = this.memoInfo.content; + } + + ////////////////////////////////////////////////////////////////////// + + viewAppointmentDetail(): void { + this.getAppointmentDetail(this.client.id).then((_) => { + const unread = !this.client.consultantReadTime; + if (unread) { + this.readAppointment(); + } + this.$router.push(`/appointment/${this.client.id}`); + }); + } + + showAddInterviewDialog(): void { + this.isShowAddInterviewDialog = true; + } + + navigateToCloseAppointment(): void { + this.getAppointmentDetail(this.client.id).then((_) => { + this.$router.push(`/appointment/${this.client.id}/close`); + }); + } + + inviteReview(): void { + reviewsService.sendSatisfactionToClient(this.client.id).then(res => { + this.isShowInviteReviewDialog = true ; + }) + } + + openDetail() { + setTimeout(() => { + (this.$refs.clientCardRef as any).$el.classList.add('currentShowStyle'); + }, 0) + this.dialogWidth = UtilsService.isMobileDevice() ? '80%' : ''; + this.isShowInformDialog = true; + } + + markAppointment() { + myConsultantService.markAsContact(this.client.id).then(data => { + this.updateMyAppointmentList(data); + this.isShowInformDialog = false; + }) + } + + closeInformDialog(): void { + this.readAppointment(); + this.isEdit = false; + this.clearAppointmentIdFromMsg(); + } + + private readAppointment(): void { + appointmentService.recordRead(this.client.id).then((_) => { + const updatedClient = {...this.client}; + updatedClient.consultantReadTime = new Date().toString(); + this.updateMyAppointmentList(updatedClient); + this.updateAppointmentDetail(this.client.id); + }); + } + + private clearAppointmentIdFromMsg() { + this.storageClearAppointmentIdFromMsg(); + this.$router.push({query: {}}); + setTimeout(() => { + (this.$refs.clientCardRef as ElRow).$el.classList.remove('currentShowStyle') + },1000) + } + + saveMemo() { + if (this.client.appointmentMemoList.length > 0) { + const params = { + content: this.memo, + id: this.client.appointmentMemoList[0].id + }; + this.updateMemo(params); + return; + } + + const params = { + content: this.memo, + appointmentId: this.client.id, + } + this.createMemo(params); + } + + private createMemo(params) { + appointmentService.createMemo(params).then(memoRes => { + this.storeMemo(memoRes); + }); + } + + private updateMemo(params) { + appointmentService.updateMemo(params).then(memoRes => { + this.storeMemo(memoRes); + }); + } + + private storeMemo(memoRes) { + this.memoInfo = memoRes; + this.memo = this.memoInfo.content; + this.client.appointmentMemoList[0] = this.memoInfo; + this.isEdit = false; + } + + editMemo() { + this.isEdit = !this.isEdit; + this.memo = this.memoInfo.content; + } + + cancelEditMemo() { + this.isEdit = false; + this.memo = this.memoInfo.content; + } + + get newAppointment(): boolean { + return !this.client.consultantViewTime + && this.client.communicateStatus === 'reserved'; + } + + get isReserved() { + return this.client.communicateStatus === 'reserved'; + } + + get isRead() { + return !!this.client.consultantReadTime; + } get requirements() { return this.client.requirement.split(','); @@ -94,7 +390,7 @@ if (this.client.gender) { return this.client.gender === 'male' ? '���' : '憟單��'; } - return '' + return ''; } get hopeContactTime() { @@ -102,37 +398,30 @@ return contactList.filter(item => !!item && item !== ",") } - get time() { - const formatDate = (this.$options.filters as any).formatDate(this.client.appointmentDate); - return formatDate.split(' ')[1] + get reservedTxt(): string { + if (this.isReserved) { + return this.client.consultantReadTime ? '撌脰�' : '�霈�'; + } else { + return '撌脰蝯�' + } + } + + get displayTime(): string { + if (this.isReserved) { + return this.client.appointmentDate; + } else { + return this.client.lastModifiedDate; + } } get date() { - const formatDate = (this.$options.filters as any).formatDate(this.client.appointmentDate); - return formatDate.split(' ')[0] + const formatDate = (this.$options.filters as any).formatDate(this.displayTime); + return formatDate.split(' ')[0]; } - get isReserved() { - return this.client.communicateStatus === 'reserved'; - } - - openDetail() { - this.width = isMobileDevice() ? '80%' : ''; - this.isVisibleDialog = true; - } - - markAppointment() { - markAsContact(this.client.id).then(data => { - // TODO: 閬敺����� updated client 鞈�� - Ben 2021/11/16 - - const updatedClient = {...this.client}; - updatedClient.communicateStatus = 'contacted'; - updatedClient.appointmentDate = new Date(); - - this.updateMyAppointment(updatedClient); - this.isVisibleDialog = false; - - }) + get time() { + const formatDate = (this.$options.filters as any).formatDate(this.displayTime); + return formatDate.split(' ')[1] } } @@ -140,69 +429,104 @@ <style lang="scss" scoped> .rowStyle { - padding: 10px; background-color: $PRIMARY_WHITE; - margin-bottom: 10px; - display: flex; - justify-content: space-between; - - .satisfaction { - font-size: 12px; - font-weight: bold; - margin-top: 5px; + border-left : solid 4px transparent; + display : flex; + justify-content : space-between; + margin-bottom : 10px; + padding : 10px 15px 10px 5px; + transition: background-color 0.5s; + &.new { + border-color: $YELLOW; } - - .message { - margin:10px 0; + &.currentShowStyle { + background-color: rgba(236, 195, 178, 0.5); + transition : background-color 0.5s; } - - .professionals { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - .professionalsTxt { - font-size: 12px; - font-weight: bold; - margin-right: 5px; + .unread { + align-self: center; + .circle { + background-color: $PRIMARY_RED; + border-radius : 50%; + height : 10px; + margin : auto; + width : 10px; } - - .noProfessionalsTxt { - color: $PRUDENTIAL_GREY; + } + .satisfaction { + font-size : 12px; + font-weight: bold; + margin-top : 5px; + .unfilled { + color : $MID_GREY; font-weight: lighter; } } + .professionals { + overflow : hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + .professionalsTxt { + font-size : 12px; + margin-right: 5px; + + } + .noProfessionalsTxt { + color : $PRUDENTIAL_GREY; + font-weight: lighter; + } + } .contactInfo { text-align: right; .date { font-size: 12px; } } - - .reserved { + .unread-txt { @extend .text--primary; } - - .contacted { + .read-txt { color: $SKY_BLUE; } } - .flex-column { - display: flex; - flex-direction: column; + display : flex; + flex-direction : column; justify-content: space-between; } - .dialogTxt { - font-size: 20px; - overflow-y:scroll; - height:400px; + font-size : 20px; + max-height: 25vh; + overflow-y: scroll; + @include desktop { + height: 400px; + } } - - - .text-right { - text-align: right; + .memoTitleStyle { + display : flex; + flex-direction : row; + justify-content: space-between; + .edit { + align-self: flex-end; + } } -</style> \ No newline at end of file + .fixed-Height { + height: 16px; + } + .test{ + display: flex; + } + .invite-msg{ + width: 96px; + color: $PRIMARY_RED; + @extend .text--underline; + } + .invite-review{ + align-items : center; + display : flex; + flex-direction: column; + } +</style> -- Gitblit v1.8.0