保誠-保戶業務員媒合平台
HelenHuang
2021-11-05 cf6803cd4f2c535afb1eaa935fd9e9735279656b
Merge branch 'master' of https://192.168.0.10:8443/r/pcalife/PAM
修改13個檔案
新增22個檔案
修改2個檔案名稱
1812 ■■■■■ 已變更過的檔案
PAMapp/assets/scss/_common.scss 20 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/utilities/_utilities.scss 10 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/vendors/elementUI/_carousel.scss 4 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/ts/device.ts 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientCard.vue 166 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Client/ClientList.vue 43 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Consultant/ConsultantCard.vue 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/NavBar.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue 58 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/QuickFilter/QuickFilterSelector.vue 49 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiDialog.vue 16 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiDrawer.vue 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiPagination.vue 7 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiTags.vue 27 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/clientReservedList.vue 74 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/clientReservedList/contactedList.vue 28 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/clientReservedList/reservedList.vue 28 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/consultantLogin/index.vue 158 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myConsultantList.vue 27 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/quickFilter/index.vue 99 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/plugins/api/home.ts 83 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/pom.xml 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/sql/20211104_w.sql 25 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/domain/Consultant.java 239 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/enums/ContactStatusEnum.java 14 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/enums/GenderEnum.java 13 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java 14 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java 49 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/AddConsultantParam.java 15 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/ConsultantDTO.java 93 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/ConsultantDetailDTO.java 165 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/FastQueryConsultantParam.java 42 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/dto/StrictQueryConsultantParam.java 90 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/mapper/ConsultantMapper.java 71 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/ConsultantResource.java 56 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/errors/ConsultantNotFoundException.java 8 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/assets/scss/_common.scss
@@ -41,4 +41,24 @@
    &:disabled {
        background-color: $LIGHT_GREY;
    }
}
.pam-cus-tabs {
    display: flex;
    width: 100%;
    height: 45px;
    .cus-tab-item {
        width: 50%;
        text-align: center;
        font-size: 24px;
        border-bottom: solid 3px $LIGHT_GREY;
        cursor: pointer;
    }
    .is-active {
        font-weight: bold;
        border-bottom: solid 3px $PRIMARY_BLACK;
    }
}
PAMapp/assets/scss/utilities/_utilities.scss
@@ -52,7 +52,9 @@
.pr-10 {
  padding-right: 10px;
}
.pr-5{
  padding-right: 5px;
}
.pl-10 {
  padding-left: 10px;
}
@@ -68,3 +70,9 @@
.pam-page {
  padding: 20px 30px 40px 30px;
}
@for $fontSize from 12 through 45 {
  .fs-#{$fontSize} {
    font-size: #{$fontSize} + 'px';
  }
}
PAMapp/assets/scss/vendors/elementUI/_carousel.scss
@@ -36,9 +36,9 @@
        }
    }
    @include desktop {
    @media (min-width: 768px) {
        .el-carousel__container {
            transform: translateX(25%);
            transform: translateX(27%);
        }
    }
}
PAMapp/assets/ts/device.ts
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,5 @@
export function isMobileDevice() {
    let mobileDevices = ['Android', 'webOS', 'iPhone', 'iPad', 'iPod', 'BlackBerry', 'Windows Phone'];
    console.log(navigator.userAgent);
    return mobileDevices.some(e => navigator.userAgent.match(e));
}
PAMapp/components/Client/ClientCard.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,166 @@
<template>
    <div>
        <el-row type="flex" class="rowStyle">
            <el-col :xs="5" :sm="3">
                <el-avatar
                    :size="50"
                    src=""
                    class="cursor--pointer"
                ></el-avatar>
                <div class="satisfaction">
                    <i class="icon-star pam-icon icon--yellow satisfaction"></i>
                    <span>{{'1'}}</span>
                </div>
            </el-col>
            <el-col :xs="14" :sm="15">
                <div class="smTxt_bold name">{{client.name}}</div>
                <div class="message">預約成功</div>
                <div class="professionals">
                    <template v-if="client.requirements.length > 0">
                        <span
                            v-for="(item, index) in client.requirements"
                            :key="index"
                            class="professionalsTxt"
                        >#{{item}}</span>
                    </template>
                    <template v-else>
                        <span class="professionalsTxt noProfessionalsTxt"
                        >(客戶未提供需求項目)</span>
                    </template>
                </div>
            </el-col>
            <el-col class="flex-column contactInfo" :xs="5" :sm="6">
                <div class="smTxt_bold cursor--pointer"
                    :class="client.communicateStatus"
                    @click="openDetail"
                >{{isReserved ? '已預約' : '已聯絡'}}
                </div>
                <div class="date xsTxt text--mid_grey">{{date}}</div>
                <div class="xsTxt text--mid_grey">{{time}}</div>
            </el-col>
        </el-row>
        <Ui-Dialog
            :isVisible.sync="isVisibleDialog"
            :width="width"
        >
            <h5 class="subTitle text--center mb-30"
            >{{isReserved ? '預約成功' : '已聯絡資訊'}}</h5>
            <p class="smTxt text-right">今天 10:00</p>
            <div class="dialogTxt">
                <p>姓名:{{client.name}}</p>
                <p>電話:0912345678</p>
                <p>Email:</p>
                <p>性別:{{client.gender === 'male' ? '男性' : '女性'}}</p>
                <p>年齡:{{client.age}}</p>
                <p>職業:一般職業</p>
                <p>需求:{{client.requirements}}</p>
                <p>連絡時段一:星期一,星期日 18:00~21:00</p>
                <div class="mt-30 text--center">
                    <el-button>{{isReserved ? '標註為已連絡' : '發送滿意度'}}</el-button>
                </div>
            </div>
        </Ui-Dialog>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { isMobileDevice } from '~/assets/ts/device';
import { Clients } from '~/pages/clientReservedList.vue';
@Component
export default class ClientList extends Vue {
    @Prop() client!: Clients;
    isVisibleDialog = false;
    width = '';
    get time() {
        const hours = this.client.time.getHours();
        const minutes = this.client.time.getMinutes();
        return `${hours} : ${minutes}`
    }
    get date() {
        const month = this.client.time.getMonth();
        const date = this.client.time.getDate();
        return `${month} / ${date}`
    }
    get isReserved() {
        return this.client.communicateStatus === 'reserved';
    }
    openDetail() {
        this.width = isMobileDevice() ? '80%' : '';
        this.isVisibleDialog = true;
    }
}
</script>
<style lang="scss" scoped>
    .rowStyle {
        padding: 10px;
        background-color: $PRIMARY_WHITE;
        margin-bottom: 10px;
        display: flex;
        justify-content: space-between;
        .satisfaction {
            font-size: 12px;
            font-weight: bold;
            margin-top: 5px;
        }
        .message {
            margin:10px 0;
        }
        .professionals {
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            .professionalsTxt {
                font-size: 12px;
                font-weight: bold;
                margin-right: 5px;
            }
            .noProfessionalsTxt {
                color: $PRUDENTIAL_GREY;
                font-weight: lighter;
            }
        }
        .contactInfo {
            text-align: right;
            .date {
                font-size: 12px;
            }
        }
        .reserved {
            @extend .text--primary;
        }
        .contacted {
            color: $SKY_BLUE;
        }
    }
    .flex-column {
        display: flex;
        flex-direction: column;
        justify-content: space-between;
    }
    .dialogTxt {
        font-size: 20px;
    }
    .text-right {
        text-align: right;
    }
</style>
PAMapp/components/Client/ClientList.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,43 @@
<template>
    <div>
        <template v-if="clients.length > 0">
            <ClientCard
                v-for="(client, index) in clients"
                :key="index"
                :client="client"
            ></ClientCard>
        </template>
        <template v-else>
            <div class="emptyRowStyle">
                <div class="smTxt txt">{{title === 'reservedList' ? '您目前無已預約客戶' : '您目前無已聯絡客戶'}}</div>
            </div>
        </template>
    </div>
</template>
<script lang='ts'>
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { Clients } from '~/pages/clientReservedList.vue';
@Component
export default class ClientList extends Vue {
    @Prop() clients!: Clients[];
    @Prop() title!: string;
}
</script>
<style lang="scss" scoped>
    .emptyRowStyle {
        background-color: $PRIMARY_WHITE;
        width: 100%;
        height: 100px;
        display: flex;
        justify-content: center;
        align-items: center;
        .txt {
            color: $PRUDENTIAL_GREY;
            margin-left: 17px;
        }
    }
</style>
PAMapp/components/Consultant/ConsultantCard.vue
@@ -43,7 +43,10 @@
            </el-col>
        </el-row>
        <Ui-Dialog :isVisible.sync="isVisibleDialog">
        <Ui-Dialog
            :isVisible.sync="isVisibleDialog"
            :width="width"
        >
            <h5 class="subTitle text--center mb-30">預約成功</h5>
            <p class="smTxt">今天 10:00</p>
            <div class="dialogInfo">
@@ -63,11 +66,13 @@
<script lang="ts">
import { Vue, Component, Prop, Emit } from 'nuxt-property-decorator';
import { Agents } from '~/plugins/api/home';
import { isMobileDevice } from '~/assets/ts/device';
@Component
export default class ConsultantCard extends Vue {
    @Prop() agentInfo!: Agents;
    isVisibleDialog = false;
    width: string = '';
    get contactTxt() {
        if (this.agentInfo.contactStatus === 'contacted') {
@@ -93,6 +98,7 @@
        if (this.agentInfo.contactStatus === 'picked') {
            this.$router.push('/communication/myDemand')
        } else {
            this.width = isMobileDevice() ? '80%' : '';
            this.isVisibleDialog = true;
        }
    }
PAMapp/components/NavBar.vue
@@ -16,7 +16,7 @@
              <li class="pam-header__dropdown-item" @click="$router.push('/record/contactRecord')">查看紀錄</li>
              <li class="pam-header__dropdown-item" @click="$router.push('/contactList/consultantList')">我的顧問清單</li>
              <li class="pam-header__dropdown-item">登出</li>
              <li class="pam-header__dropdown-item pam-header__dropdown-divider" @click="$router.push('/login')">顧問登入</li>
              <li class="pam-header__dropdown-item pam-header__dropdown-divider" @click="$router.push('/consultantLogin')">顧問登入</li>
            </el-dropdown-menu>
          </el-dropdown>
        </div>
PAMapp/components/QuickFilter/QuickFilterConsultantList.vue
File was renamed from PAMapp/components/QuickFilter/QuickFilterConsultantCarousel.vue
@@ -17,8 +17,13 @@
                @touchstart="touchStart"
                @touchend="moveCard"
            >
                <img class="avator cursor--pointer" @click="$router.push('/agentInfo')" src="" />
                <div class="mdTxt mt-30 mb-10">蔡美莓(伯樂保險經紀人)</div>
                <el-avatar
                    :size="200"
                    class="mx-auto cursor--pointer"
                    @click="$router.push('/agentInfo')"
                    src=""
                />
                <div class="mdTxt mt-30 mb-10 text--center">蔡美莓(伯樂保險經紀人)</div>
                <el-row>
                    <el-col :span="12">
                        <div class="smTxt_bold mb-10 text--prudential_grey">服務資歷</div>
@@ -35,7 +40,7 @@
                <div class="p bold">#財務規劃</div>
            </div>
            <div class="parallelBtns">
                <el-button @click="isVisibleDrawer = true">
                <el-button @click="addConsultant">
                    <i class="icon-add smTxt"></i>
                    <span>顧問清單</span>
                </el-button>
@@ -59,20 +64,33 @@
    >
        <div class="text--center mdTxt">
            <p class="mb-50">成功加入顧問清單</p>
            <p class="text--primary" @click="isVisibleDrawer = false">我知道了</p>
            <p class="text--primary cursor--pointer"
                @click="isVisibleDrawer = 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>
</div>
</template>
<script lang="ts">
import { ElCarousel } from 'element-ui/types/carousel';
import { Vue, Component, Prop } from 'vue-property-decorator';
import { Vue, Component } from 'vue-property-decorator';
import { isMobileDevice } from '~/assets/ts/device';
@Component
export default class UiCardCarousel extends Vue {
export default class QuickFilterConsultantList extends Vue {
    isVisibleDrawer = false;
    isVisibleDialog = false;
    startPosition = 0;
    endPosition = 0;
@@ -101,6 +119,12 @@
        (this.$refs.carouselRef as ElCarousel).prev();
    }
    addConsultant() {
        isMobileDevice()
            ? this.isVisibleDrawer = true
            : this.isVisibleDialog = true;
    }
}
</script>
@@ -113,11 +137,7 @@
        padding: 20px 30px 30px 30px;
    }
    .avator {
        width: 200px;
        height: 200px;
        border-radius: 50%;
        background-color: $MID_GREY;
    .mx-auto {
        margin: 0 auto;
    }
@@ -180,8 +200,22 @@
        position: relative;
    }
        .absolute {
    .absolute {
        position: absolute;
    }
    @media (min-width: 768px) {
        .relative {
            overflow: hidden;
        }
        .arrow-right-position {
            right: -20px;
        }
        .arrow-left-position {
            left: -20px;
        }
    }
</style>
PAMapp/components/QuickFilter/QuickFilterSelector.vue
File was renamed from PAMapp/components/QuickFilter/QuickFilterDrawer.vue
@@ -1,11 +1,9 @@
<template>
    <Ui-Drawer
        :isVisible.sync="isVisible"
        :size="questionOption.name === 'style' ? '50%' : '30%'"
        @close="closeDrawer"
    >
    <div>
        <div class="mb-10" v-if="questionOption.title !== '顧問性別'">
            <span class="mdTxt">{{questionOption.title === '顧問滿意度' ? '保險顧問滿意度' : questionOption.title}}</span>
            <span class="mdTxt"
            >{{questionOption.title === '顧問滿意度' ? '保險顧問滿意度' : questionOption.title}}
            </span>
            <span
                class="smTxt_bold text--primary"
                v-if="questionOption.name === 'style'"
@@ -17,7 +15,7 @@
        </div>
        <div class="quickBtnBlock" v-if="questionOption.name === 'style'">
            <el-checkbox-group class="pam-quickFilter-checkbox" v-model="style">
            <el-checkbox-group class="pam-quickFilter-checkbox" v-model="pickedItem.style">
                <el-checkbox
                    v-for="(i, index) in questionOption.detail"
                    :key="index"
@@ -28,7 +26,7 @@
        </div>
        <div class="quickBtnBlock" v-else-if="questionOption.name === 'gender'">
            <el-radio-group class="pam-quickFilter-radio" v-model="gender">
            <el-radio-group class="pam-quickFilter-radio" v-model="pickedItem.gender">
                <el-radio
                    v-for="(i, index) in questionOption.detail"
                    :key="index"
@@ -37,7 +35,7 @@
        </div>
        <div class="quickBtnBlock" v-else-if="questionOption.name === 'loginState'">
            <el-radio-group class="pam-quickFilter-radio" v-model="loginState">
            <el-radio-group class="pam-quickFilter-radio" v-model="pickedItem.loginState">
                <el-radio
                    v-for="(i, index) in QuestionOption.detail"
                    :key="index"
@@ -46,9 +44,9 @@
        </div>
        <div v-else>
            <el-rate class="pam-quickFilter-rate" v-model="satisfaction"></el-rate>
            <el-rate class="pam-quickFilter-rate" v-model="pickedItem.satisfaction"></el-rate>
        </div>
    </Ui-Drawer>
    </div>
</template>
<script lang="ts">
@@ -57,34 +55,21 @@
@Component
export default class QuickFilterDrawer extends Vue {
    style: string[] = [];
    loginState: string = '';
    gender: string = '';
    satisfaction: number = 0;
    pickedItem: selectedItem = {
        style: [],
        onlineState: '',
        gender: '',
        satisfaction: 0
    }
    @PropSync('drawerVisible') isVisible!: boolean;
    @Prop() selectedItem!: selectedItem;
    @Prop() questionOption!: QuestionOption;
    @Emit('selected') emitSelected() {
        return ({
            name: this.questionOption.name,
            gender: this.gender,
            style: this.style,
            satisfaction: this.satisfaction,
            loginState: this.loginState
        });
    }
    closeDrawer() {
        this.emitSelected()
    }
    @Watch('selectedItem', {deep: true, immediate: true}) watchSelected(newValue: selectedItem) {
        if (newValue) {
            this.gender = newValue.gender;
            this.style = newValue.style;
            this.satisfaction = newValue.satisfaction;
            this.pickedItem = JSON.parse(JSON.stringify(this.selectedItem))
        }
    }
}
PAMapp/components/Ui/UiDialog.vue
@@ -3,7 +3,8 @@
        <el-dialog
            title=""
            :visible.sync="dialogVisible"
            width="80%"
            :width="width"
            @close="close"
        >
            <slot></slot>
        </el-dialog>
@@ -11,10 +12,19 @@
</template>
<script lang="ts">
import { Vue, Component, PropSync } from 'vue-property-decorator';
import { Vue, Component, PropSync, Prop, Emit } from 'vue-property-decorator';
@Component
export default class UiDialog extends Vue {
    @PropSync('isVisible') dialogVisible!: boolean
    @PropSync('isVisible') dialogVisible!: boolean;
    @Prop() width!: string;
    @Emit('closeDialog') closeDialog() {
        return;
    }
    close() {
        this.closeDialog();
    }
}
</script>
PAMapp/components/Ui/UiDrawer.vue
@@ -5,7 +5,6 @@
            :visible.sync="drawerVisible"
            :direction="'btt'"
            @close="close"
            class="cus_drawer"
        >
            <div class="close" @click="drawerVisible = false"></div>
            <slot></slot>
@@ -21,7 +20,7 @@
    @PropSync('isVisible') drawerVisible!: boolean;
    @Prop() size!: number;
    @Emit('close') closeDrawer() {
    @Emit('closeDrawer') closeDrawer() {
        return;
    }
PAMapp/components/Ui/UiPagination.vue
@@ -1,9 +1,11 @@
<template>
    <el-pagination
        :current-page.sync="currentPage"
        layout="prev, pager, next"
        :total="totalList.length"
        :page-size="pageSize"
        @current-change="handleCurrentChange"
        class="mt-10"
    >
    </el-pagination>
</template>
@@ -28,6 +30,11 @@
    }
    handleCurrentChange(currentPage: number) {
        if (this.totalList.length <= this.pageSize && currentPage !== 1) {
            currentPage -= 1;
        }
        if (this.totalList) {
            this.pageList = this.totalList.slice(this.pageSize * currentPage - this.pageSize, this.pageSize * currentPage)
            this.chagnePage();
PAMapp/components/Ui/UiTags.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,27 @@
<template>
    <el-tag
        class="pam-tag p"
        closable
        type="''"
        @close="removeTag"
        :disable-transitions="true"
    >
        <slot></slot>
    </el-tag>
</template>
<script lang="ts">
import { Vue, Component, Emit } from 'nuxt-property-decorator';
@Component
export default class UiTags extends Vue {
    @Emit('removeTag') removeTag() {
        return;
    }
    remove() {
        this.removeTag();
    }
}
</script>
PAMapp/pages/clientReservedList.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,74 @@
<template>
    <div>
        <div class="pam-cus-tabs mb-30">
            <div
                class="cus-tab-item"
                :class="{'is-active': activeTabName === 'reservedList'}"
                @click="tabClick('reservedList')"
            >客戶預約
                <span class="p">({{reservedList.length}})</span>
            </div>
            <div
                class="cus-tab-item"
                :class="{'is-active': activeTabName === 'contactedList'}"
                @click="tabClick('contactedList')"
            >已聯絡
                <span class="p">({{contactedList.length}})</span>
            </div>
        </div>
        <NuxtChild
            :contactedList="contactedList"
            :reservedList="reservedList"
        ></NuxtChild>
    </div>
</template>
<script lang="ts">
import { Context } from '@nuxt/types';
import { Vue, Component } from 'nuxt-property-decorator';
@Component
export default class ClientReservedList extends Vue {
    activeTabName = 'reservedList';
    reservedList: Clients[] = [];
    contactedList: Clients[] = [];
    clients: Clients[] = [];
    async asyncData(context: Context) {
        let reservedList: Clients[] = [];
        let contactedList: Clients[] = [];
        let clients: Clients[] = [];
        await context.$service.home.clientReservedList().then((result: Clients[]) => {
            clients = result;
        })
        contactedList = clients.filter(item => item.communicateStatus === 'contacted');
        reservedList = clients.filter(item => item.communicateStatus === 'reserved');
        return {
            clients,
            contactedList,
            reservedList
        }
    }
    tabClick(path: string) {
        this.activeTabName = path;
        this.$router.push('/clientReservedList/' + this.activeTabName)
    }
}
export interface Clients {
    name: string,
    clientId: string,
    phone: string,
    time: Date,
    gender: string,
    age: string,
    job: string,
    requirements: string[],
    communicateStatus: string
}
</script>
PAMapp/pages/clientReservedList/contactedList.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,28 @@
<template>
    <div>
        <ClientList
            :clients="pageList"
            :title="'contactedList'"
        ></ClientList>
        <UiPagination
            :totalList="contactedList"
            @changePage="changePage"
        ></UiPagination>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { Clients } from '../clientReservedList.vue';
@Component
export default class ClientContactedList extends Vue {
    @Prop() contactedList!: Clients[];
    pageList: Clients[] = [];
    changePage(pageList: Clients[]) {
        this.pageList = pageList;
    }
}
</script>
PAMapp/pages/clientReservedList/reservedList.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,28 @@
<template>
    <div>
        <ClientList
            :clients="pageList"
            :title="'reservedList'"
        ></ClientList>
        <UiPagination
            :totalList="reservedList"
            @changePage="changePage"
        ></UiPagination>
    </div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator';
import { Clients } from '../clientReservedList.vue';
@Component
export default class ClientReservedList extends Vue {
    @Prop() reservedList!: Clients[];
    pageList: Clients[] = [];
    changePage(pageList: Clients[]) {
        this.pageList = pageList;
    }
}
</script>
PAMapp/pages/consultantLogin/index.vue
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,158 @@
<template>
    <div>
      <div class="pam-consultant-login">
        <div class="pam-consultant-login__header mt-30">顧問登入</div>
        <div class="pam-consultant-login__title mt-30">帳號</div>
        <div class="position-r mt-10">
          <input type="text"
            :model="loginDto.account"
            class="pam-consultant-login__input"
            placeholder="輸入eService帳號">
          <div class="pam-consultant-login__inputIcon text--primary cursor--pointer" @click="cookieAccount">
              <i :class="[isRemember ? 'icon-checkbox-1' : 'icon-checkbox','pr-5']"></i>
              è¨˜ä½
          </div>
        </div>
        <div class="pam-consultant-login__title mt-30">
           <div>密碼</div>
           <div class="text--primary fs-16 cursor--pointer" @click="forgetPassword">忘記密碼?</div>
         </div>
        <div class="position-r mt-10">
          <input :type="[isShowPassword ? 'text' : 'password']"
            :model="loginDto.password"
            class="pam-consultant-login__input"
            placeholder="輸入eService密碼">
          <div class="pam-consultant-login__inputIcon cursor--pointer" @click="isShowPassword = !isShowPassword">
            <i :class="[isShowPassword ? 'icon-eye-1 fs-25' : 'icon-eye' , 'text--primary']"></i>
          </div>
        </div>
        <div class="pam-consultant-login__title mt-30">
           <div>驗證碼</div>
           <div class="text--dark-blue fs-16 cursor--pointer" @click="regenerateCode">重新產生</div>
         </div>
        <div class="pam-consultant-login__verifyBlock mt-10">
          <div class="w-55">
            <input type="text"
              :modal="loginDto.verificationCode"
              class="pam-consultant-login__input" >
          </div>
          <div class="pam-consultant-login__verifyImg"></div>
        </div>
        <div class="pam-consultant-login__confirmBlock mt-30">
           <button class="pam-consultant-login__confirm cursor--pointer" @click="login">送出</button>
        </div>
      </div>
    </div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({
  layout:'home'
})
export default class ConsultantLogin extends Vue {
  isRemember=false;
  isShowPassword=false;
  loginDto={
    account:'',
    password:'',
    verificationCode:'',
  }
  login():void{
    console.log('login');
  }
  cookieAccount():void{
    this.isRemember = !this.isRemember;
    if(this.isRemember){
      console.log('sotre account');
    }
  }
  forgetPassword():void{
    console.log('forget password');
  }
  regenerateCode():void{
    console.log('call api regenerate verificationCode')
  }
}
</script>
<style lang="scss" scoped>
  .mt-20{
    margin-top: 20px;
  }
  .mt-25{
    margin-top: 25px;
  }
  .w-55{
    width: 55% !important;
  }
  .position-r{
    position: relative;
  }
  .pam-consultant-login{
    width: 336px;
    margin: 40px auto 30px auto;
    font-size: 20px;
    color: $PRIMARY_BLACK;
    &__header{
      text-align: center;
      font-size: 24px;
      font-weight: bold;
      letter-spacing: 1.2;
      color: $PRIMARY_BLACK;
    }
    &__title{
      display:flex;
      justify-content: space-between;
      align-items: center;
      padding: 0px 10px;
    }
    &__input{
      width: 100%;
      outline: 0;
      border: 1px solid #CCCCCC;
      border-radius: 10px;
      font-size: 20px;
      height: 50px;
      padding: 10px 90px 10px 15px;
      overflow: auto;
      box-sizing: border-box;
      -webkit-box-sizing: border-box;
      -moz-box-sizing: border-box;
      &Icon{
        position: absolute;
        display: flex;
        align-items: center;
        top: 15px;
        right: 15px;
      }
    }
    &__verifyBlock{
      display: flex;
      justify-content: space-between;
    }
    &__verifyImg{
      width:126px;
      border:1px black solid;
    }
    &__confirmBlock{
      display: flex;
      justify-content: center;
    }
    &__confirm{
      color: $PRIMARY_WHITE;
      width: 80px;
      height: 50px;
      border-radius: 30px;
      border: 1px solid $LIGHT_GREY;
      background-color:$PRIMARY_RED;
    }
  }
</style>
PAMapp/pages/myConsultantList.vue
@@ -1,6 +1,6 @@
<template>
    <div>
        <div class="flex mb-30">
        <div class="pam-cus-tabs mb-30">
            <div
                class="cus-tab-item"
                :class="{'is-active': activeTabName === 'consultantList'}"
@@ -73,27 +73,4 @@
    }
}
</script>
<style lang="scss" scoped>
    .flex {
        display: flex;
        width: 100%;
        height: 45px;
        .cus-tab-item {
            width: 50%;
            text-align: center;
            font-size: 24px;
            border-bottom: solid 3px $LIGHT_GREY;
            cursor: pointer;
        }
        .is-active {
            font-weight: bold;
            border-bottom: solid 3px $PRIMARY_BLACK;
        }
    }
</style>
</script>
PAMapp/pages/quickFilter/index.vue
@@ -7,37 +7,31 @@
                class="subTitle quickBtn"
                :disabled="question.name === 'onlineState'"
                @click="openDrawer(question)"
                @click="openPopUp(question)"
            >{{question.title}}</el-button>
        </div>
        <h5 class="mdTxt mb-10 mt-30">篩選條件</h5>
        <div>
             <el-tag
            <Ui-Tags
                v-if="selectedItem.gender"
                class="pam-tag p"
                closable
                type="''"
                @close="removeTag('gender')"
            >{{selectedItem.gender}}</el-tag>
            <el-tag
                v-if="selectedItem.satisfaction"
                class="pam-tag p"
                closable
                type="''"
                @close="removeTag('satisfaction')"
            >{{selectedItem.satisfaction + '星以上滿意度'}}</el-tag>
            <template v-if="selectedItem.style.length > 0"
                @removeTag="removeTag('gender')"
            >
                <el-tag
                {{selectedItem.gender}}
            </Ui-Tags>
            <Ui-Tags
                v-if="selectedItem.satisfaction"
                @removeTag="removeTag('satisfaction')"
            >
                {{selectedItem.satisfaction + '星以上滿意度'}}
            </Ui-Tags>
            <template v-if="selectedItem.style.length > 0">
                <Ui-Tags
                    v-for="(item, index) in selectedItem.style"
                    :key="index"
                    class="pam-tag p"
                    closable
                    type="''"
                    @close="removeTag('style', index)"
                >{{item}}</el-tag>
                    @removeTag="removeTag('style', index)"
                >
                    {{item}}
                </Ui-Tags>
            </template>
            <div class="mb-10" v-if="selectedItem.onlineState"></div>
@@ -49,19 +43,36 @@
        <h5 class="mdTxt mb-10 mt-30">快速篩選推薦</h5>
        <template v-if="consultantList.length > 0">
            <QuickFilterConsultantCarousel></QuickFilterConsultantCarousel>
            <QuickFilterConsultantList></QuickFilterConsultantList>
        </template>
        <template v-else>
            <div class="emptyBox bg-white"></div>
        </template>
        <QuickFilterDrawer
            :drawerVisible.sync="questionDrawer"
            :questionOption="questionOption"
            :selectedItem="selectedItem"
            @selected="selected"
        ></QuickFilterDrawer>
        <Ui-Drawer
            :isVisible.sync="questionDrawer"
            :size="questionOption.name === 'style' ? '50%' : '30%'"
            @closeDrawer="closePopUp"
        >
            <QuickFilterSelector
                ref="quickFilterRef"
                :drawerVisible.sync="questionDrawer"
                :questionOption="questionOption"
                :selectedItem="selectedItem"
            ></QuickFilterSelector>
        </Ui-Drawer>
        <Ui-Dialog :isVisible.sync="dialog"
            @closeDialog="closePopUp"
        >
            <QuickFilterSelector
                ref="quickFilterRef"
                :drawerVisible.sync="questionDrawer"
                :questionOption="questionOption"
                :selectedItem="selectedItem"
            ></QuickFilterSelector>
        </Ui-Dialog>
    </div>
</template>
@@ -69,9 +80,12 @@
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 QuickFilterDrawer from '~/components/QuickFilter/QuickFilterSelector.vue';
@Component
export default class QuickFilter extends Vue {
    dialog = false;
    consultantList = [];
    questionDrawer = false;
    questionOption = {};
@@ -120,26 +134,9 @@
        }
    }
    openDrawer(question: QuestionOption) {
        this.questionDrawer = true;
    openPopUp(question: QuestionOption) {
        this.questionOption = question;
    }
    selected(event: selectedItem) {
        const name = event.name;
        if (name === 'gender') {
            this.selectedItem.gender = event.gender;
        }
        if (name === 'satisfaction') {
            this.selectedItem.satisfaction = event.satisfaction;
        }
        if (name === 'style'){
            this.selectedItem.style = event.style;
        }
        isMobileDevice() ? this.questionDrawer = true : this.dialog = true;
    }
    removeTag(type: string, index: number = 0) {
@@ -158,6 +155,10 @@
    }
    closePopUp() {
        this.selectedItem = JSON.parse(JSON.stringify((this.$refs.quickFilterRef as QuickFilterDrawer).pickedItem));
    }
}
export interface QuestionOption {
PAMapp/plugins/api/home.ts
@@ -8,7 +8,7 @@
                new: true,
                agentNo: 0,
                name: '張小美',
                img: 'https://randomuser.me/api/portraits/women/31.jpg',
                img: '',
                professionals: ['財務規劃', '資產轉移'],
                satisfaction: 4.8,
                contactStatus: 'reserved',
@@ -18,7 +18,7 @@
                new: true,
                agentNo: 1,
                name: '蔣帥哥',
                img: 'https://randomuser.me/api/portraits/men/32.jpg',
                img: '',
                professionals: [],
                satisfaction: 4,
                contactStatus: 'contacted',
@@ -28,7 +28,7 @@
                new: false,
                agentNo: 2,
                name: '林美女',
                img: 'https://randomuser.me/api/portraits/women/33.jpg',
                img: '',
                professionals: ['財務規劃', '資產轉移'],
                satisfaction: 5,
                contactStatus: 'picked',
@@ -38,7 +38,7 @@
                new: false,
                agentNo: 3,
                name: '蔡美眉',
                img: 'https://randomuser.me/api/portraits/women/34.jpg',
                img: '',
                professionals: ['財務規劃', '資產轉移'],
                satisfaction: 4.3,
                contactStatus: 'picked',
@@ -48,7 +48,7 @@
                new: true,
                agentNo: 4,
                name: '張小美',
                img: 'https://randomuser.me/api/portraits/women/35.jpg',
                img: '',
                professionals: [],
                satisfaction: 4.8,
                contactStatus: 'picked',
@@ -58,7 +58,7 @@
                new: true,
                agentNo: 5,
                name: '蔣帥哥',
                img: 'https://randomuser.me/api/portraits/men/36.jpg',
                img: '',
                professionals: ['財務規劃', '資產轉移'],
                satisfaction: 4.8,
                contactStatus: 'reserved',
@@ -68,7 +68,7 @@
                new: false,
                agentNo: 6,
                name: '林美女',
                img: 'https://randomuser.me/api/portraits/women/37.jpg',
                img: '',
                professionals: ['財務規劃', '資產移轉', '節稅', '樂活退休'],
                satisfaction: 4.8,
                contactStatus: 'picked',
@@ -78,14 +78,79 @@
                new: false,
                agentNo: 7,
                name: '蔡美眉',
                img: 'https://randomuser.me/api/portraits/women/38.jpg',
                img: '',
                professionals: ['財務規劃', '節稅', '樂活退休'],
                satisfaction: 4.8,
                contactStatus: 'picked',
                updateTime: 'Tue Sep 02 2021 09:40:02 GMT+0800 (台北標準時間)'
            }
        ]
        return CommonService.prototype.withDebugData(debugData, 'https://randomuser.me/api/')
        return CommonService.prototype.withDebugData(debugData, '')
    },
    clientReservedList() {
        const debugData = [{
            name: '王聰明',
            clientId: '1',
            phone: '091234567',
            time: new Date('Tue Nov 02 2021 11:23:14 GMT+0800 (台北標準時間)'),
            gender: 'male',
            age: '26-30',
            job: 'general',
            requirements: ['保單健檢/規劃', '防疫保單相關'],
            communicateStatus: 'reserved'
        },{
            name: 'Ariel',
            clientId: '2',
            phone: '091234567',
            time: new Date('Tue Oct 15 2020 11:23:14 GMT+0800 (台北標準時間)'),
            gender: 'female',
            age: '30-40',
            job: 'general',
            requirements: ['保單健檢/規劃', '資產轉移', '防疫保單相關'],
            communicateStatus: 'contacted'
        },{
            name: 'Donna',
            clientId: '3',
            phone: '091234567',
            time: new Date('Tue Oct 31 2021 10:23:14 GMT+0800 (台北標準時間)'),
            gender: 'female',
            age: '10-20',
            job: 'general',
            requirements: ['防疫保單相關'],
            communicateStatus: 'reserved'
        },{
            name: '陳爸爸',
            clientId: '4',
            phone: '091234567',
            time: new Date('Tue Dec 31 2020 19:23:14 GMT+0800 (台北標準時間)'),
            gender: 'female',
            age: '50-60',
            job: 'general',
            requirements: ['資產轉移', '節稅'],
            communicateStatus: 'reserved'
        },{
            name: '李伯伯',
            clientId: '5',
            phone: '091234567',
            time: new Date('Tue Oct 15 2020 11:23:14 GMT+0800 (台北標準時間)'),
            gender: 'male',
            age: '60-70',
            job: 'general',
            requirements: ['節稅', '資產轉移', '防疫保單相關'],
            communicateStatus: 'contacted'
        },{
            name: '蔡媽媽',
            clientId: '6',
            phone: '091234567',
            time: new Date('Tue Dec 31 2020 19:23:14 GMT+0800 (台北標準時間)'),
            gender: 'female',
            age: '50-60',
            job: 'general',
            requirements: [],
            communicateStatus: 'reserved'
        }]
        return CommonService.prototype.withDebugData(debugData, '')
    }
})
pamapi/pom.xml
@@ -25,7 +25,7 @@
    <properties>
        <!-- Build properties -->
        <maven.version>3.3.9</maven.version>
        <java.version>11</java.version>
        <java.version>8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.build.timestamp.format>yyyyMMddHHmmss</maven.build.timestamp.format>
pamapi/src/doc/sql/20211104_w.sql
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,25 @@
CREATE TABLE public.consultant (
    id serial4 NOT NULL,
    agent_no varchar NOT NULL,
    "name" varchar NULL,
    photo_path varchar NULL,
    expertise varchar NULL,
    avg_score float4 NULL,
    title varchar NULL,
    "role" varchar NULL,
    created_date timestamp NULL,
    created_by varchar NULL,
    last_modified_date timestamp NULL,
    last_modified_by varchar NULL,
    serve_area varchar NULL,
    gender varchar NULL,
    phone_number varchar NULL,
    company_address varchar NULL,
    latest_login_time timestamp NULL,
    seniority varchar NULL,
    concept varchar NULL,
    experience varchar NULL,
    award varchar NULL,
    recommend bool NULL,
    CONSTRAINT consultant_pkey PRIMARY KEY (id)
);
pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
@@ -83,6 +83,7 @@
            .antMatchers("/api/activate").permitAll()
            .antMatchers("/api/account/reset-password/init").permitAll()
            .antMatchers("/api/account/reset-password/finish").permitAll()
            .antMatchers("/api/consultant/recommend").permitAll()
            .antMatchers("/api/admin/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/api/**").authenticated()
            .antMatchers("/websocket/**").authenticated()
pamapi/src/main/java/com/pollex/pam/domain/Consultant.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,239 @@
package com.pollex.pam.domain;
import com.pollex.pam.enums.GenderEnum;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
@Entity
@Table(name = "consultant")
public class Consultant extends AbstractAuditingEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;
    @Column(name = "agent_no")
    private String agentNo;
    @Column(name = "name")
    private String name;
    @Column(name = "photo_path")
    private String photoPath;
    @Column(name = "expertise")
    private String expertise;
    @Column(name = "avg_score")
    private Float avgScore;
    @Column(name = "title")
    private String title;
    @Column(name = "role")
    private String role;
    @Column(name = "serve_area")
    private String serveArea;
    @Enumerated(value = EnumType.STRING)
    @Column(name = "gender")
    private GenderEnum gender;
    @Column(name = "phone_number")
    private String phoneNumber;
    @Column(name = "company_address")
    private String companyAddress;
    @Column(name = "latest_login_time")
    private Instant latestLoginTime;
    @Column(name = "seniority")
    private String seniority;
    @Column(name = "concept")
    private String concept;
    @Column(name = "experience")
    private String experience;
    @Column(name = "award")
    private String award;
    @Column(name = "recommend")
    private Boolean recommend;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getAgentNo() {
        return agentNo;
    }
    public void setAgentNo(String agentNo) {
        this.agentNo = agentNo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPhotoPath() {
        return photoPath;
    }
    public void setPhotoPath(String photoPath) {
        this.photoPath = photoPath;
    }
    public String getExpertise() {
        return expertise;
    }
    public void setExpertise(String expertise) {
        this.expertise = expertise;
    }
    public Float getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(Float avgScore) {
        this.avgScore = avgScore;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getRole() {
        return role;
    }
    public void setRole(String role) {
        this.role = role;
    }
    public String getServeArea() {
        return serveArea;
    }
    public void setServeArea(String serveArea) {
        this.serveArea = serveArea;
    }
    public GenderEnum getGender() {
        return gender;
    }
    public void setGender(GenderEnum gender) {
        this.gender = gender;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    public String getCompanyAddress() {
        return companyAddress;
    }
    public void setCompanyAddress(String companyAddress) {
        companyAddress = companyAddress;
    }
    public Instant getLatestLoginTime() {
        return latestLoginTime;
    }
    public void setLatestLoginTime(Instant lastLoginTime) {
        this.latestLoginTime = lastLoginTime;
    }
    public String getSeniority() {
        return seniority;
    }
    public void setSeniority(String seniority) {
        this.seniority = seniority;
    }
    public String getConcept() {
        return concept;
    }
    public void setConcept(String concept) {
        this.concept = concept;
    }
    public String getExperience() {
        return experience;
    }
    public void setExperience(String experience) {
        this.experience = experience;
    }
    public String getAward() {
        return award;
    }
    public void setAward(String award) {
        this.award = award;
    }
    public Boolean getRecommend() {
        return recommend;
    }
    public void setRecommend(Boolean recommend) {
        this.recommend = recommend;
    }
    @Override
    public String toString() {
        return "Consultant{" +
            "id=" + id +
            ", agentNo='" + agentNo + '\'' +
            ", name='" + name + '\'' +
            ", photoPath='" + photoPath + '\'' +
            ", expertise='" + expertise + '\'' +
            ", avgScore=" + avgScore +
            ", title='" + title + '\'' +
            ", role='" + role + '\'' +
            ", serveArea='" + serveArea + '\'' +
            ", gender='" + gender + '\'' +
            ", phoneNumber='" + phoneNumber + '\'' +
            ", companyAddress='" + companyAddress + '\'' +
            ", latestLoginTime=" + latestLoginTime +
            ", seniority='" + seniority + '\'' +
            ", concept='" + concept + '\'' +
            ", experience='" + experience + '\'' +
            ", award='" + award + '\'' +
            ", recommend=" + recommend +
            '}';
    }
}
pamapi/src/main/java/com/pollex/pam/enums/ContactStatusEnum.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,14 @@
package com.pollex.pam.enums;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum ContactStatusEnum {
    @JsonProperty("picked")
    PICKED,
    @JsonProperty("reserved")
    RESERVED,
    @JsonProperty("contacted")
    CONTACTED
}
pamapi/src/main/java/com/pollex/pam/enums/GenderEnum.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,13 @@
package com.pollex.pam.enums;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum GenderEnum {
    @JsonProperty("male")
    MALE,
    @JsonProperty("female")
    FEMALE
}
pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,14 @@
package com.pollex.pam.repository;
import com.pollex.pam.domain.Consultant;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface ConsultantRepository extends JpaRepository<Consultant, Long> {
    Optional<Consultant> findFirstByAgentNo(String agentNo);
    List<Consultant> findAllByRecommendIsTrue();
}
pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,49 @@
package com.pollex.pam.service;
import com.pollex.pam.domain.Consultant;
import com.pollex.pam.repository.ConsultantRepository;
import com.pollex.pam.service.dto.*;
import com.pollex.pam.service.mapper.ConsultantMapper;
import com.pollex.pam.web.rest.errors.ConsultantNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
public class ConsultantService {
    private final ConsultantRepository consultantRepository;
    private final ConsultantMapper consultantMapper;
    public ConsultantService(ConsultantRepository consultantRepository, ConsultantMapper consultantMapper) {
        this.consultantRepository = consultantRepository;
        this.consultantMapper = consultantMapper;
    }
    public List<ConsultantDTO> getMyConsultantList() {
        return Collections.emptyList();
    }
    public List<ConsultantDTO> getRecommendConsultantList() {
        List<Consultant> recommendConsultantList = consultantRepository.findAllByRecommendIsTrue();
        return consultantMapper.toDto(recommendConsultantList);
    }
    public void addConsultantToOwnList(AddConsultantParam param) {
        List<String> agentNoList = param.getAgentNoList();
    }
    public List<ConsultantDTO> strictQueryConsultant(StrictQueryConsultantParam param) {
        return Collections.emptyList();
    }
    public List<ConsultantDTO> fastQueryConsultant(FastQueryConsultantParam param) {
        return Collections.emptyList();
    }
    public ConsultantDetailDTO getConsultantDetail(String agentNo) {
        Consultant consultant = consultantRepository.findFirstByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
        return consultantMapper.toDetailDto(consultant);
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/AddConsultantParam.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,15 @@
package com.pollex.pam.service.dto;
import java.util.List;
public class AddConsultantParam {
    List<String> agentNoList;
    public List<String> getAgentNoList() {
        return agentNoList;
    }
    public void setAgentNoList(List<String> agentNoList) {
        this.agentNoList = agentNoList;
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/ConsultantDTO.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,93 @@
package com.pollex.pam.service.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.pollex.pam.enums.ContactStatusEnum;
import java.time.Instant;
import java.util.List;
public class ConsultantDTO {
    @JsonProperty("new")
    private boolean newConsultant;
    private String agentNo;
    private String name;
    private String img;
    private List<String> expertise;
    private Float avgScore;
    private ContactStatusEnum contactStatus;
    private Instant updateTime;
    private String seniority;
    public boolean isNewConsultant() {
        return newConsultant;
    }
    public void setNewConsultant(boolean newConsultant) {
        this.newConsultant = newConsultant;
    }
    public String getAgentNo() {
        return agentNo;
    }
    public void setAgentNo(String agentNo) {
        this.agentNo = agentNo;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getImg() {
        return img;
    }
    public void setImg(String img) {
        this.img = img;
    }
    public List<String> getExpertise() {
        return expertise;
    }
    public void setExpertise(List<String> expertise) {
        this.expertise = expertise;
    }
    public Float getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(Float avgScore) {
        this.avgScore = avgScore;
    }
    public ContactStatusEnum getContactStatus() {
        return contactStatus;
    }
    public void setContactStatus(ContactStatusEnum contactStatus) {
        this.contactStatus = contactStatus;
    }
    public Instant getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(Instant updateTime) {
        this.updateTime = updateTime;
    }
    public String getSeniority() {
        return seniority;
    }
    public void setSeniority(String seniority) {
        this.seniority = seniority;
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/ConsultantDetailDTO.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,165 @@
package com.pollex.pam.service.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
import java.util.List;
public class ConsultantDetailDTO {
    private String name;
    private String agentNo;
    private String role;
    private String image;
    private Float avgScore;
    private String title;
    private String phoneNumber;
    private String serveArea;
    private String companyAddress;
    private Instant latestLoginTime;
    private String seniority;
    private Number suitability;
    private Number evaluation;
    private List<String> expertises;
    private String concept;
    private List<String> experiences;
    private String awards;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAgentNo() {
        return agentNo;
    }
    public void setAgentNo(String agentNo) {
        this.agentNo = agentNo;
    }
    public String getRole() {
        return role;
    }
    public void setRole(String role) {
        this.role = role;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }
    public Float getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(Float avgScore) {
        this.avgScore = avgScore;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getPhoneNumber() {
        return phoneNumber;
    }
    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
    public String getServeArea() {
        return serveArea;
    }
    public void setServeArea(String serveArea) {
        this.serveArea = serveArea;
    }
    public String getCompanyAddress() {
        return companyAddress;
    }
    public void setCompanyAddress(String companyAddress) {
        this.companyAddress = companyAddress;
    }
    public Instant getLatestLoginTime() {
        return latestLoginTime;
    }
    public void setLatestLoginTime(Instant latestLoginTime) {
        this.latestLoginTime = latestLoginTime;
    }
    public String getSeniority() {
        return seniority;
    }
    public void setSeniority(String seniority) {
        this.seniority = seniority;
    }
    public Number getSuitability() {
        return suitability;
    }
    public void setSuitability(Number suitability) {
        this.suitability = suitability;
    }
    public Number getEvaluation() {
        return evaluation;
    }
    public void setEvaluation(Number evaluation) {
        this.evaluation = evaluation;
    }
    public List<String> getExpertises() {
        return expertises;
    }
    public void setExpertises(List<String> expertises) {
        this.expertises = expertises;
    }
    public String getConcept() {
        return concept;
    }
    public void setConcept(String concept) {
        this.concept = concept;
    }
    public List<String> getExperiences() {
        return experiences;
    }
    public void setExperiences(List<String> experiences) {
        this.experiences = experiences;
    }
    public String getAwards() {
        return awards;
    }
    public void setAwards(String awards) {
        this.awards = awards;
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/FastQueryConsultantParam.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,42 @@
package com.pollex.pam.service.dto;
import com.pollex.pam.enums.GenderEnum;
public class FastQueryConsultantParam {
    private GenderEnum gender;
    private String communicationStyle;
    private Number avgScore;
    private String status;
    public GenderEnum getGender() {
        return gender;
    }
    public void setGender(GenderEnum gender) {
        this.gender = gender;
    }
    public String getCommunicationStyle() {
        return communicationStyle;
    }
    public void setCommunicationStyle(String communicationStyle) {
        this.communicationStyle = communicationStyle;
    }
    public Number getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(Number avgScore) {
        this.avgScore = avgScore;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
}
pamapi/src/main/java/com/pollex/pam/service/dto/StrictQueryConsultantParam.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,90 @@
package com.pollex.pam.service.dto;
import com.pollex.pam.enums.GenderEnum;
import java.util.List;
public class StrictQueryConsultantParam {
    private GenderEnum gender;
    private Number avgScore;
    private String status;
    private String area;
    private List<String> requirements;
    private String otherRequirement;
    private String seniority;
    private List<String> popularTags;
    private String otherPopularTags;
    public GenderEnum getGender() {
        return gender;
    }
    public void setGender(GenderEnum gender) {
        this.gender = gender;
    }
    public Number getAvgScore() {
        return avgScore;
    }
    public void setAvgScore(Number avgScore) {
        this.avgScore = avgScore;
    }
    public String getStatus() {
        return status;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public String getArea() {
        return area;
    }
    public void setArea(String area) {
        this.area = area;
    }
    public List<String> getRequirements() {
        return requirements;
    }
    public void setRequirements(List<String> requirements) {
        this.requirements = requirements;
    }
    public String getOtherRequirement() {
        return otherRequirement;
    }
    public void setOtherRequirement(String otherRequirement) {
        this.otherRequirement = otherRequirement;
    }
    public String getSeniority() {
        return seniority;
    }
    public void setSeniority(String seniority) {
        this.seniority = seniority;
    }
    public List<String> getPopularTags() {
        return popularTags;
    }
    public void setPopularTags(List<String> popularTags) {
        this.popularTags = popularTags;
    }
    public String getOtherPopularTags() {
        return otherPopularTags;
    }
    public void setOtherPopularTags(String otherPopularTags) {
        this.otherPopularTags = otherPopularTags;
    }
}
pamapi/src/main/java/com/pollex/pam/service/mapper/ConsultantMapper.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,71 @@
package com.pollex.pam.service.mapper;
import com.pollex.pam.domain.Consultant;
import com.pollex.pam.enums.ContactStatusEnum;
import com.pollex.pam.service.dto.ConsultantDTO;
import com.pollex.pam.service.dto.ConsultantDetailDTO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ConsultantMapper {
    private final static Character SPLIT_MASK = ',';
    public List<ConsultantDTO> toDto(List<Consultant> source) {
        return source.stream().map(this::toDto).collect(Collectors.toList());
    }
    // todo convert
    public ConsultantDTO toDto(Consultant source) {
        ConsultantDTO consultantDTO = new ConsultantDTO();
        consultantDTO.setAgentNo(source.getAgentNo());
        consultantDTO.setName(source.getName());
        consultantDTO.setAvgScore(source.getAvgScore());
        consultantDTO.setSeniority(source.getSeniority());
        // todo
        consultantDTO.setImg("");
        consultantDTO.setExpertise(splitStringWithChar(source.getExpertise()));
        consultantDTO.setNewConsultant(false);
        consultantDTO.setContactStatus(ContactStatusEnum.CONTACTED);
        consultantDTO.setUpdateTime(Instant.now());
        return consultantDTO;
    }
    public ConsultantDetailDTO toDetailDto(Consultant source) {
        ConsultantDetailDTO consultantDetailDTO = new ConsultantDetailDTO();
        consultantDetailDTO.setName(source.getName());
        consultantDetailDTO.setAgentNo(source.getAgentNo());
        consultantDetailDTO.setRole(source.getRole());
        consultantDetailDTO.setAvgScore(source.getAvgScore());
        consultantDetailDTO.setTitle(source.getTitle());
        consultantDetailDTO.setPhoneNumber(source.getPhoneNumber());
        consultantDetailDTO.setServeArea(source.getServeArea());
        consultantDetailDTO.setCompanyAddress(source.getCompanyAddress());
        consultantDetailDTO.setLatestLoginTime(source.getLatestLoginTime());
        consultantDetailDTO.setSeniority(source.getSeniority());
        consultantDetailDTO.setConcept(source.getConcept());
        consultantDetailDTO.setAwards(source.getAward());
        consultantDetailDTO.setImage("");
        consultantDetailDTO.setSuitability(0);
        consultantDetailDTO.setEvaluation(0);
        consultantDetailDTO.setExpertises(splitStringWithChar(source.getExpertise()));
        consultantDetailDTO.setExperiences(splitStringWithChar(source.getExperience()));
        return consultantDetailDTO;
    }
    private List<String> splitStringWithChar(String source) {
        return Arrays.stream(StringUtils.split(source, SPLIT_MASK))
            .map(String::trim)
            .collect(Collectors.toList());
    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/ConsultantResource.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,56 @@
package com.pollex.pam.web.rest;
import com.pollex.pam.service.ConsultantService;
import com.pollex.pam.service.dto.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/consultant")
public class ConsultantResource {
    private final ConsultantService consultantService;
    public ConsultantResource(ConsultantService consultantService) {
        this.consultantService = consultantService;
    }
    @GetMapping("")
    public ResponseEntity<List<ConsultantDTO>> getMyConsultantList() {
        List<ConsultantDTO> myConsultants = consultantService.getMyConsultantList();
        return new ResponseEntity<>(myConsultants, HttpStatus.OK);
    }
    @GetMapping("/recommend")
    public ResponseEntity<List<ConsultantDTO>> getRecommendConsultantList() {
        List<ConsultantDTO> recommendConsultantList = consultantService.getRecommendConsultantList();
        return new ResponseEntity<>(recommendConsultantList, HttpStatus.OK);
    }
    @PostMapping("")
    public ResponseEntity<Void> addConsultantToOwnList(@RequestBody AddConsultantParam param) {
        consultantService.addConsultantToOwnList(param);
        return new ResponseEntity<>(HttpStatus.ACCEPTED);
    }
    @GetMapping("/strictQuery")
    public ResponseEntity<List<ConsultantDTO>> strictQueryConsultant(@RequestBody StrictQueryConsultantParam param) {
        List<ConsultantDTO> queryResult = consultantService.strictQueryConsultant(param);
        return new ResponseEntity<>(queryResult, HttpStatus.OK);
    }
    @GetMapping("/fastQuery")
    public ResponseEntity<List<ConsultantDTO>> fastQueryConsultant(@RequestBody FastQueryConsultantParam param) {
        List<ConsultantDTO> queryResult = consultantService.fastQueryConsultant(param);
        return new ResponseEntity<>(queryResult, HttpStatus.OK);
    }
    @GetMapping("/{agentNo}")
    public ResponseEntity<ConsultantDetailDTO> getConsultantDetail(@PathVariable String agentNo) {
        ConsultantDetailDTO result = consultantService.getConsultantDetail(agentNo);
        return new ResponseEntity<>(result, HttpStatus.OK);
    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/errors/ConsultantNotFoundException.java
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,8 @@
package com.pollex.pam.web.rest.errors;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Consultant not found")
public class ConsultantNotFoundException extends RuntimeException{
}