保誠-保戶業務員媒合平台
wayne
2021-11-12 505f666d3ecf3688778c079a8bbfb366d695cf22
Merge remote-tracking branch 'origin/master'

修改30個檔案
新增6個檔案
修改2個檔案名稱
2520 ■■■■ 已變更過的檔案
PAMapp/assets/scss/_common.scss 11 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/_mixin.scss 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/_variable.scss 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/vendors/elementUI/_checkbox.scss 28 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/vendors/elementUI/_radio.scss 24 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/api/consultant.ts 117 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/auth.ts 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/storageConsultant.ts 10 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/AddAndReservedBtns.vue 61 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/AddressPicker.vue 22 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/BackActionBar.vue 30 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant-ques.vue 20 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantCard.vue 27 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantList.vue 17 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantSwiper.vue 9 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/NavBar.vue 7 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue 87 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterSelector.vue 45 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiGoToTop.vue 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiPagination.vue 10 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/multiSelectBtn.vue 41 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/phoneContactTimePicker.vue 58 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/singleSelectBtn.vue 44 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/layouts/default.vue 25 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/nuxt.config.js 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/agentInfo/_agentNo.vue 40 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/index.vue 75 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/login/index.vue 312 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList.vue 29 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList/consultantList.vue 16 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList/contactedList.vue 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/questionnaire/_agentNo.vue 179 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/quickFilter/index.vue 186 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/recommendConsultant/index.vue 705 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/recommendConsultant/result.vue 189 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/plugins/api/home.ts 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/plugins/filters/date.filter.ts 29 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/index.ts 19 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/_common.scss
@@ -4,18 +4,9 @@
    margin: 0;
    color: $PRIMARY_BLACK;
    font-family: Segoe UI;
    overflow-x: hidden;
    scroll-behavior: smooth;
}
html {
    overflow: hidden;
    height: 100%;
}
body {
    overflow-y: auto;
    height: 100%;
    overflow-x: hidden;
}
// btn
PAMapp/assets/scss/_mixin.scss
@@ -1,5 +1,5 @@
@mixin desktop() {
    @media (min-width: 1024px) {
    @media (min-width: $DESKTOP_BREAKPOINT) {
        @content;
    }
}
PAMapp/assets/scss/_variable.scss
@@ -15,3 +15,6 @@
$LIGHT_GREY: #D0D0CE;
$MID_GREY: #A7A8AA;
$GREEN: #06A633;
// DEVICE BREAKPOINT
$DESKTOP_BREAKPOINT: 768px;
PAMapp/assets/scss/vendors/elementUI/_checkbox.scss
@@ -23,12 +23,40 @@
            padding-left: 0;
            text-align: center;
            line-height: 110px;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
        }
        &.is-checked {
            background-color: #22222229;
        }
    }
    .btn_koala {
        .el-checkbox__label {
            background-image: url('~/assets/images/quickFilter/btn_koala.svg');
        }
    }
    .btn_owl {
        .el-checkbox__label {
            background-image: url('~/assets/images/quickFilter/btn_owl.svg');
        }
    }
    .btn_peacock {
        .el-checkbox__label {
            background-image: url('~/assets/images/quickFilter/btn_peacock.svg');
        }
    }
    .btn_tiger {
        .el-checkbox__label {
            background-image: url('~/assets/images/quickFilter/btn_tiger.svg');
        }
    }
}
.pam-multi-select-btn.el-checkbox-group{
PAMapp/assets/scss/vendors/elementUI/_radio.scss
@@ -21,7 +21,9 @@
            display: block;
            line-height: 110px;
            padding-left: 0;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
        }
        &.is-checked {
@@ -43,13 +45,23 @@
    }
}
.btn_woman {
    .el-radio__label {
        background-image: url('~/assets/images/quickFilter/btn_woman.svg');
    }
}
.btn_man {
    .el-radio__label {
        background-image: url('~/assets/images/quickFilter/btn_man.svg');
    }
}
.pam-single-btn.el-radio-group{
  .el-radio {
    padding: 10px 20px;
    border: 1px $LIGHT_GREY solid;
    background-color: $PRIMARY_WHITE;
    border-radius: 50px;
    font-size: 20px;
    margin-right: 10px;
    margin-bottom: 10px;
@@ -59,9 +71,14 @@
    .el-radio__label {
        text-align: center;
        font-size: 18px;
        color: $PRIMARY_BLACK;
        font-weight: normal;
        display: block;
        padding-left: 0px;
        .radio-sub-title{
            color: $PRUDENTIAL_GREY;
        }
    }
    &.is-checked {
@@ -69,6 +86,9 @@
      color: $PRIMARY_WHITE;
      .el-radio__label{
        color: $PRIMARY_WHITE;
        .radio-sub-title{
            color: $PRIMARY_WHITE;
        }
      }
    }
}
PAMapp/assets/ts/api/consultant.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,117 @@
import axios from 'axios';
import { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios';
import { MessageBox } from 'element-ui';
import { functionsIn } from 'lodash';
import Router from 'vue-router';
export const service = axios.create({
    baseURL: 'http://localhost:8080/api',
    headers: {
        Authorization: 'Bearer ' + localStorage.getItem('id_token')
    }
})
service.interceptors.request.use(function (config: AxiosRequestConfig) {
    return config;
}, function (error: AxiosError) {
    return Promise.reject(error);
});
service.interceptors.response.use(function (response: AxiosResponse) {
    return response;
}, function (error: AxiosError) {
    return Promise.reject(error);
});
// é¡§å®¢ç™»å…¥(TODO: OTP認證開發前 æš«æ™‚使用)
export function login(user: any) {
    return service.post('/authenticate', user)
}
// æŽ¨è–¦ä¿éšªé¡§å•
export function recommend() {
    return service.get('/consultant/recommend')
}
// æˆ‘的顧問清單
export function getFavoriteConsultant() {
    return service.get('/consultant/favorite');
}
// å¿«é€Ÿç¯©é¸
export function fastQuery(data: FastQueryParams) {
    return service.post('/consultant/fastQuery', data)
}
// åš´é¸é…å°
export function strictQuery(data:StrictQueryParams):Promise<AxiosResponse<AgentOfStrictQuery>>{
    return service.post('/consultant/strictQuery', data)
}
// åŠ å…¥é¡§å•
export function addFavoriteConsultant(agentNoList: string[]) {
    return service.post('/consultant/favorite', {agentNoList})
}
// é ç´„前詢問
export function appointmentDemand(data: AppointmentParams) {
    return service.post('/appointment/customer/create', data)
}
//顧問詳細資訊
export function getConsultantDetail(agentNo:string){
    return service.get('/consultant/detail', {params:{agentNo:agentNo}})
}
export interface Consultants {
    agentNo: string,
    name: string,
    img: string,
    new: boolean,
    avgScore: number,
    expertise: string[],
    updateTime: Date,
    seniority: string,
    contactStatus?: string;
}
export interface FastQueryParams {
    gender: string,
    communicationStyles: string[],
    avgScore: number,
    status: string
}
export interface AppointmentParams {
    phone: string,
    email: string,
    contactType: string,
    gender: string,
    age: string,
    job: string,
    requirement: string,
    hopeContactTime: string,
    otherRequirement: string,
    agentNo: string
}
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/assets/ts/auth.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,3 @@
export function isLogin() {
    return !!localStorage.getItem('id_token')
}
PAMapp/assets/ts/storageConsultant.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,10 @@
import { Consultants } from "./api/consultant";
export function getFavoriteFromStorage(): Consultants[] {
    const consultantList = localStorage.getItem('favoriteConsultant');
    return consultantList ? JSON.parse(consultantList) : [];
}
export function setFavoriteToStorage(consultants: Consultants[]) {
    localStorage.setItem('favoriteConsultant', JSON.stringify(consultants));
}
PAMapp/components/AddAndReservedBtns.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,61 @@
<template>
    <el-row type="flex" justify="center" :class="cusClass">
        <el-button @click="addConsultant(agentInfo)">
            <span> + é¡§å•æ¸…å–®</span>
        </el-button>
        <el-button
            @click="reserveCommunication"
            type="primary"
        >進行預約</el-button>
    </el-row>
</template>
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'nuxt-property-decorator';
import { addFavoriteConsultant, Consultants } from '~/assets/ts/api/consultant';
import { isLogin } from '~/assets/ts/auth';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/assets/ts/storageConsultant';
@Component
export default class AddAndReservedBtns extends Vue {
    @Prop() agentInfo!: Consultants;
    @Prop() cusClass!: string;
    isVisiblePopUp = false;
    addConsultant(item: Consultants) {
        console.log('click')
        if (isLogin()) {
            addFavoriteConsultant([item.agentNo]).then(res => this.openPopUp())
        } else {
            this.addConsultantToStorage(item);
        }
    }
    addConsultantToStorage(item: Consultants) {
        let agentList = [item];
        const consultantList = getFavoriteFromStorage();
        if (consultantList) {
            const isRepeat = consultantList.findIndex(i => i.agentNo === item.agentNo) === -1;
            isRepeat
                ? this.storageFavoriteAndPopUp(consultantList.concat(agentList))
                : this.openPopUp('已經加入顧問清單');
        } else {
            this.storageFavoriteAndPopUp(agentList);
        }
    }
    storageFavoriteAndPopUp(item: Consultants[]) {
        setFavoriteToStorage(item);
        this.openPopUp();
    }
    reserveCommunication() {
        isLogin() ? this.$router.push(`/questionnaire/${this.agentInfo.agentNo}`) : this.$router.push('/login');
    }
    @Emit('openPopUp') openPopUp(popUpTxt: string = '成功加入顧問清單') {
        return popUpTxt
    }
}
</script>
PAMapp/components/AddressPicker.vue
@@ -1,6 +1,6 @@
<template>
    <div class="text--center">
        <div class="subTitle mb-10">所在地區</div>
    <div class="text--center" @click="close">
        <div class="subTitle mb-10" @click="close">所在地區</div>
        <el-input
            type="text"
            class="p mt-10"
@@ -8,16 +8,16 @@
            @change="searchDistrict"
            placeholder="請輸入關鍵字"
        ></el-input>
       <Ui-ScrollPicker
        <Ui-ScrollPicker
            :options="filterOptions"
            :initValue="district"
            @change="change"
            @change="selectDistrict"
        ></Ui-ScrollPicker>
    </div>
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator';
import { Vue, Component, Emit } from 'nuxt-property-decorator';
@Component
export default class AddressPicker extends Vue {
@@ -31,13 +31,21 @@
        this.filterOptions = JSON.parse(JSON.stringify(this.options));
    }
    searchDistrict() {
        this.filterOptions = this.options.filter(e => e.match(this.keyWord));
    }
    change(value: string) {
        console.log('change', value)
    @Emit('change')
    selectDistrict(value: string): void {
        this.district = value;
    }
    @Emit()
    close(): void {
        console.log('close district drawer!');
    }
}
</script>
PAMapp/components/BackActionBar.vue
@@ -12,7 +12,35 @@
@Component
export default class UiCarousel extends Vue {
  label = 'back-action-label-text';
  get label(): string {
    if (this.$route.name) {
      const routeName = this.$route.name.split('-')[0];
      let featureLabel = '';
      switch(routeName) {
        case 'login':
          featureLabel = '登入';
          break;
        case 'recommendConsultant':
          featureLabel = '嚴選配對';
          break;
        case 'quickFilter':
          featureLabel = '快速篩選';
          break;
        case 'myConsultantList':
          featureLabel = '我的顧問清單';
          break;
        case 'agentInfo':
          featureLabel = '業務員資訊'
          break;
        default:
          featureLabel = '回首頁';
          break;
      }
      return featureLabel;
    } else {
      return '回首頁';
    }
  }
}
</script>
PAMapp/components/Consultant-ques.vue
@@ -3,20 +3,16 @@
        <div class="pam-tags">
            <div class="pb-10" v-for="(qaItem,index) in questionList" :key="index">
                <el-button  class="tags" :class="{'active': qaItem.selected}"      
                @click="qaItem.selected = !qaItem.selected">
                @click="selectQuestion(index)">
                {{qaItem.name}}
                </el-button>              
            </div>    
            <el-button class="tags" @click="other = !other" :class="{'active':other=other}">其他</el-button>
        </div>
        <div class="pb-10 pam-tags con-input">
            <input v-if="other" class="other-input" placeholder="請輸入,限X字" v-model="otherQuestion">
        </div>
    </div>
</template>
<script>
import { Vue, Component} from 'vue-property-decorator';
<script lang="ts">
import { Vue, Component, Emit} from 'vue-property-decorator';
@Component
    export default class ConsultantQues extends Vue{
@@ -49,7 +45,13 @@
                selected:false
                }       
        ];
        @Emit('change')
        selectQuestion(questionIndex: number): any[] {
            this.questionList[questionIndex].selected = !this.questionList[questionIndex].selected;
            return this.questionList;
        }
    }
</script>
<style lang="scss">
PAMapp/components/Consultant/ConsultantCard.vue
@@ -9,11 +9,11 @@
                            :size="50"
                            :src="agentInfo.img"
                            class="cursor--pointer"
                            @click.native="$router.push('/agentInfo')"
                            @click.native="showAgentDetail(agentInfo.agentNo);"
                        ></el-avatar>
                        <div class="satisfaction">
                            <i class="icon-star pam-icon icon--yellow satisfaction"></i>
                            <span>{{agentInfo.satisfaction}}</span>
                            <span>{{agentInfo.avgScore }}</span>
                        </div>
                    </el-col>
                    <el-col :xs="10" :sm="15">
@@ -21,13 +21,14 @@
                        <div class="professionals">
                            <span
                                class="professionalsTxt"
                                v-for="(professional, index) in agentInfo.professionals"
                                v-for="(expertise, index) in agentInfo.expertise"
                                :key="index"
                            >#{{professional}}</span>
                            >#{{expertise}}</span>
                        </div>
                        <div
                            class="delete"
                            v-if="agentInfo.contactStatus === 'picked'"
                            v-if="agentInfo.contactStatus !== 'reserved'
                                || agentInfo.contactStatus !== 'contacted'"
                            @click="removeAgent"
                        >移除</div>
                    </el-col>
@@ -65,12 +66,13 @@
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { Consultants } from '~/assets/ts/api/consultant';
import { isLogin } from '~/assets/ts/auth';
import { isMobileDevice } from '~/assets/ts/device';
@Component
export default class ConsultantCard extends Vue {
    @Prop() agentInfo!: Agents;
    @Prop() agentInfo!: Consultants;
    isVisibleDialog = false;
    width: string = '';
@@ -91,12 +93,13 @@
        let date = newDate.getDate();
        let hours = newDate.getHours();
        let minutes = newDate.getMinutes();
        return `${year}/${month}/${date} ${hours} : ${minutes}`
        return isLogin() ? `${year}/${month}/${date} ${hours} : ${minutes}` : ''
    }
    reserveCommunication() {
        if (this.agentInfo.contactStatus === 'picked') {
            this.$router.push('/communication/myDemand')
        const contactStatus = this.agentInfo.contactStatus;
        if (contactStatus !== 'reserved' && contactStatus !== 'contacted') {
            isLogin() ? this.$router.push(`/questionnaire/${this.agentInfo.agentNo}`) : this.$router.push('/login');
        } else {
            this.width = isMobileDevice() ? '80%' : '';
            this.isVisibleDialog = true;
@@ -106,6 +109,10 @@
    @Emit('removeAgent') removeAgent() {
        return this.agentInfo.agentNo;
    }
    showAgentDetail(agentNo: string): void {
        this.$router.push(`/agentInfo/${agentNo}`);
    }
}
</script>
PAMapp/components/Consultant/ConsultantList.vue
@@ -1,6 +1,6 @@
<template>
    <div>
        <template v-if="agents.length > 0 && noLogin">
        <template v-if="agents.length > 0">
            <ConsultantCard
                v-for="(agent, index) in agents"
                :key="index"
@@ -8,15 +8,15 @@
                @removeAgent="removeAgent"
            ></ConsultantCard>
        </template>
        <template v-else-if="agents.length === 0">
        <template v-else>
            <div class="emptyRowStyle">
                <div class="smTxt txt">您目前無已選顧問</div>
            </div>
        </template>
        <template v-else>
        <template v-if="!isLogin && agents.length > 0">
            <div class="emptyRowStyle">
                <div class="mdTxt login" @click="$router.push('/login')">登入</div>
                <div class="smTxt txt">查看更多已選顧問</div>
                <div class="smTxt txt">查看更多</div>
            </div>
        </template>
    </div>
@@ -24,13 +24,16 @@
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { Consultants } from '~/assets/ts/api/consultant';
import { isLogin } from '~/assets/ts/auth';
@Component
export default class ConsultantList extends Vue {
    @Prop() agents!: Agents[];
    @Prop() agents!: Consultants[];
    noLogin = true;
    get isLogin() {
        return isLogin();
    }
    @Emit('removeAgent') removeAgent(agentId: number) {
        return agentId;
PAMapp/components/Consultant/ConsultantSwiper.vue
@@ -14,7 +14,7 @@
              <div class="name">{{agentInfo.name}}</div>
              <div>
                <i class="icon-star pam-icon icon--yellow"></i>
                <span class="satisfaction">{{agentInfo.satisfaction}}</span>
                <span class="satisfaction">{{agentInfo.avgScore}}</span>
              </div>
          </div>
        </swiper-slide>
@@ -28,11 +28,11 @@
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
import { SwiperOptions } from 'swiper';
import { Agents } from '~/plugins/api/home'
import { Consultants } from '~/assets/ts/api/consultant'
@Component
export default class UiSwiper extends Vue {
    @Prop() agents!: Agents[];
    @Prop() agents!: Consultants[];
    swiperOptions: SwiperOptions = {
        loop: true,
@@ -56,7 +56,8 @@
    }
    clkItem(loopIndex: number, realIndex: number) {
      this.$router.push('/agentInfo');
      const agentNo = this.agents[realIndex].agentNo;
      this.$router.push(`/agentInfo/${agentNo}`);
      console.log(loopIndex, realIndex, 'clkItem')
    }
PAMapp/components/NavBar.vue
@@ -1,6 +1,6 @@
<template>
    <header class="pam-header">
      <img class="pam-header__logo" src="~/assets/images/logo.png" alt="" @click="$router.push('/login')">
      <img class="pam-header__logo" src="~/assets/images/logo.png" alt="" @click="$router.push('/')">
        <div class="pam-header__title">
          æˆ‘的幸福我作主
          <div class="pam-header__sub-title">
@@ -12,6 +12,7 @@
          <el-dropdown @command="handleCommand">
            <i class="icon-avatar text--dark-blue cursor--pointer"></i>
            <el-dropdown-menu slot="dropdown" class="pam-header__dropdown">
              <li class="pam-header__dropdown-item" @click="$router.push('/login')">登入</li>
              <li class="pam-header__dropdown-item" @click="$router.push('/accountSetting')">個人帳號設定</li>
              <li class="pam-header__dropdown-item" @click="$router.push('/record/contactRecord')">查看紀錄</li>
              <li class="pam-header__dropdown-item" @click="$router.push('/myConsultantList/consultantList')">我的顧問清單</li>
@@ -49,14 +50,14 @@
    border-left: 1px solid #CCCCCC;
    font-size: 16px;
    font-weight: bold;
    color: #68737A;
    color: $PRUDENTIAL_GREY;
    letter-spacing: 3.6px;
    flex-basis: 140px;
    .pam-header__sub-title {
      padding-top: 2px;
      font-size: 13px;
      font-weight: bold;
      color: #F09491;
      color: $CORAL;
      letter-spacing: 1.2px
    }
  }
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue
@@ -9,7 +9,7 @@
        ref="carouselRef"
    >
        <el-carousel-item
            v-for="(item, index) in 5"
            v-for="(item, index) in consultantList"
            :key="index"
        >
            <div
@@ -20,79 +20,70 @@
                <el-avatar
                    :size="200"
                    class="mx-auto cursor--pointer"
                    @click="$router.push('/agentInfo')"
                    src=""
                    @click.native="showAgentDetail(item.agentNo)"
                    :src="item.img"
                />
                <div class="mdTxt mt-30 mb-10 text--center">蔡美莓(伯樂保險經紀人)</div>
                <div class="mdTxt mt-30 mb-30 text--center">{{item.name}}(伯樂保險經紀人)</div>
                <el-row>
                    <el-col :span="12">
                        <div class="smTxt_bold mb-10 text--prudential_grey">服務資歷</div>
                        <div class="mb-10">一年12個月</div>
                        <div class="mb-10">{{item.seniority}}</div>
                    </el-col>
                    <el-col :span="12">
                        <div class="smTxt_bold mb-10 text--prudential_grey">客戶滿意度</div>
                        <div>
                            <i class="icon-star pam-icon"></i>
                            4.8</div>
                            <i class="icon-star pam-icon icon--yellow "></i>
                            {{item.avgScore}}</div>
                    </el-col>
                </el-row>
                <div class="smTxt_bold mb-10 text--prudential_grey">專長領域</div>
                <div class="p bold">#財務規劃</div>
            </div>
            <div class="fixedBtn text--center">
                <el-button @click="addConsultant">
                    <i class="icon-add smTxt"></i>
                    <span>顧問清單</span>
                </el-button>
                <el-button
                    @click="$router.push('/communication/myDemand')"
                    type="primary"
                >進行預約</el-button>
                <div class="smTxt_bold mt-10 mb-10 text--prudential_grey">專長領域</div>
                <div>
                    <span
                        v-for="(i, index) in item.expertise"
                        :key="index"
                        class="p bold mr-30 mb-10 inline-block"
                    >#{{i}}</span>
                </div>
                <AddAndReservedBtns
                    :cusClass="'fixedBtn'"
                    :agentInfo="item"
                    @openPopUp="openPopUp"
                ></AddAndReservedBtns>
            </div>
        </el-carousel-item>
    </el-carousel>
    <div class="absolute arrow-left-position" @click="nextCard">
    <div class="absolute arrow-left-position" @click="prevCard">
        <i class="icon-left pam-left-arrow"></i>
    </div>
    <div class="absolute arrow-right-position" @click="nextCard">
        <i class="icon-right pam-right-arrow"></i>
    </div>
    <Ui-Drawer
        :isVisible.sync="isVisibleDrawer"
    <PopUpFrame :isOpen.sync="isVisiblePopUp"
    >
        <div class="text--center mdTxt">
            <p class="mb-50">成功加入顧問清單</p>
            <p class="mb-50">{{popUpTxt}}</p>
            <p class="text--primary cursor--pointer"
                @click="isVisibleDrawer = false">我知道了</p>
                @click="isVisiblePopUp = false">我知道了</p>
        </div>
    </Ui-Drawer>
    <Ui-Dialog
        :isVisible.sync="isVisibleDialog"
    >
        <div class="text--center mdTxt">
            <p class="mb-50">成功加入顧問清單</p>
            <p class="text--primary cursor--pointer"
                @click="isVisibleDialog = false">我知道了</p>
        </div>
    </Ui-Dialog>
    </PopUpFrame>
</div>
</template>
<script lang="ts">
import { ElCarousel } from 'element-ui/types/carousel';
import { Vue, Component } from 'vue-property-decorator';
import { isMobileDevice } from '~/assets/ts/device';
import { Vue, Component, Prop } from 'vue-property-decorator';
import { Consultants } from '~/assets/ts/api/consultant';
@Component
export default class QuickFilterConsultantList extends Vue {
    isVisibleDrawer = false;
    isVisibleDialog = false;
    @Prop() consultantList!: Consultants[];
    isVisiblePopUp = false;
    startPosition = 0;
    endPosition = 0;
    popUpTxt = '成功加入顧問清單';
    touchStart(event: TouchEvent) {
        this.startPosition = event.changedTouches[0].clientX;
@@ -102,12 +93,10 @@
        this.endPosition = event.changedTouches[0].clientX;
        if (this.endPosition < this.startPosition) {
            this.nextCard();
            return;
        }
        if (this.endPosition > this.startPosition) {
            this.prevCard();
            return;
        }
    }
@@ -119,11 +108,14 @@
        (this.$refs.carouselRef as ElCarousel).prev();
    }
    addConsultant() {
        isMobileDevice()
            ? this.isVisibleDrawer = true
            : this.isVisibleDialog = true;
    openPopUp(txt: string) {
        this.popUpTxt = txt;
        this.isVisiblePopUp = true;
    }
    showAgentDetail(agentNo: string): void {
        this.$router.push(`/agentInfo/${agentNo}`);
    }
}
</script>
@@ -146,7 +138,6 @@
        bottom: 30px;
        left: 50%;
        transform: translateX(-50%);
        width: 100%;
    }
    .pam-left-arrow,.pam-right-arrow {
@@ -197,6 +188,10 @@
        position: absolute;
    }
    .inline-block {
        display: inline-block;
    }
    @media (min-width: 768px) {
        .relative {
            overflow: hidden;
PAMapp/components/QuickFilter/QuickFilterSelector.vue
@@ -6,21 +6,22 @@
            </span>
            <span
                class="smTxt_bold text--primary"
                v-if="questionOption.name === 'style'"
                v-if="questionOption.name === 'communicationStyles'"
            >可複選</span>
            <span
                class="smTxt_bold text--primary"
                v-if="questionOption.name === 'satisfaction'"
                v-if="questionOption.name === 'avgScore'"
            >選取星星</span>
        </div>
        <div class="quickBtnBlock" v-if="questionOption.name === 'style'">
            <el-checkbox-group class="pam-quickFilter-checkbox" v-model="pickedItem.style">
        <div class="quickBtnBlock" v-if="questionOption.name === 'communicationStyles'">
            <el-checkbox-group class="pam-quickFilter-checkbox" v-model="pickedItem.communicationStyles">
                <el-checkbox
                    v-for="(i, index) in questionOption.detail"
                    :key="index"
                    :label="i"
                    :name="i"
                    :label="i.value"
                    :name="i.value"
                    :class="i.className"
                ></el-checkbox>
            </el-checkbox-group>
        </div>
@@ -30,48 +31,54 @@
                <el-radio
                    v-for="(i, index) in questionOption.detail"
                    :key="index"
                    :label="i"></el-radio>
                    :label="i.value"
                    :class="i.className"
                >{{i.name}}</el-radio>
            </el-radio-group>
        </div>
        <div class="quickBtnBlock" v-else-if="questionOption.name === 'loginState'">
            <el-radio-group class="pam-quickFilter-radio" v-model="pickedItem.loginState">
        <div class="quickBtnBlock" v-else-if="questionOption.name === 'status'">
            <el-radio-group class="pam-quickFilter-radio" v-model="pickedItem.status">
                <el-radio
                    v-for="(i, index) in QuestionOption.detail"
                    :key="index"
                    :label="i"></el-radio>
                    :label="i.value"
                    :class="i.className"
                ></el-radio>
            </el-radio-group>
        </div>
        <div v-else>
            <el-rate class="pam-quickFilter-rate" v-model="pickedItem.satisfaction"></el-rate>
            <el-rate class="pam-quickFilter-rate" v-model="pickedItem.avgScore"></el-rate>
        </div>
    </div>
</template>
<script lang="ts">
import { Vue, Component, PropSync, Prop, Watch, Emit } from 'nuxt-property-decorator';
import { QuestionOption, selectedItem } from '~/pages/quickFilter/index.vue';
import { Vue, Component, PropSync, Prop, Watch } from 'nuxt-property-decorator';
import { FastQueryParams } from '~/assets/ts/api/consultant';
import { QuestionOption } from '~/pages/quickFilter/index.vue';
@Component
export default class QuickFilterDrawer extends Vue {
    pickedItem: selectedItem = {
        style: [],
        onlineState: '',
    pickedItem: FastQueryParams = {
        communicationStyles: [],
        status: '',
        gender: '',
        satisfaction: 0
        avgScore: 0
    }
    @PropSync('drawerVisible') isVisible!: boolean;
    @Prop() selectedItem!: selectedItem;
    @Prop() selectedItem!: FastQueryParams;
    @Prop() questionOption!: QuestionOption;
    @Watch('selectedItem', {deep: true, immediate: true}) watchSelected(newValue: selectedItem) {
    @Watch('selectedItem', {deep: true, immediate: true}) watchSelected(newValue: FastQueryParams) {
        if (newValue) {
            this.pickedItem = JSON.parse(JSON.stringify(this.selectedItem))
        }
    }
}
</script>
PAMapp/components/Ui/UiGoToTop.vue
@@ -18,7 +18,7 @@
    created() {
        if (process.browser) {
            window.onscroll = () => { this.scrollFunction() };
            document.body.addEventListener('scroll', this.scrollFunction);
        }
    }
@@ -34,6 +34,10 @@
        document.body.scrollTop = 0;
        document.documentElement.scrollTop = 0;
    }
    destroyed() {
        document.body.removeEventListener('scroll', this.scrollFunction);
    }
}
</script>
PAMapp/components/Ui/UiPagination.vue
@@ -12,20 +12,20 @@
<script lang="ts">
import { Vue, Component, Prop, Emit, Watch } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { Consultants } from '~/assets/ts/api/consultant';
@Component
export default class UiPagination extends Vue {
    @Prop() totalList!: Agents[];
    @Prop() totalList!: Consultants[];
    pageSize = 5;
    currentPage = 1;
    pageList: Agents[] = [];
    pageList: Consultants[] = [];
    mounted() {
        this.handleCurrentChange(this.currentPage);
    }
    @Emit('changePage') chagnePage(): Agents[] {
    @Emit('changePage') chagnePage(): Consultants[] {
        return this.pageList
    }
@@ -41,7 +41,7 @@
        }
    }
    @Watch('totalList') watchtotalList(newValue: Agents[]) {
    @Watch('totalList') watchtotalList(newValue: Consultants[]) {
        if (newValue) {
            this.handleCurrentChange(this.currentPage);
        }
PAMapp/components/multiSelectBtn.vue
@@ -4,13 +4,21 @@
      <el-checkbox
        v-for="(option, index) in options"
        :key="index"
        :label="option"
        :name="option"
      ></el-checkbox>
        :label="option.label">
          {{option.title}}
        </el-checkbox>
      <template v-if="nameOfSelectAll">
        <button class="pam-selectAll-btn cursor--pointer" :class="{'selected':isSelectAll}" :model="isSelectAll" @click="selectAll">
         <span>{{nameOfSelectAll}}</span>
       </button>
      </template>
      <template v-if="nameOfOtherOption">
        <button class="pam-selectAll-btn cursor--pointer" :class="{'selected':isSelectOtherOption}" :model="isSelectOtherOption" @click="selectOther">
         <span>{{nameOfOtherOption}}</span>
        </button>
        <div>
          <input class="pam-muti-select-other cursor--pointer" v-if="isSelectOtherOption" v-model="syncOtherSelect" placeholder="請輸入,限20字">
        </div>
      </template>
    </el-checkbox-group>
  </div>
@@ -18,12 +26,16 @@
<script lang="ts">
  import { Vue, Component, Prop, PropSync} from 'vue-property-decorator';
  import { OptionBtnDto } from './singleSelectBtn.vue';
  @Component
  export default class MultiSelectBtn extends Vue {
    @PropSync('mutiSelect',{type:Array,default:()=>[]}) syncMutiSelect!:string[];
    @Prop({default:()=>[]}) options!:OptionBtnDto[];
    @Prop({default:''}) nameOfSelectAll!:string;
    @Prop({default:()=>[]}) options!:string[]|[];
    @PropSync('otherSelect',{default:''}) syncOtherSelect!:string;
    @Prop({type:String,default:''}) nameOfOtherOption!:string;
    isSelectOtherOption=false;
    isSelectAll=false;
    updated() {
@@ -32,9 +44,17 @@
    selectAll():void{
      this.isSelectAll= !this.isSelectAll;
      this.syncMutiSelect = this.isSelectAll ? this.options:[];
      this.syncMutiSelect = this.isSelectAll ? this.optionsFormat(this.options):[];
    }
    optionsFormat(optios:OptionBtnDto[]):string[]{
      return optios.map(o=>o.title);
    }
    selectOther():void{
      this.isSelectOtherOption = !this.isSelectOtherOption;
      if(!this.isSelectOtherOption){
        this.syncOtherSelect = '';
      }
    }
  }
</script>
@@ -52,4 +72,13 @@
      color: $PRIMARY_WHITE;
    }
  }
  .pam-muti-select-other {
    height: 50px;
    padding: 5px;
    -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
    -moz-box-sizing: border-box;    /* Firefox, other Gecko */
    box-sizing: border-box;
    width: 316px;
    border: 1px solid #CCCCCC;
  }
</style>
PAMapp/components/phoneContactTimePicker.vue
@@ -33,7 +33,6 @@
    <PopUpFrame class="pam-popUpFrame" 
      :isOpen.sync="isOpenByStep_1"
      :model="isOpenByStep_1"
      :drawerSize="drawerSize"
      :dialogWidth="dialogWidth">
        <div class="pam-popUp-title">{{popUpTitle}}</div>
@@ -75,16 +74,62 @@
<script lang="ts">
  import { Component,PropSync,Vue } from "nuxt-property-decorator";
  import * as _ from "lodash";
import { OptionBtnDto } from "./singleSelectBtn.vue";
  @Component
  export default class PhoneContactTimePicker extends Vue {
    @PropSync('scheduleList',{type:Array,default:()=>[]}) syncScheduleList!:scheduleDto[];
    private weekOptions ={
      selectAll:'每天',
      options:['禮拜一','禮拜二','禮拜三','禮拜四','禮拜五','禮拜六','禮拜日'],
      options:[
        {
          title:'禮拜一',
          label:'禮拜一'
        },
         {
          title:'禮拜二',
          label:'禮拜二'
        },
         {
          title:'禮拜三',
          label:'禮拜三'
        },
         {
          title:'禮拜四',
          label:'禮拜四'
        },
         {
          title:'禮拜五',
          label:'禮拜五'
        }, {
          title:'禮拜六',
          label:'禮拜六'
        },
         {
          title:'禮拜日',
          label:'禮拜日'
        },
      ],
    };
    private timesOfDayOptions ={
      selectAll:'全天',
      options:['9:00~12:00','12:00~14:00','14:00~18:00','18:00~21:00'],
      options:[
        {
          title:'9:00~12:00',
          label:'9:00~12:00',
        },
        {
          title:'12:00~14:00',
           label:'12:00~14:00',
        },
        {
          title:'14:00~18:00',
           label:'14:00~18:00',
        },
        {
          title:'18:00~21:00',
          label:'18:00~21:00',
        }
      ],
    };
    private drawerSize="40%";
    private dialogWidth="376px";
@@ -117,8 +162,9 @@
      this.selectedSchedule.selectWeekOptions = this.getOptionsBySort(this.weekOptions.options,this.initPickerControl.selectWeekOptions);
      this.selectedSchedule.selectTimesOptions = this.getOptionsBySort(this.timesOfDayOptions.options,this.initPickerControl.selectTimesOptions);
    }
    getOptionsBySort(options:string[],selectedOptions:string[]):string[]{
      return options.filter( o => _.includes(selectedOptions,o));
    getOptionsBySort(options:OptionBtnDto[],selectedOptions:string[]):string[]{
      return options.map( o => _.includes(selectedOptions,o.title) ? o.title :'').filter(String);
      // return
    }
    addNewSchedule():void{
      const newScheduleDto={
@@ -140,7 +186,7 @@
      const chineseNumber = ['一','二','三','四','五','六','七','八','九','十']
      return '時段'+chineseNumber[index];
    }
    optionsFormat(options:string[],needToCompareList:OptionDto):string{
    optionsFormat(options:OptionBtnDto[],needToCompareList:OptionDto):string{
      return _.isEqual(options.length,needToCompareList.options.length) ? needToCompareList.selectAll: _.join(options,',');
    }
  }
PAMapp/components/singleSelectBtn.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,44 @@
<template>
  <div>
    <el-radio-group class="pam-single-btn"
      v-model="syncSingleSelected">
      <el-radio v-for="(option,index) in options"
        :key="index"
        :label="option.label"
        @click.native.prevent="patchValue(option.label)">
        <span class="text--middle">
          {{option.title}}
        </span>
        <span class="radio-sub-title"
          v-if="option.subTitle">
            {{option.subTitle}}
        </span>
      </el-radio>
    </el-radio-group>
  </div>
</template>
<script lang="ts">
  import { Component, Prop, PropSync, Vue } from "nuxt-property-decorator";
  import * as _ from 'lodash';
  @Component
  export default class SingleSelectBtn extends Vue {
    @PropSync('singleSelected', { default: '' }) syncSingleSelected!: string | number;
    @Prop({ type:Array , default:()=>[] }) options!:OptionBtnDto[];
    // ä¸»è¦è§£æ±ºæŒ‰éˆ•點擊兩次能回到,未點選的狀態
    patchValue(value: string): void {
      this.syncSingleSelected = _.isEqual(this.syncSingleSelected, value) ? "" : value;
    }
  }
  export interface OptionBtnDto {
    title: string,
    subTitle?: string,
    label: string | number,
  }
</script>
<style lang="scss"
  scoped>
</style>
PAMapp/layouts/default.vue
@@ -3,7 +3,7 @@
        <UiGoToTop></UiGoToTop>
        <BackActionBar></BackActionBar>
        <div class="banner" :class="bannerClassName"></div>
        <Nuxt class="page-container" :style="{ height: pageHieght}" ref="pageContainer"></Nuxt>
        <Nuxt class="page-container"></Nuxt>
        <Footer ref="defaultLayoutFooter"></Footer>
    </div>
</template>
@@ -24,22 +24,7 @@
  }
  noBanner(routerName : string) {
    return routerName.match('communication') || routerName.match('agentInfo');
  }
  mounted() {
      window.addEventListener('resize', this.handleResize);
      this.handleResize();
  }
  handleResize(): void {
    const pageContainer: any = this.$refs.pageContainer;
    const footer: any = this.$refs.defaultLayoutFooter;
    const pageAlignPadding = 80;
    const deviceExtraHeight = 36;
    if ((pageContainer.$el.clientHeight + footer.$el.clientHeight) <  window.innerHeight) {
      this.pageHieght = (window.innerHeight - footer.$el.clientHeight - pageAlignPadding - deviceExtraHeight) + 'px';
    }
    return routerName.match('questionnaire') || routerName.match('agentInfo');
  }
}
@@ -48,6 +33,12 @@
<style lang="scss" scoped>
  .pam-background {
    background-color: #F8F9FA;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    .page-container {
      flex: 1;
    }
  }
  .page-container {
PAMapp/nuxt.config.js
@@ -35,7 +35,8 @@
    '~/plugins/element-ui.js',
    { src: '~/plugins/vue-awesome-swiper.js', mode: 'client' },
    '~/plugins/service.ts',
    '~/plugins/vue-scroll-picker'
    '~/plugins/vue-scroll-picker',
    '~/plugins/filters/date.filter.ts'
  ],
  // Auto import components: https://go.nuxtjs.dev/config-components
@@ -60,5 +61,15 @@
    scss: [
      '~/assets/scss/main.scss'
    ]
  },
  router: {
    scrollBehavior (to, from, savedPosition) {
      if (savedPosition) {
        return savedPosition;
      } else {
        document.body.scrollTop = 0;
        document.documentElement.scrollTop = 0;
      }
    }
  }
}
PAMapp/pages/agentInfo/_agentNo.vue
File was renamed from PAMapp/pages/agentInfo/index.vue
@@ -3,10 +3,10 @@
      <el-row
        type="flex"
        justify="center">
         <el-avatar
        <el-avatar
          size="large"
          src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png">
         </el-avatar>
        </el-avatar>
      </el-row>
      <el-row
@@ -15,7 +15,7 @@
        justify="center"
        align="middle">
          <i class="pam-icon icon--primary icon-star"></i>
          <h3 class="mdTxt">{{ agentInfo.avgReviews }}</h3>
          <h3 class="mdTxt">{{ agentInfo.avgScore }}</h3>
      </el-row>
      <el-row
@@ -158,27 +158,23 @@
</template>
<script lang="ts">
import { Context } from '@nuxt/types';
import { Vue, Component } from 'vue-property-decorator';
import { getConsultantDetail } from '~/assets/ts/api/consultant';
@Component
export default class AgentInfoComponent extends Vue {
  agentInfo: AgentInfo = {
    name: '蔡美眉',
    role: '伯樂保險經紀人',
    avgReviews: 4.8,
    title: '專案經理',
    phoneNumber: '0912345689',
    serveArea: '台北市地區、新北市地區',
    companyAddress: '台北市信義區忠孝東路一段1號',
    lastestLoginTime: new Date(),
    seniority: '4å¹´2個月',
    suitability: 53,
    evaluation: 31,
    expertises: ['財務規劃', '資產移轉', '節稅', '樂活退休'],
    concept: '壽險路上沒有捷徑,唯有給客戶信任感、安全感,才是最好的方法。從業以來,我一直秉持著「助人為快樂之本」的信念堅持著,她相信,一個好的業務人員,必須抱持著一顆熱心助人的心,才是永續經營壽險事業的不二法門。',
    experiences: ['台大財金系', '美莓有精算師執照'],
    awards: '入選:2020年伯樂十大最佳業務員 æ“æœ‰è­‰ç…§ï¼šäººèº«ä¿éšªæ¥­å‹™å“¡è­‰ç…§ã€å¤–幣收付保險證照、人身保險代理人證照、財產保險代理人證照'
  agentInfo!: AgentInfo;
  async asyncData(context: Context) {
    const agentNo = context.route.params.agentNo;
    let agentInfo = {};
    await getConsultantDetail(agentNo).then((res) => agentInfo = res.data )
    return {
      agentInfo
    }
  }
  get agentName(): string {
    return `${this.agentInfo.name}(${this.agentInfo.role})`;
@@ -187,13 +183,15 @@
interface AgentInfo {
  name: string;
  agentNo:string;
  role: string;
  avgReviews: number;
  image: string;
  avgScore: number;
  title: string;
  phoneNumber: string;
  serveArea: string;
  companyAddress: string;
  lastestLoginTime: Date;
  lastestLoginTime: Date | null;
  seniority: string;
  suitability: number;
  evaluation: number;
PAMapp/pages/index.vue
@@ -1,5 +1,7 @@
<template>
    <div>
        <el-button @click="login">登入</el-button>
        <el-button @click="remove">登出</el-button>
        <Ui-Carousel></Ui-Carousel>
        <div class="page-container">
            <h5 class="mdTxt mb-30">預約保險顧問</h5>
@@ -20,7 +22,10 @@
                    <span class="mdTxt">我的顧問清單</span>
                    <span class="smTxt_bold amount">共 {{consultantList.length}} ç­†</span>
                </el-col>
                <el-col :span="8" class="mdTxt readMore"
                <el-col
                    :span="8"
                    class="mdTxt readMore"
                    v-if="consultantList.length > 3"
                    @click.native="routerPush('/myConsultantList/consultantList')">查看更多</el-col>
            </el-row>
            <ConsultantList
@@ -31,35 +36,45 @@
                <h5 class="mdTxt">推薦保險顧問</h5>
                <img class="absulate img" src="~/assets/images/index_recommend.svg" alt="">
            </div>
            <ConsultantSwiper :agents="agents"></ConsultantSwiper>
            <ConsultantSwiper :agents="recommendList"></ConsultantSwiper>
        </div>
    </div>
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { Context } from '@nuxt/types/app';
import { Vue, Component, State, Action } from 'nuxt-property-decorator';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/assets/ts/storageConsultant';
import { addFavoriteConsultant, Consultants } from '~/assets/ts/api/consultant';
import { login, getFavoriteConsultant } from '~/assets/ts/api/consultant';
import { isLogin } from '~/assets/ts/auth';
@Component({
    layout: 'home'
})
export default class MainComponent extends Vue {
    agents: Agents[] = [];
    consultantList: Agents[] = [];
    consultantList: Consultants[] = [];
    agents: Consultants[] = [];
    @State('recommendList') recommendList!: Consultants[];
    @Action storeRecommendList!: any;
    async asyncData(context: Context) {
        let agents: Agents[] = [];
        let consultantList: Agents[] = [];
        await context.$service.home.recommendConsultantList().then((result: Agents[]) => {
            agents = result;
        })
    mounted() {
        if (!this.recommendList) {
            this.storeRecommendList();
        }
        consultantList = agents.filter(item => item.contactStatus !== 'contacted');
        if (isLogin()) {
            this.addFavoriteFromStorageToApi();
            getFavoriteConsultant().then((response) => this.consultantList = response.data);
        } else {
            this.consultantList = getFavoriteFromStorage();
        }
    }
        return {
            agents,
            consultantList
    addFavoriteFromStorageToApi() {
        const agentNoList = getFavoriteFromStorage().map(i => i.agentNo)
        if (agentNoList.length > 0) {
            addFavoriteConsultant(agentNoList).then(res => res);
            localStorage.removeItem('favoriteConsultant');
        }
    }
@@ -67,12 +82,34 @@
        this.$router.push(path);
    }
    removeAgent(agentNo: number) {
    removeAgent(agentNo: string) {
        const findIndex = this.consultantList.findIndex((item, i) => {
            return item.agentNo === agentNo;
        })
        this.consultantList.splice(findIndex, 1)
        this.consultantList.splice(findIndex, 1);
        if (!isLogin()) {
            setFavoriteToStorage(this.consultantList)
        }
    }
    // TODO: åƒ…OTP認證開發前 æš«æ™‚使用
    login() {
        const user = {
            username: "user",
            password: "user"
        }
        login(user).then((res) => {
            localStorage.setItem('id_token', res.data.id_token);
            this.$router.go(0);
        })
    }
    // TODO: åƒ…OTP認證開發前 æš«æ™‚使用
    remove() {
        localStorage.removeItem('id_token');
        this.$router.go(0)
    }
}
</script>
PAMapp/pages/login/index.vue
@@ -1,5 +1,187 @@
<template>
    <div>登入</div>
    <div class="pam-login-page">
      <div class="text--middle">登入</div>
      <div class="pam-paragraph">
        <div class="mdTxt">
        ç¶å®šæ–¹å¼
        </div>
        <div class="pam-tags">
          <el-row type="flex" class="pt-10">
            <el-button
              :class="{ 'active': connectDevice === 'MOBILE'}"
              @click="connectDevice = 'MOBILE'">手機號碼</el-button>
            <el-button
              :class="{ 'active': connectDevice === 'EMAIL'}"
              @click="connectDevice = 'EMAIL'">Email</el-button>
          </el-row>
        </div>
          <el-row type="flex" class="pt-10" v-show="connectDevice === 'MOBILE'">
            <input
              class="pam-input"
              :class="{
                'is-invalid': !phoneNumber
              }"
              v-model="phoneNumber"
              placeholder="請輸入手機號碼"
              >
          </el-row>
          <el-row class="pt-10" v-show="connectDevice === 'EMAIL'">
            <input
              class="pam-input"
              :class="{
                'is-invalid': !phoneNumber
              }"
              v-model="email"
              placeholder="請輸入 Email åœ°å€"
              >
          </el-row>
          <div class="pt-30" v-show="showPhoneOtpCodeField">
            <el-row type="flex" justify="space-between">
                <div class="mdTxt">輸入驗證碼</div>
                <div class="otp-count-timer">
                  13:50
                </div>
            </el-row>
            <el-row class="pt-10">
              <input
                class="pam-input"
                :class="{
                  'is-invalid': !otpCode
                }"
                v-model="otpCode"
                placeholder="請輸入驗證碼"
                >
            </el-row>
            <el-row class="pt-10">
              <button
                class="pam-otp-resend-btn"
                :class="{'disabled': true}">
                <i class="icon-arrow"></i>
                é‡ç™¼é©—證碼({{ otpResendCounter }})
              </button>
            </el-row>
          </div>
          <div v-show="showEmailVerifyField">
            <el-row class="pt-10">
              <button
                class="pam-otp-resend-btn"
                :class="{'disabled': onEmailVerifyResendStatus === 'CANNOT_RESEND'}">
                <i class="icon-arrow"></i>
                é‡ç™¼é©—證碼({{ emailResendCounter }})
              </button>
            </el-row>
          </div>
      </div>
      <el-row type="flex" justify="center" class="pam-login-page__action-bar">
        <div v-if="connectDevice === 'MOBILE'">
          <el-button
            type="primary"
            v-if="onPhoneVerifyStep === 'APPLY_OTP'"
            :disabled="!phoneNumber"
            @click="applyOtpVerification">
            ç™¼é€é©—證碼
          </el-button>
          <el-button
            type="primary"
            v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'"
            :disabled="!otpCode"
            @click="registerDialogVisable = true">
            é€å‡º
          </el-button>
        </div>
      </el-row>
      <el-dialog
        title="歡迎新使用者"
        :custom-class="'pam-register-dialog'"
        :visible.sync="registerDialogVisable"
        :fullscreen="true"
        :close-on-click-modal="false"
        :show-close="false"
        center>
        <span>
          <el-row>
            <input
              class="pam-input"
              :class="{
                'is-invalid': !name
              }"
              v-model="name"
              placeholder="請輸入姓名"
              >
          </el-row>
          <el-row class="pt-30">
            <div class="mdTxt">
              è«‹å¯©é–±æ¢æ¬¾ï¼Œä¸¦æ ¸å‹¾ç¢ºèªå·²é–±è®€ä¸”同意相關條款內容
            </div>
          </el-row>
          <el-row class="pt-10">
            <div class="mdTxt pam-register-dialog__contract">
              è’é›†å€‹äººè³‡æ–™å‘ŠçŸ¥äº‹é …
              éµå®ˆå€‹äººè³‡æ–™ä¿è­·æ³•規定,在您提供個人資料予本處前,依法告
              çŸ¥ä¸‹åˆ—事項:
              ä¸€ã€ç²å–您下列個人資料類別:姓名、出生年月日、國民身分證統一編號、性別、職業、教育、
              é€£çµ¡æ–¹å¼(包括但不限於電話號碼、E-MAIL、居住或工作地址)等,或其他得以直接
              æˆ–間接識別您個人之資料。
              äºŒã€æœ¬è™•將依個人資料保護法及相關法令之規定下,依本處隱私權保護政策,蒐集、
              è™•理及利用您的個人資料。
              ä¸‰ã€æœ¬è™•將於蒐集目的之存續期間合理利用您的個人資料。
              å››ã€é™¤è’é›†ä¹‹ç›®çš„æ¶‰åŠåœ‹é𛿥­å‹™æˆ–活動外,本處僅於中華民國領域內利用您的個人資
              æ–™ã€‚
              äº”、本處將於原蒐集之特定目的、本次以外之產業之推廣、宣導及輔導、以及其他公
              å‹™æ©Ÿé—œè«‹æ±‚行政協助之目的範圍內,合理利用您的個人資料。
              å…­ã€æ‚¨å¯ä¾å€‹äººè³‡æ–™ä¿è­·æ³•第 3 æ¢è¦å®šï¼Œå°±æ‚¨çš„個人資料向本處行使之下列權利:
              (一) æŸ¥è©¢æˆ–請求閱覽。
              (二) è«‹æ±‚製給複製本。
              (三) è«‹æ±‚補充或更正。
              (四) è«‹æ±‚停止蒐集、處理及利用。
              (五) è«‹æ±‚刪除。
              æ‚¨å› è¡Œä½¿ä¸Šè¿°æ¬Šåˆ©è€Œå°Žè‡´å°æ‚¨çš„æ¬Šç›Šç”¢ç”Ÿæ¸›ææ™‚,本處不負相關賠償責任。另依
              å€‹äººè³‡æ–™ä¿è­·æ³•第 14 æ¢è¦å®šï¼Œæœ¬è™•得酌收行政作業費用。
              ä¸ƒã€è‹¥æ‚¨æœªæä¾›æ­£ç¢ºä¹‹å€‹äººè³‡æ–™ï¼Œæœ¬è™•將無法為您提供特定目的之相關業務。
              å…«ã€æœ¬è™•因業務需要而委託其他機關處理您的個人資料時,本處將會善盡監督之責。
              ä¹ã€æ‚¨çž­è§£æ­¤ä¸€åŒæ„æ›¸ç¬¦åˆå€‹äººè³‡æ–™ä¿è­·æ³•及相關法規之要求,且同意本處留存此同
              æ„æ›¸ï¼Œä¾›æ—¥å¾Œå–出查驗。
              å€‹äººè³‡æ–™ä¹‹åŒæ„æä¾›
              ä¸€ã€æœ¬äººå·²å……分知悉貴處上述告知事項。
              äºŒã€æœ¬äººåŒæ„è²´è™•蒐集、處理、利用本人之個人資料,以及其他公務機關請求行政協
              åŠ©ç›®çš„ä¹‹æä¾›ã€‚
            </div>
          </el-row>
          <el-row class="pt-30">
            <div class="pam-agree-radio">
              <label for="agreeControct" class="pam-radio">
                <input
                  type="radio"
                  id="agreeControct"
                  @click="agreeControct = !agreeControct"
                  value="agreeControct">
                  <i :class="agreeControct ?'icon-checkbox-1': 'icon-checkbox'"></i>我同意並繼續
              </label>
            </div>
          </el-row>
        </span>
        <span slot="footer" class="dialog-footer">
          <el-button
            type="primary"
            :disabled="!name || !agreeControct"
            @click="applyAccount"
            >建立新帳號
          </el-button>
        </span>
      </el-dialog>
    </div>
</template>
<script lang="ts">
@@ -7,6 +189,132 @@
@Component
export default class Login extends Vue {
  connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE';
  phoneNumber = '';
  otpCode = '';
  onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP';
  otpResendCounter = 30;
  email = '';
  onEmailVerifyResendStatus: 'CAN_RESEND' | 'CANNOT_RESEND' = 'CANNOT_RESEND';
  emailResendCounter = 30;
  registerDialogVisable = false;
  name = '';
  agreeControct = false;
  get showPhoneOtpCodeField(): boolean {
    return this.connectDevice === 'MOBILE' && this.onPhoneVerifyStep === 'INPUT_OTP';
  }
  get showEmailVerifyField(): boolean {
    return this.connectDevice === 'EMAIL';
  }
  applyOtpVerification(): void {
    this.onPhoneVerifyStep = 'INPUT_OTP';
  }
  applyAccount(): void {
    console.log('apply new account!')
  }
}
</script>
</script>
<style lang="scss">
.pam-login-page {
  font-size: 20px !important;
  display: flex;
  flex-direction: column;
  .pam-login-page__action-bar {
    display: flex;
    flex: 1;
    align-items: flex-end;
  }
}
.pam-input {
  height: 26px;
  width: calc(100% - 36px);
  border-radius: 10px !important;
  padding: 12px 18px !important;
  border-width: 1px;
  outline: none;
  @extend .text--middle;
  &::placeholder {
    color: $PRUDENTIAL_GREY;
  }
  &.is-invalid {
    border: 1px solid $PRIMARY_RED !important;
    border-radius: 20px;
  }
}
.pam-otp-resend-btn {
  background: transparent;
  border: none;
  color: $PRIMARY_RED;
  font-weight: bold;
  i {
    margin-right: 10px;
  }
  &.disabled {
    color: $LIGHT_GREY;
  }
}
.pam-register-dialog__contract {
  $DEVICE_EXTRA_HEIGHT: 42px;
  $ALIGN_PADDING: 60px;
  $TOP_CONTENT_HEIGHT: 186px;
  $BOTTOM_CONTENT_HEIGHT: 131px;
  max-height: calc(100vh - $DEVICE_EXTRA_HEIGHT - $ALIGN_PADDING - $TOP_CONTENT_HEIGHT - $BOTTOM_CONTENT_HEIGHT);
  overflow-y: scroll;
  border-radius: 6px;
  border: 1px solid #707070;
  padding: 20px;
}
.pam-radio {
  color: $PRIMARY_RED;
  align-items: center;
  display: flex;
  font-size: 20px;
  font-weight: bold;
  input {
    display: none;
  }
  i {
    font-size: 27px;
    padding-right: 5px;
  }
}
.pam-register-dialog {
  padding: 30px 20px;
  display: flex;
  flex-direction: column;
  border-radius: 0;
  &.el-dialog {
    border-radius: 0;
  }
  .el-dialog__header {
    padding: 0;
    margin-bottom: 30px;
    .el-dialog__title {
      @extend .subTitle;
    }
  }
  .el-dialog__body {
    flex: 1;
    padding: 0;
    margin-bottom: 30px;
  }
  .el-dialog__footer {
    padding: 0 !important;
  }
}
</style>
PAMapp/pages/myConsultantList.vue
@@ -29,14 +29,16 @@
import { Context } from '@nuxt/types';
import { Vue, Component, Watch } from 'vue-property-decorator';
import { Route } from 'vue-router/types/router.d'
import { Agents } from '~/plugins/api/home';
import { getFavoriteFromStorage, setFavoriteToStorage } from '~/assets/ts/storageConsultant';
import { Consultants, getFavoriteConsultant } from '~/assets/ts/api/consultant';
import { isLogin } from '~/assets/ts/auth';
@Component
export default class myConsultantList extends Vue {
    activeTabName = 'consultantList';
    agents: Agents[] = [];
    contactedList: Agents[] = [];
    consultantList: Agents[] = [];
    agents: Consultants[] = [];
    contactedList: Consultants[] = [];
    consultantList: Consultants[] = [];
    tabClick(path: string) {
        this.activeTabName = path;
@@ -44,13 +46,15 @@
    }
    async asyncData(context: Context) {
        let agents: Agents[] = [];
        let contactedList: Agents[] = [];
        let consultantList: Agents[] = [];
        let agents: Consultants[] = [];
        let contactedList: Consultants[] = [];
        let consultantList: Consultants[] = [];
        await context.$service.home.recommendConsultantList().then((result: Agents[]) => {
            agents = result;
        })
        if (isLogin()) {
            await getFavoriteConsultant().then((response) => agents = response.data);
        } else {
            agents = getFavoriteFromStorage();
        }
        contactedList = agents.filter(item => item.contactStatus === 'contacted');
        consultantList = agents.filter(item => item.contactStatus !== 'contacted');
@@ -62,9 +66,12 @@
        }
    }
    removeAgent(agentNo: number) {
    removeAgent(agentNo: string) {
        const fintIndex = this.consultantList.findIndex(item => item.agentNo === agentNo);
        this.consultantList.splice(fintIndex, 1);
        if (!isLogin()) {
            setFavoriteToStorage(this.consultantList);
        }
    }
    @Watch('$route') watchRouter(currentRoute: Route) {
PAMapp/pages/myConsultantList/consultantList.vue
@@ -13,24 +13,22 @@
</template>
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { Vue, Component, Prop, Emit, Getter } from 'nuxt-property-decorator';
import { Consultants } from '~/assets/ts/api/consultant';
@Component
export default class ConsultantPage extends Vue {
    @Prop() consultantList!: Agents[];
    pageList: Agents[] = [];
    @Prop() consultantList!: Consultants[];
    @Getter isLogin!: boolean;
    pageList: Consultants[] = [];
    @Emit('remove') remove(agentNo: number) {
    @Emit('remove') removeAgent(agentNo: number) {
        return agentNo;
    }
    changePage(pageList: Agents[]) {
    changePage(pageList: Consultants[]) {
        this.pageList = pageList;
    }
    removeAgent(agentNo: number) {
        this.remove(agentNo);
    }
}
</script>
PAMapp/pages/myConsultantList/contactedList.vue
@@ -13,14 +13,14 @@
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator' ;
import { Agents } from '~/plugins/api/home';
import { Consultants } from '~/assets/ts/api/consultant';
@Component
export default class ContactedList extends Vue {
    @Prop() contactedList!: Agents[];
    pageList: Agents[] = [];
    @Prop() contactedList!: Consultants[];
    pageList: Consultants[] = [];
    changePage(pageList: Agents[]) {
    changePage(pageList: Consultants[]) {
        this.pageList = pageList;
    }
}
PAMapp/pages/questionnaire/_agentNo.vue
File was renamed from PAMapp/pages/questionnaire/index.vue
@@ -1,41 +1,41 @@
<template>
    <div class="pam-page">
        <div class="pb-18">
        <div class="pb-18">
            <span class="mdTxt  required">請問您希望保險顧問以電話或Email聯繫您呢?</span>
            <span class="hint">可複選</span>
        </div>
        <div class="pam-tags">
            <el-button :class="{ 'active': isConnectMobile}" @click="toggleConnectDevice('mobile')" class="contactMax Btn">手機</el-button>
            <el-button :class="{ 'active': isConnectEmail}" @click="toggleConnectDevice('email')" class="contactMax Btn">Email</el-button>
        </div>
            <el-button :class="{ 'active': isConnectEmail}" @click="toggleConnectDevice('email')" class="contactMax Btn">Email</el-button>
        </div>
        <div class="connectDesktop">
            <div v-if="isConnectMobile">
                <span class="fz-20 phone-no">0912345678</span>
            </div>
            </div>
            <el-row class="pb-10 pt-10">
                <el-input class="pam-input" v-if="isConnectEmail" placeholder="請輸入電子郵件信箱" v-model="email">
                </el-input>
            </el-row>
            </el-row>
            <div v-if="isConnectMobile">
                <div class="datepicker">
                    <span class="mdTxt required">手機連絡的方便時間</span>
                    <PhoneContactTimePicker :scheduleList.sync="initScheduleList"/>
                </div>
                </div>
            </div>
        </div>
        <div class="mb-30">
            <div>
            <div class="pb-10 mt-10 mdTxt required">想要詢問的問題
            <span class="hint text--bold" @click="showDrawer = true">
            <i class="icon-information text--bold" @click="showDrawer = true"></i>可複選</span></div>
            <ConsultantQues />
            <ConsultantQues @change="selectedQuestion = $event" class="mb-30"/>
            </div>
        </div>
        <div>
            <div class="pb-10 mdTxt pt-10">您的性別</div>
            <div class="mb-30 pam-tags">
            <div class="mb-30 pam-tags">
                <el-button class="desktopBtn tags" @click="gender = 'male'" :class="{ 'active': gender ==='male'}">男性</el-button>
                <el-button class="desktopBtn tags" @click="gender = 'female'" :class="{'active': gender === 'female'}">女性</el-button>
            </div>
@@ -52,94 +52,84 @@
                <el-button class="ageDesktopP2" @click="age = '70'" :class="{'active': age === '70'}">61-70æ­²</el-button>
                <el-button class="mb-10" @click="age = '71'" :class="{'active': age === '71'}">71歲以上</el-button>
            </div>
        </div>
        </div>
        <div class="mdTxt pt-10 pb-10">職業</div>
        <div class="job-pick" @click="showJobDrawer = true">
            <input class="fz-20 pl-18 input"
        <div class="job-pick" @click="showJobDrawer = true">
            <input class="fz-20 pl-18 input"
                style="outline:none margin-right:-18px"
                @click="showJobDrawer = true" placeholder="請選擇" :value="staff === '其他' ? `其他, ${inputValue}` : staff">
            <i class="icon-down down-icon " style="margin-right:18px" @click="showJobDrawer = true" ></i>
        </div>
        <div class="ques-footer pt-30">
            <el-button type="primary" @click="sendReserve = true">送出</el-button>
            <el-button type="primary"
            :disabled=" isInitScheduleDisabled || !isSelected"
            @click="sentDemand">送出</el-button>
        </div>
        <UiDrawer :is-visible.sync="showDrawer" size='95%'>
            <div class="qaTextTitle mdTxt">想要詢問的問題</div>
                <div class="qa-dialog">
        <PopUpFrame :isOpen.sync="showDrawer" :drawerSize=" '95%' ">
            <div class="qaTextTitle mdTxt"><strong>想要詢問的問題</strong></div>
                <div class="qa-dialog">
                    <div v-for="(qaText,index) in queaAboutList" :key="index" >
                        <div class="pt-10">
                            <p class=" p bold">{{qaText.title}}</p>
                            <p class="p">{{qaText.content}}</p>
                        </div>
                        </div>
                    </div>
                </div>
            <div class="qa-dialog-footer mdTxt" @click="showDrawer = false"><p>我知道了</p></div>
        </UiDrawer>
        <UiDrawer  :is-visible.sync="showJobDrawer" size='60%'>
            <div class="qa-dialog-footer mdTxt" @click="showDrawer = false"><p>我知道了</p></div>
        </PopUpFrame>
        <PopUpFrame :isOpen.sync="showJobDrawer" drawerSize='60%'>
            <div class="job-drawerTxt fz-20">
                <div class="subTitle mt-18">職業</div>
                <!-- <p class="mt-30 fz-20" @click="staff ='內勤'" :class="{'jobBtn':staff === '內勤' }">內勤</p> -->
                <div class="radio-btn">
                    <el-radio-group  class="pam-radio-group--col" v-model="staff">
                        <el-radio-button style="margin-top:30px" label="外勤"></el-radio-button>
                        <el-radio-button style="margin-top:30px" text-color='#F09491' label="外勤"></el-radio-button>
                        <el-radio-button style="margin-top:30px" label="內勤"></el-radio-button>
                        <el-radio-button style="margin-top:30px" label="其他"></el-radio-button>
                    </el-radio-group>
                </div>
                <div class="job-inputDiv"><input v-if="staff === '其他'" class="job-input mb-30 fz-20 pl-20" v-model="inputValue" > </div>
                <el-button type="primary" class="job-drawerBtn" @click="showJobDrawer = false">確定</el-button>
            </div>
        </UiDrawer>
        <UiDrawer :is-visible.sync="sendReserve">
            <div class="fz-20 mt-30">預約成功!您預約的保險顧問會</div>
            </div>
        </PopUpFrame>
        <PopUpFrame :isOpen.sync="sendReserve" @update:isOpen="closeReservePopUp">
            <div class="fz-20 mt-30 sendReserve-txt">預約成功!您預約的保險顧問會</div>
            <div class="fz-20 sendReserve-txt">儘速與您聯絡!</div>
            <div class="qa-dialog-footer mdTxt" @click="sendReserve = false"><p>我知道了</p></div>
        </UiDrawer>
            <div class="qa-dialog-footer mdTxt" @click="closeReservePopUp"><p>我知道了</p></div>
        </PopUpFrame>
    </div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { appointmentDemand } from '~/assets/ts/api/consultant';
@Component
    export default class Questionnaire extends Vue {
                gender: 'male'|'female' = 'male';
                // connectTarget:'mobile'|'email'='mobile';
                connectDevices = ['1111'];
                connectDevices = new Array();
                mobileNumber = '';
                email = '';
                inputValue='';
                // staff:'in'|'out'|'jobOther'='in';
                selectedQuestion: SelectedQuestion[] = [];
                staff = '外勤';
            
                age:'20'|'30'|'40'|'50'|'55'|'60'|'70'|'71' = '20';
                showDrawer= false;
                showJobDrawer = false;
                sendReserve = false;
                // datepicker Dto
                initScheduleList=[
                  {
                initScheduleList=[{
                    selectWeekOptions:[],
                    selectTimesOptions:[],
                  }
                ]
                    }]
                // editJob(targetJob: string): void {
                //     const isExist = this.jobList.find((job) => job === targetJob);
                //     if (isExist) {
                //         const jobIndex = this.jobList.findIndex((job) => job === targetJob);
                //         this.jobList.splice(jobIndex, 1);
                //     } else {
                //         this.jobList.push(targetJob);
                //     }
                // }
                queaAboutList=[
                    {
                        title:'健康與保障',
@@ -170,11 +160,12 @@
                        content:'匹配度是透過嚴選配對或快速篩選後,將每一位保險顧問資料進行比對後排序推薦給您的媒合數值,您可以作為選擇適合顧問的參考值。'
                    },
                ]
                agentNo!: string;
                get disableActionButton(): boolean {
                    return true;
                };
                get isConnectMobile(): boolean {
                    return this.connectDevices.includes('mobile');
@@ -184,16 +175,92 @@
                    return this.connectDevices.includes('email');
                }
                mounted() {
                    this.agentNo = this.$route.params.agentNo;
                }
                get isSelected() {
                    return this.selectedQuestion.findIndex(i => i.selected)>=0
                }
                toggleConnectDevice(selectDevice: 'mobile' | 'email'): void {
                    const deviceSelected = this.connectDevices.includes(selectDevice);
                    if (deviceSelected) {
                        const deviceIndex = this.connectDevices.findIndex((device) => device === selectDevice);
                        this.connectDevices.splice(deviceIndex, 1);
                        if (selectDevice === 'mobile') {
                            this.initScheduleList = [{
                                selectWeekOptions:[],
                                selectTimesOptions:[],
                            }]
                        }
                        if (selectDevice === 'email') {
                            this.email = '';
                        }
                        return;
                    }
                    this.connectDevices.push(selectDevice);
                }
                sentDemand() {
                    const data = {
                        phone: '09123456789',
                        email: this.email,
                        contactType: this.connectDevices.toString(),
                        gender: this.gender,
                        age: this.age,
                        job: this.staff,
                        requirement: this.getRequirement(),
                        hopeContactTime: this.getHopeContactTime(),
                        otherRequirement: '',
                        agentNo: this.agentNo
                    }
                    appointmentDemand(data).then(res => {
                        this.sendReserve = true
                    })
                }
                getRequirement() {
                    const requirement = this.selectedQuestion.filter(item => item.selected)
                    return requirement.map(item => item.name).toString();
                }
                getHopeContactTime() {
                    const initScheduleList = this.initScheduleList.map(item => {
                        return {
                            selectWeekOptions: item.selectWeekOptions.toString(),
                            selectTimesOptions: item.selectTimesOptions.toString()
                        }
                    })
                    return initScheduleList.map(i => {
                        return `'${i.selectWeekOptions},${i.selectTimesOptions}'`}
                    ).toString()
                }
                closeReservePopUp() {
                    this.sendReserve = false;
                    this.$router.push('/')
                }
                get isInitScheduleDisabled() {
                    console.log(this.isConnectMobile)
                    if (this.isConnectMobile && this.isConnectEmail) {
                        return !this.initScheduleList[0].selectWeekOptions.length || !this.initScheduleList[0].selectTimesOptions.length || !this.email
                    } else if (this.isConnectMobile) {
                        return !this.initScheduleList[0].selectWeekOptions.length || !this.initScheduleList[0].selectTimesOptions.length
                    } else if (this.isConnectEmail) {
                        return !this.email
                    }
                    return true;
                }
    }
    export interface SelectedQuestion {
        name: string;
        selected: boolean;
    }
</script>
<style lang="scss">
@@ -270,6 +337,7 @@
.ques-footer{
    display: flex;
    justify-content: center;
}
.job-inputDiv{
    height:90px;
@@ -288,6 +356,7 @@
    border:1px solid #D0D0CE ;
    display: flex;
    justify-content: space-between;
    background-color: #FFFFFF;
}
.qa-dialog-footer{
@@ -363,7 +432,7 @@
.qa-dialog-footer{
    display: flex;
    justify-content: center;
    margin-bottom: 81px;
    margin-bottom: 81px;
}
.el-button+.el-button{
    margin-left: 0;
@@ -411,7 +480,9 @@
        padding-right: 16px;
        padding-top: 11px;
}
.addDate{
    margin-left: -20px;
}
.ageTags{
    display: flex;
    flex-wrap: wrap;
PAMapp/pages/quickFilter/index.vue
@@ -5,8 +5,7 @@
                v-for="(question, index) in questionList"
                :key="index"
                class="subTitle quickBtn"
                :disabled="question.name === 'onlineState'"
                :disabled="question.name === 'status'"
                @click="openPopUp(question)"
            >{{question.title}}</el-button>
        </div>
@@ -16,19 +15,19 @@
                v-if="selectedItem.gender"
                @removeTag="removeTag('gender')"
            >
                {{selectedItem.gender}}
                {{selectedItem.gender === 'male' ? '男性' : '女性'}}
            </Ui-Tags>
            <Ui-Tags
                v-if="selectedItem.satisfaction"
                @removeTag="removeTag('satisfaction')"
                v-if="selectedItem.avgScore"
                @removeTag="removeTag('avgScore')"
            >
                {{selectedItem.satisfaction + '星以上滿意度'}}
                {{selectedItem.avgScore + '星以上滿意度'}}
            </Ui-Tags>
            <template v-if="selectedItem.style.length > 0">
            <template v-if="selectedItem.communicationStyles">
                <Ui-Tags
                    v-for="(item, index) in selectedItem.style"
                    v-for="(item, index) in selectedItem.communicationStyles"
                    :key="index"
                    @removeTag="removeTag('style', index)"
                    @removeTag="removeTag('communicationStyles', index)"
                >
                    {{item}}
                </Ui-Tags>
@@ -36,107 +35,107 @@
            <div class="mb-10" v-if="selectedItem.onlineState"></div>
            <div class="emptyBox text--mid_grey"
                v-if="!selectedItem.gender && !selectedItem.satisfaction && selectedItem.style.length === 0 && !selectedItem.onlineState">
                v-if="isEmpty">
                <p class="smTxt">尚無篩選</p>
            </div>
        </div>
        <h5 class="mdTxt mb-10 mt-30">快速篩選推薦</h5>
        <template v-if="consultantList.length > 0">
            <QuickFilterConsultantList></QuickFilterConsultantList>
        </template>
        <div class="mb-10 mt-30">
            <span class="mdTxt">快速篩選推薦</span>
            <span class="smTxt_bold text--prudential_grey ml-10">共 {{consultantList.length}} ç­†</span>
        </div>
        <div class="recommend">
            <img class="img" src="~/assets/images/quickFilter/recommend.svg" alt="">
        <template v-else>
            <div class="emptyBox bg-white"></div>
        </template>
            <template v-if="consultantList.length > 0">
                <QuickFilterConsultantList :consultantList="consultantList"></QuickFilterConsultantList>
            </template>
        <Ui-Drawer
            :isVisible.sync="questionDrawer"
            :size="questionOption.name === 'style' ? '50%' : '30%'"
            @closeDrawer="closePopUp"
            <template v-else>
                <div class="emptyBox text--mid_grey recommendStyle">
                    <p class="smTxt">尚無推薦資料</p>
                </div>
            </template>
        </div>
        <PopUpFrame
            :isOpen.sync="questionPopUp"
            :drawerSize="questionOption.name === 'communicationStyles' ? '50%' : '30%'"
            @update:isOpen="closePopUp"
        >
            <QuickFilterSelector
                ref="quickFilterRef"
                :drawerVisible.sync="questionDrawer"
                :drawerVisible.sync="questionPopUp"
                :questionOption="questionOption"
                :selectedItem="selectedItem"
            ></QuickFilterSelector>
        </Ui-Drawer>
        </PopUpFrame>
        <Ui-Dialog :isVisible.sync="dialog"
            @closeDialog="closePopUp"
        >
            <QuickFilterSelector
                ref="quickFilterRef"
                :drawerVisible.sync="questionDrawer"
                :questionOption="questionOption"
                :selectedItem="selectedItem"
            ></QuickFilterSelector>
        </Ui-Dialog>
    </div>
</template>
<script lang="ts">
import { Context } from '@nuxt/types';
import { Vue, Component } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { isMobileDevice } from '~/assets/ts/device';
import { Consultants, FastQueryParams } from '~/assets/ts/api/consultant';
import QuickFilterDrawer from '~/components/QuickFilter/QuickFilterSelector.vue';
import { fastQuery } from '~/assets/ts/api/consultant';
@Component
export default class QuickFilter extends Vue {
    dialog = false;
    consultantList = [];
    questionDrawer = false;
    questionPopUp = false;
    consultantList: Consultants[] = [];
    questionOption = {};
    selectedItem: selectedItem = {
    selectedItem: FastQueryParams = {
        gender: '',
        satisfaction: 0,
        style: [],
        onlineState: ''
        avgScore: 0,
        communicationStyles: [],
        status: ''
    };
    questionList: QuestionOption[] = [
        {
            name: 'gender',
            title: '顧問性別',
            detail: ['男性', '女性'],
            type: 'radio'
            detail: [
                { name: '男性', value: 'male', className: 'btn_man'},
                { name: '女性', value: 'female', className: 'btn_woman'}
            ],
            type: 'radio',
        },
        {
            name: 'satisfaction',
            name: 'avgScore',
            title: '顧問滿意度',
            detail: [],
            type: ''
        },
        {
            name: 'style',
            name: 'communicationStyles',
            title: '溝通風格',
            detail: ['謹慎務實', '明快主動', '耐心傾聽', '健談風趣'],
            detail: [
                { value: '謹慎務實', className: 'btn_owl'},
                { value: '明快主動', className: 'btn_tiger'},
                { value: '耐心傾聽', className: 'btn_koala'},
                { value: '健談風趣', className: 'btn_peacock'}
            ],
            type: 'checkbox'
        },
        {
            name: 'onlineState',
            name: 'status',
            title: '上線狀態',
            detail: [],
            type: 'radio'
        }
    ];
    async asyncData(context: Context) {
        let consultantList: Agents[] = [];
        await context.$service.home.recommendConsultantList().then((result: Agents[]) => {
            consultantList = result;
        })
        return {
            consultantList,
        }
    get isEmpty() {
        return !this.selectedItem.gender
            && !this.selectedItem.avgScore
            && this.selectedItem.communicationStyles.length === 0
            && !this.selectedItem.status
    }
    openPopUp(question: QuestionOption) {
        this.questionOption = question;
        isMobileDevice() ? this.questionDrawer = true : this.dialog = true;
        this.questionPopUp =true;
    }
    removeTag(type: string, index: number = 0) {
@@ -145,52 +144,65 @@
            this.selectedItem.gender = ''
        }
        if (type === 'satisfaction') {
             this.selectedItem.satisfaction = 0
        if (type === 'avgScore') {
             this.selectedItem.avgScore = 0
        }
        if (type === 'style') {
            this.selectedItem.style.splice(index, 1)
        if (type === 'communicationStyles') {
            this.selectedItem.communicationStyles.splice(index, 1)
        }
        this.isEmpty ? this.consultantList = [] : this.getRecommendList();
    }
    closePopUp() {
        this.selectedItem = JSON.parse(JSON.stringify((this.$refs.quickFilterRef as QuickFilterDrawer).pickedItem));
        this.getRecommendList();
    }
    getRecommendList() {
        const data = {
            gender: this.selectedItem.gender,
            communicationStyles: this.selectedItem.communicationStyles,
            avgScore: this.selectedItem.avgScore,
            status: this.selectedItem.status
        }
        fastQuery(data).then((res) => this.consultantList = res.data)
    }
}
export interface QuestionOption {
    title: string;
    detail: string[];
    detail: Detail[];
    type: string;
    name: string;
}
export interface selectedItem {
interface Detail {
    value: string;
    name?: string;
    gender: string;
    satisfaction: number;
    style: string[];
    onlineState: string;
    className: string;
}
</script>
<style lang="scss" scoped>
    .emptyBox {
        width: 100%;
        height: 100px;
        border: solid 1px $LIGHT_GREY;
        text-align: center;
        border-radius: 10px;
        .smTxt {
            line-height: 100px;
        }
    }
    .bg-white {
    .recommendStyle {
        box-shadow: 0 0 6px #00000029;
        background-color: $PRIMARY_WHITE;
    }
@@ -206,35 +218,33 @@
            height: 56px;
            text-align: center;
            box-shadow: 0 0 6px #22222229;
            border-color: $PRIMARY_WHITE;
            border-radius: 10px;
            color: $PRIMARY_BLACK;
            &:hover,&:focus {
                color: $PRIMARY_BLACK;
                border-color: $PRIMARY_WHITE;
                background-color: $PRIMARY_WHITE;
            }
            color: $PRIMARY_WHITE;
            background-size: cover;
            background-position: center;
            background-repeat: no-repeat;
            background-image: url('~/assets/images/quickFilter/btn_bg.svg');
            &:disabled {
                color: $PRIMARY_WHITE;
                border-color: $LIGHT_GREY;
                background-color: $LIGHT_GREY;
                background-image: none;
            }
        }
        .quickBtn+.quickBtn {
            margin-left: 0;
        }
        .is-disabled {
            background-color: $LIGHT_GREY;
            color: $PRIMARY_WHITE;
    }
            &:hover,&:focus {
                color: $PRIMARY_WHITE;
                border-color: $LIGHT_GREY;
                background-color: $LIGHT_GREY;
            }
    .recommend {
        position: relative;
        .img {
            position: absolute;
            top: -50px;
            right: 10px;
        }
    }
PAMapp/pages/recommendConsultant/index.vue
@@ -1,396 +1,459 @@
<template>
<div  class="pam-page">
  <div class="pam-page">
    <div class="pb-10 mdTxt">您的性別</div>
    <div class="mb-30 pam-tags ">
            <el-button style="margin-right:10px" @click="gender = 'male'" :class="{ 'active': gender ==='male'}">男性</el-button>
            <el-button  @click="gender = 'female'" :class="{'active': gender === 'female'}">女性</el-button>
    <SingleSelectBtn :singleSelected.sync="strictQueryDto.gender" :options="genderOptions"/>
    <div class="mt-30">
      <div class="pb-10 mdTxt required">所在地區</div>
      <div class="job-pick cursor--pointer"
        @click="showAddress = true">
        <input class="fz-20 input cursor--pointer pl-10"
          :value="strictQueryDto.area"
          placeholder="請選擇">
        <i class="icon-down down-icon"></i>
      </div>
      <PopUpFrame :isOpen.sync="showAddress"
        :drawerSize="'45%'">
        <AddressPicker @close="showAddress = false"
          @change="area => strictQueryDto.area = area" />
      </PopUpFrame>
    </div>
    <div class="mb-30">
        <div class="pb-10 mdTxt required">所在地區</div>
        <div class="area-choice"><div class="fz-20 area-txt">新北市</div><i class="icon-down area-icon"></i></div>
    <div class="mt-30">
      <div class="pb-10 mdTxt required" @click="showDialog = true">
        æƒ³è¦è©¢å•çš„問題
        <span class="hint text--bold">
          <i class="icon-information text--bold"></i>可複選
        </span>
      </div>
      <MultiSelectBtn :mutiSelect.sync="strictQueryDto.requirements" :options="requirementOptions" />
    </div>
    <div class="mb-10">
        <div class="pb-10 mdTxt required">想要詢問的問題<span class="hint text--bold" @click="showDialog = true"><i class="icon-information text--bold" @click="showDialog = true"></i>可複選</span></div>
    <div class="mt-30 pam-tags">
      <div class="pb-10 mdTxt">顧問年資</div>
       <SingleSelectBtn :singleSelected.sync="strictQueryDto.seniority" :options="seniorityOptions"/>
    </div>
        <ConsultantQues class="mb-30"/>
    <div class="rate-consultant mt-30">
      <div class="pb-10 mdTxt">保險顧問滿意度</div>
      <el-rate v-model="strictQueryDto.avgScore"
        :colors="elRateColors"
        class="rate">
      </el-rate>
    </div>
    <div class="mt-30">
      <div class="rec-popular">
        <div class="pb-10 mdTxt">熱門檢索</div>
        <div class="hint text--bold ml-10">可複選</div>
      </div>
      <MultiSelectBtn :mutiSelect.sync="strictQueryDto.popularTags"
        :options="popularOptions"
        :nameOfOtherOption="'#其他'" :otherSelect.sync="strictQueryDto.otherPopularTags" />
    </div>
    <div class="rec-footer mt-30">
      <el-button type="primary"
        :disabled="notFinishByRequireRules"
        @click="makePair">馬上配對</el-button>
    </div>
    <div class="mb-30 pam-tags">
        <div class= "pb-10 mdTxt">顧問年資</div>
        <div class="rec-ageDesktop">
            <el-button class="mb-10 qaTags" @click="seniority = 'unlimited'" :class="{'active':seniority === 'unlimited'}">
                <span class="text--bold">不限 </span><span>年齡不是問題</span>
            </el-button>
            <el-button class="mb-10 qaTags" @click="seniority = 'junior'" :class="{'active':seniority === 'junior'}">
                <span class="text--bold">年輕 </span><span>給年輕人一個機會</span>
            </el-button>
            <el-button class="mb-10 qaTags" @click="seniority = 'senior'" :class="{'active':seniority === 'senior'}">
                <span class="text--bold">資深 </span><span>薑是老的辣</span>
            </el-button>
    <PopUpFrame :isOpen.sync="showDialog"
      :drawerSize=" '95%' ">
      <div class="qaTextTitle mdTxt">
        <strong>想要詢問的問題</strong>
      </div>
      <div class="qa-dialog">
        <div v-for="(qaText,index) in queaAboutList"
          :key="index">
          <div class="pt-10">
            <p class=" p bold">{{qaText.title}}</p>
            <p class="p">{{qaText.content}}</p>
          </div>
        </div>
      </div>
      <div class="qa-dialog-footer mdTxt"
        @click="showDialog = false">
        <p>我知道了</p>
      </div>
    </PopUpFrame>
    </div>
    <div class="rate-consultant mb-30">
        <div>
            <div  class="pb-10 mdTxt">保險顧問滿意度</div>
        </div>
        <el-rate v-model="ratevalue" :colors="elRateColors" class="rate" @change="selected"></el-rate>
    </div>
    <div class="mb-30">
        <div class="rec-popular">
            <div class="pb-10 mdTxt">熱門檢索</div>
            <div class="hint text--bold ml-10">可複選</div>
        </div>
        <div class="pop-tag pam-tags">
            <div v-for="(popularItem,index) in popularList" :key="index">
                <el-button class="rec-pop-btn" :class="{'active': popularItem.reversation}"
                @click="popularTarget(index)">{{'#'+popularItem.name}}
                </el-button>
            </div>
            <div class="pam-tags popOtherBtn">
                <el-button class="other-PopBtn " @click="popOther=!popOther" :class="{'active':popOther=popOther}">#其他</el-button>
            </div>
        </div>
            <div>
                <input v-if="popOther" class="other-input " placeholder="請輸入,限X字">
            </div>
    </div>
    <div class="rec-footer">
        <el-button
            :disabled="disableActionButton"
            @click="$router.push('recommendConsultant/result')">馬上配對</el-button>
    </div>
    <UiDrawer :is-visible.sync="showDialog" size='95%'>
            <div class="qaTextTitle mdTxt"><strong>想要詢問的問題</strong></div>
                <div class="qa-dialog">
                    <div v-for="(qaText,index) in queaAboutList" :key="index" >
                        <div class="pt-10">
                            <p class=" p bold">{{qaText.title}}</p>
                            <p class="p">{{qaText.content}}</p>
                        </div>
                    </div>
                </div>
            <div class="qa-dialog-footer mdTxt" @click="showDialog = false"><p>我知道了</p></div>
        </UiDrawer>
        <!-- <UiDialog :is-visible.sync="showDialog" >
        </UiDialog> -->
</div>
  </div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component
    export default class RecommendConsultant extends Vue {
        ratevalue = null;
        showDialog = false;
        elRateColors=['#ED1B2E','#ED1B2E','#ED1B2E'];
        gender: 'male'|'female' = 'male';
        seniority:'unlimited'|'junior'|'senior' = 'unlimited' ;
        popOther = '';
        yearList=[
            {
                title:'不限 ',
                subTitle:'年齡不是問題',
                reversation:false
            },
            {
                title:'年輕 ',
                subTitle:'給年輕人一個機會',
                reversation:false
            },
            {
                title:'資深 ',
                subTitle:'薑是老的辣',
                reversation:false
            },
        ]
        popularList=[
            {
                name:'防疫',
                reversation:false
            },
            {
                name:'失能',
                reversation:false
            },
            {
                name:'防癌',
                reversation:false
            },
            {
                name:'醫療',
                reversation:false
            },
            {
                name:'壽險',
                reversation:false
            },
            {
                name:'儲蓄',
                reversation:false
            },
            {
                name:'投資',
                reversation:false
            },
            {
                name:'意外',
                reversation:false
            }
        ]
        queaAboutList=[
                    {
                        title:'健康與保障',
                        content:'突發的意外或疾病,往往造成個人或家庭沉重的經濟負擔,周全的保險才能擁有一個無憂的未來。'
                    },
                    {
                        title:'子女教育',
                        content:'利用分紅保單,規劃教育基金 ææ—©ç‚ºå­å¥³ä½œæº–備,讓生活更有保障!'
                    },
                    {
                        title:'資產規劃',
                        content:'當財務責任加重時,規劃充足的保障、提供經濟上的庇護,是人生最堅強的後盾。'
                    },
                    {
                        title:'樂活退休',
                        content:'兼具保險與投資雙重功能,可靈活搭配各種附約,順應人生不同階段的靈活需要。'
                    },
                    {
                        title:'保單健檢/規劃',
                        content:'全面檢視自己的保障結構是否符合現在或未來的風險移轉需求,透過「斷、捨、離」把錢花在刀口上。'
                    },
                    {
                        title:'防疫保單',
                        content:'匹配度是透過嚴選配對或快速篩選後,將每一位保險顧問資料進行比對後排序推薦給您的媒合數值,您可以作為選擇適合顧問的參考值。'
                    },
                    {
                        title:'其他',
                        content:'匹配度是透過嚴選配對或快速篩選後,將每一位保險顧問資料進行比對後排序推薦給您的媒合數值,您可以作為選擇適合顧問的參考值。'
                    },
                ]
        ageTarget(index: any){
            this.yearList[index].reversation = !this.yearList[index].reversation ;
        }
        popularTarget(index: any){
            this.popularList[index].reversation = !this.popularList[index].reversation ;
        }
  import {
    Vue,
    Component
  } from 'vue-property-decorator';
  import {strictQuery} from '~/assets/ts/api/consultant';
  import * as _ from 'lodash';
  @Component
  export default class RecommendConsultant extends Vue {
    strictQueryDto={
      gender:'',
      area:'',
      status:'',
      requirements:[],
      otherRequirement:'',
      seniority:'',
      avgScore:0,
      popularTags:[],
      otherPopularTags:'',
    };
    genderOptions=[
      {
        title:'男性',
        label:Gender.MALE,
      },
      {
        title:'女性',
        label:Gender.FEMALE,
      }
    ];
    requirementOptions=[
      {
        title:'健康與保障',
        label:'健康與保障',
      },
      {
        title:'子女教育',
        label:'資產規劃',
      },
      {
        title:'樂活退休',
        label:'樂活退休',
      },
      {
        title:'保單健檢/規劃',
        label:'保單健檢/規劃',
      },
      {
        title:'防疫保單相關',
        label:'防疫保單相關',
      },
    ];
    seniorityOptions=[
      {
        title:'不限',
        subTitle:'年齡不是問題',
        label:'不限',
      },
      {
        title:'年輕',
        subTitle:'給年輕人一個機會',
        label:'年輕',
      },
      {
        title:'資深',
        subTitle:'薑是老的辣',
        label:'資深',
      }
    ];
    popularOptions=[
      {
        title: '#防疫',
        label:''
      },
      {
        title: '#失能',
        label:'失能'
      },
      {
        title: '#防癌',
        label:'防癌'
      },
      {
        title: '#醫療',
        label:'醫療'
      },
      {
        title: '#壽險',
        label: '壽險'
      },
      {
        title: '#儲蓄',
        label:'儲蓄'
      },
      {
        title: '#投資',
        label:'投資'
      },
      {
        title: '#意外',
        label:'意外'
      }
    ];
    queaAboutList = [
      {
        title: '健康與保障',
        content: '突發的意外或疾病,往往造成個人或家庭沉重的經濟負擔,周全的保險才能擁有一個無憂的未來。'
      },
      {
        title: '子女教育',
        content: '利用分紅保單,規劃教育基金 ææ—©ç‚ºå­å¥³ä½œæº–備,讓生活更有保障!'
      },
      {
        title: '資產規劃',
        content: '當財務責任加重時,規劃充足的保障、提供經濟上的庇護,是人生最堅強的後盾。'
      },
      {
        title: '樂活退休',
        content: '兼具保險與投資雙重功能,可靈活搭配各種附約,順應人生不同階段的靈活需要。'
      },
      {
        title: '保單健檢/規劃',
        content: '全面檢視自己的保障結構是否符合現在或未來的風險移轉需求,透過「斷、捨、離」把錢花在刀口上。'
      },
      {
        title: '防疫保單',
        content: '匹配度是透過嚴選配對或快速篩選後,將每一位保險顧問資料進行比對後排序推薦給您的媒合數值,您可以作為選擇適合顧問的參考值。'
      },
      {
        title: '其他',
        content: '匹配度是透過嚴選配對或快速篩選後,將每一位保險顧問資料進行比對後排序推薦給您的媒合數值,您可以作為選擇適合顧問的參考值。'
      },
    ];
    showDialog = false;
    showAddress = false;
    elRateColors = ['#ED1B2E', '#ED1B2E', '#ED1B2E'];
        get disableActionButton(): boolean {
            return true;
        }
        // @Prop() ilist
        // remove() {
        // }
        // get agentList() {
        //     return
        // }
        selected() {
            console.log(this.ratevalue);
        }
    makePair():void{
      strictQuery(this.strictQueryDto).then(res=>{
        console.log('resultData',res.data);
         this.$router.push('/recommendConsultant/result');
      });
    }
    get notFinishByRequireRules():boolean{
      const area = this.strictQueryDto.area;
      const requirementLength = this.strictQueryDto.requirements.length;
      return !(area && requirementLength >0)
    }
  }
  enum Gender{
    MALE="male",
    FEMALE="female",
  }
</script>
<style lang="scss">
.popOtherBtn{
  input:focus,
  textarea:focus {
    outline: none;
  }
  .input {
    border: none;
    width: 90%;
    border-radius: 10px;
  }
  .job-pick {
    height: 50px;
    border-radius: 10px;
    border: 1px solid #D0D0CE;
    display: flex;
    justify-content: space-between;
    background-color: #FFFFFF;
  }
  .down-icon {
    color: #ED1B2E;
    font-size: 25px;
    align-self: center;
    margin-right: 15px;
  }
  .popOtherBtn {
    margin-left: -190px;
    margin-top: 45px;
}
.genderBtn{
    width:80px;
    height:47px;
  }
  .genderBtn {
    width: 80px;
    height: 47px;
    display: contents;
}
.qa-dialog{
    overflow-y:auto;
    height: 500px;
  }
  .qa-dialog {
    overflow-y: auto;
    height: auto;
    margin-top: 20px;
}
.qaTextTitle{
    margin-top:30px
}
.qa-dialog-footer{
  }
  .qaTextTitle {
    margin-top: 30px
  }
  .qa-dialog-footer {
    display: flex;
    justify-content: center;
    margin-bottom: 81px;
    color: #ED1B2E;
}
.el-drawer__container ::-webkit-scrollbar {
        display: none;
    }
.el-button+.el-button{
  }
  .el-drawer__container ::-webkit-scrollbar {
    display: none;
  }
  .el-button+.el-button {
    margin-left: 0;
}
.seniority-choice{
  }
  .seniority-choice {
    display: flex;
    flex-wrap: wrap;
}
.area-choice{
    height:50px;
    border-radius:10px;
    border:1px solid #D0D0CE ;
  }
  .area-choice {
    height: 50px;
    border-radius: 10px;
    border: 1px solid #D0D0CE;
    display: flex;
    justify-content: space-between;
    background-color: #FFFFFF;
}
.area-icon{
        color:#ED1B2E;
        font-size: 25px;
        display: flex;
        justify-content: flex-end;
        padding-right: 16px;
        padding-top: 11px;
    }
input::-webkit-input-placeholder{
  }
  .area-icon {
    color: #ED1B2E;
    font-size: 25px;
    display: flex;
    justify-content: flex-end;
    padding-right: 16px;
    padding-top: 11px;
  }
  input::-webkit-input-placeholder {
    font-size: 20px;
    padding-left: 10px;
}
.el-button.is-disabled{
  }
  .el-button.is-disabled {
    font-size: 20px;
    border-radius: 20px;
    color:#FFFFFF;
    background-color: #A7A8AA;
    border:1px solid #A7A8AA;
}
.rec-footer{
    height:70px;
    color: #FFFFFF;
    background-color: #A7A8AA;
    border: 1px solid #A7A8AA;
  }
  .rec-footer {
    height: 70px;
    display: flex;
    justify-content: center;
    align-items: center;
    margin: -10px;
}
.other-PopBtn{
  }
  .other-PopBtn {
    width: 90px;
    height: 47px;
    margin-top: 10px;
}
.other-input{
    margin-top: 20px;
    height: 50px;
    width:316px;
    border:1px solid #CCCCCC;
    ;
}
.rec-ques-location{
    display:flex;
  }
  .rec-ques-location {
    display: flex;
    align-items: center;
}
.pop-tag{
  }
  .pop-tag {
    display: flex;
    flex-wrap: wrap;
    margin: -10px;
}
.rec-popular{
  }
  .rec-popular {
    display: flex;
    align-items: baseline;
    padding-top: 10px;
    margin-bottom: 10px;
}
  }
.rec-btn-type{
            padding-bottom: 10px;
        }
.rec-question{
        display: flex;
        flex-direction:column;
    }
.rec-banner{
    height:120px;
    background-color:#D0D0CE;
}
.rec-btn{
  .rec-btn-type {
    padding-bottom: 10px;
  }
  .rec-question {
    display: flex;
    flex-direction: column;
  }
  .rec-banner {
    height: 120px;
    background-color: #D0D0CE;
  }
  .rec-btn {
    font-size: 20px;
    border-radius: 20px;
    color:black;
    border:1px solid #D0D0CE;
}
.rec-pop-btn{
    color: black;
    border: 1px solid #D0D0CE;
  }
  .rec-pop-btn {
    font-size: 20px;
    border-radius: 20px;
    color:black;
    margin:0px 0px 10px 10px;
    border:1px solid #D0D0CE;
    color: black;
    margin: 0px 0px 10px 10px;
    border: 1px solid #D0D0CE;
    width: 90px;
    height:47px;
}
.rate-consultant{
    .el-rate__icon{
        font-size:35px
    height: 47px;
  }
  .rate-consultant {
    .el-rate__icon {
      font-size: 35px
    }
}
.el-progress__text{
  }
  .el-progress__text {
    display: none;
}
.el-progress-bar{
  }
  .el-progress-bar {
    padding-right: 0;
}
.el-progress-bar__inner{
    background-color:#ED1B2E;
}
  }
.required {
  .el-progress-bar__inner {
    background-color: #ED1B2E;
  }
  .required {
    position: relative;
    &::before {
        content: '*';
        position: absolute;
        color: #FF0000;
        transform: translate(-12px, 0);
    }
}
.hint {
    &::before {
      content: '*';
      position: absolute;
      color: #FF0000;
      transform: translate(-12px, 0);
    }
  }
  .hint {
    font-size: 16px;
    color: #ED1B2E;
    font-weight: bold;
    .icon-information {
        padding: 0 5px;
      padding: 0 5px;
    }
}
  }
.area-txt{
  .area-txt {
    display: flex;
    align-items: center;
    margin-left: 18px;
}
  }
@include desktop {
    .other-input{
    height: 50px;
    width:316px;
    border:1px solid #CCCCCC;
    margin-left: 10px;
}
    .desktopBtn{
        margin-right: 10px;
        height:47px
  @include desktop {
    .desktopBtn {
      margin-right: 10px;
      height: 47px
    }
    .popOtherBtn{
    margin-left:10px;
    margin-top:-10px;
}
}
</style>
    .popOtherBtn {
      margin-left: 10px;
      margin-top: -10px;
    }
  }
</style>
PAMapp/pages/recommendConsultant/result.vue
@@ -2,73 +2,74 @@
<div>
    <div class="mdTxt pb-10">嚴選顧問推薦</div>
    <ul class="pam-rec-agent__list">
        <li class="pam-rec-agent-card" v-for="(info,index) in recAgentList" :key="index">
        <li class="pam-rec-agent-card" v-for="(info,index) in pageList" :key="index">
            <div class="pam-rec-agent-card__content">
              <div class="pam-rec-agent-card__content-header">
                <div class="pam-rec-agent-card__content-header">
                <div class="pam-rec-agent-card__avatar">
                    <img :src="info.avatar" class="avatar">
                </div>
                <div class="pam-rec-agent-card__main-info">
                    <div class="fz-20 pt-10">{{ info.name }}</div>
                    <div class="fz-20 pt-10 rec-desktop-name">{{ info.name }}</div>
                    <div class="rec-role">{{ info.role }}</div>
                    <span class="rec-detail pt-30">詳細資料</span>
                    <span class="rec-detail">詳細資料</span>
                </div>
              </div>
              <div class="pam-rec-agent-card__content-body">
            </div>
            <div class="pam-rec-agent-card__content-body">
                <el-row type="flex" class="pam-paragraph">
                  <div class="field">
                    <div class="field">
                    <div class="field__label">
                    å°ˆé•·é ˜åŸŸ
                    </div>
                    <div class="field__content expertieses-container">
                      <div class="pr-10 pb-10" v-for="(expert, index) in info.expertises" :key="index">
                    <div class="pr-10 pb-10" v-for="(expert, index) in info.expertises" :key="index">
                        #{{ expert }}
                      </div>
                    </div>
                  </div>
                    </div>
                </div>
                </el-row>
                <el-row type="flex" class="pam-paragraph">
                  <el-col :span="12">
            <el-row type="flex" class="pam-paragraph">
                <el-col :span="12">
                    <div class="field__label">
                    æœå‹™è³‡æ­·
                    </div>
                    <div class="field__content">
                      {{ info.seniority }}
                    {{ info.seniority }}
                    </div>
                  </el-col>
                  <el-col :span="12">
                </el-col>
                <el-col :span="12">
                    <div class="field__label">
                    å®¢æˆ¶æ»¿æ„åº¦
                    </div>
                    <div class="field__content">
                      {{ info.avgScore }}
                        <i class="icon-star" style="color:#F2C75C"></i>
                    {{ info.avgScore }}
                    </div>
                  </el-col>
                    </el-col>
                </el-row>
              </div>
              <div class="pam-rec-agent-card__content-footer">
                </div>
                <div class="pam-rec-agent-card__content-footer">
                <el-row
                  type="flex"
                  justify="center"
                  class="">
                    <el-button>+ é¡§å•æ¸…å–®</el-button>
                    <el-button type="primary" style="margin-left: 10px">進行預約</el-button>
                    type="flex"
                    justify="center"
                    >
                    <el-button class="btn">+ é¡§å•æ¸…å–®</el-button>
                    <el-button class="btn2" type="primary" style="margin-left: 10px" @click="$router.push('/questionnaire')"
                    >進行預約</el-button>
                </el-row>
              </div>
                </div>
            </div>
        </li>
    </ul>
    <div class="mt-30">
      åˆ†é å™¨
    </div>
    <UiPagination
            :totalList="recAgentList"
            @changePage="changePage"
        ></UiPagination>
</div>
</template>
<script>
<script lang="ts">
import {Vue,Component} from 'vue-property-decorator';
@Component
@@ -124,20 +125,36 @@
            avgScore:4.8
        }
    ];
    pageList: any[] = [];
    changePage(pageList: any[]) {
        this.pageList = pageList;
    }
}
</script>
<style lang="scss" scoped>
.btn{
    width: 139px;
    height: 47px;
}
.btn2{
    width: 120px;
    height: 47px;
}
.pam-rec-agent-card {
    margin-bottom: 10px;
    border-radius: 10px;
    border: 1px solid $LIGHT_GREY;
    padding: 20px 33px;
    margin-right:-20px;
    margin-left:-20px;
    .pam-rec-agent-card__content {
      width: 270px;
      .pam-rec-agent-card__content-header {
        width: 270px;
        .pam-rec-agent-card__content-header {
        display: flex;
        .pam-rec-agent-card__avatar {
            display: flex;
@@ -163,9 +180,10 @@
                font-size: 20px;
                color:$PRIMARY_RED;
                font-weight: bold;
                padding-top: 30px;
            }
        }
      }
        }
    }
}
@@ -175,9 +193,106 @@
    font-weight:bold;
    margin-bottom: 7px;
}
.field__content{
    font-size: 18px;
}
.expertieses-container {
  display: flex;
  flex-wrap: wrap;
    display: flex;
    flex-wrap: wrap;
}
@include desktop{
    .pam-rec-agent__list{
        display: flex;
        flex-wrap: wrap;
    }
    .pam-paragraph{
        margin-top: 10px;
    }
    .pam-rec-agent-card {
        margin-right: 20px;
        margin-bottom: 10px;
        border-radius: 10px;
        border: 1px solid $LIGHT_GREY;
        padding-top: 15px;
        padding-bottom: 15px;
        padding-left: 28px;
        padding-right: 20px;
        width: 30%;
        margin-left: 10px;
    .pam-rec-agent-card__content {
        width: 190px;
        .pam-rec-agent-card__content-header {
        display: flex;
        .pam-rec-agent-card__avatar {
            display: flex;
            flex-direction: row;
            margin-right: 20px;
            .avatar{
                width: 80px;
                height: 80px;
                border-radius: 50%;
                margin-bottom: 10px;
            }
        }
        .pam-rec-agent-card__main-info {
            display: flex;
            flex-direction: column;
            justify-content: center;
            .rec-desktop-name{
                font-size: 12px;
                font-weight: bold;
            }
            .rec-role {
                font-size: 12px;
                color:$PRUDENTIAL_GREY;
            }
            .rec-detail{
                font-size: 12px;
                color:$PRIMARY_RED;
                font-weight: bold;
                padding-top: 10px;
            }
        }
        }
    }
    }
.field__label {
    font-size: 12px;
    color: $PRUDENTIAL_GREY;
    font-weight:bold;
    margin-bottom: 7px;
}
.field__content{
    font-size: 12px;
}
.expertieses-container {
    display: flex;
    flex-wrap: wrap;
}
.btn{
    width: 90px;
    height: 43px;
    font-size: 14px;
    display: flex;
    justify-content: center;
    padding-top: 12px;
    margin-right: 20px;
}
.btn2{
    width: 90px;
    height: 43px;
    font-size: 14px;
    display: flex;
    justify-content: center;
    padding-top: 12px;
    margin-right: -10px;
}
.el-row--flex.is-justify-center{
    justify-content:none;
}
}
</style>
PAMapp/plugins/api/home.ts
@@ -152,15 +152,4 @@
        return CommonService.prototype.withDebugData(debugData, '')
    }
})
export interface Agents {
    agentNo: number,
    name: string,
    img: string,
    new: boolean,
    satisfaction: number,
    professionals: string[],
    contactStatus: string,
    updateTime: Date
  }
})
PAMapp/plugins/filters/date.filter.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,29 @@
import Vue from 'vue'
Vue.filter('formatDate', (value: string): string => {
  const date = new Date(value);
  const today = new Date();
  const isToday = (compareDate: Date): boolean => {
    return compareDate.getFullYear() === today.getFullYear()
          && compareDate.getMonth() === today.getMonth()
          && compareDate.getDate() === today.getDate();
  };
  const isThisYear = (compareDate: Date): boolean => {
    return compareDate.getFullYear() === today.getFullYear();
  }
  if (!value) {
    return '尚無紀錄';
  }
  if (isThisYear(date)) {
    return isToday(date)
          ? `今天 ${date.getHours()}:${date.getMinutes()}`
          : `${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes()}`;
  } else {
    return `${date.getFullYear()}å¹´${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes()}`;
  }
})
PAMapp/store/index.ts
@@ -1,6 +1,23 @@
import { Module, VuexModule } from 'vuex-module-decorators'
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
import { Consultants,recommend,AgentOfStrictQuery} from '~/assets/ts/api/consultant';
@Module
export default class Store extends VuexModule {
    recommendList: Consultants[] | null = null;
    strictQueryList: AgentOfStrictQuery[] = [];
    @Mutation updateRecommend(data: Consultants[]) {
        this.recommendList = data;
    }
    @Mutation updateStrictQueryList(data: AgentOfStrictQuery[]) {
        this.strictQueryList = data;
    }
    @Action storeRecommendList() {
        recommend().then(res => {
            this.context.commit('updateRecommend', res.data)
        })
    }
}