保誠-保戶業務員媒合平台
Jack
2022-01-19 0e27da107dd3329b4d0c11ce7d0173c3bd1358e9
Merge branch 'Phase3' of ssh://dev.pollex.com.tw:29418/pcalife/PAM into Phase3
修改12個檔案
新增5個檔案
891 ■■■■■ 已變更過的檔案
PAMapp/components/Appointment/AppointmentInterviewList.vue 141 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Appointment/AppointmentRecordList.vue 103 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/BackActionBar.vue 26 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientCard.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewAdd.vue 66 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewCard.vue 145 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewMsg.vue 110 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Interview/InterviewRecordCard.vue 99 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/index.vue 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/interviewList/index.vue 38 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/appointment/_appointmentId/recordList/index.vue 32 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/consultantLogin/index.vue 9 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/agent-info.model.ts 37 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/appointment.store.ts 7 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/index.ts 42 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/localStorage.ts 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/login.store.ts 28 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Appointment/AppointmentInterviewList.vue
@@ -1,82 +1,20 @@
<template>
    <div>
      <div class="interview__header">
          <div class="mdTxt">約訪紀錄</div>
          <div class="pam-link-button--lg"
          @click="addInterview">+新增</div>
      </div>
      <template v-if="!interviewList.length">
          <div class="record-card record-card--empty">
            ç„¡ç´„訪紀錄
          </div>
      </template>
      <template v-if="interviewList.length">
        <div
            v-for="(item, index) in futureList"
            :key="index + 'feature'"
            class="interview--future"
            @click="editInterview(item)"
        >
            <div class="record-card">
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        <div class="interview__header">
            <div class="mdTxt">約訪紀錄</div>
            <div class="pam-link-button--lg"
            @click="addInterview">+新增</div>
        </div>
        <InterviewCard :interviewList="displayList.slice(0, 3)"></InterviewCard>
        <section
            class="interview--past"
            v-for="(item, index) in pastList"
            :key="index + 'past'"
            @click="editInterview(item)"
        >
            <div class="record-card">
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        <section class="text--right mt-30" v-if="interviewList.length > 3">
                <div class="pam-link-button--lg" @click="readMoreBtn">展開看更多</div>
        </section>
        <section class="more-log-action">
                <div class="pam-link-button--lg">展開看更多</div>
        </section>
      </template>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop, Watch, Mutation } from 'nuxt-property-decorator';
import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator';
import { InterviewRecord } from '~/shared/models/appointment.model';
@Component
@@ -84,13 +22,8 @@
  @Prop()
  interviewList!: InterviewRecord[];
  @Mutation
  updateInterviewRecord!: (data: InterviewRecord) => void;
  appointmentId!: string;
  futureList: InterviewRecord[] = [];
  pastList: InterviewRecord[] = [];
  displayList: InterviewRecord[] = [];
  //////////////////////////////////////////////////////////////////////
@@ -103,10 +36,9 @@
  @Watch('interviewList', {immediate: true})
  updateInterviewList() {
      if (this.interviewList && this.interviewList.length > 0) {
          this.futureList = this.interviewList
            .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime())
          this.pastList = this.interviewList
            .filter(item =>  new Date(item.interviewDate).getTime() < new Date().getTime());
          this.displayList = this.interviewList
            .map((i) => ({ ...i, sortDate: new Date(i.interviewDate)}))
            .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate);
      }
  }
@@ -116,9 +48,8 @@
    this.$router.push(`/appointment/${this.appointmentId}/interview/new`);
  }
  editInterview(interviewRecord) {
    this.updateInterviewRecord(interviewRecord);
    this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`);
  readMoreBtn() {
      this.$router.push(`/appointment/${this.appointmentId}/interviewList`);
  }
}
@@ -129,49 +60,5 @@
  display        : flex;
  justify-content: space-between;
  margin-bottom  : 10px;
}
.interview--future{
    border-bottom: 1px solid #CCCCCC;
    padding-bottom: 17px;
    margin-bottom: 17px;
    .record{
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    }
}
.record-card {
    height: 62px;
    border: 1px solid #707070;
    border-radius: 5px;
    display: flex;
    border-bottom: 1px solid #000;
    .record-card-date{
        display: flex;
        flex-direction: column;
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
    .record-card-content{
        height: 42px;
        margin-top: 10px;
        margin-right: 10px;
        line-height: 1.2;
    }
  &.record-card--empty {
    align-items     : center;
    background-color: #fff;
    color           : $MID_GREY;
    justify-content : center;
  }
}
.line-space{
    letter-spacing: 1px;
}
.more-log-action{
    margin-top: 30px;
    display: flex;
    justify-content:flex-end;
}
</style>
PAMapp/components/Appointment/AppointmentRecordList.vue
@@ -2,42 +2,11 @@
    <div class="record-log-component">
        <div class="mdTxt mt-30 mb-10">系統通知紀錄</div>
            <div v-for="(item, index) in displayLogs"
                :key="index">
                <section
                    class="record-log-card"
                >
                    <div class="record-log-card-date-container">
                        <div class="record-log-card-date-container-circle">
                            <div class="xxsTxt bold line-height">{{item.createdDate | formatYear}}</div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt bold line-height"
                                    :date="item.createdDate"
                                    onlyShowSection="DAY" />
                            </div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt mt-4 line-space"
                                    :date="item.createdDate"
                                    onlyShowSection="TIME" />
                            </div>
                        </div>
                    </div>
                        <div class="record-log-msg">
                            <div>發送約訪通知
                                <span v-if="item.email && item.phone">(手機簡訊、Email)</span>
                                <span v-else-if="item.email">(Email)</span>
                                <span v-else>(手機簡訊)</span>
                            </div>
                            <div class="mt-10">預約{{item.interviewDate | formatDate}}</div>
                        </div>
                </section>
                <div class="time-line"></div>
            </div>
            <InterviewRecordCard :noticeLogsList="displayLogs.slice(0, 3)"></InterviewRecordCard>
            <section class="more-log-action">
            <section class="text--right mt-30" v-if="displayLogs.length > 3">
                <div class="pam-link-button--lg"
                @click="readMoreBtn"
                >展開看更多</div>
            </section>
    </div>
@@ -47,15 +16,7 @@
import { Vue, Component, Prop, Watch } from 'nuxt-property-decorator';
import { NoticeLogs } from '~/shared/models/appointment.model';
@Component({
    filters: {
        formatYear(value) {
            if (value) {
                return new Date(value).getFullYear();
            }
        }
    }
})
@Component
export default class AppointmentRecordList extends Vue {
    @Prop()
@@ -63,6 +24,8 @@
    appointmentId: string       = '';
    displayLogs  : NoticeLogs[] = [];
    //////////////////////////////////////////////////////////////////////
    mounted() {
        this.appointmentId = this.$route.params.appointmentId;
@@ -79,53 +42,11 @@
      }
    }
    //////////////////////////////////////////////////////////////////////
    readMoreBtn() {
        this.$router.push(`/appointment/${this.appointmentId}/recordList`);
    }
}
</script>
<style lang="scss" scoped>
.record-log-component{
    display: flex;
    flex-direction: column;
    .record-log-card{
        display: flex;
        .record-log-card-date-container{
            position:relative;
            .record-log-card-date-container-circle{
                display: flex;
                flex-direction: column;
                width: 56px;
                height: 56px;
                border-radius: 50%;
                border:1px solid $PRIMARY_BLACK;
                justify-content: center;
                align-items: center;
                align-content: center;
            }
        }
    }
}
.mt-4{
    margin-top: 4px;
}
.line-space{
    letter-spacing: 1px;
}
.line-height{
    line-height:1.2;
}
.time-line{
    border-left: 1px solid black;
    height: 30px;
    margin-left: 28px;
}
.record-log-msg{
    margin-left: 13px;
    margin-top: 10px;
}
.more-log-action{
    display: flex;
    justify-content:flex-end;
}
</style>
PAMapp/components/BackActionBar.vue
@@ -14,7 +14,8 @@
import * as _ from 'lodash';
import { Role } from '~/shared/models/enum/Role';
const roleStorage = namespace('localStorage');
const appointmentStore = namespace('appointment.store');
const roleStorage      = namespace('localStorage');
@Component
export default class UiCarousel extends Vue {
@@ -22,11 +23,16 @@
  @roleStorage.Getter
  currentRole!:string;
  @appointmentStore.Getter
  isCloseAppointment!: boolean;
  //////////////////////////////////////////////////////////////////////
  goBack(): void {
    const pathName = this.$route.name;
    pathName?.includes('myConsultantList') ? this.$router.push('/') : this.$router.go(-1);
    pathName?.includes('myConsultantList')
      ? this.$router.push('/')
      : this.$router.go(-1);
  }
  get label(): string {
@@ -78,11 +84,21 @@
          featureLabel = 'F&Q å¸¸è¦‹å•é¡Œ';
          break;
        case 'appointment':
          const appointmentFeatureLabel = this.$route.name.includes('close') ? '結案' : '預約資訊';
          const appointmentFeatureLabel = this.$route.name.includes('close')
                                                            ? '結案'
                                                            : this.isCloseAppointment ? '結案明細' : '預約資訊';
          const inInterview = this.$route.name.includes('interview');
          const addNewInterview = this.$route.name.includes('new');
          if (inInterview) {
            featureLabel = addNewInterview ? '新增約訪紀錄' : '編輯約訪紀錄';
          const interviewList = this.$route.name.includes('interviewList');
          const recordList = this.$route.name.includes('recordList');
          if (interviewList) {
            featureLabel = '約訪紀錄';
          } else if (recordList) {
            featureLabel = '系統通知紀錄';
          } else if (inInterview) {
            featureLabel = addNewInterview
                  ? '新增約訪紀錄'
                  : '編輯約訪紀錄';
          } else {
            featureLabel = appointmentFeatureLabel;
          }
PAMapp/components/Client/ClientCard.vue
@@ -210,7 +210,7 @@
    updateMyAppointmentList!: (data: Appointment) => void;
    @appointmentStore.Action
    setAppointmentDetail!: (appointmentId: number) => Promise<Appointment>;
    getAppointmentDetail!: (appointmentId: number) => Promise<Appointment>;
    @appointmentStore.Getter
    appointmentProgress!: ContactStatus;
@@ -256,7 +256,7 @@
    //////////////////////////////////////////////////////////////////////
    viewAppointmentDetail(): void {
      this.setAppointmentDetail(this.client.id).then((_) => {
      this.getAppointmentDetail(this.client.id).then((_) => {
        this.$router.push(`/appointment/${this.client.id}`);
      });
    }
PAMapp/components/Interview/InterviewAdd.vue
@@ -1,6 +1,6 @@
<template>
  <div class="edit-appointment-record">
      <div class="edit-appointment-record-date" v-if="interviewId">
      <div class="edit-appointment-record-date" v-if="interviewId && interviewRecord">
          <span>{{interviewRecord.createdDate | formatDate}} å»ºç«‹</span>
          <span>{{interviewRecord.lastModifiedDate | formatDate}} æ›´æ–°</span>
      </div>
@@ -85,25 +85,26 @@
      <InterviewMsg
        :isVisible.sync="showInterviewMsgPopup"
        :client="appointmentDetail"
        @closeDialog="closePopup"
      ></InterviewMsg>
  </div>
</template>
<script lang="ts">
import { InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model';
import { Vue, Component, Prop, State, Mutation, Watch, Action } from 'nuxt-property-decorator';
import { Appointment, InterviewRecord, InterviewRecordInfo } from '~/shared/models/appointment.model';
import { Vue, Component, Watch, namespace } from 'nuxt-property-decorator';
import appointmentService from '~/shared/services/appointment.service';
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewAdd extends Vue {
    @State
    interviewRecord!: InterviewRecord;
    @Mutation
    updateInterviewRecord!: (data: InterviewRecord) => void;
    @appointmentStore.State
    appointmentDetail!: Appointment;
    @Mutation
    clearInterviewRecord!: () => void;
    @appointmentStore.Action
    updateAppointmentDetail!: (id: number) => Appointment;
    interviewTime = '';
    content = '';
@@ -121,38 +122,29 @@
    defaultValue = '';
    interviewRecord!: InterviewRecord;
    ////////////////////////////////////////////////////////////////////
    mounted() {
        this.interviewId = this.$route.params.interviewId;
        this.appointmentId = this.$route.params.appointmentId;
        const isEditPage = this.interviewId && this.interviewRecord;
        if (isEditPage) {
            this.checkInterviewRecord();
        }
    }
    private checkInterviewRecord() {
        if (this.interviewRecord.appointmentId !== +this.appointmentId
                || this.interviewRecord.id !== +this.interviewId) {
            appointmentService.getAppointmentDetail(+this.appointmentId).then((data) => {
                const currentInterviewRecord = data.interviewRecordDTOs.filter(item => item.id === +this.interviewId)[0];
                this.updateInterviewRecord(currentInterviewRecord);
            })
        }
    }
    destroyed() {
        this.clearInterviewRecord();
        this.onAppointmentDetailChange();
    }
    ////////////////////////////////////////////////////////////////////
    @Watch('interviewRecord', {immediate: true})
    watchInterviewRecord() {
        if (this.interviewRecord && this.interviewRecord.content) {
            this.content = this.interviewRecord.content;
            this.defaultValue = this.interviewRecord.interviewDate;
    @Watch('appointmentDetail', {immediate: true})
    onAppointmentDetailChange() {
        if (this.appointmentDetail && this.appointmentDetail.id === +this.appointmentId) {
            this.interviewRecord = this.appointmentDetail.interviewRecordDTOs
                .filter(item => item.id === +this.interviewId)[0];
                if (this.interviewRecord && this.interviewId) {
                    this.content = this.interviewRecord.content;
                    this.defaultValue = this.interviewRecord.interviewDate;
                }
        }
    }
@@ -173,6 +165,7 @@
            }
            this.updateRecord(updateInterviewRecord);
        }
        this.updateAppointmentDetail(+this.appointmentId);
    }
    private createdRecord(interviewRecordInfo) {
@@ -197,14 +190,11 @@
        }
    }
    closePopup() {
        this.$router.push(`/appointment/${this.appointmentId}`);
    }
    deleteInterviewRecord() {
        appointmentService.deleteInterviewRecord(this.interviewId).then(res => {
            this.confirmTxt = '刪除成功';
            this.showConfirmPopup = true;
            this.updateAppointmentDetail(+this.appointmentId);
        });
    }
@@ -214,10 +204,14 @@
           this.defaultValue = this.interviewRecord.interviewDate;
           this.isEdit = false;
        } else {
           this.$router.push(`/appointment/${this.appointmentId}`);
           this.$router.go(-1);
        }
    }
    closePopup() {
        this.$router.go(-1);
    }
    ////////////////////////////////////////////////////////////////////
    get formatInterviewDate() {
PAMapp/components/Interview/InterviewCard.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,145 @@
<template>
    <div>
       <template v-if="!interviewList.length">
          <div class="record-card record-card--empty">
            ç„¡ç´„訪紀錄
          </div>
      </template>
      <template v-else>
        <div class="interview--future">
            <div class="record-card mb-10"
                v-for="(item, index) in futureList"
                :key="index + 'feature'"
                @click="editInterview(item)"
            >
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        </div>
        <section class="interview--past" v-if="pastList.length">
            <div class="record-card mb-10"
                v-for="(item, index) in pastList"
                :key="index + 'past'"
                @click="editInterview(item)"
            >
                <div class="record-card-date">
                    <div>
                        <UiDateFormat
                            class="date bold"
                            :date="item.interviewDate"
                            onlyShowSection="DAY" />
                    </div>
                    <div>
                        <UiDateFormat
                            class="time mt-5 line-space"
                            :date="item.interviewDate"
                            onlyShowSection="TIME" />
                    </div>
                </div>
                <div class="record-card-content">
                    <span>{{item.content}}</span>
                </div>
            </div>
        </section>
      </template>
    </div>
</template>
<script lang="ts">
import { Component, Prop, Vue, Watch } from "nuxt-property-decorator";
import { InterviewRecord } from "~/shared/models/appointment.model";
@Component
export default class InterviewCard extends Vue {
    @Prop()
    interviewList!: InterviewRecord[];
    futureList: InterviewRecord[] = [];
    pastList: InterviewRecord[] = [];
    appointmentId!: number;
    mounted() {
        this.appointmentId = +this.$route.params.appointmentId;
    }
    @Watch('interviewList', {immediate: true})
    onInterviewListChange() {
        if (this.interviewList.length > 0) {
            this.futureList = this.interviewList
            .filter(item => new Date(item.interviewDate).getTime() >= new Date().getTime())
            .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate));
          this.pastList = this.interviewList
            .filter(item =>  new Date(item.interviewDate).getTime() < new Date().getTime())
            .sort((preItem, nextItem) => +new Date(nextItem.interviewDate) - +new Date(preItem.interviewDate));
        }
    }
    editInterview(interviewRecord) {
        this.$router.push(`/appointment/${this.appointmentId}/interview/${interviewRecord.id}`);
    }
}
</script>
<style lang="scss" scoped>
.interview--future{
    .record{
        display: flex;
        justify-content: space-between;
        margin-bottom: 10px;
    }
}
.interview--past {
    border-top: 1px solid #CCCCCC;
    padding-top: 17px;
    margin-top: 17px;
}
.record-card {
    height: 62px;
    border: 1px solid #707070;
    border-radius: 5px;
    display: flex;
    border-bottom: 1px solid #000;
    .record-card-date{
        display: flex;
        flex-direction: column;
        margin-left: 10px;
        margin-right: 10px;
        margin-top: 10px;
    }
    .record-card-content{
        height: 42px;
        margin-top: 10px;
        margin-right: 10px;
        line-height: 1.2;
    }
  &.record-card--empty {
    align-items     : center;
    background-color: #fff;
    color           : $MID_GREY;
    justify-content : center;
  }
}
.line-space{
    letter-spacing: 1px;
}
</style>
PAMapp/components/Interview/InterviewMsg.vue
@@ -1,5 +1,5 @@
<template>
  <div>
  <div class="interview-msg-component">
    <el-dialog
      :visible.sync="dialogVisible"
      :width="dialogWidth"
@@ -14,10 +14,10 @@
      <el-input
        type="textarea"
        :rows="9"
        :autosize="true"
        placeholder="約訪通知"
        resize="none"
        v-model="interviewTxt">
        v-model="isInterviewTxt">
        </el-input>
      <div class="mdTxt mt-30 mb-10">預計約訪時段</div>
@@ -41,29 +41,23 @@
  </div>
</template>
<script lang="ts">
import { Vue, Component, Prop, PropSync, Emit, namespace } from 'nuxt-property-decorator';
import { Vue, Component, Prop, PropSync, Emit, Action, namespace } from 'nuxt-property-decorator';
import appointmentService from '~/shared/services/appointment.service';
import { Appointment, ToInformAppointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
import { AgentInfo } from '~/shared/models/agent-info.model';
const loginStore = namespace('login.store');
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewMsg extends Vue {
    @appointmentStore.Action
    getMyAppointmentList!: () => Promise<Appointment[]>;
    @Action
    storeMyAppointmentList!: () => Promise<number>;
    @appointmentStore.Action
    updateMyAppointmentList!:(appointment: Appointment) => Appointment[];
    @appointmentStore.Action
    updateAppointmentDetail!: (appointmentId: number) => Promise<Appointment>;
    @appointmentStore.State
    appointmentDetail!: Appointment;
    updateAppointmentDetail!: (id: number) => Appointment;
    @PropSync('isVisible')
    dialogVisible!: boolean;
@@ -79,12 +73,13 @@
        return;
    }
    interviewTime      = '';
    interviewTxt       = '';
    @loginStore.State
    loginConsultant!: AgentInfo;
    isShowSuccessAlert = false;
    contactStatus = ContactStatus;
    interviewTxt = "";
    interviewTime = '';
    //////////////////////////////////////////////////////////////////////
    addInterview() {
@@ -95,57 +90,58 @@
        message      : this.interviewTxt,
        phone        : this.client?.phone,
      };
      appointmentService.informAppointment(appointmentInformation).then((_) => {
        this.isShowSuccessAlert = true ;
        const updatedAppointment = {
          ...this.appointmentDetail,
          communicateStatus: this.contactStatus.CONTACTED,
        };
        this.updateMyAppointmentList(updatedAppointment);
        this.updateAppointmentDetail(updatedAppointment.id);
        this.updateAppointmentDetail(this.client.id);
      });
    }
    closeAllDialog() {
      this.isShowSuccessAlert = false ;
      this.dialogVisible = false;
      this.getMyAppointmentList();
      this.storeMyAppointmentList();
    }
    get isInterviewTxt() : string{
      return this.interviewTxt = "您好!我是保誠媒合平台的保險顧問" + this.loginConsultant?.name + ",感謝您的預約!我預計會在下述的時間與您聯繫"+"\n"+"以下是我的電話號碼/Email:"+"\n" + this.loginConsultant?.phoneNumber + "\n" + this.loginConsultant?.email + "\n"+"若此時間不方便,請與我聯繫!謝謝!"
    }
}
</script>
<style lang="scss" scoped>
.msg-dialog-title{
  display: flex;
  justify-content: center;
  margin-bottom:30px;
  color: $PRIMARY_BLACK;
}
.send-msg-nav{
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
  color: $PRIMARY_BLACK;
}
.el-dialog{
  width:90%
}
.el-textarea__inner{
  font-size: 20px;
  padding:10px;
  text-align: justify;
  font-weight: 500;
}
.msg-dialog-btn{
  margin-top: 30px;
  display: flex;
  justify-content: center;
}
.invite-review{
<style lang="scss" >
.interview-msg-component{
  .msg-dialog-title{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-bottom:30px;
    color: $PRIMARY_BLACK;
  }
  .send-msg-nav{
    display: flex;
    justify-content: space-between;
    margin-bottom: 10px;
    color: $PRIMARY_BLACK;
  }
  .el-dialog{
    width:90%
  }
  .el-textarea__inner{
    font-size: 20px;
    padding:10px;
    text-align: justify;
    font-weight: 600;
  }
  .msg-dialog-btn{
    margin-top: 30px;
    display: flex;
    justify-content: center;
  }
  .invite-review{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
}
</style>
PAMapp/components/Interview/InterviewRecordCard.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,99 @@
<template>
    <div class="record-log-component">
        <div v-for="(item, index) in noticeLogsList"
                :key="index">
                <section
                    class="record-log-card"
                >
                    <div class="record-log-card-date-container">
                        <div class="record-log-card-date-container-circle">
                            <div class="xxsTxt bold line-height">{{item.createdDate | formatYear}}</div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt bold line-height"
                                    :date="item.createdDate"
                                    onlyShowSection="DAY" />
                            </div>
                            <div>
                                <UiDateFormat
                                    class="xxsTxt mt-4 line-space"
                                    :date="item.createdDate"
                                    onlyShowSection="TIME" />
                            </div>
                        </div>
                    </div>
                        <div class="record-log-msg">
                            <div>發送約訪通知
                                <span v-if="item.email && item.phone">(手機簡訊、Email)</span>
                                <span v-else-if="item.email">(Email)</span>
                                <span v-else>(手機簡訊)</span>
                            </div>
                            <div class="mt-10">預約{{item.interviewDate | formatDate}}</div>
                        </div>
                </section>
                <div class="time-line"></div>
            </div>
    </div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from "nuxt-property-decorator";
import { NoticeLogs } from "~/shared/models/appointment.model";
@Component({
    filters: {
        formatYear(value) {
            if (value) {
                return new Date(value).getFullYear();
            }
        }
    }
})
export default class RecordCard extends Vue {
    @Prop()
    noticeLogsList!: NoticeLogs[];
}
</script>
<style lang="scss" scoped>
.record-log-component{
    display: flex;
    flex-direction: column;
    .record-log-card{
        display: flex;
        .record-log-card-date-container{
            position:relative;
            .record-log-card-date-container-circle{
                display: flex;
                flex-direction: column;
                width: 56px;
                height: 56px;
                border-radius: 50%;
                border:1px solid $PRIMARY_BLACK;
                justify-content: center;
                align-items: center;
                align-content: center;
            }
        }
    }
}
.mt-4{
    margin-top: 4px;
}
.line-space{
    letter-spacing: 1px;
}
.line-height{
    line-height:1.2;
}
.time-line{
    border-left: 1px solid black;
    height: 30px;
    margin-left: 28px;
}
.record-log-msg{
    margin-left: 13px;
    margin-top: 10px;
}
</style>
PAMapp/pages/appointment/_appointmentId/index.vue
@@ -91,12 +91,9 @@
</template>
<script lang="ts">
import { Context } from '@nuxt/types';
import { Vue, Component } from 'vue-property-decorator';
import { namespace } from 'nuxt-property-decorator';
import appointmentService from '~/shared/services/appointment.service';
import { Appointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
PAMapp/pages/appointment/_appointmentId/interviewList/index.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,38 @@
<template>
    <div>
        <div class="text--right mb-30">
            <div class="pam-link-button--lg"
            @click="addInterview">+新增</div>
        </div>
        <InterviewCard :interviewList="appointmentDetail.interviewRecordDTOs"></InterviewCard>
    </div>
</template>
<script lang="ts">
import { Component, namespace, Vue } from "nuxt-property-decorator";
import { Appointment } from "~/shared/models/appointment.model";
const appointmentStore = namespace('appointment.store');
@Component
export default class InterviewList extends Vue {
    @appointmentStore.State
    appointmentDetail!: Appointment;
    appointmentId!: number;
    ////////////////////////////////////////////////////////
    mounted() {
      this.appointmentId = +this.$route.params.appointmentId;
    }
    ////////////////////////////////////////////////////////
    addInterview(): void {
        this.$router.push(`/appointment/${this.appointmentId}/interview/new`);
    }
}
</script>
PAMapp/pages/appointment/_appointmentId/recordList/index.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,32 @@
<template>
    <InterviewRecordCard :noticeLogsList="displayLogs"></InterviewRecordCard>
</template>
<script lang="ts">
import { Component, namespace, Vue, Watch } from "nuxt-property-decorator";
import { Appointment, NoticeLogs } from "~/shared/models/appointment.model";
const appointmentStore = namespace('appointment.store');
@Component
export default class RecordList extends Vue {
    @appointmentStore.State
    appointmentDetail!: Appointment;
    displayLogs: NoticeLogs[] = [];
    ////////////////////////////////////////////////////////
    @Watch('appointmentDetail', {immediate: true})
    onAppointmentDetailChange() {
      if (this.appointmentDetail?.appointmentNoticeLogs.length) {
        this.displayLogs = this.appointmentDetail?.appointmentNoticeLogs
                            .map((i) => ({ ...i, sortDate: new Date(i.createdDate)}))
                            .sort((preItem, nextItem) => +nextItem.sortDate - +preItem.sortDate);
      }
    }
}
</script>
PAMapp/pages/consultantLogin/index.vue
@@ -61,8 +61,11 @@
  import { Role } from '~/shared/models/enum/Role';
  import messageBoxService from '~/shared/services/message-box.service';
  import loginService from '~/shared/services/login.service'
import { AgentInfo } from '~/shared/models/agent-info.model';
  const loginStore  = namespace('login.store');
  const roleStorage = namespace('localStorage');
  @Component({
    layout: 'home'
  })
@@ -75,6 +78,9 @@
    @roleStorage.Mutation
    storageConsultantId!:(id:string) => void;
    @loginStore.Action
    getLoginConsultantDetail!: (agentNo: string) => Promise<AgentInfo>;
    consultantDto = {
      password: '',
@@ -130,7 +136,7 @@
    private verify():void{
      loginService.getVerificationStatus(this.verificationCode).then( verifySuccess => {
        if(verifySuccess.data){
          this.loginWithConsultant()
          this.loginWithConsultant();
        }else{
          this.clearValue();
          this.regenerateImgOfVerification();
@@ -141,6 +147,7 @@
    private loginWithConsultant(): void {
      loginService.logInToConsultant(this.consultantDto).then(res => {
        this.getLoginConsultantDetail(this.consultantDto.username);
        this.storageIdToken(res.data.id_token);
        this.storageRole(Role.ADMIN);
        this.storageConsultantId(this.consultantDto.username)
PAMapp/shared/models/agent-info.model.ts
@@ -1,21 +1,22 @@
export interface AgentInfo {
  name            : string;
  agentNo         : string;
  role            : string;
  img             : string;
  avgScore        : number;
  title           : string;
  phoneNumber     : string;
  serveArea       : string;
  companyAddress  : string;
  latestLoginTime : Date  ;
  seniority       : string;
  suitability     : number;
  evaluation      : number;
  expertise       : string[];
  concept         : string;
  experiences     : string;
  awards          : string;
  gender            : string,
  agentNo           : string;
  avgScore          : number;
  awards            : string;
  communicationStyle: string;
  companyAddress    : string;
  concept           : string;
  email?            : string;
  evaluation        : number;
  experiences       : string;
  expertise         : string[];
  gender            : string,
  img               : string;
  latestLoginTime   : Date  ;
  name              : string;
  phoneNumber       : string;
  role              : string;
  seniority         : string;
  serveArea         : string;
  suitability       : number;
  title             : string;
}
PAMapp/store/appointment.store.ts
@@ -35,6 +35,11 @@
    .filter(item => item.communicateStatus === this.contactStatus.DONE || item.communicateStatus === this.contactStatus.CLOSE ).length;
  }
  get isCloseAppointment(): boolean {
    const closedStatusList = [this.contactStatus.DONE, this.contactStatus.CLOSE, this.contactStatus.CANCEL];
    return closedStatusList.includes(this.appointmentDetail!.communicateStatus);
  }
  //////////////////////////////////////////////////////////////////////
  @Mutation
@@ -63,7 +68,7 @@
  }
  @Action({ commit: 'SET_APPOINTMENT'})
  async setAppointmentDetail(appointmentId: number): Promise<Appointment> {
  async getAppointmentDetail(appointmentId: number): Promise<Appointment> {
    if (this.appointmentDetail && this.appointmentDetail.id === appointmentId) {
      return this.appointmentDetail;
    } else {
PAMapp/store/index.ts
@@ -1,18 +1,13 @@
import { StrictQueryParams } from '~/shared/models/strict-query.model';
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant';
import myConsultantService from '~/shared/services/my-consultant.service';
import queryConsultantService from '~/shared/services/query-consultant.service';
import appointmentService from '~/shared/services/appointment.service';
import reviewsService from '~/shared/services/reviews.service';
import { Consultant } from '~/shared/models/consultant.model';
import { Appointment, AppointmentLog, InterviewRecord } from '~/shared/models/appointment.model';
import { AgentOfStrictQuery } from '~/shared/models/strict-query.model';
import { AgentInfo } from '~/shared/models/agent-info.model';
import { agentCommunicationStyleList } from '~/shared/const/agent-communication-style-list';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant';
import { AppointmentLog } from '~/shared/models/appointment.model';
import { AgentOfStrictQuery, StrictQueryParams } from '~/shared/models/strict-query.model';
@Module
export default class Store extends VuexModule {
    recommendList: Consultant[] = [];
@@ -20,16 +15,6 @@
    myConsultantList: Consultant[] = [];
    myAppointmentReviewLogList: AppointmentLog[] = [];
    interviewRecord: InterviewRecord = {
        appointmentId   : 0,
        content         : '',
        createdBy       : '',
        createdDate     : '',
        id              : 0,
        interviewDate   : '',
        lastModifiedBy  : '',
        lastModifiedDate: ''
    }
    get isUserLogin() {
        return this.context.getters['localStorage/isUserLogin'];
@@ -53,25 +38,6 @@
    @Mutation
    updateMyAppointmentReviewLog(data: AppointmentLog[]) {
        this.myAppointmentReviewLogList = data;
    }
    @Mutation
    updateInterviewRecord(data: InterviewRecord) {
        this.interviewRecord = data;
    }
    @Mutation
    clearInterviewRecord() {
        this.interviewRecord = {
            appointmentId   : 0,
            content         : '',
            createdBy       : '',
            createdDate     : '',
            id              : 0,
            interviewDate   : '',
            lastModifiedBy  : '',
            lastModifiedDate: ''
        }
    }
    @Action
PAMapp/store/localStorage.ts
@@ -75,6 +75,7 @@
    localStorage.removeItem('current_role');
    localStorage.removeItem('consultant_id');
    localStorage.removeItem('appointment');
    localStorage.removeItem('login_consultant');
    this.id_token = localStorage.getItem('id_token');
    this.current_role = localStorage.getItem('current_role');
    this.consultant_id = localStorage.getItem('consultant_id');
PAMapp/store/login.store.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,28 @@
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import myConsultantService from '~/shared/services/my-consultant.service';
import { AgentInfo } from '~/shared/models/agent-info.model';
@Module
export default class AppointmentStore extends VuexModule {
  loginConsultant?: AgentInfo = JSON.parse(localStorage.getItem('login_consultant')!);
  //////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////////////////////////////////
  @Mutation
  SET_LOGIN_CONSULTANT(agentInfo: AgentInfo): void {
    this.loginConsultant = agentInfo;
    localStorage.setItem('login_consultant', JSON.stringify(agentInfo));
  }
  //////////////////////////////////////////////////////////////////////
  @Action({ commit: 'SET_LOGIN_CONSULTANT' })
  async getLoginConsultantDetail(agentNo: string): Promise<AgentInfo> {
    return await myConsultantService.getConsultantDetail(agentNo).then((res) => res);
  }
}