浏览代码

fenzhihebing

lianbingjie 11 月之前
父节点
当前提交
dc24d0f749

+ 17 - 13
src/App.vue

@@ -8,26 +8,29 @@ import { getParam } from '@/utils/'
 import { mapState, mapActions } from 'vuex'
 export default {
   name: 'app',
-  created () {
+  async created () {
     const element = document.getElementById('myElement')
     if (getParam('iframe') === 'shareBox') {
       element.style.background = 'rgba(0, 0, 0, 0)'
     } else {
       element.style.background = 'rgba(245, 244, 249, 1)'
     }
-    this.getIdentityList().then(() => {
-      const { positionType } = this.userIdentity
-      if (positionType === 1) {
-        // 企业身份下取企业用户信息
-        this.getEntUserInfoFn()
-        this.getMySelectEntInfo()
-      } else {
-        this.getPersonalUserInfo()
-      }
-    })
+    await this.getUserLogin()
+    if (this.isLogin) {
+      this.getIdentityList().then(() => {
+        const { positionType } = this.userIdentity
+        if (positionType === 1) {
+          // 企业身份下取企业用户信息
+          this.getEntUserInfoFn()
+          this.getMySelectEntInfo()
+        } else {
+          this.getPersonalUserInfo()
+        }
+      })
+    }
   },
   computed: {
-    ...mapState('user', ['userIdentity']),
+    ...mapState('user', ['userIdentity', 'isLogin']),
     getParam () {
       return this.$route.query.resource === 'BI'
     }
@@ -39,7 +42,8 @@ export default {
     ...mapActions({
       getIdentityList: 'user/getIdentityList',
       getEntUserInfoFn: 'user/getEntUserInfoFn',
-      getPersonalUserInfo: 'user/getPersonalUserInfo'
+      getPersonalUserInfo: 'user/getPersonalUserInfo',
+      getUserLogin: 'user/getUserLogin'
     })
   }
 }

+ 1 - 1
src/api/index.js

@@ -27,7 +27,7 @@ service.interceptors.response.use(
   (response) => {
     const res = response.data
     if (response.status === 200) {
-      if (res) {
+      if (res && !response.config.noToast) {
         // 判断是否需要登录
         if (res.error_msg === '需要登录' || res.error_msg === '未登录' || res.error_code === 1001) {
           if (!debug) {

+ 4 - 0
src/api/modules/index.js

@@ -7,4 +7,8 @@ export * from './entbase'
 export * from './bigmember'
 export * from './publicapply'
 export * from './communicateBook'
+<<<<<<< HEAD
 export * from './biService'
+=======
+export * from './problem'
+>>>>>>> master

+ 25 - 0
src/api/modules/problem.js

@@ -0,0 +1,25 @@
+import request from '@/api'
+
+// 获取客户问题所有标签
+export function getProblemLabel () {
+  return request({
+    url: '/social/problemlabel',
+    method: 'post'
+  })
+}
+// 获取客户问题已打标签内容
+export function getClientProblemList (data) {
+  return request({
+    url: '/social/problemLabel/list',
+    method: 'post',
+    data
+  })
+}
+// 客户问题标签保存
+export function saveClientProblemLabel (data) {
+  return request({
+    url: '/social/problemLabel/save',
+    method: 'post',
+    data
+  })
+}

+ 17 - 0
src/api/modules/userCenter.js

@@ -8,6 +8,14 @@ export function getUserInfo (data) {
     data
   })
 }
+// 获取游客用户信息
+export function getTouristInfo (data) {
+  return request({
+    url: '/social/tourist/info',
+    method: 'post',
+    data
+  })
+}
 // 获取企业用户信息
 export function geEntUserInfo () {
   return request({
@@ -15,3 +23,12 @@ export function geEntUserInfo () {
     method: 'post'
   })
 }
+// 获取用户手机号邮箱等信息(也用于判断用户是否登录)
+export function getUserID () {
+  return request({
+    baseURL: '/jypay',
+    url: '/user/getSimpleData',
+    noToast: true,
+    method: 'post'
+  })
+}

+ 1 - 0
src/assets/style/in-app.scss

@@ -4,5 +4,6 @@
     height: calc(100% - 48px);
     max-width: unset;
     margin: 24px;
+    overflow-x: auto;
   }
 }

+ 119 - 12
src/components/CustomerInfo.vue

@@ -20,6 +20,7 @@
     </div>
     <div class="info-content" v-loading="loading">
       <div class="item_" v-show="active === 1">
+<<<<<<< HEAD
         <Cell>
           <template v-slot:title>
             <div class="custom-title flex">
@@ -48,15 +49,52 @@
                 :current-page="listInfo.pageNum" @current-change="handleCurrentChange" :hide-on-single-page="true"
                 :total="listInfo.total">
               </el-pagination>
+=======
+        <div v-show="showProblemList">
+          <problemList :uid="userId" :list="clintProblemInfo.list" @back="viewMoreProblem"></problemList>
+        </div>
+        <div  v-show="!showProblemList">
+          <!-- P570需求--游客 --客户信息仅展示服务总结模块-->
+          <Cell title="客户基础信息" v-if="userInfo.isTourist !== 2">
+            <div class="item-main">
+              <span class="account-info">用户手机号:{{ userInfo.phone }}</span>
+              <span v-if="userInfo.vipStatus > 0" class="account-icon icon-vip"></span>
+              <span v-if="userInfo.memberStatus > 0" class="account-icon icon-member"></span>
+              <!-- <span v-if="userInfo.entStatus" class="account-icon icon-ent"></span> -->
+>>>>>>> master
             </div>
-          </div>
-          <div class="empty-text" v-else>暂无订单信息</div>
-        </Cell>
-        <Cell title="服务总结" :border="false" v-if="sessionId">
-          <div class="item-main" v-show="evalLoading">
-            <Summary :eval-num="evalNum" :handle-remark="handleRemark" @submit="onSubmitEval" :key="userId"></Summary>
-          </div>
-        </Cell>
+          </Cell>
+          <Cell title="订阅设置" isLink v-if="userInfo.isTourist !== 2">
+            <div class="item-main" v-if="userInfo.keywords && userInfo.keywords.length > 0">
+              <div class="keyword-item" v-for="(item, index) in userInfo.keywords" :key="index">{{ item }}</div>
+            </div>
+            <div v-else class="empty-text">暂无订阅</div>
+          </Cell>
+          <Cell class="problem-tag-cell" v-if="clintProblemInfo.current_cycle">
+            <template #title>
+              <span>用户问题标签</span>
+              <span class="view-more" @click="viewMoreProblem(true)" v-if="clintProblemInfo.list.length > 0">更多<i class="el-icon-arrow-right"></i></span>
+            </template>
+            <tagRow v-if="clintProblemInfo.finished" :show-select="true" :uid="userId" :info="clintProblemInfo.current_cycle"/>
+          </Cell>
+          <Cell title="订单信息" v-if="userInfo.isTourist !== 2">
+            <div class="item-main order" v-if="listInfo.list && listInfo.list.length > 0">
+              <Order v-for="order in listInfo.list" :key="order.id" v-bind="order"></Order>
+              <div class="pagination">
+                <el-pagination background layout="prev, pager, next" :pager-count="5" :page-size="listInfo.pageSize"
+                               :current-page="listInfo.pageNum" @current-change="handleCurrentChange" :hide-on-single-page="true"
+                               :total="listInfo.total">
+                </el-pagination>
+              </div>
+            </div>
+            <div class="empty-text" v-else>暂无订单信息</div>
+          </Cell>
+          <Cell title="服务总结" :border="false" v-if="sessionId">
+            <div class="item-main" v-show="evalLoading">
+              <Summary :eval-num="evalNum" :handle-remark="handleRemark" @submit="onSubmitEval" :key="userId"></Summary>
+            </div>
+          </Cell>
+        </div>
       </div>
       <div class="item_" v-show="active === 2">
         <div class="verbalTrick-content">
@@ -88,8 +126,15 @@ import { Pagination, Input, Popover } from 'element-ui'
 import Cell from '@/components/fold-cell/'
 import Order from '@/components/order-item/'
 import Summary from '@/components/summary/'
+<<<<<<< HEAD
 import { getUserInfo, getOrderList, isEvalInfo, setSummary, commonPhrasesClassList, getFindClueInfo } from '@/api/modules/'
+=======
+import { getUserInfo, getOrderList, isEvalInfo, setSummary, commonPhrasesClassList, getClientProblemList } from '@/api/modules/'
+>>>>>>> master
 import { momentTime, formatNum } from '@/utils/'
+import tagRow from '@/components/problem-tag/tag-row.vue'
+import problemList from '@/components/problem-tag/list.vue'
+import { throttle } from 'lodash'
 export default {
   name: 'CustomerInfo',
   props: {
@@ -108,7 +153,9 @@ export default {
     Summary,
     [Pagination.name]: Pagination,
     [Input.name]: Input,
-    [Popover.name]: Popover
+    [Popover.name]: Popover,
+    tagRow,
+    problemList
   },
   data () {
     return {
@@ -124,7 +171,8 @@ export default {
         phone: '',
         keywords: [],
         headImg: '',
-        nickName: ''
+        nickName: '',
+        isTourist: 0
       },
       keywords: [],
       listInfo: {
@@ -195,7 +243,23 @@ export default {
       evalNum: 0,
       handleRemark: '',
       evalLoading: false,
-      timer: null
+      timer: null,
+      // 客户问题标签列表
+      clintProblemInfo: {
+        list: [],
+        current_cycle: {},
+        page: 1,
+        size: 500,
+        isLoading: false,
+        finished: false
+      },
+      // 是否展示用户问题标签列表
+      showProblemList: false
+    }
+  },
+  provide () {
+    return {
+      getProblemInfo: this.getProblemInfo
     }
   },
   mounted () {
@@ -203,6 +267,7 @@ export default {
     this.getOrderList()
     this.getEvalInfo()
     this.doSearch()
+    this.getProblemInfo()
   },
   methods: {
     momentTime,
@@ -275,6 +340,7 @@ export default {
         this.userInfo.keywords = this.formatKeywords(keywords)
         this.userInfo.headImg = headimg
         this.userInfo.nickName = nickname
+        this.userInfo.isTourist = data.isTourist
       }
     },
     async getOrderList () {
@@ -621,6 +687,29 @@ export default {
     },
     formatAreaText (value, old) {
       return value === -1 ? '全国' : ((old ? (value - old) : value) + '个省')
+    },
+    // 获取当前用户的问题标签
+    async getProblemInfo () {
+      const params = {
+        uid: this.userId,
+        page: this.clintProblemInfo.page,
+        size: this.clintProblemInfo.size
+      }
+      this.clintProblemInfo.isLoading = true
+      const { error_code: code, data } = await getClientProblemList(params)
+      this.clintProblemInfo.isLoading = false
+      if (code === 0 && data) {
+        const list = data.list || []
+        this.clintProblemInfo.current_cycle = data.current_cycle || {}
+        this.clintProblemInfo.list = list
+      }
+      this.clintProblemInfo.finished = true
+    },
+    // 用户标签查看更多
+    viewMoreProblem (val) {
+      this.showProblemList = val
+      this.clintProblemInfo.list = []
+      this.getProblemInfo()
     }
   }
 }
@@ -925,4 +1014,22 @@ export default {
     content: "\e79d" !important;
     font-size: 14px;
   }
-}</style>
+  .problem-tag-cell{
+    .cell-title{
+      margin-bottom: 15.5px;
+      .cell-left{
+        width: 100%;
+        display: flex;
+        justify-content: space-between;
+      }
+      .view-more {
+        font-size: 14px;
+        line-height: 22px;
+        color: #686868;
+        font-weight: normal;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 28 - 5
src/components/action-list/index.vue

@@ -12,6 +12,7 @@
       >
         <el-upload
           v-if="item.type == 'image'"
+          :disabled="disabled"
           action="#"
           accept=".jpg,.png,.svg,.jpeg,.gif,.bmp"
           :file-list="imageList"
@@ -20,11 +21,14 @@
           :multiple="true"
           :before-upload="beforeUploadImage"
           slot="reference"
+          class="mr16"
         >
-          <span class="action-img image"></span>
+          <span class="action-img image"  @click="checkLogin"></span>
         </el-upload>
         <el-upload
+          class="mr16"
           v-else-if="item.type == 'attach'"
+          :disabled="disabled"
           action="#"
           accept=".pdf,.word,.docx,.ppt,.pptx"
           :file-list="attachList"
@@ -34,10 +38,10 @@
           :before-upload="beforeUploadAttach"
           slot="reference"
         >
-          <span class="action-img attach"></span>
+          <span class="action-img attach"  @click="checkLogin"></span>
         </el-upload>
         <div v-else-if="item.type == 'rate'" slot="reference">
-          <span v-show="isRobot === 2" class="action-img rate" :class="item.type" @click="onClick(item.type)"></span>
+          <span v-show="isRobot === 2" class="action-img rate mr16" :class="item.type" @click="onClick(item.type)"></span>
         </div>
       </el-popover>
     </div>
@@ -52,6 +56,7 @@
 <script>
 import { Popover, Upload } from 'element-ui'
 import { fileUpload } from '@/api/modules/'
+import {mapGetters} from "vuex";
 
 export default {
   name: 'action-container',
@@ -90,7 +95,8 @@ export default {
     return {
       list: this.options,
       imageList: [],
-      attachList: []
+      attachList: [],
+      disabled: false
     }
   },
   watch: {
@@ -99,6 +105,7 @@ export default {
     }
   },
   computed: {
+    ...mapGetters('user', ['isLogin']),
     getOptions () {
       const list = this.list
       const arr = list.map(v => {
@@ -181,6 +188,13 @@ export default {
         this.$toast('上传失败,只能选择PDF、WORD、PPT格式')
         return false
       }
+    },
+    checkLogin () {
+      if (!this.isLogin) {
+        this.disabled = true
+        this.$emit('close')
+        this.$showLoginDialog()
+      }
     }
   }
 }
@@ -211,7 +225,7 @@ export default {
     display: inline-block;
     width: 24px;
     height: 24px;
-    margin-right: 16px;
+    //margin-right: 16px;
     background-repeat: no-repeat;
     background-position: center;
     background-size: contain;
@@ -232,6 +246,9 @@ export default {
       background-image: url('~@/assets/image/rate.png');
     }
   }
+  .mr16{
+    margin-right: 16px;
+  }
   .image:hover{
     background-image: url('~@/assets/image/image_h.png');
   }
@@ -249,4 +266,10 @@ export default {
     cursor: pointer;
   }
 }
+::v-deep {
+  .el-upload {
+    width: 23px;
+    height: 23px;
+  }
+}
 </style>

+ 2 - 2
src/components/chart-container/index.vue

@@ -70,7 +70,7 @@ export default {
     }
   },
   computed: {
-    ...mapState('user', ['personalUserInfo']),
+    ...mapState('user', ['personalUserInfo', 'isLogin']),
     ...mapGetters('user', ['selfName', 'selfImg']),
     // 与客服聊天时,需要使用个人身份昵称、头像
     autoUserInfo () {
@@ -96,7 +96,7 @@ export default {
           } catch (error) {}
         }
         // 需要处理个人信息
-        if (v.fool === 1) {
+        if (v.fool === 1 || !this.isLogin) {
           v.loginName = this.autoUserInfo.name
           v.loginImg = this.autoUserInfo.img
         }

+ 6 - 1
src/components/chart-header/index.vue

@@ -20,7 +20,7 @@
     </div>
     <div class="chart-header-right">
       <i v-if="showInvite" class="icon-group-invite" @click="onInvite"></i>
-      <i v-if="from" class="j-icon icon-full" @click="onFullScreen"></i>
+      <i v-if="showFullScreen" class="j-icon icon-full" @click="onFullScreen"></i>
       <i v-if="from" class="j-icon icon-close" @click="onClose"></i>
     </div>
     <el-dialog custom-class="invite-dialog" :visible.sync="inviteDialog" :width="entDialogWidth" :modal="false" :show-close="false" :append-to-body="false" destroy-on-close>
@@ -35,6 +35,7 @@
 import { Input, Dialog } from 'element-ui'
 import { updateGroupName, getUserPosition } from '@/api/modules/'
 import SelectPersonInEnt from '@/components/share/SelectPersonInEnt.vue'
+import { mapGetters } from "vuex"
 
 export default {
   name: 'ChartHeader',
@@ -87,6 +88,7 @@ export default {
     }
   },
   computed: {
+    ...mapGetters('user', ['isLogin']),
     shortName () {
       const { userType } = this.other
       return userType === 2 ? this.groupName.slice(-2) : this.groupName.slice(0, 4)
@@ -98,6 +100,9 @@ export default {
       } else {
         return '710px'
       }
+    },
+    showFullScreen () {
+      return this.isLogin && this.from
     }
   },
   watch: {

+ 2 - 1
src/components/chart-list/index.vue

@@ -9,7 +9,8 @@
     </div>
     <ChartItem
       v-for="(item, index) in list"
-      :key="index" v-bind="item"
+      :key="index"
+      v-bind="item"
       :loginType="loginType"
       :otherType="otherType"
       :readonly="readonly"

+ 66 - 0
src/components/problem-tag/list.vue

@@ -0,0 +1,66 @@
+<template>
+  <div class="problem-tag-list">
+    <div class="back-row" @click="backPage">
+      <i class="el-icon-arrow-left"></i>返回
+    </div>
+    <h3 class="title">用户问题标签</h3>
+    <div class="all-list">
+      <tagRow v-for="(item, index) of list" :uid="uid" :info="item" :key="'problem_row_' + index"></tagRow>
+    </div>
+  </div>
+</template>
+
+<script>
+import tagRow from './tag-row'
+export default {
+  components: {
+    tagRow
+  },
+  props: {
+    uid: {
+      type: String,
+      default: ''
+    },
+    list: {
+      type: Array,
+      default: () => {
+        return []
+      }
+    }
+  },
+  data () {
+    return {
+      bottom: 100
+    }
+  },
+  methods: {
+    backPage () {
+      this.$emit('back', false)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .problem-tag-list {
+    .back-row {
+      margin: 16px 0;
+      font-size: 16px;
+      line-height: 24px;
+      color: #686868;
+      cursor: pointer;
+    }
+    h3{
+      font-size: 14px;
+      font-weight: 700;
+      line-height: 22px;
+      margin-bottom: 16px;
+    }
+    .problem-tag-row{
+      margin-bottom: 2px;
+    }
+    .all-list{
+
+    }
+  }
+</style>

+ 315 - 0
src/components/problem-tag/select-tag.vue

@@ -0,0 +1,315 @@
+<template>
+  <el-popover
+      class="select-tag-container"
+      ref="selectTagRef"
+      placement="bottom-end"
+      trigger="manual"
+      :append-to-body="false"
+      popper-class="select-tag-popover"
+      :visible-arrow="false"
+      :offset="0"
+      :width="childrenList.length > 0 ? 393 : 200"
+      v-model="show">
+    <div class="selector-container">
+      <div class="selector-content">
+          <ul class="level-box level-2-box" v-if="childrenList.length > 0">
+            <li class="check-item" :class="{'active': child.checked }" v-for="(child, cIndex) in childrenList" :key="child.id">
+              <el-checkbox v-model="child.checked"   @change="onChildChange($event, child, cIndex)" :label="child.id" >{{child.name}}</el-checkbox>
+            </li>
+          </ul>
+        <ul class="level-box level-1-box">
+          <li class="check-item" :class="{'active': parent.checked }" @mouseover="onLevel1MouseOver(parent, pIndex)" @mouseout="onLevel1MouseOut($event)" v-for="(parent, pIndex) in list" :key="parent.id" >
+            <el-checkbox v-model="parent.checked" :indeterminate="parent.indeterminate" :label="parent.id"  @change="onParentChange($event, parent, pIndex)">{{parent.name}}</el-checkbox>
+            <i class="el-icon-arrow-right" v-if="parent.children.length > 0"></i>
+          </li>
+        </ul>
+      </div>
+      <div class="selector-footer">
+        <el-button type="default" @click="cancelHandle">取消</el-button>
+        <el-button type="primary" @click="confirmHandle">确定</el-button>
+      </div>
+    </div>
+    <div class="handle-btn" @click.stop="showPop" slot="reference">选择标签<i :class="show ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i></div>
+  </el-popover>
+</template>
+
+<script>
+import { getProblemLabel, saveClientProblemLabel } from '@/api/modules/'
+import { debounce } from 'lodash'
+export default {
+  props: {
+    uid: {
+      type: String,
+      default: ''
+    },
+    labelIds: {
+      type: Array,
+      default: () => []
+    }
+  },
+  inject: ['getProblemInfo'],
+  data () {
+    return {
+      show: false,
+      list: [],
+      childrenList: [],
+      pActive: 0,
+      pTimer: false,
+      timer: null
+    }
+  },
+  created () {
+    this.getList()
+    const _this = this
+    document.addEventListener('click', function () {
+      _this.show = false
+    })
+  },
+  beforeDestroy () {
+    if (this.timer) clearTimeout(this.timer)
+  },
+  methods: {
+    async getList () {
+      const { error_code: code, data = [] } = await getProblemLabel()
+      if (code === 0 && data && Array.isArray(data)) {
+        for (const item of data) {
+          item.checked = false
+          if (item.children) {
+            for (const childItem of item.children) {
+              childItem.checked = false
+            }
+            item.indeterminate = false
+          } else {
+            item.children = []
+          }
+          item._childrenLen = item.children.length
+        }
+        this.list = data
+        this.childrenList = this.list[0].children
+        // 回选
+        this.setStatus(this.labelIds)
+      }
+    },
+    onLevel1MouseOut (e) {
+      clearTimeout(this.pTimer)
+      this.pTimer = null
+    },
+    onLevel1MouseOver (parent, pIndex) {
+      if (!this.pTimer) {
+        this.pTimer = setTimeout(() => {
+          this.onOpenLevel2(parent, pIndex)
+          const calcLeft = parent.children.length === 0
+          this.calcPopoverPosition('hover', calcLeft)
+        }, 150)
+      }
+    },
+    // 父级选中/取消选中
+    onParentChange (checked, parent, pIndex) {
+      this.onOpenLevel2(parent, pIndex)
+      parent.checked = checked
+      parent.indeterminate = false
+      parent.children.forEach((second) => {
+        second.checked = checked
+      })
+    },
+    // 子级选中/取消选中
+    onChildChange (checked, child, cIndex) {
+      child.checked = checked
+      this.setWholeCheckedStatus(child.pid)
+    },
+    // 设置全选、半选状态
+    setWholeCheckedStatus (pid) {
+      const pList = this.list.filter(item => item.id === pid)
+      const pItem = pList[0] || []
+      const checkedList = pItem.children.filter(item => item.checked)
+      this.list[this.pActive].indeterminate = false
+      if (checkedList.length > 0) {
+        this.list[this.pActive].indeterminate = true
+        this.list[this.pActive].checked = false
+      }
+      if (pItem._childrenLen === checkedList.length) {
+        this.list[this.pActive].indeterminate = false
+        this.list[this.pActive].checked = true
+      }
+    },
+    onOpenLevel2 (parent, pIndex) {
+      this.pActive = pIndex
+      this.childrenList = parent.children
+    },
+    getStatus () {
+      const ids = []
+      for (const item of this.list){
+        if (item.checked) {
+          ids.push(item.id)
+        } else if (item.indeterminate){
+          for (const cItem of item.children) {
+            if (cItem.checked) {
+              ids.push(cItem.id)
+            }
+          }
+        }
+      }
+      return ids.join(',')
+    },
+    setStatus (ids) {
+      if (ids && Array.isArray(ids)) {
+        const idList = ids.map(temp => Number(temp))
+        for (const item of this.list) {
+          if (idList.includes(item.id)) {
+            item.checked = true
+          }
+          for (const cItem of item.children) {
+            if (idList.includes(cItem.id)) {
+              item.indeterminate = true
+              cItem.checked = true
+            }
+            if (item.checked && !item.indeterminate) {
+              cItem.checked = true
+            }
+          }
+        }
+      }
+    },
+    showPop () {
+      this.show = !this.show
+      this.childrenList = this.list[0].children
+      this.pActive = 0
+      if (this.show) {
+        this.calcPopoverPosition()
+      }
+    },
+    initPopoverPosition () {
+      const popover = this.$refs.selectTagRef
+      if (popover) {
+        this.$nextTick(() => {
+          popover.updatePopper()
+        })
+      }
+    },
+    // 处理问题:由于外层overflow:hidden,元素也不能插入body,但是父元素其他内容过长,所剩底部区域过小时,popover展示不全
+    calcPopoverPosition (isHover, calcLeft) {
+      const parent = document.querySelector('#customer-info')
+      const parentRect = parent.getBoundingClientRect()
+      const elementRect = this.$el.getBoundingClientRect()
+      const left = elementRect.left
+      const bottom = parentRect.bottom - elementRect.bottom
+      if (bottom < 260 && this.$refs.selectTagRef) {
+        const popover = this.$refs.selectTagRef
+        if (popover) {
+          if (isHover) {
+            popover.popperElm.style.top = (elementRect.bottom - 350) + 'px'
+            popover.popperElm.style.left = calcLeft ? (left - 123) + 'px' : (left - 323) + 'px'
+          } else {
+            if (this.timer) clearTimeout(this.timer)
+            this.timer = setTimeout(() => {
+              popover.popperElm.style.top = (elementRect.bottom - 350) + 'px'
+              popover.popperElm.style.left = (left - 323) + 'px'
+            })
+          }
+        }
+      } else {
+        this.initPopoverPosition()
+      }
+    },
+    cancelHandle () {
+      this.show = false
+    },
+    confirmHandle: debounce(async function () {
+      const ids = this.getStatus()
+      if (!ids) {
+        this.$toast('请选择标签!')
+        return
+      }
+      const params = {
+        label_ids: ids,
+        uid: this.uid
+      }
+      const { error_code: code } = await saveClientProblemLabel(params)
+      if (code === 0) {
+        this.show = false
+        this.getProblemInfo()
+      }
+    })
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .select-tag-container {
+    .handle-btn{
+      font-size: 14px;
+      line-height: 22px;
+      color: #2ABED1;
+      cursor: pointer;
+    }
+    .selector-content {
+      display: flex;
+      height: 250px;
+      .level-box{
+        height:100%;
+        overflow-y: auto;
+        background: #fff;
+        padding-top: 16px;
+      }
+      .level-1-box {
+        width:200px;
+        border-radius: 8px 8px 0 0;
+      }
+      .level-2-box {
+        box-sizing: border-box;
+        width:193px;
+        border-right: 1px solid #eee;
+        border-top-left-radius: 8px;
+      }
+      .check-item{
+        padding: 5px 8px;
+        display: flex;
+        width: 100% !important;
+        justify-content: space-between;
+        align-items: center;
+        &:hover{
+          background: rgba(234, 248, 250, 1);
+        }
+        &.active {
+          background: rgba(234, 248, 250, 1);
+        }
+      }
+    }
+    .selector-footer{
+      padding: 12px 18px 12px 0;
+      text-align: right;
+      border-top:1px solid #eee;
+      .el-button{
+        padding: 4px 23px;
+        font-size:14px;
+        line-height: 22px;
+      }
+      .el-button--primary {
+        margin-left:16px;
+      }
+    }
+  }
+  ::v-deep {
+    .el-popover.select-tag-popover {
+      max-width: 393px;
+      min-width: 200px;
+      padding: 0;
+      box-sizing: border-box;
+      border-radius: 8px;
+      border: 1px solid #2ABED1;
+      box-shadow: 0 0 28px 0 rgba(0, 0, 0, 0.08);
+      margin-top:4px;
+    }
+    .el-checkbox{
+      .el-checkbox__label{
+        color: #1d1d1d;
+      }
+      .el-checkbox__inner{
+        border-color: #e0e0e0;
+      }
+      .el-checkbox__input.is-checked .el-checkbox__inner {
+        border-color: #2CB7CA;
+      }
+    }
+  }
+</style>

+ 79 - 0
src/components/problem-tag/tag-row.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="problem-tag-row">
+    <div class="week-date">
+      <span>{{ info.cycle_time }}</span>
+      <selectTag v-if="showSelect" :uid="uid" :label-ids="tagIdList"></selectTag>
+    </div>
+    <div class="tag-row" v-if="tagList.length > 0">
+      <span class="tag-col" v-for="(item, index) in tagList" :key="'tag_' + tagIdList[index]">{{ item }}</span>
+    </div>
+    <span class="tag-col no-tip" v-else>暂无</span>
+  </div>
+</template>
+<script>
+import selectTag from './select-tag'
+export default {
+  components: {
+    selectTag
+  },
+  props: {
+    showSelect: {
+      type: Boolean,
+      default: false
+    },
+    uid: {
+      type: String,
+      default: ''
+    },
+    info: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    }
+  },
+  computed: {
+    tagList () {
+      return this.info.label_name || []
+    },
+    tagIdList () {
+      return this.info && this.info.label_id ? this.info.label_id.split(',') : []
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+  .problem-tag-row{
+    .week-date {
+      display: flex;
+      justify-content: space-between;
+      font-size: 14px;
+      line-height: 22px;
+      text-align: left;
+      color: #686868;
+      margin-bottom: 8px;
+    }
+    .tag-row{
+      display: flex;
+      flex-direction: row;
+      flex-wrap: wrap;
+    }
+    .tag-col {
+      padding: 6px 8px;
+      background: #F5F6F7;
+      border: 1px solid #ececec;
+      font-size: 12px;
+      line-height: 18px;
+      color: #1D1D1D;
+      border-radius: 4px;
+      margin-right: 8px;
+      margin-bottom: 8px;
+    }
+    .no-tip {
+      padding:0;
+      border: none;
+      background: unset;
+    }
+  }
+</style>

+ 74 - 23
src/components/search/index.vue

@@ -2,12 +2,24 @@
   <div class="search-filter">
     <div class="search-item">
       <span class="search-label long-search-label">人工客服介入:</span>
-       <el-select v-model="involvedVal" placeholder="全部" clearable value-key :popper-append-to-body="false">
+      <el-select v-model="involvedVal" placeholder="全部" clearable value-key popper-class="involved-select-popover" :popper-append-to-body="false">
         <el-option
-          v-for="item in involvedOptions"
-          :key="item.value"
-          :label="item.label"
-          :value="item.value"
+            v-for="item in involvedOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+        >
+        </el-option>
+      </el-select>
+    </div>
+    <div class="search-item">
+      <span class="search-label">是否游客:</span>
+      <el-select v-model="isTourist" placeholder="全部" clearable popper-class="tourist-select-popover" value-key :popper-append-to-body="false">
+        <el-option
+            v-for="item in visitorOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
         >
         </el-option>
       </el-select>
@@ -56,8 +68,6 @@ export default {
   },
   data () {
     return {
-      phoneVal: '',
-      timeVal: '',
       // 人工客服是否介入
       involvedOptions: [
         {
@@ -69,7 +79,25 @@ export default {
           label: '否'
         }
       ],
-      involvedVal: ''
+      // 是否游客
+      visitorOptions: [
+        {
+          value: 0,
+          label: '全部'
+        },
+        {
+          value: 2,
+          label: '是'
+        },
+        {
+          value: 1,
+          label: '否'
+        }
+      ],
+      involvedVal: null,
+      isTourist: 0,
+      phoneVal: '',
+      timeVal: ''
     }
   },
   methods: {
@@ -77,7 +105,8 @@ export default {
       const params = {
         phone: this.phoneVal,
         time: this.timeVal,
-        isArtificial: this.involvedVal ? this.involvedVal : 0
+        isArtificial: this.involvedVal ? this.involvedVal : 0,
+        isTourist: this.isTourist || 0
       }
       this.$emit('search', params)
     }
@@ -97,7 +126,7 @@ export default {
   .search-label{
     flex-shrink: 0;
     color: #686868;
-    width: 48px;
+    width: 60px;
     margin-right: 8px;
     text-align: right;
     &.long-search-label{
@@ -124,14 +153,16 @@ export default {
     .el-date-editor.el-input__inner{
       width: 170px;
       flex: 1;
-      padding: 3px 6px;
+      align-items: center;
+      padding: 3px 4px 3px 6px;
     }
     .el-date-editor .el-range-input{
       font-size: 12px;
     }
     .el-date-editor .el-range__close-icon{
-      width: 20px;
-      line-height: 20px;
+      width: 16px;
+      line-height: 18px;
+      margin-top: 5px;
     }
     .el-date-editor .el-range-input{
       width: 48%;
@@ -148,26 +179,46 @@ export default {
     .el-select__caret{
       line-height: 28px;
     }
+    .el-select-dropdown {
+      margin-top:0;
+      .popper__arrow{
+        display: none;
+      }
+
+    }
     .el-select-dropdown__item.selected{
       color: $color_main;
     }
-    .el-scrollbar{
-      height: 64px;
+    .involved-select-popover{
+      .el-scrollbar{
+        height: 65px;
+      }
+      .el-select-dropdown__wrap{
+        height:  65px;
+        max-height: unset;
+        overflow: auto;
+      }
     }
-    .el-select-dropdown__wrap{
-      height: 64px;
-      max-height: unset;
-      overflow: auto;
+    .tourist-select-popover{
+      .el-scrollbar{
+        height: 96px;
+      }
+      .el-select-dropdown__wrap{
+        height: 96px;
+        max-height: unset;
+        overflow: auto;
+      }
     }
+
     .el-select-dropdown__list{
       padding: 0;
     }
     .el-select-dropdown__item{
       height: 32px;
-      // &.hover,
-      // &:hover{
-      //   background-color: ;
-      // }
+       &.hover,
+       &:hover{
+         background-color: #ECECEC;
+       }
     }
   }
 }

+ 1 - 0
src/main.js

@@ -7,6 +7,7 @@ import store from './store'
 import '@/assets/style/element-variables.scss'
 import ElementUI, { Message, MessageBox, Loading } from 'element-ui'
 import '@/utils'
+import '@/utils/common'
 import Toast from '@/utils/toast/'
 import $bus from './utils/bus'
 Vue.prototype.$bus = $bus

+ 38 - 2
src/store/modules/user.js

@@ -1,4 +1,4 @@
-import { identityList, identitySwitch, geEntUserInfo, getUserInfo } from '@/api/modules/'
+import { identityList, identitySwitch, geEntUserInfo, getUserInfo, getUserID, getTouristInfo } from '@/api/modules/'
 
 export default {
   namespaced: true,
@@ -10,7 +10,9 @@ export default {
     // 企业下的用户信息
     entUserInfo: {},
     // 个人身份下的用户信息
-    personalUserInfo: {}
+    personalUserInfo: {},
+    // 是否登录
+    isLogin: false
   },
   getters: {
     // 取自己的名字
@@ -21,6 +23,10 @@ export default {
     selfImg (state) {
       const { positionType } = state.userIdentity
       return positionType === 1 ? '' : state.personalUserInfo?.headimg
+    },
+    // 是否登录
+    isLogin (state) {
+      return state.isLogin
     }
   },
   mutations: {
@@ -43,9 +49,24 @@ export default {
     setPersonalUserInfo (state, data) {
       state.personalUserInfo = data
       sessionStorage.setItem('personalUserInfo-login-clear', JSON.stringify(state.personalUserInfo))
+    },
+    setUserLogin (state, val) {
+      state.isLogin = val
     }
   },
   actions: {
+    async getUserLogin ({ commit }) {
+      try {
+        const { error, userId } = await getUserID()
+        if (userId) {
+          commit('setUserLogin', true)
+        }
+        if (error === '需要登录' && !userId) {
+          commit('setUserLogin', false)
+        }
+      } catch (userId) {
+      }
+    },
     // 获取用户身份列表
     async getIdentityList ({ commit }) {
       try {
@@ -98,6 +119,21 @@ export default {
       } catch (error) {
         return error
       }
+    },
+    // 获取未登录用户的信息(游客身份调用)
+    async getTouristUserInfo ({ commit }) {
+      try {
+        const { data } = await getTouristInfo()
+        if (data) {
+          if (data.headImg) {
+            data.headimg = location.origin + data.headImg
+          }
+          commit('setPersonalUserInfo', data)
+        }
+        return data
+      } catch (error) {
+        return error
+      }
     }
   }
 }

+ 10 - 0
src/utils/common.js

@@ -0,0 +1,10 @@
+import Vue from 'vue'
+
+Vue.prototype.$showLoginDialog = function () {
+  try {
+    top.openLoginDig && top.openLoginDig(false, 'reload')
+  } catch (error) {
+    console.log(error)
+    location.href = '/notin/page?close_goBack=1'
+  }
+}

+ 6 - 4
src/views/CustomerServiceView.vue

@@ -729,7 +729,7 @@ export default {
     /**
      * 获取客服所有聊天列表,用于筛选出已结束的会话(过滤掉会话中、派对中)
      */
-    async getUserListFn ({ phone = '', startTime = '', endTime = '', isArtificial = 0, filtrationId = '' }) {
+    async getUserListFn ({ phone = '', startTime = '', endTime = '', isArtificial = 0, filtrationId = '', isTourist = 0 }) {
       let filtration_
       if (!filtrationId) {
         const qLen = this.queueList
@@ -747,6 +747,7 @@ export default {
         endTime: endTime,
         userType: 1, // 1:客服 2:用户
         isArtificial: isArtificial,
+        isTourist: isTourist, // 是否游客
         page: this.allList.page,
         size: this.allList.size,
         filtrationId: filtration_
@@ -1044,8 +1045,8 @@ export default {
     sortSearchParams (data) {
       // 整理数据
       if (data) {
-        const { phone, time, isArtificial } = data
-        const params = { phone, startTime: time ? time[0] : '', endTime: time ? time[1] : '', isArtificial }
+        const { phone, time, isArtificial, isTourist } = data
+        const params = { phone, startTime: time ? time[0] : '', endTime: time ? time[1] : '', isArtificial, isTourist }
         return params
       } else {
         // 获取数据并整理
@@ -1055,7 +1056,8 @@ export default {
           const getData = {
             phone: searchFilter.phoneVal,
             time: searchFilter.timeVal,
-            isArtificial: searchFilter.involvedVal === '' ? 0 : searchFilter.involvedVal
+            isArtificial: searchFilter.involvedVal === '' ? 0 : searchFilter.involvedVal,
+            isTourist: searchFilter.isTourist || 0
           }
           return this.sortSearchParams(getData)
         } catch (error) {

+ 42 - 11
src/views/CustomerView.vue

@@ -28,7 +28,7 @@
               </div>
             </transition>
             <div class="group-chart-footer">
-              <ChartAction :options="getActionList" :isLink="isTurnPeople" :isRobot="isShowNps" @action="onChartAction" @upload-image="onUploadImage" @upload-attach="onUploadAttach" @turn="onTurnPeople"></ChartAction>
+              <ChartAction :options="getActionList" :isLink="isTurnPeople" :isRobot="isShowNps" @action="onChartAction" @upload-image="onUploadImage" @upload-attach="onUploadAttach" @turn="onTurnPeople"  @close="onCloseScreen"></ChartAction>
               <ChartInput ref="chartInput" v-model="msgVal" @on-input="onChartInput" @confirm="onSendMsg" @m-blur="messageBlur">
                 <div class="hint-box" v-show="hintShow">
                   <img src="@/assets/image/close_.png" alt="" class="close" @click="hintShow = false">
@@ -181,7 +181,7 @@ export default {
   computed: {
     ...mapState('message', ['userSessionBadge']),
     ...mapState('user', ['userIdentity', 'personalUserInfo']),
-    ...mapGetters('user', ['selfName', 'selfImg']),
+    ...mapGetters('user', ['selfName', 'selfImg', 'isLogin']),
     ...mapGetters('webSocket', ['socketMsg', 'socketStatus']),
     // 聊天框操作配置项
     getActionList () {
@@ -246,6 +246,9 @@ export default {
     }
   },
   watch: {
+    isLogin () {
+      this.removeUserSessionBadge()
+    },
     socketMsg (val) {
       // 监听接收到的消息,进行处理
       this.todoMessage(val)
@@ -277,17 +280,19 @@ export default {
     this.mini = Number(this.$route.query.mini) // 0: 不带会话列表 1:带会话列表
     this.from = this.$route.query.from
     this.resource = this.$route.query.resource // BI标识参数
-    if (this.resource === 'BI') {
+    await this.getUserLogin()
+    if (this.resource === 'BI' && this.isLogin) {
       // BI沟通记录群组信息被折叠隐藏,查询群组成员接口没有调,导致拉人失败
       // 处理结果:进页面先调一次接口
       this.getGroupInfo()
     }
     // this.other.userType = Number(this.$route.query.userType)
-
-    // 客户聊天时,需要获取个人信息作为参数传递给接口
-    // if (this.$route.query?.userType?.toString() === '1') {
-    this.getPersonalUserInfo()
-    // }
+    console.log('登录了:' +this.isLogin)
+    if (this.isLogin) {
+      this.getPersonalUserInfo()
+    } else {
+      this.getTouristUserInfo()
+    }
 
     const storage = this.userSessionBadge
     if (storage && Object.keys(storage).length > 0) {
@@ -295,7 +300,11 @@ export default {
     }
     await this.getRobotInfo(false)
     this.getSocketUrl()
-    this.getUserListFn()
+    if (this.isLogin) {
+      this.getUserListFn()
+    } else {
+      this.getUserNoLoginFn()
+    }
     this.getMessageCountFn()
   },
   mounted () {
@@ -316,11 +325,13 @@ export default {
       setPersonalUserInfo: 'user/setPersonalUserInfo'
     }),
     ...mapActions({
+      getTouristUserInfo: 'user/getTouristUserInfo',
       getPersonalUserInfo: 'user/getPersonalUserInfo',
       webSocketInit: 'webSocket/webSocketInit',
       webSocketSend: 'webSocket/webSocketSend',
       saveMessageAction: 'message/saveMessageAction',
-      modifyMessageAction: 'message/modifyMessageAction'
+      modifyMessageAction: 'message/modifyMessageAction',
+      getUserLogin: 'user/getUserLogin'
     }),
     async getGroupInfo () {
       const { data } = await getChartGroupInfo({
@@ -410,7 +421,9 @@ export default {
       // 重连后需要重新设置客服身份 读取当前会话得历史数据
       if (flag) {
         this.getUserListFn()
-        this.getFindMessage(this.$refs.chartContainer.msgList[this.$refs.chartContainer.msgList.length - 1]?.messageId)
+        if (this.$refs.chartContainer.msgList && this.$refs.chartContainer.msgList.length > 0) {
+          this.getFindMessage(this.$refs.chartContainer.msgList[this.$refs.chartContainer.msgList.length - 1]?.messageId)
+        }
       }
     },
     // 处理websocket接收的数据
@@ -1182,6 +1195,24 @@ export default {
       }
       this.onSendCommon({ apiParams, socketParams })
     },
+    getUserNoLoginFn () {
+      if (this.mini !== 1) {
+        // 客服
+        const qType = Number(this.$route.query.userType)
+        if (qType === 1) {
+          this.other.userType = qType
+          const kfInfo = {
+            userId: this.other.userId,
+            userType: qType,
+            nickName: this.service.nickName,
+            headImg: this.service.headImg
+          }
+          this.onSelectCurMsg({
+            item: kfInfo
+          }, 'init')
+        }
+      }
+    },
     // 获取已咨询过的客户列表
     async getUserListFn () {
       const { data } = await getUserList({