保誠-保戶業務員媒合平台
Mila
2021-12-22 bdae23a40c461c2c6b6ee614f661eac731c949c8
Merge branch 'master' of https://192.168.0.10:8443/r/pcalife/PAM

# Conflicts:
# PAMapp/components/QuickFilter/QuickFilterSelector.vue
# PAMapp/pages/quickFilter/index.vue
# PAMapp/shared/const/quickFilter-questionList.ts
刪除12個檔案
修改46個檔案
新增9個檔案
修改28個檔案名稱
1186 ■■■■ 已變更過的檔案
PAMapp/assets/ts/api/appointment.ts 29 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/api/consultant.ts 164 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/api/share.ts 67 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/UserReviewsConsultantsParams.ts 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/agentOfStrictQuery.ts 12 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/appointmentParams.ts 12 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/enum/Role.ts 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/fastQueryParams.model.ts 7 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/models/strictQueryParams.ts 12 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/services/my-consultant.service.ts 29 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/services/pamService.service.ts 90 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/services/query-consultant.service.ts 14 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/AddAndReservedBtns.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/BackActionBar.vue 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientCard.vue 11 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantCard.vue 15 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantSwiper.vue 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/NavBar.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterSelector.vue 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiField.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiPagination.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/popUpFrame.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/accountSetting/index.vue 9 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/agentInfo/_agentNo.vue 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/consultantLogin/index.vue 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/faq.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/index.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/login/index.vue 16 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList/appointmentList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList/contactedList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList/consultantList.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList/contactedList.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/questionnaire/_agentNo.vue 21 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/quickFilter/index.vue 12 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/recommendConsultant/index.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/recommendConsultant/result.vue 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/record/index.vue 18 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/userReviews/index.vue 16 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/userReviewsRecord/index.vue 16 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/const/faqList.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/const/hide-reviews.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/const/quickFilter-questionList.ts 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/device.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/errorService.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/ConsultantLoginInfo.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/account.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/agent-info.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/appointment.model.ts 39 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/client.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/clientInfo.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/consultant.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/editAppointmentParams.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/enum/ContactType.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/enum/Gender.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/enum/Role.ts 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/enum/otpErrorCode.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/enum/seniority.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/loginRequest.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/loginSuccessToken.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/loginVerify.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/otpInfo.model.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/quick-filter.model.ts 14 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/registerInfo.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/reviews.model.ts 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/models/strict-query.model.ts 24 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/account-setting.service.ts 17 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/appointment.service.ts 18 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/httpClient.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/login.service.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/my-consultant.service.ts 42 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/query-consultant.service.ts 37 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/reviews.service.ts 18 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/storageConsultant.ts 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/storageRequests.ts 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/index.ts 36 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/localStorage.ts 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/sql/20211222_w.sql 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java 27 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/domain/Consultant.java 16 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/SendMsgService.java 136 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/SendMailResponse.java 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java 10 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/TestSendMsgResource.java 28 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SendSMSFailException.java 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-dev.yml 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-pollex.yml 11 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-sit.yml 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-uat.yml 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/templates/mail/appointmentNotifyEmail.html 15 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/api/appointment.ts
Àɮפw§R°£
PAMapp/assets/ts/api/consultant.ts
Àɮפw§R°£
PAMapp/assets/ts/api/share.ts
Àɮפw§R°£
PAMapp/assets/ts/models/UserReviewsConsultantsParams.ts
Àɮפw§R°£
PAMapp/assets/ts/models/agentOfStrictQuery.ts
Àɮפw§R°£
PAMapp/assets/ts/models/appointmentParams.ts
Àɮפw§R°£
PAMapp/assets/ts/models/enum/Role.ts
Àɮפw§R°£
PAMapp/assets/ts/models/fastQueryParams.model.ts
Àɮפw§R°£
PAMapp/assets/ts/models/strictQueryParams.ts
Àɮפw§R°£
PAMapp/assets/ts/services/my-consultant.service.ts
Àɮפw§R°£
PAMapp/assets/ts/services/pamService.service.ts
Àɮפw§R°£
PAMapp/assets/ts/services/query-consultant.service.ts
Àɮפw§R°£
PAMapp/components/AddAndReservedBtns.vue
@@ -12,7 +12,7 @@
<script lang="ts">
import { Vue, Component, Prop, Emit, Action, State, namespace } from 'nuxt-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { Consultant } from '~/shared/models/consultant.model';
const localStorage = namespace('localStorage');
@Component
@@ -41,4 +41,4 @@
                ? true : false
    }
}
</script>
</script>
PAMapp/components/BackActionBar.vue
@@ -11,13 +11,14 @@
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component,} from 'vue-property-decorator';
import * as _ from 'lodash';
import { Role } from '~/assets/ts/models/enum/role';
import { Role } from '~/shared/models/enum/role';
const roleStorage = namespace('localStorage');
@Component
export default class UiCarousel extends Vue {
  @roleStorage.Getter currentRole!:string;
  get label(): string {
    if (this.$route.name) {
      const routeName = this.$route.name.split('-')[0];
      let featureLabel = '';
PAMapp/components/Client/ClientCard.vue
@@ -96,10 +96,11 @@
<script lang="ts">
import { Vue, Component, Prop, Action } from 'nuxt-property-decorator';
import appointmentService from '~/assets/ts/services/appointment.service';
import { isMobileDevice } from '~/assets/ts/device';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { ClientInfo } from '~/assets/ts/models/client.model';
import appointmentService from '~/shared/services/appointment.service';
import { isMobileDevice } from '~/shared/device';
import { hideReviews } from '~/shared/const/hide-reviews';
import { ClientInfo } from '~/shared/models/client.model';
import myConsultantService from '~/shared/services/my-consultant.service';
@Component({
@@ -198,7 +199,7 @@
    }
    markAppointment() {
        appointmentService.markAsContact(this.client.id).then(data => {
        myConsultantService.markAsContact(this.client.id).then(data => {
            // TODO: è¦æŽ¥å¾Œå°å‚³å›žçš„ updated client è³‡æ–™ - Ben 2021/11/16
            const updatedClient = {...this.client};
            updatedClient.communicateStatus = 'contacted';
PAMapp/components/Client/ClientList.vue
@@ -18,7 +18,7 @@
<script lang='ts'>
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { ClientInfo } from '~/assets/ts/models/client.model';
import { ClientInfo } from '~/shared/models/client.model';
@Component
export default class ClientList extends Vue {
PAMapp/components/Consultant/ConsultantCard.vue
@@ -129,12 +129,13 @@
<script lang="ts">
import { Vue, Component, Prop, Action, namespace } from 'nuxt-property-decorator';
import appointmentService from '~/assets/ts/services/appointment.service';
import { isMobileDevice } from '~/assets/ts/device';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { UserReviewsConsultantsParams, userReviewsConsultants } from '~/assets/ts/api/consultant';
import { Consultant, ConsultantWithAppointmentId } from '~/assets/ts/models/consultant.model';
import { Appointment } from '~/assets/ts/models/appointment.model';
import appointmentService from '~/shared/services/appointment.service';
import reviewsService from '~/shared/services/reviews.service';
import { isMobileDevice } from '~/shared/device';
import { hideReviews } from '~/shared/const/hide-reviews';
import { Consultant, ConsultantWithAppointmentId } from '~/shared/models/consultant.model';
import { Appointment } from '~/shared/models/appointment.model';
import { UserReviewsConsultantsParams } from '~/shared/models/reviews.model';
const localStorage = namespace('localStorage');
@Component({
@@ -354,7 +355,7 @@
        }
        this.appointmentDetail.satisfactionScore = this.inputScore;
        userReviewsConsultants(reviewParams).then((res) => {
        reviewsService.userReviewsConsultants(reviewParams).then((res) => {
            this.reviewsBtn = false;
            this.storeConsultantList();
        });
PAMapp/components/Consultant/ConsultantList.vue
@@ -24,7 +24,7 @@
<script lang="ts">
import { Vue, Component, Prop, namespace } from 'nuxt-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { Consultant } from '~/shared/models/consultant.model';
const roleStorage = namespace('localStorage');
PAMapp/components/Consultant/ConsultantSwiper.vue
@@ -29,8 +29,8 @@
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
import { SwiperOptions } from 'swiper';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { Consultant } from '~/shared/models/consultant.model';
import { hideReviews } from '~/shared/const/hide-reviews';
@Component
export default class UiSwiper extends Vue {
@@ -38,7 +38,7 @@
    @Prop() agents!: Consultant[];
    hideReviews = hideReviews ;
    swiperOptions: SwiperOptions = {
      loop: false,
        slideToClickedSlide: false,
PAMapp/components/NavBar.vue
@@ -34,7 +34,7 @@
<script lang="ts">
  import { Vue, Component } from 'vue-property-decorator';
  import { namespace } from 'nuxt-property-decorator';
  import { Role } from '~/assets/ts/models/enum/role';
  import { Role } from '~/shared/models/enum/role';
  import * as _ from 'lodash';
  const roleStorage = namespace('localStorage');
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue
@@ -82,8 +82,8 @@
<script lang="ts">
import { ElCarousel } from 'element-ui/types/carousel';
import { Vue, Component, Prop } from 'vue-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { Consultant } from '~/shared/models/consultant.model';
import { hideReviews } from '~/shared/const/hide-reviews';
@Component
export default class QuickFilterConsultantList extends Vue {
@@ -219,4 +219,4 @@
        }
    }
</style>
</style>
PAMapp/components/QuickFilter/QuickFilterSelector.vue
@@ -98,8 +98,8 @@
<script lang="ts">
import { Vue, Component, Prop, Watch, Emit } from 'nuxt-property-decorator';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { FastQueryParams, QuestionOption, Selected } from '~/assets/ts/models/quickFilter.model';
import { hideReviews } from '~/shared/const/hide-reviews';
import { FastQueryParams, QuestionOption, Selected } from '~/shared/models/quick-filter.model';
@Component
export default class QuickFilterDrawer extends Vue {
    pickedItem: FastQueryParams = {
@@ -227,4 +227,4 @@
        }
    }
</style>
</style>
PAMapp/components/Ui/UiField.vue
@@ -13,7 +13,7 @@
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
import { isMobileDevice } from '~/assets/ts/device';
import { isMobileDevice } from '~/shared/device';
@Component
export default class UiField extends Vue {
PAMapp/components/Ui/UiPagination.vue
@@ -12,7 +12,7 @@
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'nuxt-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { Consultant } from '~/shared/models/consultant.model';
@Component
export default class UiPagination extends Vue {
PAMapp/components/popUpFrame.vue
@@ -15,7 +15,7 @@
<script lang="ts">
  import { Vue, Component, Prop,Emit, Watch, PropSync} from 'vue-property-decorator';
  import { isMobileDevice } from '../assets/ts/device';
  import { isMobileDevice } from '~/shared/device';
  @Component
  export default class PopUpFrame extends Vue {
    @PropSync('isOpen',{type:Boolean,default:false}) syncIsOpen!:boolean;
@@ -32,7 +32,7 @@
    private get isUseDialog() : boolean {
      return this.syncIsOpen && !isMobileDevice();
    }
    private set isUseDialog(value: boolean) {
      this.$emit('update:isOpen',value);
    }
PAMapp/pages/accountSetting/index.vue
@@ -70,8 +70,9 @@
<script lang="ts">
import { Vue,Component } from 'vue-property-decorator'
import { getUserAccountSetting, updateAccountSetting } from '~/assets/ts/api/consultant';
import { UserSetting } from '~/assets/ts/models/account.model';
import { UserSetting } from '~/shared/models/account.model';
import accountSettingService from '~/shared/services/account-setting.service';
@Component
export default class AccountSetting extends Vue {
@@ -139,7 +140,7 @@
                phone: this.phoneValue,
                email: this.emailValue
            }
            updateAccountSetting(editSettingInfo).then((res: any) => {
            accountSettingService.updateAccountSetting(editSettingInfo).then((res: any) => {
                console.log('updateRes:', res);
                this.resetSettingForm();
            });
@@ -153,7 +154,7 @@
        }
        mounted(){
            getUserAccountSetting().then((userInfo: UserSetting)=>{
            accountSettingService.getUserAccountSetting().then((userInfo: UserSetting)=>{
                this._userSetting = {
                    name: userInfo.name || '',
                    phone: userInfo.phone || '',
PAMapp/pages/agentInfo/_agentNo.vue
@@ -190,10 +190,10 @@
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component } from 'vue-property-decorator';
import myConsultantService from '~/assets/ts/services/my-consultant.service';
import { AgentInfo } from '~/assets/ts/models/agent-info.model';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { Role } from '~/assets/ts/models/enum/role';
import myConsultantService from '~/shared/services/my-consultant.service';
import { AgentInfo } from '~/shared/models/agent-info.model';
import { hideReviews } from '~/shared/const/hide-reviews';
import { Role } from '~/shared/models/enum/role';
const roleStorage = namespace('localStorage');
PAMapp/pages/consultantLogin/index.vue
@@ -58,9 +58,9 @@
<script lang="ts">
  import { Vue, Component , namespace } from 'nuxt-property-decorator';
  import { AxiosError } from 'axios';
  import { Role } from '~/assets/ts/models/enum/role';
  import ErrorMessageBox from '~/assets/ts/errorService';
  import loginService from '~/assets/ts/services/login.service'
  import { Role } from '~/shared/models/enum/role';
  import ErrorMessageBox from '~/shared/errorService';
  import loginService from '~/shared/services/login.service'
  const roleStorage = namespace('localStorage');
  @Component({
PAMapp/pages/faq.vue
@@ -9,7 +9,7 @@
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator';
import { faqList } from '~/assets/ts/const/faqList';
import { faqList } from '~/shared/const/faqList';
@Component
export default class Faq extends Vue {
@@ -22,4 +22,4 @@
}
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped></style>
PAMapp/pages/index.vue
@@ -44,7 +44,7 @@
<script lang="ts">
  import { Vue, Component, State, Action, Watch, namespace } from 'nuxt-property-decorator';
  import { Consultant } from '~/assets/ts/models/consultant.model';
  import { Consultant } from '~/shared/models/consultant.model';
  const localStorage = namespace('localStorage');
  @Component({
PAMapp/pages/login/index.vue
@@ -335,14 +335,14 @@
<script lang="ts">
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component, Ref } from 'vue-property-decorator';
import ErrorMessageBox from '~/assets/ts/errorService';
import { OtpErrorCode } from '~/assets/ts/models/enum/otpErrorCode';
import { Role } from '~/assets/ts/models/enum/role';
import { LoginRequest } from '~/assets/ts/models/loginRequest.model';
import { LoginVerify } from '~/assets/ts/models/loginVerify.model';
import { OtpInfo } from '~/assets/ts/models/otpInfo.model';
import { RegisterInfo } from '~/assets/ts/models/registerInfo';
import loginService from '~/assets/ts/services/login.service';
import ErrorMessageBox from '~/shared/errorService';
import { OtpErrorCode } from '~/shared/models/enum/otpErrorCode';
import { Role } from '~/shared/models/enum/role';
import { LoginRequest } from '~/shared/models/loginRequest.model';
import { LoginVerify } from '~/shared/models/loginVerify.model';
import { OtpInfo } from '~/shared/models/otpInfo.model';
import { RegisterInfo } from '~/shared/models/registerInfo';
import loginService from '~/shared/services/login.service';
const roleStorage = namespace('localStorage');
PAMapp/pages/myAppointmentList.vue
@@ -45,7 +45,7 @@
import * as _ from 'lodash';
import { ClientInfo } from '~/assets/ts/models/client.model';
import { ClientInfo } from '~/shared/models/client.model';
@Component({
    layout: 'home',
PAMapp/pages/myAppointmentList/appointmentList.vue
@@ -25,7 +25,7 @@
<script lang="ts">
import { Vue, Component, State, Watch } from 'nuxt-property-decorator';
import { ClientInfo } from '~/assets/ts/models/client.model';
import { ClientInfo } from '~/shared/models/client.model';
@Component
export default class ClientReservedList extends Vue {
PAMapp/pages/myAppointmentList/contactedList.vue
@@ -29,7 +29,7 @@
<script lang="ts">
import { Vue, Component, Watch, State } from 'nuxt-property-decorator';
import { ClientInfo } from '~/assets/ts/models/client.model';
import { ClientInfo } from '~/shared/models/client.model';
@Component
export default class ClientContactedList extends Vue {
PAMapp/pages/myConsultantList.vue
@@ -26,7 +26,7 @@
<script lang='ts'>
import { Vue, Component, Watch, State, Action } from 'nuxt-property-decorator';
import { Consultant, ConsultantWithAppointmentId } from '~/assets/ts/models/consultant.model';
import { Consultant, ConsultantWithAppointmentId } from '~/shared/models/consultant.model';
@Component
export default class myConsultantList extends Vue {
PAMapp/pages/myConsultantList/consultantList.vue
@@ -14,7 +14,7 @@
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { Consultant } from '~/shared/models/consultant.model';
@Component
@@ -27,4 +27,4 @@
    }
}
</script>
</script>
PAMapp/pages/myConsultantList/contactedList.vue
@@ -14,7 +14,7 @@
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator' ;
import { Consultant } from '~/assets/ts/models/consultant.model';
import { Consultant } from '~/shared/models/consultant.model';
@Component
@@ -26,4 +26,4 @@
        this.pageList = pageList;
    }
}
</script>
</script>
PAMapp/pages/questionnaire/_agentNo.vue
@@ -140,13 +140,16 @@
<script lang="ts">
import { Vue, Component, State, Action, Watch, namespace } from 'nuxt-property-decorator';
import { addFavoriteConsultant, appointmentDemand, AppointmentParams, AppointmentRequests ,editAppointment } from '~/assets/ts/api/consultant';
import { getRequestQuestionFromStorage, getRequestsFromStorage, removeRequestQuestionFromStorage, setRequestsToStorage } from '~/assets/ts/storageRequests';
import { getRequestsFromStorage, removeRequestQuestionFromStorage, setRequestsToStorage } from '~/shared/storageRequests';
import _ from 'lodash';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { ContactType } from '~/assets/ts/models/enum/ContactType';
import { Gender } from '~/assets/ts/models/enum/Gender';
import { RegisterInfo } from '~/assets/ts/models/registerInfo';
import queryConsultantService from '~/shared/services/query-consultant.service';
import appointmentService from '~/shared/services/appointment.service';
import { Consultant } from '~/shared/models/consultant.model';
import { ContactType } from '~/shared/models/enum/ContactType';
import { Gender } from '~/shared/models/enum/Gender';
import { RegisterInfo } from '~/shared/models/registerInfo';
import { AppointmentParams, AppointmentRequests } from '~/shared/models/appointment.model';
  const roleStorage = namespace('localStorage');
  @Component
@@ -350,7 +353,7 @@
      if (this.isEditBtn) {
        this.sentEditAppointmentDemand();
      } else {
        addFavoriteConsultant([this.$route.params.agentNo]).then(res => this.sentAppointmentDemand());
        queryConsultantService.addFavoriteConsultant([this.$route.params.agentNo]).then(res => this.sentAppointmentDemand());
      }
    }
@@ -363,7 +366,7 @@
          agentNo: this.$route.params.agentNo
        };
        appointmentDemand(data).then(res => {
        queryConsultantService.appointmentDemand(data).then(res => {
            this.sendReserve = true;
            this.myRequest.hopeContactTime = [];
            setRequestsToStorage(this.myRequest);
@@ -378,7 +381,7 @@
          id: this.appointmentId,
          otherRequirement: null
        }
        editAppointment(info).then(res => {
        appointmentService.editAppointment(info).then(res => {
          this.sendReserve = true;
          this.myRequest.hopeContactTime = [];
          setRequestsToStorage(this.myRequest);
PAMapp/pages/quickFilter/index.vue
@@ -70,10 +70,10 @@
<script lang="ts">
import { Vue, Component, namespace } from 'nuxt-property-decorator';
import { Consultant } from '~/assets/ts/models/consultant.model';
import { questionList } from '~/assets/ts/const/quickFilter-questionList';
import { FastQueryParams, QuestionOption, Selected } from '~/assets/ts/models/quickFilter.model';
import { fastQuery } from '~/assets/ts/api/consultant';
import { Consultant } from '~/shared/models/consultant.model';
import { FastQueryParams, QuestionOption, Selected } from '~/shared/models/quick-filter.model';
import queryConsultantService from '~/shared/services/query-consultant.service';
import { questionList } from '~/shared/const/quickFilter-questionList';
const localStorage = namespace('localStorage');
@Component({
@@ -196,7 +196,7 @@
            seniority: this.getSeniority()
        }
        fastQuery(data).then((consultantList) => {
        queryConsultantService.fastQuery(data).then((consultantList) => {
            this.consultantList = consultantList;
            this.storageQuickFilter(JSON.stringify(this.confirmItem))
        })
@@ -260,4 +260,4 @@
        }
    }
</style>
</style>
PAMapp/pages/recommendConsultant/index.vue
@@ -95,7 +95,7 @@
    State
  } from 'nuxt-property-decorator';
  import * as _ from 'lodash';
  import { Seniority } from '~/assets/ts/models/enum/seniority';
  import { Seniority } from '~/shared/models/enum/seniority';
  const localStorage = namespace('localStorage');
PAMapp/pages/recommendConsultant/result.vue
@@ -83,8 +83,8 @@
</template>
<script lang="ts">
import {Vue,Component, State, namespace, Action} from 'nuxt-property-decorator';
import { AgentOfStrictQuery } from '~/assets/ts/api/consultant';
import { hideReviews } from '~/assets/ts/const/hide-reviews';
import { hideReviews } from '~/shared/const/hide-reviews';
import { AgentOfStrictQuery } from '~/shared/models/strict-query.model';
const localStorage = namespace('localStorage');
PAMapp/pages/record/index.vue
@@ -12,7 +12,7 @@
    </section>
    <section class="user-reviews-content">
        <div
        <div
            class="user-reviews-card"
            v-for="(appointmentLog, index) in myAppointmentReviewLogList"
            :key="index">
@@ -24,25 +24,25 @@
            </div>
            <div class="user-reviews-card-date">
                <div class="date">
                    <UiDateFormat
                    <UiDateFormat
                        :date="appointmentLog.lastModifiedDate"
                        onlyShowSection="DAY" />
                </div>
                <div class="time">
                    <UiDateFormat
                    <UiDateFormat
                        :date="appointmentLog.lastModifiedDate"
                        onlyShowSection="TIME" />
                </div>
            </div>
        </div>
    </section>
</div>
</template>
<script lang="ts">
import { Vue, Component, Action, State, namespace } from 'nuxt-property-decorator';
import { AppointmentLog } from '~/assets/ts/models/appointment.model';
import { AppointmentLog } from '~/shared/models/appointment.model';
const roleStorage = namespace('localStorage');
@@ -63,8 +63,8 @@
        this.storeMyAppointmentReviewLog();
    }
}
</script>
<style lang="scss" scoped>
@@ -101,7 +101,7 @@
                width:52px;
                .date{
                    margin-bottom: 2px;
                }
            }
        }
@@ -112,4 +112,4 @@
        flex: 1;
    }
}
</style>
</style>
PAMapp/pages/userReviews/index.vue
@@ -1,9 +1,9 @@
<template>
<template>
<div class="reviews-page">
    <!-- é¡§å®¢ç™¼é€æ»¿æ„åº¦çµ¦é¡§å• -->
    <div class="reviews-banner"></div>
    <section class="reviews-container">
    <section class="reviews-container">
        <section class="reviews-header">
            <div class="reviews-header-container">
                <div class="reviews-header-title">滿意度調查</div>
@@ -20,17 +20,17 @@
                    <div class="card-txt">
                        å°æ–¼é¡§å•
                        <span class="p">{{item.name}}</span>的整體服務,您給予幾顆星的評價?
                        <div
                        <div
                            class="card-score"
                            v-if="!isMobileDevice">
                            <el-rate class="user-reviews-rate" v-model="item.avgScore"></el-rate>
                        </div>
                    </div>
                </div>
                <div
                <div
                    class="card-score"
                    v-if="isMobileDevice">
                    <el-rate
                    <el-rate
                        class="user-reviews-rate"
                        v-model="item.avgScore"></el-rate>
                </div>
@@ -49,13 +49,13 @@
            <el-button type="primary" class="reviews-dialog-btn" @click.native="reviewsDialogCheck">我知道了</el-button>
        </div>
    </PopUpFrame>
</div>
</template>
<script lang="ts">
import { Vue,Component } from 'vue-property-decorator'
import { isMobileDevice } from '~/assets/ts/device';
import { isMobileDevice } from '~/shared/device';
@Component({
@@ -209,4 +209,4 @@
}
</style>
</style>
PAMapp/pages/userReviewsRecord/index.vue
@@ -12,7 +12,7 @@
    </section>
    <section class="user-reviews-content">
        <div
        <div
            class="user-reviews-card"
            v-for="(appointmentLog, index) in myAppointmentReviewLogList"
            :key="index">
@@ -24,25 +24,25 @@
            </div>
            <div class="user-reviews-card-date">
                <div class="date">
                    <UiDateFormat
                    <UiDateFormat
                        :date="appointmentLog.lastModifiedDate"
                        onlyShowSection="DAY" />
                </div>
                <div class="time">
                    <UiDateFormat
                    <UiDateFormat
                        :date="appointmentLog.lastModifiedDate"
                        onlyShowSection="TIME" />
                </div>
            </div>
        </div>
    </section>
</div>
</template>
<script lang="ts">
import { Vue, Component, Action, State, namespace } from 'nuxt-property-decorator';
import { AppointmentLog } from '~/assets/ts/models/appointment.model';
import { AppointmentLog } from '~/shared/models/appointment.model';
const roleStorage = namespace('localStorage');
@@ -62,7 +62,7 @@
    mounted() {
        this.storeMyAppointmentReviewLog();
    }
}
</script>
<style lang="scss" scoped>
@@ -99,7 +99,7 @@
                width:52px;
                .date{
                    margin-bottom: 2px;
                }
            }
        }
@@ -110,4 +110,4 @@
        flex: 1;
    }
}
</style>
</style>
PAMapp/shared/const/faqList.ts
PAMapp/shared/const/hide-reviews.ts
PAMapp/shared/const/quickFilter-questionList.ts
File was renamed from PAMapp/assets/ts/const/quickFilter-questionList.ts
@@ -1,5 +1,6 @@
import { Seniority } from "../models/enum/seniority";
import { QuestionOption } from "../models/quickFilter.model";
import { QuestionOption } from "../models/quick-filter.model";
export const questionList: QuestionOption[] = [
    {
PAMapp/shared/device.ts
PAMapp/shared/errorService.ts
PAMapp/shared/models/ConsultantLoginInfo.ts
PAMapp/shared/models/account.model.ts
PAMapp/shared/models/agent-info.model.ts
PAMapp/shared/models/appointment.model.ts
File was renamed from PAMapp/assets/ts/models/appointment.model.ts
@@ -55,4 +55,41 @@
  customerId       : number;
  name             : string;
}
export interface AppointmentParams {
  phone          : string;
  email          : string;
  contactType    : string;
  gender         : string;
  age            : string;
  job            : string;
  requirement    : string;
  hopeContactTime: string;
  agentNo        : string;
}
export interface EditAppointmentParams {
  id              : number,
  phone           : string,
  email           : string,
  contactType     : string,
  gender          : string,
  age             : string,
  job             : string,
  requirement     : string,
  hopeContactTime : string,
  otherRequirement: null
}
export interface AppointmentRequests {
  phone          : string,
  email          : string,
  contactType    : string,
  gender         : string,
  age            : string,
  job            : string,
  requirement    : string[],
  hopeContactTime: ContactTime[],
  agentNo        : string,
}
export interface ContactTime {
selectWeekOptions : string[],
selectTimesOptions: string[]
}
PAMapp/shared/models/client.model.ts
PAMapp/shared/models/clientInfo.ts
PAMapp/shared/models/consultant.model.ts
PAMapp/shared/models/editAppointmentParams.model.ts
PAMapp/shared/models/enum/ContactType.ts
PAMapp/shared/models/enum/Gender.ts
PAMapp/shared/models/enum/Role.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,5 @@
export enum Role{
    ADMIN     = 'admin',
    USER      = 'user',
    NOT_LOGIN = ''
}
PAMapp/shared/models/enum/otpErrorCode.ts
PAMapp/shared/models/enum/seniority.ts
PAMapp/shared/models/loginRequest.model.ts
PAMapp/shared/models/loginSuccessToken.model.ts
PAMapp/shared/models/loginVerify.model.ts
PAMapp/shared/models/otpInfo.model.ts
PAMapp/shared/models/quick-filter.model.ts
File was renamed from PAMapp/assets/ts/models/quickFilter.model.ts
@@ -1,13 +1,13 @@
export interface QuestionOption {
    title: string;
    title : string;
    detail: Detail[];
    type: string;
    name: string;
    type  : string;
    name  : string;
}
interface Detail {
    value: string;
    name?: string;
    value    : string;
    name?    : string;
    className: string;
    subTitle?: string
}
@@ -23,5 +23,5 @@
export interface Selected {
    option: string;
    value: any;
}
    value : any;
}
PAMapp/shared/models/registerInfo.ts
PAMapp/shared/models/reviews.model.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,4 @@
export interface UserReviewsConsultantsParams{
  appointmentId: number,
  score        : number,
}
PAMapp/shared/models/strict-query.model.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,24 @@
export interface StrictQueryParams {
  gender          : string;
  avgScore        : number;
  status          : string;    //phase 1 disable
  area            : string;
  requirements    : string[];
  otherRequirement: string;
  seniority       : string;
  popularTags     : string[];
  otherPopularTags: string;
}
export interface AgentOfStrictQuery {
  agentNo      : string;
  name         : string;
  img          : string;
  expertise    : string[];
  avgScore     : number;
  contactStatus: null;
  updateTime   : null;
  seniority    : string;
  new          : boolean;
}
PAMapp/shared/services/account-setting.service.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,17 @@
import { http } from "./httpClient";
import { UserSetting } from "~/shared/models/account.model";
class AccountSettingService{
  //取得使用者帳號資訊
  async getUserAccountSetting() : Promise<UserSetting> {
    return http.get<UserSetting>('/customer/info').then(res => res.data);
  }
  //更新使用者帳號資訊
  async updateAccountSetting(params: any) : Promise<any> {
    return http.put('/customer/info', params ).then(res => res.data);
  }
}
export default new AccountSettingService();
PAMapp/shared/services/appointment.service.ts
File was renamed from PAMapp/assets/ts/services/appointment.service.ts
@@ -1,7 +1,7 @@
import { http } from "./httpClient";
import { ClientInfo } from "../models/client.model";
import { AppointmentDetail } from "../models/appointment.model";
import { ClientInfo } from "~/shared/models/client.model";
import { AppointmentDetail, EditAppointmentParams } from "~/shared/models/appointment.model";
class AppointmentService {
@@ -16,16 +16,9 @@
    });
  }
  // é¡§å•ç™»å…¥é¡¯ç¤ºæ–°é ç´„單筆數後觸發
  private viewAllAppointment(): void {
    http.post('/consultant/record/allAppointmentsView').then();
  }
  // æ¨™è¨˜ç‚ºå·²è¯çµ¡
  markAsContact(appointmentId: number): Promise<void> {
      // TODO: è·Ÿå¾Œç«¯ç¢ºèªï¼Œé€™è£¡çš„ API ä¸æ‡‰è©²å‚³å›ž void, è€Œæ˜¯æ‡‰è©²æ˜¯æ›´æ–°å¾Œçš„資料 - Ben 2021/11/16
      // return http.post('/appointment/markAsContacted/'+appointmentId, undefined, {headers})
      //         .then(res => res.data)
      return http.post(`/appointment/markAsContacted/${appointmentId}`);
  }
  // è®€å–預約單時觸發,紀錄讀取預約單時間
@@ -43,6 +36,11 @@
    return http.delete(`/appointment/${appointmentId}`);
  }
  // ç·¨è¼¯é ç´„
  editAppointment(editAppointmentParams: EditAppointmentParams) {
    return http.put('/appointment', editAppointmentParams);
  }
}
export default new AppointmentService();
PAMapp/shared/services/httpClient.ts
PAMapp/shared/services/login.service.ts
PAMapp/shared/services/my-consultant.service.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,42 @@
import { http } from "./httpClient";
import { AgentInfo } from '~/shared/models/agent-info.model';
import { Consultant } from "../models/consultant.model";
class MyConsultantService {
  async getFavoriteConsultantList(): Promise<Consultant[]> {
    return http.get<Consultant[]>('/consultant/favorite').then((res) => {
      const hasNewConsultant = res.data.find((consultant) => !consultant.customerViewTime);
      if (hasNewConsultant) {
        this.viewMyConsultantList();
      };
      return res.data;
    });
  }
  private viewMyConsultantList(): void {
    http.post('/consultant/favorite/view');
  }
  //顧問詳細資訊
  async getConsultantDetail(agentNo:string): Promise<AgentInfo> {
    return http.get('/consultant/detail', {params:{agentNo:agentNo}}).then((res) => res.data);
  }
  // ç§»é™¤é¡§å•
  async deleteConsultant(agentId: string) {
    return http.delete(`/consultant/favorite/${agentId}`);
  }
  // æ¨™è¨˜ç‚ºå·²è¯çµ¡
  markAsContact(appointmentId: number): Promise<void> {
    // TODO: è·Ÿå¾Œç«¯ç¢ºèªï¼Œé€™è£¡çš„ API ä¸æ‡‰è©²å‚³å›ž void, è€Œæ˜¯æ‡‰è©²æ˜¯æ›´æ–°å¾Œçš„資料 - Ben 2021/11/16
    // return http.post('/appointment/markAsContacted/'+appointmentId, undefined, {headers})
    //         .then(res => res.data)
    return http.post(`/appointment/markAsContacted/${appointmentId}`);
  }
}
export default new MyConsultantService();
PAMapp/shared/services/query-consultant.service.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,37 @@
import { http } from "./httpClient";
import { Consultant } from "~/shared/models/consultant.model";
import { FastQueryParams } from "~/shared/models/quick-filter.model";
import { AgentOfStrictQuery, StrictQueryParams } from "~/shared/models/strict-query.model";
import { AppointmentParams } from "~/shared/models/appointment.model";
class QueryConsultantService {
  // æŽ¨è–¦ä¿éšªé¡§å•
  async getRecommendConsultantList(): Promise<Consultant[]> {
    return http.get<Consultant[]>('/consultant/recommend').then((res) => res.data);
  }
  // å¿«é€Ÿç¯©é¸
  async fastQuery(data: FastQueryParams): Promise<Consultant[]> {
    return http.post<Consultant[]>('/consultant/fastQuery', data).then(res => res.data);
  }
  // åš´é¸é…å°
  async strictQuery(data:StrictQueryParams): Promise<AgentOfStrictQuery[]>{
    return http.post('/consultant/strictQuery', data).then((res) => res.data);
  }
  // åŠ å…¥é¡§å•
  async addFavoriteConsultant(agentNoList: string[]) {
    return http.post('/consultant/favorite', { agentNoList });
  }
  // é ç´„前詢問
  async appointmentDemand(data: AppointmentParams) {
    return http.post('/appointment/customer/create', data);
  }
}
export default new QueryConsultantService();
PAMapp/shared/services/reviews.service.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,18 @@
import { http } from "./httpClient";
import { UserReviewsConsultantsParams } from "../models/reviews.model";
import { AppointmentLog } from "../models/appointment.model";
class ReviewsService {
  //客戶進行滿意度評分
  userReviewsConsultants(data: UserReviewsConsultantsParams) {
    return http.post('/satisfaction/create', data );
  }
  //取得所有評分紀錄
  async getMyReviewLog(): Promise<AppointmentLog[]> {
    return http.get('/satisfaction/getMySatisfaction').then(res => res.data);
  }
}
export default new ReviewsService();
PAMapp/shared/storageConsultant.ts
PAMapp/shared/storageRequests.ts
File was renamed from PAMapp/assets/ts/storageRequests.ts
@@ -1,4 +1,4 @@
import { AppointmentRequests } from "./api/consultant";
import { AppointmentRequests } from "./models/appointment.model";
export function getRequestsFromStorage(): AppointmentRequests {
  const requests = localStorage.getItem('myRequests');
PAMapp/store/index.ts
@@ -1,15 +1,17 @@
import { StrictQueryParams } from '~/shared/models/strict-query.model';
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { getMyReviewLog } from '~/assets/ts/api/appointment';
import { recommend, AgentOfStrictQuery, addFavoriteConsultant, deleteConsultant, strictQuery } from '~/assets/ts/api/consultant';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/assets/ts/storageConsultant';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/shared/storageConsultant';
import myConsultantService from '~/assets/ts/services/my-consultant.service';
import appointmentService from '~/assets/ts/services/appointment.service';
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 '~/assets/ts/models/consultant.model';
import { AppointmentLog } from '~/assets/ts/models/appointment.model';
import { ClientInfo } from '~/assets/ts/models/client.model';
import { Consultant } from '~/shared/models/consultant.model';
import { AppointmentLog } from '~/shared/models/appointment.model';
import { ClientInfo } from '~/shared/models/client.model';
import { AgentOfStrictQuery } from '~/shared/models/strict-query.model';
@Module
export default class Store extends VuexModule {
    recommendList: Consultant[] = [];
@@ -57,7 +59,7 @@
    @Action
    storeRecommendList() {
        recommend().then(data => {
        queryConsultantService.getRecommendConsultantList().then(data => {
            this.context.commit('updateRecommend', data)
        })
    }
@@ -74,7 +76,7 @@
        if (localData?.length) {
            const agentNoList = localData.map(i => i.agentNo)
            await addFavoriteConsultant(agentNoList).then(res => {
            await queryConsultantService.addFavoriteConsultant(agentNoList).then(res => {
                localStorage.removeItem('favoriteConsultant')
            })
        }
@@ -94,7 +96,7 @@
        if (!this.isUserLogin) {
            setFavoriteToStorage(left);
        } else {
            await deleteConsultant(agentNo)
            await myConsultantService.deleteConsultant(agentNo)
        }
        this.context.commit('updateConsultantList', left)
@@ -109,7 +111,7 @@
            if (!found) {
                const newData = [consultantToAdd].concat(this.myConsultantList);
                if (this.isUserLogin) {
                    await addFavoriteConsultant([consultantToAdd.agentNo])
                    await queryConsultantService.addFavoriteConsultant([consultantToAdd.agentNo])
                } else {
                    setFavoriteToStorage(newData);
                }
@@ -134,7 +136,7 @@
    @Action
    storeMyAppointmentReviewLog() {
        getMyReviewLog().then((data) => {
        reviewsService.getMyReviewLog().then((data) => {
            const dataWithLatestDate = data.map((item) => {
                return {
                    ...item,
@@ -154,11 +156,11 @@
    }
    @Action
    async storeStrictQueryList(strictQueryDto) {
        return await strictQuery(strictQueryDto).then(res=>{
    async storeStrictQueryList(strictQueryDto: StrictQueryParams) {
        return await queryConsultantService.strictQuery(strictQueryDto).then(res=>{
            this.context.commit('localStorage/storageRecommendConsultant', JSON.stringify(strictQueryDto));
            this.context.commit('updateStrictQueryList', res.data)
            return res.data.length;
            this.context.commit('updateStrictQueryList', res)
            return res.length;
        });
    }
PAMapp/store/localStorage.ts
@@ -1,6 +1,6 @@
import { Module, Mutation, VuexModule ,Action } from 'vuex-module-decorators';
import { Role } from '~/assets/ts/models/enum/Role';
import { Selected } from '~/assets/ts/models/quickFilter.model';
import { Role } from '~/shared/models/enum/role';
import { Selected } from '~/shared/models/quick-filter.model';
@Module
export default class LocalStorage extends VuexModule {
  id_token = localStorage.getItem('id_token');
pamapi/src/doc/sql/20211222_w.sql
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1 @@
ALTER TABLE omo.consultant ADD email varchar NULL;
pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java
@@ -18,6 +18,8 @@
    private String eServiceLoginUrl;
    private String eServiceLoginFunc;
    private String eServiceLoginSys;
    private String frontEndDomain;
    private boolean sendNotifyMsg;
    private SMS sms;
    private Email email;
@@ -75,6 +77,22 @@
    public void seteServiceLoginSys(String eServiceLoginSys) {
        this.eServiceLoginSys = eServiceLoginSys;
    }
    public String getFrontEndDomain() {
        return frontEndDomain;
    }
    public void setFrontEndDomain(String frontEndDomain) {
        this.frontEndDomain = frontEndDomain;
    }
    public boolean isSendNotifyMsg() {
        return sendNotifyMsg;
    }
    public void setSendNotifyMsg(boolean sendNotifyMsg) {
        this.sendNotifyMsg = sendNotifyMsg;
    }
    public SMS getSms() {
@@ -135,6 +153,7 @@
    public static class Email {
        private String url;
        private String functionId;
        private String senderEmail;
        public String getUrl() {
            return url;
@@ -151,5 +170,13 @@
        public void setFunctionId(String functionId) {
            this.functionId = functionId;
        }
        public String getSenderEmail() {
            return senderEmail;
        }
        public void setSenderEmail(String senderEmail) {
            this.senderEmail = senderEmail;
        }
    }
}
pamapi/src/main/java/com/pollex/pam/domain/Consultant.java
@@ -71,6 +71,9 @@
    @Column(name = "communication_style")
    private String communicationStyle;
    @Column(name = "email")
    private String email;
    public Long getId() {
        return id;
    }
@@ -230,6 +233,14 @@
        this.communicationStyle = communicationStyle;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    @Override
    public String toString() {
        return "Consultant{" +
@@ -245,13 +256,14 @@
            ", gender=" + gender +
            ", phoneNumber='" + phoneNumber + '\'' +
            ", companyAddress='" + companyAddress + '\'' +
            ", seniorityYear='" + seniorityYear + '\'' +
            ", seniorityMonth='" + seniorityMonth + '\'' +
            ", seniorityYear=" + seniorityYear +
            ", seniorityMonth=" + seniorityMonth +
            ", concept='" + concept + '\'' +
            ", experience='" + experience + '\'' +
            ", award='" + award + '\'' +
            ", recommend=" + recommend +
            ", communicationStyle='" + communicationStyle + '\'' +
            ", email='" + email + '\'' +
            '}';
    }
}
pamapi/src/main/java/com/pollex/pam/service/AppointmentService.java
@@ -53,12 +53,12 @@
    @Autowired
    SatisfactionService satisfactionService;
    public void customerCreateAppointment(AppointmentCreateDTO appointmentCreateDTO) {
    public Appointment customerCreateAppointment(AppointmentCreateDTO appointmentCreateDTO) {
        Appointment appointment = appointmentDTOMapper.toAppointment(appointmentCreateDTO);
        appointment.setStatus(AVAILABLE);
        appointment.setCustomerId(SecurityUtils.getCustomerDBId());
        appointment.setCommunicateStatus(ContactStatusEnum.RESERVED);
        appointmentRepository.save(appointment);
        return appointmentRepository.save(appointment);
    }
    public void updateAppointment(AppointmentUpdateDTO updateAppointmentDTO) {
pamapi/src/main/java/com/pollex/pam/service/SendMsgService.java
@@ -1,16 +1,23 @@
package com.pollex.pam.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pollex.pam.config.ApplicationProperties;
import com.pollex.pam.config.ApplicationProperties.SMS;
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.repository.ConsultantRepository;
import com.pollex.pam.service.dto.*;
import com.pollex.pam.service.util.HttpRequestUtil;
import com.pollex.pam.web.rest.errors.SendEmailFailException;
import com.pollex.pam.web.rest.errors.SendSMSFailException;
import io.jsonwebtoken.lang.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring5.SpringTemplateEngine;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
@@ -27,10 +34,95 @@
    private static final Logger log = LoggerFactory.getLogger(SendMsgService.class);
    private final Encoder encoder = Base64.getEncoder();
    private static final String EMAIL_SUBJECT = "保誠媒合平台系統通知:新預約單";
    @Autowired
    ApplicationProperties applicationProperties;
    public void sendMsgBySMS(String toMobile, String content) throws SendSMSFailException{
    @Autowired
    ConsultantRepository consultantRepository;
    @Autowired
    SpringTemplateEngine springTemplateEngine;
    public void sendAppointmentNotify(Appointment appointment) {
        Assert.notNull(appointment);
        log.debug("is need sending notify msg = {}", applicationProperties.isSendNotifyMsg());
        if(applicationProperties.isSendNotifyMsg()) {
            log.debug("sending appointment notify, appointmentId = {}", appointment.getId());
            sendAppointmentNotifyBySMS(appointment);
            sendAppointmentNotifyByEmail(appointment);
            // todo éœ€ç¢ºèªä¿èª æ˜¯å¦æœ‰éœ€æ±‚使用html mail
            // sendAppointmentNotifyByHtmlEmail(appointment);
        }
    }
    private void sendAppointmentNotifyBySMS(Appointment appointment) throws SendSMSFailException {
        String msg = getAppointmentNotifyWording(appointment);
        String consultantMobile = consultantRepository.findOneByAgentNo(appointment.getAgentNo()).get().getPhoneNumber();
        try {
            sendMsgBySMS(consultantMobile, msg);
        } catch (SendSMSFailException e) {
            log.debug("send sms failed, appointment Id = {}", appointment.getId(), e);
        }
    }
    private void sendAppointmentNotifyByEmail(Appointment appointment) {
        // todo éœ€å¾—知保誠系統寄件信箱 (並改於設定檔中)
        String senderEmail = applicationProperties.getEmail().getSenderEmail();
        String consultantEmail = consultantRepository.findOneByAgentNo(appointment.getAgentNo()).get().getEmail();
        String content = getAppointmentNotifyWording(appointment);
        try {
            sendMsgByEmail(senderEmail, consultantEmail, EMAIL_SUBJECT, content, false);
        } catch (SendEmailFailException e) {
            log.debug("send email failed, appointment Id = {}", appointment.getId(), e);
        }
    }
    private void sendAppointmentNotifyByHtmlEmail(Appointment appointment) {
        // todo éœ€å¾—知保誠系統寄件信箱 (並改於設定檔中)
        String senderEmail = applicationProperties.getEmail().getSenderEmail();
        String consultantEmail = consultantRepository.findOneByAgentNo(appointment.getAgentNo()).get().getEmail();
        String customerMobile = appointment.getPhone();
        String normalContent;
        if(StringUtils.hasText(customerMobile)) {
            normalContent = "親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單,該客戶手機號碼為" + customerMobile + "\n";
        }
        else {
            normalContent = "親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單\n";
        }
        Context context = new Context();
        context.setVariable("content", normalContent);
        context.setVariable("urlHint", getAppointmentDetailUrl(appointment.getId()));
        String content = springTemplateEngine.process("mail/appointmentNotifyEmail", context);
        try {
            sendMsgByEmail(senderEmail, consultantEmail, EMAIL_SUBJECT, content, true);
        } catch (SendEmailFailException e) {
            log.debug("send email failed, appointment Id = {}", appointment.getId(), e);
        }
    }
    private String getAppointmentNotifyWording(Appointment appointment) {
        String normalContent;
        if(StringUtils.hasText(appointment.getPhone())) {
            normalContent = "親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單,該客戶手機號碼為" + appointment.getPhone() + "\n";
        }
        else {
            normalContent = "親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單\n";
        }
        String urlContent = "點擊網址:" + getAppointmentDetailUrl(appointment.getId()) + " é–‹å•Ÿåª’合平台查看。";
        return normalContent + urlContent;
    }
    public SendSMSResponse sendMsgBySMS(String toMobile, String content) throws SendSMSFailException {
        SMS smsProperties = applicationProperties.getSms();
        SendSMSRequest sendSMSRequest = new SendSMSRequest();
@@ -54,6 +146,7 @@
            log.debug("response status code = {}", responseEntity.getStatusCode());
            log.debug("smsResponse = {}", responseEntity.getBody());
            return responseEntity.getBody();
            // todo å¯èƒ½éœ€è¦å†è£œä¸Šå¾ŒçºŒéŒ¯èª¤è™•理,但要先測通
        }
        catch (Exception e) {
@@ -62,11 +155,20 @@
        }
    }
    public void sendMsgByEmail(String from, String to, String subject, String content, boolean htmlFormat) throws SendEmailFailException{
        sendMsgByEmail(from, to, subject, content, htmlFormat, Collections.emptyList(), Collections.emptyList());
    public String sendMsgByHtmlTestTemplateEmail(String from, String to) {
        Context context = new Context();
        context.setVariable("content", "親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單\n");
        context.setVariable("urlHint", getAppointmentDetailUrl(0L));
        String content = springTemplateEngine.process("mail/appointmentNotifyEmail", context);
        return sendMsgByEmail(from, to, EMAIL_SUBJECT, content, true);
    }
    public void sendMsgByEmail(
    public String sendMsgByEmail(String from, String to, String subject, String content, boolean htmlFormat) throws SendEmailFailException{
        return sendMsgByEmail(from, to, subject, content, htmlFormat, Collections.emptyList(), Collections.emptyList());
    }
    public String sendMsgByEmail(
        String fromAddress, String toAddress, String subject, String content, boolean htmlFormat, List<String> toCCAddress,
        List<String> attachments) throws SendEmailFailException
    {
@@ -80,21 +182,23 @@
        sendMailRequest.setHtmlFormat(htmlFormat);
        sendMailRequest.setFunctionId(applicationProperties.getEmail().getFunctionId());
        sendMsgByEmail(sendMailRequest);
        return sendMsgByEmail(sendMailRequest);
    }
    public void sendMsgByEmail(SendMailRequest sendMailRequest) throws SendEmailFailException{
    private String sendMsgByEmail(SendMailRequest sendMailRequest) throws SendEmailFailException{
        try {
            ResponseEntity<SendMailResponse> responseEntity =
                HttpRequestUtil.postWithJson( applicationProperties.getEmail().getUrl(), sendMailRequest, SendMailResponse.class);
            ResponseEntity<String> responseEntity =
                HttpRequestUtil.postWithJson( applicationProperties.getEmail().getUrl(), sendMailRequest, String.class);
            log.debug("responseEntity = {}", responseEntity);
            SendMailResponse sendMailResponse = responseEntity.getBody();
            log.debug("response status code = {}", responseEntity.getStatusCode());
            log.debug("emailResponse = {}", responseEntity.getBody());
            SendMailResponse sendMailResponse = new ObjectMapper().readValue(responseEntity.getBody(), SendMailResponse.class);
            log.debug("sendMailResponse = {}", sendMailResponse);
            if(sendMailResponse == null || sendMailResponse.getData() == null || "ADDED".equalsIgnoreCase(sendMailResponse.getData().getMessageStatus())) {
                throw new SendEmailFailException();
            }
//            if(sendMailResponse == null || sendMailResponse.getData() == null || "ADDED".equalsIgnoreCase(sendMailResponse.getData().getMessageStatus())) {
//                throw new SendEmailFailException();
//            }
            return responseEntity.getBody();
        }
        catch (SendEmailFailException e) {
            throw e;
@@ -104,4 +208,8 @@
            throw new SendEmailFailException();
        }
    }
    private String getAppointmentDetailUrl(Long appointmentId) {
        return applicationProperties.getFrontEndDomain() + "/myAppointmentList/contactedList?appointmentId=" + appointmentId;
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/SendMailResponse.java
@@ -1,5 +1,8 @@
package com.pollex.pam.service.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class SendMailResponse {
    private Data data;
@@ -11,6 +14,7 @@
        this.data = data;
    }
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class Data {
        private String messageStatus;
pamapi/src/main/java/com/pollex/pam/web/rest/AppointmentResource.java
@@ -1,5 +1,7 @@
package com.pollex.pam.web.rest;
import com.pollex.pam.domain.Appointment;
import com.pollex.pam.service.SendMsgService;
import com.pollex.pam.service.dto.AppointmentUpdateDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@@ -20,6 +22,9 @@
    @Autowired
    SatisfactionService satisfactionService;
    @Autowired
    SendMsgService sendMsgService;
    @PutMapping("")
    public ResponseEntity<Void> updateAppointment(@RequestBody AppointmentUpdateDTO appointment) {
        appointmentService.updateAppointment(appointment);
@@ -34,8 +39,9 @@
    @PostMapping("/customer/create")
    public void clientCreateAppointment(@RequestBody AppointmentCreateDTO appointmentCreateDTO) {
        appointmentService.customerCreateAppointment(appointmentCreateDTO);
    }
        Appointment appointment = appointmentService.customerCreateAppointment(appointmentCreateDTO);
        sendMsgService.sendAppointmentNotify(appointment);
    }
    @PostMapping("/markAsContacted/{appointmentId}")
    public void markAsContacted(@PathVariable Long appointmentId) {
pamapi/src/main/java/com/pollex/pam/web/rest/TestSendMsgResource.java
@@ -1,6 +1,8 @@
package com.pollex.pam.web.rest;
import com.pollex.pam.repository.AppointmentRepository;
import com.pollex.pam.service.SendMsgService;
import com.pollex.pam.service.dto.SendSMSResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -13,21 +15,35 @@
    @Autowired
    SendMsgService sendMsgService;
    @Autowired
    AppointmentRepository appointmentRepository;
    @GetMapping("/bySMS")
    public ResponseEntity<Void> bySMS(@RequestParam String toMobile, @RequestParam String content) {
        sendMsgService.sendMsgBySMS(toMobile, content);
        return ResponseEntity.noContent().build();
    public ResponseEntity<SendSMSResponse> bySMS(@RequestParam String toMobile, @RequestParam String content) {
        return ResponseEntity.ok(sendMsgService.sendMsgBySMS(toMobile, content));
    }
    @GetMapping("/byEmail")
    public ResponseEntity<Void> byEmail(
    public ResponseEntity<String> byEmail(
        @RequestParam String from,
        @RequestParam String to,
        @RequestParam String subject,
        @RequestParam String content,
        @RequestParam boolean htmlFormat
    ) {
        sendMsgService.sendMsgByEmail(from, to, subject, content, htmlFormat);
        return ResponseEntity.noContent().build();
        return ResponseEntity.ok(sendMsgService.sendMsgByEmail(from, to, subject, content, htmlFormat));
    }
    @GetMapping("/byHtmlEmail")
    public ResponseEntity<String> byHtmlEmail(
        @RequestParam String from,
        @RequestParam String to
    ) {
        return ResponseEntity.ok(sendMsgService.sendMsgByHtmlTestTemplateEmail(from, to));
    }
    @GetMapping("/appointment/{appointmentId}")
    public void sendAppointmentNotify(@PathVariable Long appointmentId) {
        sendMsgService.sendAppointmentNotify(appointmentRepository.findById(appointmentId).get());
    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/errors/SendSMSFailException.java
@@ -3,7 +3,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "send email failed")
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "send sms failed")
public class SendSMSFailException extends RuntimeException{
    public SendSMSFailException(){}
    public SendSMSFailException(String message) {
pamapi/src/main/resources/config/application-dev.yml
@@ -119,6 +119,8 @@
  e-service-login-url: https://eserviceuat.pcalife.com.tw/sso/chatbotValidate
  e-service-login-func: ValidateUsrLogin
  e-service-login-sys: epos
  front-end-domain: http://localhost:3000
  send-notify-msg: false
  sms:
    url: https://localhost:8081/testSMS
    source-code: ePos
@@ -127,3 +129,4 @@
  email:
    url: https://localhost:8081/testEmail
    function-id: epos
    sender-email: test@pollex.com.tw
pamapi/src/main/resources/config/application-pollex.yml
@@ -117,3 +117,14 @@
  e-service-login-url: https://eserviceuat.pcalife.com.tw/sso/chatbotValidate
  e-service-login-func: ValidateUsrLogin
  e-service-login-sys: epos
  front-end-domain: http://dev.pollex.com.tw:5566/pam
  send-notify-msg: false
  sms:
    url: https://localhost:8081/testSMS
    source-code: ePos
    sender: POS
    sms-type: '0017'
  email:
    url: https://localhost:8081/testEmail
    function-id: epos
    sender-email: test@pollex.com.tw
pamapi/src/main/resources/config/application-sit.yml
@@ -117,6 +117,8 @@
  e-service-login-url: https://eserviceuat.pcalife.com.tw/sso/chatbotValidate
  e-service-login-func: ValidateUsrLogin
  e-service-login-sys: epos
  front-end-domain: https://vtwlifeopensyssit.pru.intranet.asia/pam
  send-notify-msg: true
  sms:
    url: https://vtwlifewinbo66.pru.intranet.asia/MesgQueueMgmnt/rest/smsSendMsgResource
    source-code: ePos
@@ -125,3 +127,4 @@
  email:
    url: https://vtwlifeopensysuat.pru.intranet.asia/tsgw/mq/mqSendMail
    function-id: epos
    sender-email: test@pollex.com.tw
pamapi/src/main/resources/config/application-uat.yml
@@ -117,6 +117,8 @@
  e-service-login-url: https://eserviceuat.pcalife.com.tw/sso/chatbotValidate
  e-service-login-func: ValidateUsrLogin
  e-service-login-sys: epos
  front-end-domain: https://vtwlifeopensysuat.pru.intranet.asia/pam
  send-notify-msg: true
  sms:
    url: https://vtwlifewinbo66.pru.intranet.asia/MesgQueueMgmnt/rest/smsSendMsgResource
    source-code: ePos
@@ -125,3 +127,4 @@
  email:
    url: https://vtwlifeopensysuat.pru.intranet.asia/tsgw/mq/mqSendMail
    function-id: epos
    sender-email: test@pollex.com.tw
pamapi/src/main/resources/templates/mail/appointmentNotifyEmail.html
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
  <head>
    <title>新的預約單通知</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <p th:text="${content}">親愛的顧問您好,您有一筆來自保誠媒合平台的新預約單</p>
    <p>
      é»žæ“Šç¶²å€ï¼š
      <a th:href="${urlHint}" th:text="${urlHint}">Url Position</a>
      é–‹å•Ÿåª’合平台查看
    </p>
  </body>
</html>