From 68a06a99b1218f313c7fce2dd6eacd956ebf7a7f Mon Sep 17 00:00:00 2001
From: Tomas <tomasysh@gmail.com>
Date: 星期日, 20 八月 2023 19:09:43 +0800
Subject: [PATCH] Update: 添加 sandbox 屬性 allow-popups 解決無法開啟原始 Youtube 連結的情形

---
 PAMapp/pages/index.vue |  440 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 429 insertions(+), 11 deletions(-)

diff --git a/PAMapp/pages/index.vue b/PAMapp/pages/index.vue
index b267189..6136bea 100644
--- a/PAMapp/pages/index.vue
+++ b/PAMapp/pages/index.vue
@@ -2,6 +2,7 @@
   <div>
     <Ui-Carousel></Ui-Carousel>
     <div class="page-container">
+
       <div>
         <h5 class="mdTxt">����憿批��</h5>
         <div class="mt-10 pam-reserveBtn--block">
@@ -15,6 +16,7 @@
           </el-button>
         </div>
       </div>
+
       <div class="pam-paragraph">
         <el-row class="rowStyle">
           <el-col :span="16">
@@ -29,6 +31,8 @@
         <ConsultantList class="mt-10"
           :agents="consultantList.slice(0, 3)"></ConsultantList>
       </div>
+
+
       <div class='pam-paragraph'>
         <div class="pam-recommend">
           <h5 class="mdTxt">��靽憿批��</h5>
@@ -39,14 +43,140 @@
         <ConsultantSwiper :agents="recommendList"></ConsultantSwiper>
       </div>
     </div>
+
+    <Ui-Dialog
+        :isVisible.sync="isShowAppointmentDialog"
+        :width="appointmentDialogWidth"
+        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="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.consultationMethod | toConsultationMethod }}</p>
+                  <p>��瘙�{ appointmentDetail.requirement ? 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="notScoreAppointmentYet" class="reserved-btn">
+                  <el-button type="primary"
+                      @click.native="reviewsBtn = true">蝯虫�遛��漲閰��</el-button>
+              </div>
+
+              <div v-if="appointmentDetail.communicateStatus === 'reserved'" class="text--center mt-10">
+                  <el-button @click="isCancelPopup = true">������</el-button>
+                  <el-button @click="edit" type="primary">蝺刻摩</el-button>
+              </div>
+          </div>
+      </Ui-Dialog>
+
+      <PopUpFrame
+        :isOpen.sync="isShowReAppointmentDialog"
+        @closePopUp="removeUrlQueryParameter('notContactAppointmentId')"
+      >
+          <div class="pam-dialog-review">
+              <div class="mt-30 text--middle" v-if="agentInfo">
+                敺甇������<span class="text--bold">{{ consultantName }}</span>憿批�迤敹�葉嚗������蒂���隞“���
+              </div>
+
+                <el-row
+                  type="flex"
+                  class="mt-50"
+                  justify="center">
+                  <el-button
+                      type="primary"
+                      @click="reAppointment">��������隞“���</el-button>
+                </el-row>
+                <el-row
+                  type="flex"
+                  class="mt-20"
+                  justify="center">
+                  <el-button
+                      class="outline_btn"
+                      @click="cancelAppointment">������</el-button>
+                </el-row>
+          </div>
+      </PopUpFrame>
+
+      <PopUpFrame
+        :isOpen.sync="isShowReviewDialog"
+        @closePopUp="removeUrlQueryParameter('appointmentId')"
+      >
+          <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 class="video-container"
+         v-if="isShowFilmPlayer"
+         style="position: fixed; bottom: 30px; right: 30px; z-index: 9999; display: flex; justify-content: flex-end;"
+    >
+      <iframe
+        id="ytplayer" type="text/html" width="360" height="270"
+        frameborder="0"
+        allowfullscreen
+        sandbox="allow-scripts allow-same-origin allow-popups"
+        :src="filmVideoSrc"
+      ></iframe>
+      <div class="close-btn" @click="closeVideo()">X</div>
+    </div>
+
   </div>
 </template>
 
 <script lang="ts">
-  import { Vue, Component, State, Action, Watch, namespace } from 'nuxt-property-decorator';
-  import { Consultant } from '~/shared/models/consultant.model';
+import {Action, Component, Getter, Mutation, namespace, State, Vue, Watch} from 'nuxt-property-decorator';
 
-  const localStorage = namespace('localStorage');
+import appointmentService from '~/shared/services/appointment.service';
+import utilService, {AccessFroms} from '~/shared/services/utils.service';
+import reviewsService from '~/shared/services/reviews.service';
+import myConsultantService from '~/shared/services/my-consultant.service';
+import {Appointment, AppointmentClosedInfo} from '~/shared/models/appointment.model';
+import {Consultant} from '~/shared/models/consultant.model';
+import {UserReviewParams} from '~/shared/models/reviews.model';
+import {StrictQueryParams} from '~/shared/models/strict-query.model';
+import {AgentInfo} from '~/shared/models/agent-info.model';
+import {ContactStatus} from '~/shared/models/enum/contact-status';
+import {SatisfactionType} from '~/shared/models/enum/satisfaction-type';
+
+const localStorage = namespace('localStorage');
+  const roleStorage = namespace('localStorage');
 
   @Component({
     layout: 'home'
@@ -59,10 +189,20 @@
     @State('myConsultantList')
     myConsultantList!: Consultant[];
 
+    @roleStorage.Getter
+    isAdminLogin!: boolean;
+
+    @roleStorage.Getter
+    isUserLogin!: boolean;
+
     @Action
     storeRecommendList!: any;
 
-    @Action storeConsultantList!: any;
+    @Action
+    storeConsultantList!: any;
+
+    @Mutation
+    setAccessSource!: (accessSource: AccessFroms) => void;
 
     @localStorage.Mutation
     storageClearQuickFilter!: () => void;
@@ -70,18 +210,125 @@
     @localStorage.Mutation
     storageClearRecommendConsultant!: () => void;
 
+    @localStorage.Getter
+    currentSatisfactionIdFromMsg!: string;
+
+    @localStorage.Getter
+    currentNotContactAppointmentIdFromMsg!: string;
+
+    @localStorage.Mutation
+    storageClearSatisfactionIdFromMsg!: () => void;
+
+    @localStorage.Mutation
+    storageClearNotContactAppointmentIdFromMsg!: () => void;
+
+    @localStorage.Mutation
+    storageStrickQueryItem!: (strictQueryDto: StrictQueryParams) => void;
+
     consultantList: Consultant[] = [];
+
+    appointmentDialogWidth    = '';
+    inputScore                = 0;
+    isShowAppointmentDialog   = false;
+    isShowReAppointmentDialog = false;
+    isShowReviewDialog        = false;
+    consultantName = '';
+    contactStatus = ContactStatus;
+
+    appointmentDetail: Appointment = {
+      age               : '',
+      agentNo           : '',
+      appointmentClosedInfo: {} as AppointmentClosedInfo,
+      appointmentDate   : '',
+      appointmentMemoList: [],
+      appointmentNoticeLogs: [],
+      communicateStatus: this.contactStatus.PICKED,
+      consultantReadTime: '',
+      consultantViewTime: '',
+      contactTime       : '',
+      contactType       : '',
+      customerId        : 0,
+      email             : '',
+      gender            : '',
+      hopeContactTime   : '',
+      interviewRecordDTOs: [],
+      id                : 0,
+      job               : '',
+      lastModifiedDate  : '',
+      name              : '',
+      otherRequirement  : '',
+      phone             : '',
+      requirement       : '',
+      satisfactionScore : 0,
+      consultationMethod    : ''
+    };
+
+    agentInfo: Consultant = {
+      agentNo            : '',
+      name               : '',
+      img                : '',
+      expertise          : [],
+      avgScore           : 0,
+      contactStatus      : '',
+      createTime         : '',
+      updateTime         : '',
+      customerViewTime   : '',
+      role               : '',
+      seniority          : '',
+      appointments       : []
+    };
+
+    isCloseVideo = false;
+
+    @Getter
+    fromAccess!: AccessFroms;
+
+    get filmVideoSrc() {
+      if (!this.fromAccess) return '';
+      return this.fromAccess === AccessFroms.FILM_1
+        ? 'https://www.youtube.com/embed/zVBLW9hWD0g?autoplay=1&mute=1&loop=1&playlist=zVBLW9hWD0g'
+        : 'https://www.youtube.com/embed/655JnwbuRGA?autoplay=1&mute=1&loop=1&playlist=655JnwbuRGA';
+    }
+
+
+    get isShowFilmPlayer() {
+      return !!this.fromAccess && !this.isCloseVideo;
+    }
 
     //////////////////////////////////////////////////////////////////////
 
     mounted() {
-      if (!this.recommendList?.length) {
-        this.storeRecommendList();
+      if (this.$route.query.from) {
+        const fromSource = this.$route.query.from as AccessFroms;
+        this.setAccessSource(fromSource);
+        utilService.insertAccessFrom(fromSource);
+      }
+      if (this.isAdminLogin) {
+        this.$router.push('/myAppointmentList/appointmentList');
+      } else {
+        if (!this.recommendList?.length) {
+          this.storeRecommendList();
+        }
+
+        this.storeConsultantList();
+        this.storageClearQuickFilter();
+        this.storageClearRecommendConsultant();
+
+        if (this.isUserLogin) {
+          appointmentService.getNotContactAppointment().then((appointment) => {
+            if (appointment) {
+              this.$router.push({ query: { notContactAppointmentId: appointment.id + ''}});
+              this.autoOpenAppointmentBy('askReAppointment', appointment.id);
+            }
+          });
+        }
+
       }
 
-      this.storeConsultantList();
-      this.storageClearQuickFilter();
-      this.storageClearRecommendConsultant();
+    }
+
+    destroyed() {
+      this.removeUrlQueryParameter();
     }
 
     //////////////////////////////////////////////////////////////////////
@@ -89,15 +336,149 @@
     @Watch('myConsultantList')
     onMyConsultantListChange() {
       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.currentNotContactAppointmentIdFromMsg) {
+        this.autoOpenAppointmentBy('askReAppointment', +this.currentNotContactAppointmentIdFromMsg);
+        return;
+      }
+
+      if (this.currentSatisfactionIdFromMsg) {
+        this.autoOpenAppointmentBy('inviteReviewConsultant',+this.currentSatisfactionIdFromMsg);
+        this.storageClearSatisfactionIdFromMsg();
+        return;
+      }
+    }
+
+    private autoOpenAppointmentBy(reason: string, targetAppointmentId: number): void {
+        const setAgentInfo = new Promise((resolve, reject) => {
+          this.agentInfo = this.myConsultantList.filter(item => {
+            const appointmentIndex = item.appointments?.findIndex(i => i.id === targetAppointmentId);
+            return appointmentIndex !== undefined && appointmentIndex > -1;
+          })[0];
+          if (this.agentInfo) {
+            myConsultantService.getConsultantDetail(this.agentInfo.agentNo).then((res) => resolve(res));
+          }
+        });
+
+        const setAppointment = new Promise((resolve, reject) => {
+           appointmentService.getAppointmentDetail(targetAppointmentId).then((res) => resolve(res));
+        });
+
+        Promise.all([setAgentInfo, setAppointment]).then((values) => {
+          const agentInfo = values[0] as AgentInfo;
+          const appointmentInfo = values[1] as Appointment;
+          this.consultantName = agentInfo.name;
+          this.appointmentDetail = appointmentInfo;
+          this.appointmentDialogWidth = utilService.isMobileDevice() ? '80%' : '';
+          this.isShowAppointmentDialog = true;
+          switch (reason) {
+            case 'inviteReviewConsultant':
+              if (this.notScoreAppointmentYet) {
+                setTimeout(() => {
+                  this.isShowReviewDialog = true;
+                }, 500);
+              }
+              break;
+            case 'askReAppointment':
+              setTimeout(() => {
+                this.isShowReAppointmentDialog = true;
+              }, 500);
+              break;
+          }
+        });
+
     }
 
     //////////////////////////////////////////////////////////////////////
 
     navigateToRoute(path: string): void {
       this.$router.push(path);
+    }
+
+    edit() {
+        this.isShowAppointmentDialog = false;
+        this.$router.push({path: `/questionnaire/${this.agentInfo.agentNo}`, query: {'edit': 'true'}});
+    }
+
+    reAppointment(): void {
+      appointmentService.cancelAppointment(this.appointmentDetail.id).then(() => {
+        const requirements = this.appointmentDetail && this.appointmentDetail.requirement
+                            ? this.appointmentDetail.requirement.split(',')
+                            : [];
+        this.storeConsultantList();
+        this.storageStrickQueryItem({ requirements: requirements });
+        this.storageClearNotContactAppointmentIdFromMsg();
+        this.removeUrlQueryParameter('notContactAppointmentId');
+        this.$router.push('/recommendConsultant');
+      });
+    }
+
+    cancelAppointment(): void {
+      appointmentService.cancelAppointment(this.appointmentDetail.id).then(() => {
+        this.storeConsultantList();
+        this.storageClearNotContactAppointmentIdFromMsg();
+        this.removeUrlQueryParameter('notContactAppointmentId');
+        this.isShowReAppointmentDialog = false;
+        this.isShowAppointmentDialog = false;
+        this.$router.push('/');
+      });
+    }
+
+    userReviewsConsultants() {
+      const reviewParams: UserReviewParams = {
+            appointmentId: this.appointmentDetail.id,
+            score: this.inputScore,
+            type: SatisfactionType.APPOINTMENT
+        }
+        this.appointmentDetail.satisfactionScore = this.inputScore;
+
+        reviewsService.userReviewsConsultants(reviewParams).then((res) => {
+            this.isShowReviewDialog = false;
+        });
+    }
+
+    removeUrlQueryParameter(targetKey?: string): void {
+        // NOTE: ���摰�� query parameter [Tomas, 2022/1/24 11:36]
+        // [REF] How to remove a parameter from this.$router.query Nuxt.js? https://reurl.cc/X45aMD
+        let newRouteQuery = {};
+        if (targetKey) {
+          Object.keys(this.$route.query).forEach((key) => {
+            if (key !== targetKey) {
+              newRouteQuery[key] = this.$route.query[key]
+            }
+          })
+        }
+        this.$router.push(newRouteQuery);
+        this.storageClearSatisfactionIdFromMsg();
+        this.storageClearNotContactAppointmentIdFromMsg();
+    }
+
+    closeVideo(): void {
+      this.isCloseVideo = true;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////
+
+    get gender(): string {
+        if (this.appointmentDetail.gender) {
+            return this.appointmentDetail.gender === 'male' ? '���' : '憟單��';
+        }
+        return ''
+    }
+
+    get hopeContactTime(): string[] {
+        const contactList = this.appointmentDetail.hopeContactTime
+            .split("'").map((item: any) => item.slice(0, item.length));
+        return contactList.filter((item: any) => !!item && item !== ",")
+    }
+
+    get notScoreAppointmentYet(): boolean {
+      if (this.appointmentDetail.communicateStatus === 'closed' || this.appointmentDetail.communicateStatus === 'done') {
+        return !this.appointmentDetail.satisfactionScore;
+      };
+      return false;
     }
 
   }
@@ -157,6 +538,7 @@
     align-items: center;
   }
 
+
   @media (min-width: 576px) and (max-width: 767px) {
     .quickFilter.el-button--default {
       background-image: url('~/assets/images/quickFilter/banner_web.svg');
@@ -173,6 +555,15 @@
       margin: 0 auto;
     }
 
+      .landing-container {
+    border: 2px solid blue;
+    border-radius: 10px;
+    padding: 10px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+
     .pam-reserveBtn--block {
       display: flex;
       justify-content: space-between;
@@ -183,4 +574,31 @@
     }
   }
 
+  /* 蝣箔�� .video-container ��撠�/蝯����摰祝摨� */
+  .video-container {
+    position: relative;
+    width: auto; /* �����蔣��偕撖貉矽� */
+  }
+
+  /* �����見撘� */
+  .close-btn {
+    position: absolute;
+    top: -10px;
+    right: -10px;
+    cursor: pointer;
+    font-size: 20px;
+    color: #fff;
+    background-color: #000;
+    width: 30px;
+    height: 30px;
+    text-align: center;
+    line-height: 30px;
+    border-radius: 50%;
+    opacity: 0.7;
+  }
+
+  .close-btn:hover {
+    opacity: 1;
+  }
+
 </style>

--
Gitblit v1.8.0