소스 검색

联系人

李妹妹 2 주 전
부모
커밋
013a93376e

+ 1 - 1
.env.development

@@ -27,4 +27,4 @@ VUE_APP_PATIENT_INFO = '客户信息'
 VUE_APP_ADD_PATIENT = '添加信息'
 
 # IM webSocket地址
-VUE_APP_IM_WS_URL = ws://127.0.0.1:8667/qwImSocket
+VUE_APP_IM_WS_URL = ws://192.168.10.125:8667/qwImSocket

+ 50 - 6
src/api/qw/im.js

@@ -7,7 +7,6 @@ export function getContacts(deviceId) {
   })
 }
 
-
 export function getContactsByUser(userId) {
   return request({
     url: '/qw/contact/listByUser/'+userId,
@@ -16,10 +15,11 @@ export function getContactsByUser(userId) {
 }
 
 
-export function getConversations(userId) {
+export function getConversations(userId,query) {
   return request({
     url: '/qw/qwMsg/conversationList/'+userId,
-    method: 'get'
+    method: 'get',
+     params:query
   })
 }
 
@@ -48,9 +48,28 @@ export function getQwUserList() {
     method: 'get',
   })
 }
-
-
-
+//联系人列表
+export function getContactList(userId) {
+  return request({
+    url: '/qw/qwMsg/contactList/'+userId,
+    method: 'get'
+  })
+}
+//群聊
+export function getGroupList(userId) {
+  return request({
+    url: '/qw/qwMsg/groupList/'+userId,
+    method: 'get'
+  })
+}
+//会话记录生成
+export function getConversationId(query) {
+  return request({
+    url: '/qw/qwMsg/getConversationId',
+  method: 'get',
+  params: query
+  })
+}
 
 // 发送文本消息
 export function sendMsg(data) {
@@ -154,5 +173,30 @@ export function createMiniLink(data) {
   })
 }
 
+// 订单列表
+export function getQwExternalContactOrderList(query) {
+  return request({
+    url: '/qw/qwMsg/getQwExternalContactOrderList',
+    method: 'get',
+    params: query
+  })
+}
+
 
+// 查询短链课程看课记录列表
+export function getQwExternalContactWatchLogList(query) {
+  return request({
+    url: '/qw/qwMsg/getQwExternalContactWatchLogList',
+    method: 'get',
+    params: query
+  })
+}
 
+//访问记录
+export function getQwExternalContactVisitList(query) {
+  return request({
+    url: '/qw/qwMsg/getQwExternalContactVisitList',
+    method: 'get',
+    params: query
+  })
+}

BIN
src/assets/icon/send_icon16.png


BIN
src/assets/image/add_modify_icon.png


BIN
src/assets/image/chat_icon44.png


BIN
src/assets/image/end_of_class.png


BIN
src/assets/image/filter_icon.png


BIN
src/assets/image/gender_female.png


BIN
src/assets/image/gender_male.png


BIN
src/assets/image/learning_courses.png


BIN
src/assets/image/no_page.png


BIN
src/assets/image/small_program.png


BIN
src/assets/image/small_program_login.png


BIN
src/assets/image/sq_arrow_down.png


BIN
src/assets/image/view_class_records.png


+ 3 - 2
src/components/LemonUI/components/chatmsg.vue

@@ -347,6 +347,7 @@ export default {
       const menuItem = this._renderMenuItem();
       return (
         <div class="lemon-menu" v-show={!this.hideMenu}>
+        {this.$slots["menu-top"]}
           {
             <lemon-avatar
               v-show={!this.hideMenuAvatar}
@@ -1273,7 +1274,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
 +b(lemon-container)
   flex 1
   flex-column()
-  background #f4f4f4
+  background #F1F4F8
   word-break()
   position relative
   z-index 10
@@ -1325,7 +1326,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
     &::before
       border-left-color #e6eeff
   .lemon-container
-    background #fff
+    background #F1F4F8
   .lemon-sidebar
     background #f9f9f9
     .lemon-contact

+ 14 - 8
src/components/LemonUI/components/index.vue

@@ -339,6 +339,8 @@ export default {
       const menuItem = this._renderMenuItem();
       return (
         <div class="lemon-menu" v-show={!this.hideMenu}>
+          {this.$slots["menu-top"]}
+           <div class='lemon-menu-right'>
           {
             <lemon-avatar
               v-show={!this.hideMenuAvatar}
@@ -349,12 +351,13 @@ export default {
               src={this.user.avatar}
             />
           }
-          {menuItem.top}
+         {menuItem.top}
           {this.$slots.menu}
           <div class="lemon-menu__bottom">
             {this.$slots["menu-bottom"]}
             {menuItem.bottom}
           </div>
+          </div>
         </div>
       );
     },
@@ -492,6 +495,7 @@ export default {
           <div class="lemon-sidebar__scroll">{children}</div>
         </div>
       );
+
     },
     _renderDrawer() {
       return this._menuIsMessages() && this.currentContactId ? (
@@ -610,10 +614,11 @@ export default {
                   if (isEmpty(curactNew.lastContent)) {
                     this.updateContact({
                       id: curactNew.id,
-                      lastContent: " ",
+                      lastContent: "",
                     });
                   }
                   this.changeContact(curactNew.conversationId, DEFAULT_MENU_LASTMESSAGES);
+                  this.$emit("change-contact",curactNew, this);
                 }}
               >
                 发送消息
@@ -881,7 +886,7 @@ export default {
           unread: 0,
           click: null,
           render: menu => {
-            return <i class="lemon-icon-message" />;
+            return <i class="el-icon-chat-dot-round" />;
           },
           isBottom: false,
         },
@@ -941,6 +946,7 @@ export default {
      * @param {Array<Contact>} data 会话列表
      */
     initConversations(data) {
+      //console.log(data,'data')
       this.conversations = data;
       this.sortConversations();
     },
@@ -999,10 +1005,10 @@ export default {
             lastContent: "",
           },conversation
         );
-       console.log("qxj insConversation:"+JSON.stringify(insConversation));
+       //console.log("qxj insConversation:"+JSON.stringify(insConversation));
        this.conversations.unshift(insConversation);
-      //  this.updateContact(insConversation);
-       //this.conversations.push(insConversation);
+       //this.updateContact(insConversation);
+       // this.conversations.push(insConversation);
       return true;
     },
     removeContact(id) {
@@ -1276,7 +1282,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
 +b(lemon-container)
   flex 1
   flex-column()
-  background #f4f4f4
+  background #F1F4F8
   word-break()
   position relative
   z-index 10
@@ -1328,7 +1334,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
     &::before
       border-left-color #e6eeff
   .lemon-container
-    background #fff
+    background #F1F4F8
   .lemon-sidebar
     background #f9f9f9
     .lemon-contact

+ 4 - 3
src/components/LemonUI/components/message/basic.vue

@@ -54,7 +54,7 @@ export default {
           />
         </div>
         <div class="lemon-message__inner">
-          <div class="lemon-message__title">
+          <div class="lemon-message__title" style="display:none">
             {this.hideName == false && (
               <span
                 on-click={e => {
@@ -215,13 +215,14 @@ arrow()
       left 26px
       right auto
     +e(content)
-      background #35d863
+      background #409EFF
+      color #fff
       &:before
         arrow()
         left auto
         right -4px
         border-right none
-        border-left-color #35d863
+        border-left-color #409EFF
     +e(title)
       text-align right
     +e(avatar)

+ 2 - 2
src/components/LemonUI/components/message/event.vue

@@ -29,8 +29,8 @@ export default {
   +e(content)
     user-select none
     display inline-block
-    background #e9e9e9
-    color #aaa
+    // background #e9e9e9
+    color #84868C
     font-size 12px
     margin 0 auto
     padding 5px 10px

+ 1 - 1
src/components/LemonUI/components/message/miniprogram.vue

@@ -33,7 +33,7 @@ export default {
           content = { title: '小程序' }
         }
       }
-      
+
       return (
         <div>
           <div class="lemon-message-miniprogram__header">

+ 12 - 11
src/components/LemonUI/components/records.vue

@@ -194,7 +194,7 @@ export default {
         if (message.type == 'event'){
             // if(this.user.id == message.fromUser.id){}
             unread = 0;
-        } 
+        }
         if (messageList === undefined) {
           //console.log("qxj appendMessage messageList:"+messageList+",message"+JSON.stringify(message));
           conversation.id=message.toContactId;
@@ -207,7 +207,7 @@ export default {
           let hasMsg = messageList.some(({id})=>id == message.id);
           if (hasMsg) return;
           this._addMessage(message, message.toContactId, 1);
-          
+
           conversation.lastContent=this.lastContentRender(message);
           conversation.lastSendTime=message.sendTime;
           if (message.toContactId == this.currentContactId) {
@@ -355,6 +355,7 @@ export default {
       const menuItem = this._renderMenuItem();
       return (
         <div class="lemon-menu" v-show={!this.hideMenu}>
+        {this.$slots["menu-top"]}
           {
             <lemon-avatar
               v-show={!this.hideMenuAvatar}
@@ -543,7 +544,7 @@ export default {
             </div>,
           );
       }
-      
+
       //聊天消息View
       nodes.push(
         <div
@@ -574,9 +575,9 @@ export default {
                 on-reach-bottom={this._emitPullUpMessages}
                 messages={this.currentMessages}
               />
-          
+
             </div>
-           
+
           </div>
         </div>,
       );
@@ -698,7 +699,7 @@ export default {
         editorValue,
         lastContent,
       });
-      
+
       var tempDraft=this.CacheDraft.get(cid);
       //console.log("qxj editorValue:"+editorValue+",tempDraft:"+JSON.stringify(tempDraft)+"lastContent:"+lastContent);
 
@@ -932,7 +933,7 @@ export default {
     initConversations(data) {
       this.conversations = data;
       this.sortConversations();
-      
+
     },
 
     /**
@@ -1023,7 +1024,7 @@ export default {
      */
     updateContact(data) {
       const conversationId = data.conversationId;
-      const index = this.findConversationIndexById(conversationId);  
+      const index = this.findConversationIndexById(conversationId);
       if (index !== -1) {
         // if(isString(data.unread)) {
         //     if (unread.indexOf("+") === 0 || unread.indexOf("-") === 0) {
@@ -1039,7 +1040,7 @@ export default {
     updateContact1(data) {
       const contactId = data.id;
       delete data.id;
-      const index = this.findContactIndexById(contactId);  
+      const index = this.findContactIndexById(contactId);
       if (index !== -1) {
         const { unread } = data;
         if (isString(unread)) {
@@ -1266,7 +1267,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
 +b(lemon-container)
   flex 1
   flex-column()
-  background #f4f4f4
+  background #F1F4F8
   word-break()
   position relative
   z-index 10
@@ -1318,7 +1319,7 @@ bezier = cubic-bezier(0.645, 0.045, 0.355, 1)
     &::before
       border-left-color #e6eeff
   .lemon-container
-    background #fff
+    background #F1F4F8
   .lemon-sidebar
     background #f9f9f9
     .lemon-contact

+ 24 - 6
src/layout/index.vue

@@ -14,13 +14,14 @@
 
       <div class="qw-im">
         <el-badge :value="totalUnreadCount" :max="99" :hidden="totalUnreadCount < 1">
-          <img alt="" class="qw-im-img" @click="() => {this.qw.open = !this.qw.open}" src="../assets/image/qw-im.png"/>
+          <img alt="" class="qw-im-img" @click="() => {this.qw.open = !this.qw.open}" src="../assets/image/chat_icon44.png"/>
+          <div class="qw-im-text">IM聊天</div>
         </el-badge>
       </div>
     </div>
 
     <el-dialog append-to-body
-               :width="qw.isMaximized ? '100vw' : '80vw'"
+               :width="qw.isMaximized ? '100vw' : '90vw'"
                custom-class="im-dialog"
                :fullscreen="qw.isMaximized"
                :visible.sync="qw.open"
@@ -154,25 +155,42 @@ export default {
     position: fixed;
     right: 30px;
     bottom: 30px;
-    width: 54px;
-    height: 54px;
+   width: 108px;
+   height: 108px;
+   text-align: center;
+   background: linear-gradient( 135deg, #006CFF 0%, #68B1FF 100%);
+   border-radius: 54px 54px 54px 54px;
+   display: flex;
+       align-items: center;
+       justify-content: center;
+       flex-direction: column;
     .qw-im-img {
-      width: 100%;
+      width: 44px;
+    }
+    .qw-im-text{
+     font-weight: 600;
+     font-size: 16px;
+     color: #FFFFFF;
     }
     .el-badge__content {
       border: unset;
     }
   }
-
   ::v-deep .im-dialog {
     max-width: 100vw;
     max-height: 100vh;
+
     .el-dialog__header {
       display: none;
     }
     .el-dialog__body {
       padding: 0 !important;
     }
+     &:not(.is-fullscreen) {
+      top: 50% !important;
+        transform: translateY(-50%) !important;
+        margin-top: 0 !important;
+    }
   }
 
   .qw-im-content {

+ 295 - 50
src/views/qw/qwChat/qq.vue

@@ -1,29 +1,31 @@
 <template>
   <div style="width: 100%;height: 100%">
 
-    <el-tabs class="im-tabs" type="card" v-model="appKey" @tab-click="qwUserChange">
-      <el-tab-pane
-        v-for="item in qwUserList"
-        :key="item.id"
-        :name="item.appKey">
-        <span slot="label">
-          <el-badge :value="getUnreadCount(item.appKey)"
-                    :max="99"
-                    v-if="getUnreadCount(item.appKey) > 0">
-            {{ item.qwUserName }}
-          </el-badge>
-          <template v-else>
-            {{ item.qwUserName }}
-          </template>
-        </span>
-      </el-tab-pane>
-    </el-tabs>
-
-    <i class="el-icon-close" @click="handleClose" style="font-size: 22px;position: absolute;top: 8px;right: 8px;cursor: pointer;"/>
+ <!-- <el-tabs class="im-tabs" type="card" v-model="appKey" @tab-click="qwUserChange">
+                            <el-tab-pane
+                              v-for="item in qwUserList"
+                              :key="item.id"
+                              :name="item.appKey">
+                              <span slot="label">
+                                <el-badge :value="getUnreadCount(item.appKey)"
+                                          :max="99"
+                                          v-if="getUnreadCount(item.appKey) > 0">
+                                  {{ item.qwUserName }}
+                                </el-badge>
+                                <template v-else>
+                                  {{ item.qwUserName }}
+                                </template>
+                              </span>
+                            </el-tab-pane>
+                          </el-tabs> -->
+
+<!-- <div class="close-box">
+            <i class="el-icon-close" @click="handleClose" style="font-size: 22px;margin-right: 8px;cursor: pointer;"/>
+          </div> -->
 
     <div class="imui-center qq-lemon-imui" v-show="showQW">
       <lemon-imui  class="lemon-slot"
-                   :width="isMaximized ? '100vw' : windowWidth"
+                   :width="isMaximized ? '100vw' : '100%'"
                    :height="isMaximized ? '90vh' : windowHeight"
                    :user="userData"
                    ref="IMUI"
@@ -42,6 +44,18 @@
                    @menu-avatar-click="handleMenuAvatarClick"
                    @pick-image="handleImageClick"
                    @send="handleSend">
+                    <template #menu-top>
+                      <div class="qiwei">
+                         <div  v-for="item in qwUserList" :key="item.id" @click="qwUserChange(item)" :class="appKey==item.appKey?'q-item active':'q-item'">
+                          <el-badge :value="getUnreadCount(item.appKey)"
+                                     :max="99"
+                                     :hidden="getUnreadCount(item.appKey) < 1">
+                                       <el-avatar shape="square" :src="item.avatar"></el-avatar>
+                                       <div class="name">{{item.qwUserName}}</div>
+                           </el-badge>
+                         </div>
+                      </div>
+                    </template>
         <template #sidebar-message-top>
           <div style="padding: 8px; display: flex; align-items: center;">
             <el-input
@@ -51,7 +65,8 @@
               size="small"
               clearable
               @input="handleSearch"
-              style="flex: 1;height: 32px;"
+              style="flex: 1;height: 32px;margin-right: 8px;"
+              class="top-input"
             />
             <el-popover
               placement="bottom-end"
@@ -64,6 +79,7 @@
                 <el-checkbox-group v-model="selectedFilters">
                   <el-checkbox label="1">去掉重粉</el-checkbox>
                   <el-checkbox label="2">去掉黑粉</el-checkbox>
+                  <!-- <el-checkbox label="3">按催课排序</el-checkbox> -->
                 </el-checkbox-group>
                 <div style="margin-top: 10px; text-align: right;">
                   <el-button size="mini" type="primary" @click="applyFilter">确定</el-button>
@@ -72,14 +88,17 @@
               <el-button
                 slot="reference"
                 icon="el-icon-setting"
-                style="padding: 8px;height: 32px;"
-                size="small"
+                style="padding: 8px;height: 32px;border: none;background: #F1F4F8;width:32px;"
+                size="medium"
                 :type="selectedFilters.length > 0 ? 'primary' : 'default'"
               ></el-button>
             </el-popover>
           </div>
         </template>
         <template #message-title="contact">
+          <div class="user-name">
+            {{contact.displayName}}
+          </div>
 <!--          <div @click="openDrawer('right')" style="position: absolute;right: 14px;top: 10px;">-->
 <!--            <i class="el-icon-more" style="cursor: pointer"/>-->
 <!--          </div>-->
@@ -93,9 +112,16 @@
 <!--          </div>-->
         </template>
         <template #message-extend="contact">
+           <div class="close-box">
+             <i class="el-icon-close" @click="handleClose" style="font-size: 22px;cursor: pointer;"/>
+           </div>
           <div v-if="contact.extId" style="width: 100%;height: 100%;overflow: auto">
-            <userDetail :ext-id="contact.extId" :extend="true" ref="userDetail" />
+            <userDetail :ext-id="contact.extId" :conversation-id="contact.conversationId" :extend="true" ref="userDetail" />
           </div>
+            <!-- 课程管理 -->
+            <div v-if="showCourseManage">
+                <course-manage :conversationId="contact.conversationId" :extend="true"/>
+            </div>
         </template>
       </lemon-imui>
     </div>
@@ -130,12 +156,13 @@
 
 <script>
 
-import {getConversations,getMessageList,getConversation,sendMsg,getQwUserList} from '@/api/qw/im';
+import {getConversations,getMessageList,getConversation,sendMsg,getQwUserList,getContactList,getGroupList,getConversationId} from '@/api/qw/im';
 import Conversations from "@/components/LemonUI/database/conversations";
 import EmojiData from "@/components/LemonUI/database/emoji";
 import '@/components/LemonUI/index.css';
 import VideoPlayer from '@/components/VideoPlayer/VueAliplayer.vue'
 import UserDetail from "@/views/qw/qwChat/userDetail/index.vue";
+import courseManage from "@/views/qw/qwChat/userDetail/courseManage.vue";
 import {ImSocket} from "@/utils/ImSocket";
 import {getToken} from '@/utils/auth'
 import {uploadOss} from "@/api/common";
@@ -148,7 +175,8 @@ export default {
   name: "qqChat",
   components: {
     VideoPlayer,
-    UserDetail
+    UserDetail,
+    courseManage
   },
   props: {
     showQw: Boolean,
@@ -192,7 +220,7 @@ export default {
       roomMembers:[],
       roomAdmins:[],
       roomInfo:null,
-      windowWidth: document.documentElement.clientWidth * 0.8,  //实时屏幕宽度
+      windowWidth: document.documentElement.clientWidth * 0.9,  //实时屏幕宽度
       windowHeight: document.documentElement.clientHeight * 0.85,   //实时屏幕高度
       queryParams: {
         pageNum: 1,
@@ -200,6 +228,10 @@ export default {
         conversationId: null,
         userId:null
       },
+      pageParams: {
+        pageNum: 1,
+        pageSize: 500
+      },
       detail: {
         title: '',
         open: false
@@ -217,12 +249,17 @@ export default {
       companyId: null,
       // 存储每个企微账号的未读消息数
       qwUserUnreadCount: [],
+      //联系人列表
+      qwContactList:[],
+      //群聊列表
+      qwGrouList:[],
+      //课程列表
+      showCourseManage:false
     };
   },
   created(){
     // 初始化企微账号会话页码对象
     this.qwUserPages = {};
-
     //获取企微列表
     getQwUserList().then(response => {
       this.qwUserList = response.data;
@@ -232,7 +269,6 @@ export default {
         this.companyId = this.qwUser.companyId; // 保存公司ID用于WebSocket连接
         this.setQwUserInfo();
         this.getConversation();
-
         // 初始化单个WebSocket连接
         this.initImSocket();
 
@@ -242,6 +278,7 @@ export default {
         this.qwUser={};
       }
     });
+    console.log(this.contact,'---')
   },
   computed: {
     ...mapState('qwIm', ['shareCourse']),
@@ -260,14 +297,12 @@ export default {
       }
     },
     shareCourse(nv) {
-      if (nv) {
-        // 发送小程序卡片消息
+      if (nv) {        // 发送小程序卡片消息
         const IMUI = this.$refs.IMUI;
         const contact = IMUI.currentContact;
         if (!contact) {
           return;
         }
-
         // 创建消息对象
         let message = {
           type: 'miniprogram',
@@ -276,7 +311,6 @@ export default {
           content: nv.url,
           toContactId: contact.conversationId,
         };
-
         this.detail.open = false
         this.$store.dispatch('qwIm/shareCourse', null)
 
@@ -310,6 +344,9 @@ export default {
     IMUI.initMenus([
       {
         name: "messages",
+      },
+      {
+        name:'contacts'
       }
     ]);
     IMUI.initEmoji(EmojiData);
@@ -371,7 +408,7 @@ export default {
             }
 
             let conversation = IMUI.findConversation(message.toContactId);
-            if (conversation) {
+            if (conversation && Object.keys(conversation).length !==0) {
               conversation.lastSendTime = message.sendTime;
               conversation.lastContent = IMUI.lastContentRender(message);
               IMUI.topPopConversations(conversation);
@@ -462,17 +499,32 @@ export default {
     },
 
     // 切换企微账号
-    qwUserChange(tab, event){
-      this.appKey = tab.name;
-      let index= this.qwUserList.findIndex(item => item.appKey === tab.name);
+    qwUserChange(tab){
+      this.appKey = tab.appKey;
+      let index= this.qwUserList.findIndex(item => item.appKey === tab.appKey);
       this.qwUser=this.qwUserList[index];
-
       // 清除当前账号的未读消息数
       this.clearUnreadCount(this.appKey);
 
       // 不需要重新创建WebSocket连接,只需更新当前账号信息
       this.setQwUserInfo();
       this.getConversation();   //获取会话信息
+      this.initGroup(this.qwUser.id)
+      this.initContact(this.qwUser.id)
+    },
+    //联系人列表
+    initContact(id){
+      getContactList(id).then(response => {
+        this.qwContactList = response.rows;
+        this.$refs.IMUI.initContacts(this.qwContactList);
+        // this.getConversationId(id)
+      });
+    },
+    //群聊列表
+    initGroup(id){
+      getGroupList(id).then(response => {
+        this.qwGrouList = response.rows;
+      });
     },
     setQwUserInfo(){
       this.userData.id=this.qwUser.id;
@@ -501,13 +553,13 @@ export default {
         }
       } else {
         // 如果没有缓存,则从服务器获取会话记录
-        getConversations(this.qwUser.id).then(response => {
-          this.conversationData = response.data;
+        getConversations(this.qwUser.id,this.pageParams).then(response => {
+          this.conversationData = response.data.list;
 
           // 缓存会话记录
-          this.qwUserSessions[this.appKey] = response.data;
+          this.qwUserSessions[this.appKey] = response.data.list;
 
-          IMUI.initConversations(response.data);
+          IMUI.initConversations(response.data.list);
           const fstConversation = this.conversationData[0];
           if(fstConversation) {
             IMUI.changeContact(fstConversation.conversationId);
@@ -549,6 +601,12 @@ export default {
       });
     },
     handleChangeConversation(conversation, instance) {
+      console.log(conversation,'conversation')
+        this.showCourseManage=false
+      if(conversation.isGroup){
+        // this.contact.extId=conversation.extId
+        this.showCourseManage=conversation.isGroup
+      }
       // 保存当前选中的会话ID
       this.qwUserSessions[`${this.appKey}_selected`] = conversation.conversationId;
 
@@ -581,8 +639,21 @@ export default {
         }
       }
     },
-    handleChangeContact(contact, instance) {
-      console.log("ChangeContact:", contact, instance)
+    handleChangeContact(contact) {
+      const IMUI = this.$refs.IMUI;
+      console.log(contact,'contact')
+      if(contact.conversationId==null){
+        const data={
+          id:contact.id,
+          isGroup:contact.isGroup,
+          qwUserId:this.qwUser.id,
+        }
+        getConversationId(data).then(response => {
+          IMUI.appendConversation(response.data)
+          IMUI.changeContact(contact.conversationId);
+          // IMUI.currentContactId=contact.conversationId;
+        });
+      }
     },
 
     //收到消息后添加消息显示
@@ -952,14 +1023,14 @@ export default {
       delete this.qwUserSessions[`${appKey}_selected`];
 
       // 重新获取会话列表
-      getConversations(this.qwUser.id).then(response => {
-        this.conversationData = response.data;
+      getConversations(this.qwUser.id,this.pageParams).then(response => {
+        this.conversationData = response.data.list;
 
         // 缓存会话记录
-        this.qwUserSessions[appKey] = response.data;
+        this.qwUserSessions[appKey] = response.data.list;
 
         if (isCurrent) {
-          this.$refs.IMUI.initConversations(response.data);
+          this.$refs.IMUI.initConversations(response.data.list);
           const fstConversation = this.conversationData[0];
           if(fstConversation) {
             this.$refs.IMUI.changeContact(fstConversation.conversationId);
@@ -975,6 +1046,91 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+  .close-box{
+    width: 100%;
+    height: 49px;
+    padding: 12px;
+    background: #FFFFFF;
+    // border-bottom: 1px solid #EDEFF2;
+    display: flex;
+    align-items: center;
+    justify-content: right;
+  }
+  .qiwei{
+    width: 90px;
+    padding: 10px;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items:center;
+     position: relative;
+     background: #409EFF;
+    // overflow: hidden;
+    position:relative;
+    .q-item{
+      width: 72px;
+      padding: 12px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+ transition: border-radius 0.2s ease, background 0.2s ease; /* 只过渡需要变化的属性 */
+border-radius: 0 6px 6px 0;
+
+      .el-avatar{
+        width: 36px;
+        height: 36px;
+        border-radius: 4px 4px 4px 4px;
+      }
+      .name{
+        margin-top: 10px;
+        font-weight: 500;
+        font-size: 14px;
+        width: 72px;
+            padding: 0 2px;
+         white-space: nowrap; /* 强制文本不换行 */
+          overflow: hidden;    /* 超出部分隐藏 */
+          text-overflow: ellipsis; /* 超出部分显示省略号 */
+        color: #fff;
+        text-align: center;
+      }
+      &.active{
+        background: #F1F4F8;
+        border-radius: 6px;
+        //border-radius: 6px 0px 0px 6px;
+        position: relative; /* 确保z-index生效 */
+          z-index: 1; /* 高于相邻元素的默认层级 */
+        .name{
+          color: #fff;
+          color: #202124;
+        }
+        // &:before{
+        //   content: '';
+        //   position: absolute;
+        //   z-index: 0;
+        //   right: 0;
+        //   width: 10px;
+        //   height: auto;
+        //   background: #F1F4F8;
+        // }
+      }
+    }
+    /* 下一个元素:仅保留右上圆角,清除默认的右下圆角 */
+    // .q-item.active + .q-item {
+    //   border-top-right-radius: 6px;
+    //   border-bottom-right-radius: 0; /* 避免与选中元素底部衔接处的多余圆角 */
+    // }
+    // /* 上一个元素:仅保留右下圆角,清除默认的右上圆角 */
+    // .q-item + .q-item.active {
+    //   border-bottom-right-radius: 6px;
+    //   border-top-right-radius: 0; /* 避免与选中元素顶部衔接处的多余圆角 */
+    // }
+  }
+  .user-name{
+     font-weight: 500;
+     font-size: 18px;
+     color: #202124;
+  }
 .imui-center{
   align-items: center;
   justify-content: center;
@@ -1025,14 +1181,103 @@ export default {
     right: unset
   }
 }
-
 </style>
 
 
 <style lang="stylus">
+  .q-item{
+    .el-badge{
+      display: flex;
+          flex-direction: column;
+          align-items: center;
+    }
+    .el-badge__content {
+      background-color:#FC4B0E !important;
+      border: none !important;;
+    }
+  }
+
 .lemon-container__title{
-  padding-bottom:10px;
+  padding:12px !important;
+  background: #fff !important;
+}
+.lemon-menu{
+  width: 150px !important;
+  flex-direction: row !important;
+   background: #F1F4F8 !important;
+  .lemon-avatar{
+    width: 36px !important;
+    height: 36px !important;
+    line-height:36px !important;
+    margin-bottom: 0 !important
+  }
+
+}
+.lemon-menu-right{
+  width:60px;
+  height: 100%;
+   display: flex;
+   flex-direction: column;
+   align-items: center;
+   padding: 12px;
+   .lemon-menu__item{
+     padding:0 !important;
+     margin-top: 12px;
+     color:#666 !important;
+   }
+   .lemon-menu__item--active{
+     color:#409EFF !important;
+   }
+}
+.lemon-container{
+  border-left: 1px solid #EDEFF2;
+}
+.lemon-editor__tool{
+  box-shadow: none !important;
+   border-top: 1px solid #EDEFF2;
 }
+.lemon-editor__tool-item{
+  padding: 0px 10px 0 0 !important;
+}
+.lemon-container div{
+  scrollbar-width: none; /* Firefox */
+  -ms-overflow-style: none; /* IE 和 Edge */
+}
+/* Chrome、Safari 和 Opera */
+.lemon-container div::-webkit-scrollbar {
+  display: none;
+}
+.lemon-menu{
+  padding: 0!important;
+   // background-color:#409EFF !important;
+}
+.lemon-message-event__content{
+  background: #F1F4F8 !important;
+  color:#84868C !important;
+}
+.lemon-sidebar{
+  background: #fff !important;
+}
+.lemon-contact{
+  background: #fff !important;
+}
+.lemon-sidebar__scroll{
+
+}
+.lemon-contact--active{
+  background: #E9F1FE !important;
+}
+.lemon-editor{
+  background: #fff !important;
+}
+.lemon-button--color-default{
+  color: #fff !important;
+      background: #409EFF !important;
+}
+.top-input .el-input__inner{
+    border: none !important;
+    background-color: #F1F4F8 !important;
+  }
 .slot-group{
   width:200px;
 }

+ 173 - 56
src/views/qw/qwChat/userDetail/courseManage.vue

@@ -3,35 +3,64 @@
     <!-- 顶部标题栏 -->
     <div>
       <div style="position: relative; display: flex; align-items: center; justify-content: center; height: 48px; background: #fff;">
-        <span style="position: absolute; left: 16px; cursor: pointer;" @click="handleBack">
+        <span v-if="userId" style="position: absolute; left: 16px; cursor: pointer;" @click="handleBack">
           <i class="el-icon-arrow-left" style="font-size: 20px; color: #333;"></i>
         </span>
-        <span style="font-weight: bold; font-size: 16px;">课程管理</span>
+        <span style="font-weight: bold; font-size: 18px;color: #202124;">课程管理</span>
       </div>
+       <div class="tab-box" v-if="courseList.length>0">
+         <div :class="activeIndex==item.courseId?'tabs active':'tabs'" v-for="(item,index) in courseList.slice(0,5)" :key="index"
+           @click="tabsClick(item.courseId)">
+           {{item.courseName}}
+         </div>
+         <div class="filter-box">
+           <el-popover
+             placement="bottom"
+             trigger="click"
+             v-model="visible">
+             <div class="tab-list">
+               <div class="tab-text" v-for="(item,index) in courseList" :key="index" @click="tabsClick(item.courseId)">
+                 {{item.courseName}}
+               </div>
+               <div
+                 v-if="courseQueryParams.hasNextPage"
+                 class="load-more"
+                 v-loading="courseQueryParams.loading"
+               >
+                 <span v-if="!courseQueryParams.loading">加载更多</span>
+               </div>
+             </div>
+             <div slot="reference" class="filter">筛选<img src="@/assets/image/filter_icon.png" width="16"/></div>
+           </el-popover>
+         </div>
+         <div>
 
-      <el-select
-        v-model="courseId"
-        placeholder="请点击选择课程"
-        class="courseSelect"
-        popper-class="courseSelectList"
-        @visible-change="handleSelectOpen"
-        @change="courseChange"
-        ref="courseSelect"
-      >
-        <el-option
-          v-for="item in courseList"
-          :key="item.courseId"
-          :label="item.courseName"
-          :value="item.courseId">
-        </el-option>
-        <div
-          v-if="courseQueryParams.hasNextPage"
-          class="load-more"
-          v-loading="courseQueryParams.loading"
-        >
-          <span v-if="!courseQueryParams.loading">加载更多</span>
-        </div>
-      </el-select>
+         </div>
+       </div>
+
+          <!-- <el-select
+              v-model="courseId"
+              placeholder='筛选'
+              class="courseSelect"
+              popper-class="courseSelectList"
+              @visible-change="handleSelectOpen"
+              @change="courseChange"
+              ref="courseSelect"
+            >
+              <el-option
+                v-for="item in courseList"
+                :key="item.courseId"
+                :label="item.courseName"
+                :value="item.courseId">
+              </el-option>
+              <div
+                v-if="courseQueryParams.hasNextPage"
+                class="load-more"
+                v-loading="courseQueryParams.loading"
+              >
+                <span v-if="!courseQueryParams.loading">加载更多</span>
+              </div>
+            </el-select> -->
 
       <el-input
         placeholder="请输入课程名称"
@@ -53,24 +82,27 @@
           <div class="video-info">
             <div class="video-title">{{item.title}}</div>
             <div class="video-meta">
-              <i class="el-icon-s-order" style="font-size: 18px;margin-right: 5px"></i>
+              <!-- <i class="el-icon-s-order" style="font-size: 18px;margin-right: 5px"></i> -->
               <span class="video-date">{{formatDate(item.createTime)}}</span>
             </div>
+            <div class="video-actions">
+              <el-button
+                type="primary"
+               icon="el-icon-top-right"
+                plain
+                size="mini"
+                :loading="sharingVideoId === item.videoId"
+                :disabled="sharingVideoId === item.videoId"
+                @click="handleShare(item)">分享课程</el-button>
+            </div>
           </div>
-          <div class="video-actions">
-            <el-button
-              type="primary"
-              size="mini"
-              round
-              :loading="sharingVideoId === item.videoId"
-              :disabled="sharingVideoId === item.videoId"
-              @click="handleShare(item)">分享课程</el-button>
-          </div>
+
         </li>
       </ul>
       <div v-if="videoList.length === 0 && !videoQueryParams.loading" class="empty-tip">
-        <i class="el-icon-video-camera"></i>
-        <p>暂无视频</p>
+        <!-- <i class="el-icon-video-camera"></i> -->
+        <img src="@/assets/image/no_page.png" width="280"/>
+        <p>暂无课程</p>
       </div>
       <div v-if="videoQueryParams.loading" class="loading-more">
         <i class="el-icon-loading"></i>
@@ -96,13 +128,18 @@ export default {
     extend: {
       type: Boolean,
       default: false
+    },
+    conversationId:{
+     type: String,
+     default: null
     }
   },
   data() {
     return {
       courseId: null,
       courseQueryParams: {
-        extId: this.userId,
+        //qwUserId: this.userId,
+        sessionId:this.conversationId,
         page: 1,
         limit: 10,
         hasNextPage: false,
@@ -118,23 +155,33 @@ export default {
         hasNextPage: false,
         loading: false,
       },
+      visible:false,
       videoList: [],
       defaultImage: require('@/assets/image/default-image.png'),
-      sharingVideoId: null
+      sharingVideoId: null,
+      activeIndex:0,
     }
   },
   created() {
+    // console.log(this.conversationId,'conversationId')
     this.getFsCourseListBySidebar()
   },
   methods: {
     handleBack() {
       this.$emit('back')
     },
+    tabsClick(id) {
+      this.visible=false
+      this.activeIndex = id
+      this.courseChange(id)
+    },
     getFsCourseListBySidebar() {
       this.courseQueryParams.loading = true;
       getFsCourseListBySidebar(this.courseQueryParams).then(response => {
         if (this.courseQueryParams.page === 1) {
           this.courseList = response.data.list;
+          this.activeIndex = response.data.list[0].courseId
+          this.courseChange(this.activeIndex)
         } else {
           this.courseList = [...this.courseList, ...response.data.list];
         }
@@ -236,7 +283,8 @@ export default {
     },
     handleShare(item) {
       const params = {
-        extId: this.userId,
+        // extId: this.userId,
+        sessionId:this.conversationId,
         courseId: item.courseId,
         videoId: item.videoId,
         title: item.title,
@@ -263,7 +311,70 @@ export default {
 
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+  .tab-box {
+      background: #FFFFFF;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+       white-space: nowrap !important;
+      border-top: 1px solid #F1F4F8;
+      .tabs {
+        // flex:1;
+        padding: 0 10px;
+        font-weight: 400;
+        font-size: 14px;
+        color: #5C5F66;
+        text-align: center;
+  height: 48px;
+  line-height: 48px;
+   position: relative;
+   cursor: pointer;
+        &.active {
+          font-weight: 600;
+          color: #202124;
+          &:after {
+            content: '';
+            position: absolute;
+            z-index: 9999;
+            bottom: 0;
+            width: 30px;
+            height: 4px;
+            background: #409EFF;
+            transition: all 0.25s ease;
+            left: 50%;
+                transform: translateX(-50%);
+          }
+        }
+      }
+      .filter-box{
+        padding: 0 10px;
+         cursor: pointer;
+        .filter{
+          font-weight: 400;
+          font-size: 14px;
+          color: #5C5F66;
+          display: flex;
+          align-items: center;
+        }
+
+      }
+
+    }
+    .tab-list{
+      // line-height: 1.6;
+      // text-align: center;
+      // cursor: pointer;
+      .tab-text{
+        line-height: 1.6;
+        text-align: center;
+        cursor: pointer;
+        &:hover{
+          color:#409EFF
+        }
+      }
+
+    }
 .courseSelect {
   width: 100%;
 }
@@ -288,23 +399,25 @@ export default {
 }
 .videoSearch {
   padding: 10px;
-  border-radius: 8px;
+  border-radius: 4px;
+  background: #F1F4F8;
 }
 ::v-deep .videoSearch input {
-  border-radius: 22px;
+  border-radius: 4px;
+  border: none;
 }
 
 .infinite-list-wrapper {
   height: calc(100vh - 200px);
   overflow-y: auto;
   padding: 0 10px;
-  background-color: #f5f7fa;
+  background-color: #F1F4F8;
 }
 .infinite-list-wrapper-extend {
   height: calc(100vh - 330px);
   overflow-y: auto;
   padding: 0 10px;
-  background-color: #f5f7fa;
+  background-color: #F1F4F8;
 }
 
 
@@ -316,10 +429,10 @@ export default {
 
 .video-item {
   display: flex;
-  padding: 15px;
-  margin-bottom: 5px;
+  padding: 12px;
+  margin-bottom: 10px;
   background-color: #fff;
-  border-radius: 8px;
+  border-radius: 4px;
   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
   transition: transform 0.2s;
 }
@@ -332,8 +445,8 @@ export default {
 .video-thumbnail {
   position: relative;
   flex: 0 0 120px;
-  height: 80px;
-  margin-right: 15px;
+  height: 85px;
+  margin-right: 10px;
   border-radius: 4px;
   overflow: hidden;
 }
@@ -375,10 +488,10 @@ export default {
 
 .video-meta {
   display: flex;
-  align-items: flex-end;
+  align-items: center;
   font-size: 12px;
-  color: #909399;
-  margin-bottom: 5px;
+color: #84868C;
+  margin-bottom: 11px;
 }
 
 .video-date {
@@ -387,10 +500,14 @@ export default {
 
 .video-actions {
   display: flex;
-  flex-direction: column;
-  justify-content: center;
+  align-items: center;
+ /* flex-direction: column;
+  justify-content: center; */
   gap: 8px;
-  margin-left: 10px;
+  /* margin-left: 10px; */
+  .el-button--primary.is-plain{
+    font-size: 14px;
+  }
 }
 
 .loading-more {

+ 579 - 281
src/views/qw/qwChat/userDetail/index.vue

@@ -1,50 +1,137 @@
 <template>
-  <div :style="{backgroundColor: '#f0f2f5', paddingBottom: !extend && '20px', minHeight: '100%'}">
+  <div
+    :style="{background: 'linear-gradient(#E9F1FE, #F1F4F8)', minHeight: '100%'}">
     <!-- 客户详情 -->
-    <div v-if="showDetail">
-      <div v-if="!extend" style="padding: 20px; background-color: #fff;">
-        客户详情
+    <div v-if="showDetail" style="padding: 12px;">
+      <div class="top-box">
+        <div class="left">
+          <img class="avatar-box" :src="qwUserInfo.avatar"/>
+          <div class="left-box">
+            <div class="title">{{qwUserDetail.name || '未知'}}</div>
+            <div class="age-box">
+              <img v-if="qwUserDetail.sex=='女'" src="@/assets/image/gender_female.png" width="12" height="12" />
+              <img v-else src="@/assets/image/gender_male.png" width="12" height="12" />
+              <div class="age-text">{{(qwUserDetail.age||'-')+'岁' || '未知'}}</div>
+            </div>
+          </div>
+        </div>
+        <div class="right">
+          <img src="@/assets/image/add_modify_icon.png" width="12" height="12" />
+          <div class="text" @click="updateQwDetail">修改用户信息</div>
+        </div>
+
+      </div>
+      <div style="background-color: #fff;padding: 12px;border-radius: 6px 6px 6px 6px;">
+        <div class="title-top">
+          <div class="title">客户信息</div>
+            <div class="down-text" @click="handleDown">{{isExpanded?'收起':'展开'}}
+            <img src="@/assets/image/sq_arrow_down.png" width="12" height="12" :class="{ 'rotate-90': !isExpanded }"/></div>
+        </div>
+        <!--  基本信息 -->
+        <el-descriptions :column="2" :class="extend ? 'detail-description-extend' : 'detail-description'" :style="{display:isExpanded?'':'none'}" size="medium">
+         <!-- <template slot="extra">
+
+          </template> -->
+          <!-- <el-descriptions-item label="姓名">{{qwUserInfo.name || '未知'}}</el-descriptions-item>
+          <el-descriptions-item label="性别">{{qwUserDetail.sex || '未知'}}</el-descriptions-item>
+          <el-descriptions-item label="年龄">{{qwUserDetail.age || '未知'}}</el-descriptions-item> -->
+          <!-- <el-descriptions-item label="行为习惯">{{qwUserDetail.habits || '无'}}</el-descriptions-item> -->
+          <el-descriptions-item label="患病时间">{{qwUserDetail.illnessTime || '未知'}}</el-descriptions-item>
+          <el-descriptions-item label="疾病">{{qwUserDetail.disease || '无'}}</el-descriptions-item>
+          <el-descriptions-item label="家人的疾病">{{qwUserDetail.familyDisease || '无'}}</el-descriptions-item>
+          <el-descriptions-item label="是否线下就诊">{{qwUserDetail.isLine || '无'}}</el-descriptions-item>
+          <el-descriptions-item label="体质">{{qwUserDetail.constitution || '-'}}</el-descriptions-item>
+          <el-descriptions-item label="使用药品">{{qwUserDetail.medicine || '-'}}</el-descriptions-item>
+          <el-descriptions-item label="咨询产品">{{qwUserDetail.consultProduct || '-'}}</el-descriptions-item>
+          <el-descriptions-item label="是否已购产品">{{qwUserDetail.isBuy || '无'}}</el-descriptions-item>
+        </el-descriptions>
+      </div>
+
+      <div :style="{backgroundColor: '#FFF',margin: !extend ? '12px 0' : '12px 0'}">
+        <div class="tab-box">
+          <div :class="activeName==index?'tabs active':'tabs'" v-for="(item,index) in tabs" :key="index"
+            @click="tabsClick(index)">
+            {{item.label}}
+          </div>
+        </div>
+        <div v-if="activeName==0" class="tab-content">
+           <!-- 近七天看课记录 -->
+          <div style="display: flex;justify-content: space-between;align-items: center;flex-direction: row;background: #FFF;padding: 10px">
+            <div class="section-title">
+              近七天看课记录
+            </div>
+            <el-button type="primary" @click="courseManage">课程管理</el-button>
+          </div>
+          <!-- 状态 -->
+          <div class="status-container">
+            <div v-for="(item, index) in logTypeList" :key="index" class="status-item">
+              <div class="status-color-block" :style="{ backgroundColor: item.color }"></div>
+              <span class="status-text">{{ item.text }}</span>
+            </div>
+          </div>
+          <div class="course-record-container">
+            <div v-for="item in courseWatch7day" :key="item.id" class="course-record-item">
+
+              <div class="course-status-block" :style="{ backgroundColor: getLogTypeColor(item.logType) }"
+                :title="getLogTypeText(item.logType)">
+                <img v-if="item.logType!==2" src="@/assets/image/view_class_records.png" width="20" height="20"/>
+                <img v-if="item.logType==2" src="@/assets/image/end_of_class.png" width="20" height="20"/>
+                </div>
+                <span class="course-date">{{ item.date.split('-')[2] }}日</span>
+            </div>
+            <div v-if="courseWatch7day.length === 0" class="empty-record">
+              暂无看课记录
+            </div>
+          </div>
+ <!-- 已看过课程 -->
+      <div class="record-box" v-if="localExtId">
+        <div class="top">
+          <div class="title">
+            用户看课记录
+          </div>
+          <div class="more" @click="courseManage">
+            全部看课记录
+            <i class="el-icon-arrow-right" style="font-size: 14px; color: #979B9E;"></i>
+          </div>
+        </div>
+        <userCourseWatchLog ref="userWatchLog" />
+      </div>
+
+        </div>
+        <div v-if="activeName==1" class="tab-content">
+          <div v-if="localExtId">
+            <!-- <div style="padding: 10px;font-weight: bold">
+              用户订单
+            </div> -->
+            <userStorerDetails ref="userDetails"/>
+          </div>
+
+        </div>
+        <!-- 访问记录 -->
+        <div v-if="activeName==2" class="tab-content">
+          <div v-if="localExtId">
+            <userBehavior ref="userBehaviorLog"/>
+          </div>
+
+        </div>
+
       </div>
-      <!--  基本信息 -->
-      <el-descriptions :class="extend ? 'detail-description-extend' : 'detail-description'" title="基本信息" :column="extend ? 2 : 3" size="medium" border>
-        <template slot="extra">
-          <el-button size="mini" round @click="updateQwDetail">修改用户信息</el-button>
-        </template>
-        <el-descriptions-item label="姓名">{{qwUserInfo.name || '未知'}}</el-descriptions-item>
-        <el-descriptions-item label="性别">{{qwUserDetail.sex || '未知'}}</el-descriptions-item>
-        <el-descriptions-item label="年龄">{{qwUserDetail.age || '未知'}}</el-descriptions-item>
-        <el-descriptions-item label="行为习惯">{{qwUserDetail.habits || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="患病时间">{{qwUserDetail.illnessTime || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="疾病">{{qwUserDetail.disease || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="家人的疾病">{{qwUserDetail.familyDisease || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="是否线下就诊">{{qwUserDetail.isLine || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="体质">{{qwUserDetail.constitution || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="使用药品">{{qwUserDetail.medicine || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="咨询产品">{{qwUserDetail.consultProduct || '无'}}</el-descriptions-item>
-        <el-descriptions-item label="是否已购产品">{{qwUserDetail.isBuy || '无'}}</el-descriptions-item>
-      </el-descriptions>
+
 
       <!-- 看课记录 -->
-      <div :style="{backgroundColor: '#FFF',margin: !extend ? '10px' : '5px 0'}">
-        <div style="padding: 10px;font-weight: bold">
+     <!-- <div :style="{backgroundColor: '#FFF',margin: !extend ? '10px' : '5px 0'}"> -->
+       <!-- <div style="padding: 10px;font-weight: bold">
           看课记录
-        </div>
+        </div> -->
         <!-- 状态 -->
-        <div class="status-container">
-          <div
-            v-for="(item, index) in logTypeList"
-            :key="index"
-            class="status-item"
-          >
-            <div
-              class="status-color-block"
-              :style="{ backgroundColor: item.color }"
-            ></div>
+       <!-- <div class="status-container">
+          <div v-for="(item, index) in logTypeList" :key="index" class="status-item">
+            <div class="status-color-block" :style="{ backgroundColor: item.color }"></div>
             <span class="status-text">{{ item.text }}</span>
           </div>
-        </div>
+        </div> -->
         <!-- 近七天看课记录 -->
-        <div>
+        <!-- <div>
           <div style="display: flex;justify-content: space-between;align-items: center;flex-direction: row;background: #FFF;padding: 10px 10px 20px">
             <div class="section-title">
               近七天看课记录
@@ -52,284 +139,495 @@
             <el-button size="mini" round @click="courseManage">课程管理</el-button>
           </div>
           <div class="course-record-container">
-            <div
-              v-for="item in courseWatch7day"
-              :key="item.id"
-              class="course-record-item"
-            >
+            <div v-for="item in courseWatch7day" :key="item.id" class="course-record-item">
               <span class="course-date">{{ item.date.split('-')[2] }}日</span>
-              <div
-                class="course-status-block"
-                :style="{ backgroundColor: getLogTypeColor(item.logType) }"
-                :title="getLogTypeText(item.logType)"
-              ></div>
+              <div class="course-status-block" :style="{ backgroundColor: getLogTypeColor(item.logType) }"
+                :title="getLogTypeText(item.logType)"></div>
             </div>
             <div v-if="courseWatch7day.length === 0" class="empty-record">
               暂无看课记录
             </div>
           </div>
-        </div>
-      </div>
+        </div> -->
+      <!-- </div> -->
 
       <!-- 购买记录 -->
-      <div :style="{backgroundColor: '#FFF',margin: !extend ? '10px' : '5px 0'}" v-if="qwUserInfo.fsUserId">
+    <!--  <div :style="{backgroundColor: '#FFF',margin: !extend ? '10px' : '5px 0'}" v-if="qwUserInfo.fsUserId">
         <div style="padding: 10px;font-weight: bold">
           用户订单
         </div>
-        <userStorerDetails  ref="userDetails" />
-      </div>
+        <userStorerDetails ref="userDetails" />
+      </div> -->
 
       <!-- 已看过课程 -->
-      <div :style="{backgroundColor: '#FFF',margin: extend ? '5px 0' : '10px'}" v-if="qwUserInfo.fsUserId">
+      <!-- <div :style="{backgroundColor: '#FFF',margin: extend ? '5px 0' : '10px'}" v-if="qwUserInfo.fsUserId">
         <div style="padding: 10px;font-weight: bold">
           用户看课记录
         </div>
-        <userCourseWatchLog  ref="userWatchLog" />
-      </div>
+        <userCourseWatchLog ref="userWatchLog" />
+      </div> -->
 
     </div>
 
     <!-- 修改用户信息 -->
-    <user-info-edit
-      v-if="showUpdate"
-      :userDetail="qwUserDetail"
-      @back="handleBack"
-      @save-success="handleSaveSuccess"
-    />
+    <user-info-edit v-if="showUpdate" :userDetail="qwUserDetail" @back="handleBack" @save-success="handleSaveSuccess" />
 
     <!-- 课程管理 -->
-    <course-manage
-      v-if="showCourseManage"
-      :extend="extend"
-      :userId="qwUserInfo.id"
-      @back="handleBack"
-    />
+    <course-manage v-if="showCourseManage" :conversationId="conversationId"  :extend="extend" :userId="qwUserInfo.id" @back="handleBack" />
   </div>
 </template>
 
 <script>
-import userStorerDetails from "@/views/qw/qwChat/userDetail/userStorerDetails.vue"
-import userCourseWatchLog from "@/views/qw/qwChat/userDetail/userCourseWatchLog.vue";
-import userInfoEdit from "@/views/qw/qwChat/userDetail/userInfoEdit.vue";
-import courseManage from "@/views/qw/qwChat/userDetail/courseManage.vue";
-import {getQwExternalContactDetails, getQwUserInfo, queryCourseWatchStatistics} from "@/api/qw/im";
-
-export default {
-  name: "userDetail",
-  props: {
-    extId: {
-      type: String,
-      default: null
-    },
-    extend: {
-      type: Boolean,
-      default: false
-    }
-  },
-  components: {
-    userStorerDetails,
-    userCourseWatchLog,
-    userInfoEdit,
-    courseManage,
-  },
-  data() {
-    // 定义颜色常量
-    const logTypeColors = {
-      notWatched: '#909399', // 未看课
-      watching: '#0bc6ff',   // 看课中
-      completed: '#67c23a',  // 完课
-      pending: '#f55a4f',    // 待看课
-      interrupted: '#ffd700' // 看课中断
-    };
-
-    return {
-      localExtId: this.extId,
-      qwUserInfo: {},
-      qwUserDetail: {},
-      showDetail: true,
-      showUpdate: false,
-      courseWatch7day: [],
-      logTypeColors,
-      // 定义状态列表用于渲染
-      logTypeList: [
-        { type: 0, text: '未看课', color: logTypeColors.notWatched },
-        { type: 1, text: '看课中', color: logTypeColors.watching },
-        { type: 2, text: '完课', color: logTypeColors.completed },
-        { type: 3, text: '待看课', color: logTypeColors.pending },
-        { type: 4, text: '看课中断', color: logTypeColors.interrupted }
-      ],
-      showCourseManage: false,
-    }
-  },
-  created() {
-    if (this.extId) {
-      this.getDetail(this.extId)
-    }
-  },
-  watch: {
-    extId(newVal) {
-      if (newVal) {
-        this.getDetail(newVal);
+  import userStorerDetails from "@/views/qw/qwChat/userDetail/userStorerDetails.vue"
+  import userCourseWatchLog from "@/views/qw/qwChat/userDetail/userCourseWatchLog.vue";
+  import userInfoEdit from "@/views/qw/qwChat/userDetail/userInfoEdit.vue";
+  import courseManage from "@/views/qw/qwChat/userDetail/courseManage.vue";
+  import userBehavior from '@/views/qw/qwChat/userDetail/userBehavior.vue';
+  import {
+    getQwExternalContactDetails,
+    getQwUserInfo,
+    queryCourseWatchStatistics
+  } from "@/api/qw/im";
+
+  export default {
+    name: "userDetail",
+    props: {
+      extId: {
+        type: String,
+        default: null
+      },
+      conversationId:{
+        type: String,
+        default: null
+      },
+      extend: {
+        type: Boolean,
+        default: false
       }
-    }
-  },
-  methods: {
-    getDetail(extId) {
-      this.localExtId = extId
-      this.getQwExternalContactDetails()
-      this.getQwUserInfo()
-      this.queryCourseWatchStatistics()
     },
-    queryCourseWatchStatistics() {
-      const data = {
-        type: 0,
-        qwExternalContactId: this.localExtId
-      }
-      queryCourseWatchStatistics(data).then(response => {
-        this.courseWatch7day = response.data.data
-      })
+    components: {
+      userStorerDetails,
+      userCourseWatchLog,
+      userInfoEdit,
+      courseManage,
+      userBehavior
     },
-    getQwExternalContactDetails() {
-      const query = {
-        qwExternalContactId: this.localExtId
+    data() {
+      // 定义颜色常量
+      const logTypeColors = {
+        notWatched: '#D9E4FF', // 未看课
+        watching: '#0bc6ff', // 看课中
+        completed: '#67c23a', // 完课
+        pending: '#f55a4f', // 待看课
+        interrupted: '#ffd700' // 看课中断
+      };
+
+      return {
+        localExtId: this.extId,
+        qwUserInfo: {},
+        qwUserDetail: {},
+        showDetail: true,
+        showUpdate: false,
+        courseWatch7day: [],
+        logTypeColors,
+        activeName: 0,
+        isExpanded: true,
+        tabs: [{
+            label: '看课记录'
+          },
+          {
+            label: '用户订单'
+          },
+          {
+            label: '访问记录'
+          },
+        ],
+        // 定义状态列表用于渲染
+        logTypeList: [{
+            type: 0,
+            text: '未看课',
+            color: logTypeColors.notWatched
+          },
+          {
+            type: 1,
+            text: '看课中',
+            color: logTypeColors.watching
+          },
+          {
+            type: 2,
+            text: '完课',
+            color: logTypeColors.completed
+          },
+          {
+            type: 3,
+            text: '待看课',
+            color: logTypeColors.pending
+          },
+          {
+            type: 4,
+            text: '看课中断',
+            color: logTypeColors.interrupted
+          }
+        ],
+        showCourseManage: false,
       }
-      getQwExternalContactDetails(query).then(response => {
-        this.qwUserInfo = response.data
-        if (this.qwUserInfo.fsUserId) {
-          this.$nextTick(() => {
-            this.$refs.userDetails.getUserDetails(this.qwUserInfo.fsUserId)
-            this.$refs.userWatchLog.getUserWatchLog(this.qwUserInfo.fsUserId)
-          })
-        }
-      })
     },
-    getQwUserInfo() {
-      const query = {
-        qwExternalContactId: this.localExtId
+    created() {
+      if (this.extId) {
+        this.getDetail(this.extId)
       }
-      getQwUserInfo(query).then(response => {
-        this.qwUserDetail = response.moreInfo
-      })
-    },
-    updateQwDetail() {
-      this.showDetail = false
-      this.showUpdate = true
-    },
-    handleBack() {
-      this.getDetail(this.localExtId)
-      this.showDetail = true
-      this.showUpdate = false
-      this.showCourseManage = false
     },
-    handleSaveSuccess() {
-      this.handleBack()
-    },
-    getLogTypeColor(logType) {
-      const type = parseInt(logType);
-      // 使用对象映射替代switch语句
-      const colorMap = {
-        0: this.logTypeColors.notWatched,
-        1: this.logTypeColors.watching,
-        2: this.logTypeColors.completed,
-        3: this.logTypeColors.pending,
-        4: this.logTypeColors.interrupted
-      };
-      return colorMap[type] || '';
-    },
-    getLogTypeText(logType) {
-      const type = parseInt(logType);
-      // 使用对象映射替代switch语句
-      const textMap = {
-        0: '未看课',
-        1: '看课中',
-        2: '完课',
-        3: '待看课',
-        4: '看课中断'
-      };
-      return textMap[type] || '';
-    },
-    courseManage() {
-      this.showDetail = false
-      this.showCourseManage = true
+    watch: {
+      extId(newVal) {
+        if (newVal) {
+          this.getDetail(newVal);
+        }
+      }
     },
+    methods: {
+      getDetail(extId) {
+        this.localExtId = extId
+        this.getQwExternalContactDetails()
+        this.getQwUserInfo()
+        this.queryCourseWatchStatistics()
+      },
+      queryCourseWatchStatistics() {
+        const data = {
+          type: 0,
+          qwExternalContactId: this.localExtId
+        }
+        queryCourseWatchStatistics(data).then(response => {
+          this.courseWatch7day = response.data.data
+        })
+      },
+      getQwExternalContactDetails() {
+        const query = {
+          qwExternalContactId: this.localExtId
+        }
+        getQwExternalContactDetails(query).then(response => {
+          this.qwUserInfo = response.data
+          if (this.localExtId) {
+             this.$nextTick(() => {
+                    // 调用 userWatchLog 前先判断
+                    if (this.$refs.userWatchLog && this.activeName==0) {
+                      this.$refs.userWatchLog.getUserWatchLog(this.localExtId)
+                    }
+                    // 调用 userDetails 前先判断
+                    if (this.$refs.userDetails && this.activeName==1) {
+                      this.$refs.userDetails.getUserDetails(this.localExtId)
+                    }
+                    if (this.$refs.userBehaviorLog && this.activeName==2) {
+                       this.$refs.userBehaviorLog.getDetails(this.localExtId);
+                    }
+                  })
+          }
+        })
+      },
+      getQwUserInfo() {
+        const query = {
+          qwExternalContactId: this.localExtId
+        }
+        getQwUserInfo(query).then(response => {
+          this.qwUserDetail = response.moreInfo
+
+        })
+      },
+      updateQwDetail() {
+        this.showDetail = false
+        this.showUpdate = true
+      },
+      handleDown(){
+        this.isExpanded = !this.isExpanded;
+      },
+      handleBack() {
+        this.getDetail(this.localExtId)
+        this.showDetail = true
+        this.showUpdate = false
+        this.showCourseManage = false
+      },
+      handleSaveSuccess() {
+        this.handleBack()
+      },
+      tabsClick(index) {
+        this.activeName = index
+        this.getQwExternalContactDetails()
+      },
+      getLogTypeColor(logType) {
+        const type = parseInt(logType);
+        // 使用对象映射替代switch语句
+        const colorMap = {
+          0: this.logTypeColors.notWatched,
+          1: this.logTypeColors.watching,
+          2: this.logTypeColors.completed,
+          3: this.logTypeColors.pending,
+          4: this.logTypeColors.interrupted
+        };
+        return colorMap[type] || '';
+      },
+      getLogTypeText(logType) {
+        const type = parseInt(logType);
+        // 使用对象映射替代switch语句
+        const textMap = {
+          0: '未看课',
+          1: '看课中',
+          2: '完课',
+          3: '待看课',
+          4: '看课中断'
+        };
+        return textMap[type] || '';
+      },
+      courseManage() {
+        this.showDetail = false
+        this.showCourseManage = true
+      },
+    }
   }
-}
 </script>
-<style scoped>
-.status-container {
-  display: flex;
-  gap: 20px;
-  padding: 10px 0 0 20px;
-  color: #8c939d;
-}
-
-.status-item {
-  display: flex;
-  align-items: center;
-  gap: 5px;
-}
-
-.status-color-block {
-  border-radius: 4px;
-  width: 20px;
-  height: 20px;
-}
-
-.status-text {
-  font-size: 12px;
-}
-
-.section-title {
-  padding: 10px;
-  font-size: 12px;
-  color: #8c939d;
-}
-
-.course-record-container {
-  display: flex;
-  padding: 0 10px 15px 10px;
-  gap: 10px;
-  overflow-x: auto;
-}
-
-.course-record-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  min-width: 40px;
-}
-
-.course-date {
-  font-size: 12px;
-  color: #606266;
-  margin-bottom: 5px;
-}
-
-.course-status-block {
-  width: 20px;
-  height: 20px;
-  border-radius: 4px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.empty-record {
-  width: 100%;
-  text-align: center;
-  color: #909399;
-  font-size: 14px;
-}
-.detail-description {
-  margin: 10px;
-  padding: 10px;
-  background-color: #FFF;
-}
-.detail-description-extend {
-  padding: 10px;
-  background-color: #FFF;
-}
-::v-deep .detail-description-extend .el-descriptions__title {
-  font-size: 14px;
-}
+<style scoped lang="scss">
+  .rotate-90 {
+    transform: rotate(-90deg);
+    transition: transform 0.3s ease;
+  }
+  .top-box {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-top: 5px;
+    padding-bottom: 15px;
+
+    .left {
+      display: flex;
+      align-items: center;
+       .avatar-box{
+         width: 44px;
+         height: 44px;
+         border-radius: 4px 4px 4px 4px;
+         object-fit:cover;
+       }
+      .left-box {
+        margin-left: 12px;
+        display: flex;
+        align-items: center;
+        flex-direction: column;
+
+        .title {
+          font-weight: 500;
+          font-size: 16px;
+          color: #141F36;
+          margin-bottom: 4px;
+        }
+
+        .age-box {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          width: 57px;
+          height: 20px;
+          background: #FFFFFF;
+          border-radius: 12px 12px 12px 12px;
+
+          .age-text {
+            font-weight: 400;
+            font-size: 11px;
+            color: #757575;
+            line-height: 20px;
+          }
+        }
+      }
+    }
+
+    .right {
+      display: flex;
+      align-items: center;
+
+      .text {
+        margin-left: 4px;
+        font-weight: 400;
+        font-size: 12px;
+        color: #409EFF;
+      }
+    }
+  }
+   .title-top{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .title{
+        font-weight: 500;
+        font-size: 16px;
+        color: #141F36;
+      }
+      .down-text {
+        display: flex;
+        align-items: center;
+        font-weight: 400;
+        font-size: 12px;
+        color: #84868C;
+        cursor: pointer;
+        img {
+          margin-left: 4px;
+        }
+      }
+    }
+  .el-descriptions__extra {
+
+
+  }
+
+  .tab-box {
+    background: #FFFFFF;
+    border-radius: 8px 8px 8px 8px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: 1px solid #F1F4F8;
+
+    .tabs {
+      flex:1;
+      font-weight: 400;
+      font-size: 16px;
+      color: #5C5F66;
+      text-align: center;
+height: 48px;
+line-height: 48px;
+ position: relative;
+ cursor: pointer;
+      &.active {
+        font-weight: 600;
+        color: #202124;
+        &:after {
+          content: '';
+          position: absolute;
+          z-index: 9;
+          bottom: 0;
+          width: 30px;
+          height: 4px;
+          background: #409EFF;
+          transition: all 0.25s ease; /* 快速过渡更显灵敏 */
+          left: 50%; /* 水平居中 */
+              transform: translateX(-50%); /* 配合 left:50% 实现精确居中 */
+        }
+      }
+    }
+  }
+  .record-box{
+    width: 100%;
+    padding: 10px;
+    .top{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .title{
+        font-weight: 500;
+        font-size: 16px;
+        color: #141F36;
+      }
+      .more{
+        font-weight: 400;
+        font-size: 14px;
+        color: #84868C;
+        cursor: pointer;
+      }
+    }
+
+  }
+  .status-container {
+    display: flex;
+    justify-content: space-between;
+    gap: 20px;
+    padding: 10px 15px;
+    color: #8c939d;
+  }
+
+  .status-item {
+    display: flex;
+    align-items: center;
+    gap: 5px;
+  }
+
+  .status-color-block {
+    border-radius: 4px;
+    width: 12px;
+    height: 12px;
+  }
+
+  .status-text {
+    font-size: 12px;
+    color: #84868C;
+  }
+
+  .section-title {
+   line-height: 24px;
+    font-weight: 500;
+    font-size: 16px;
+    color: #141F36;
+  }
+
+  .course-record-container {
+    display: flex;
+    justify-content: space-between;
+    margin:10px;
+    gap: 10px;
+    overflow-x: auto;
+    padding-bottom: 20px;
+    border-bottom: 1px solid #F1F4F8;
+  }
+
+  .course-record-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    min-width: 40px;
+  }
+
+  .course-date {
+    font-weight: 500;
+    font-size: 14px;
+    color: #141F36;
+    margin-top: 5px;
+  }
+
+  .course-status-block {
+    width: 32px;
+    height: 32px;
+    background: #D9E4FF;
+    border-radius: 6px 6px 6px 6px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .empty-record {
+    width: 100%;
+    text-align: center;
+    color: #909399;
+    font-size: 14px;
+  }
+
+  .detail-description {
+    margin-top: 20px;
+    margin-bottom: 10px;
+    // padding: 10px;
+    background-color: #FFF;
+  }
+
+  .detail-description-extend {
+    // padding: 12px;
+    background-color: #FFF;
+margin-top: 20px;
+  }
+
+  ::v-deep .detail-description-extend .el-descriptions__title {
+    font-size: 16px;
+  }
+
+  ::v-deep .detail-description-extend {
+    .el-descriptions-item__label {
+      color: #84868C;
+    }
+
+    .el-descriptions-item__content {
+      color: #141F36;
+    }
+  }
 </style>

+ 488 - 0
src/views/qw/qwChat/userDetail/userBehavior.vue

@@ -0,0 +1,488 @@
+<template>
+  <div class="behavior-track-container">
+    <!-- 筛选区域 -->
+    <!-- <div class="filter-section">
+      <div class="filter-item">
+        <label>操作类型:</label>
+        <el-select
+          v-model="queryParams.operationType"
+          class="type-select"
+          @change="handleTypeChange"
+          placeholder="请选择行为类型"
+          clearable
+        >
+          <el-option
+            v-for="item in typeList"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictLabel"
+          ></el-option>
+        </el-select>
+      </div>
+    </div> -->
+    <div v-if="steps.length === 0" class="empty-tip">
+      <img src="@/assets/image/no_page.png" width="280"/>
+      <p>暂无记录</p>
+    </div>
+    <!-- 按日期分组展示(分页后的数据) -->
+    <div v-if="steps.length != 0" v-for="(records, date) in groupedPaginatedSteps" :key="date" class="date-group">
+      <!-- 日期标题(如果是今天,显示“今天”,否则显示具体日期) -->
+    <!--  <div class="date-title">
+        {{ isToday(date) ? '今天' : date }}
+      </div>
+      <div class="date-divider"></div> -->
+
+      <!-- 当前日期下的记录 -->
+      <el-steps
+        direction="vertical"
+        :space="120"
+        process-status="process"
+        finish-status="success"
+        class="custom-steps"
+      >
+        <!-- <el-step
+          v-for="(step, index) in records"
+          :key="index"
+          :icon="index === 0 ? 'el-icon-s-help' : 'el-icon-bangzhu'"
+        > -->
+        <el-step
+          v-for="(step, index) in records"
+          :key="index"
+        >
+         <template #icon>
+           <img v-if="step.operationType == '小程序登录'" src="@/assets/image/small_program_login.png" width="24"/>
+           <img v-else src="@/assets/image/learning_courses.png" width="24"/>
+           </template>
+          <template #title>
+            <div class="step-title">
+              {{ step.operationType }}
+              <span class="step-time">{{ step.createTime }}</span> <!-- 每个步骤的时间 -->
+            </div>
+          </template>
+          <template #description>
+            <!-- 答题 -->
+            <div v-if="step.operationType == '答题'" class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-notebook-2"></i> 课节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+            <!-- 发放奖励 -->
+            <div v-else-if="step.operationType == '发送奖励'" class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-gift"></i> 课程名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-coin"></i> 小节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.title }}</span>
+                </div>
+                <div v-if="step.fsCourseRedPacketLog" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-coin"></i> 发放金额:</span>
+                  <span class="detail-value">{{ step.fsCourseRedPacketLog.amount }} 元</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+
+            <!-- 其他类型 -->
+            <div v-else class="step-content">
+              <!-- 第一行 -->
+              <div class="step-row first-row">
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-office-building"></i> 训练营:</span>
+                  <span class="detail-value">{{ step.paramVo.trainingCampName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-collection-tag"></i> 营期名称:</span>
+                  <span class="detail-value">{{ step.paramVo.periodName }}</span>
+                </div>
+                <div v-if="step.paramVo" class="detail-item">
+                  <span class="detail-label"><i class="el-icon-notebook-2"></i> 课节名称:</span>
+                  <span class="detail-value">{{ step.paramVo.courseName }}</span>
+                </div>
+              </div>
+
+              <!-- 第二行 -->
+              <div v-if="step.details" class="step-row second-row">
+                <div class="detail-item">
+                  <span class="detail-label"><i class="el-icon-chat-dot-round"></i> 备注:</span>
+                  <span class="detail-value">{{ step.details }}</span>
+                </div>
+              </div>
+            </div>
+          </template>
+
+        </el-step>
+      </el-steps>
+    </div>
+
+    <!-- 分页 -->
+   <!-- <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    /> -->
+  </div>
+
+
+</template>
+
+<script>
+import { listUserOperationLog} from "@/api/store/userOperationLog";
+import {getQwExternalContactVisitList} from "@/api/qw/im";
+export default {
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      typeList: [],
+      queryParams: {
+        pageNum: 1,
+        pageSize: 5,
+        qwExternalContactId:null,
+        // userId:null,
+      },
+      total: 0,
+      steps: [
+      ]
+    };
+  },
+  created() {
+    this.getDicts("fs_user_operation_type").then(response => {
+      this.typeList = response.data;
+    });
+  },
+  computed: {
+    // 删除这个属性,直接在 getList 中使用后端的操作类型
+    groupedPaginatedSteps() {
+      const groups = {};
+      this.steps.forEach((step) => {
+        const date = step.createTime ?
+          (step.createTime instanceof Date ? step.createTime.toISOString().substr(0, 10) : new Date(step.createTime).toISOString().substr(0, 10))
+          : null;
+        if (!groups[date]) {
+          groups[date] = [];
+        }
+        groups[date].push(step);
+      });
+      return groups;
+    },
+
+  },
+  methods: {
+    getList() {
+      this.loading = true;
+      getQwExternalContactVisitList(this.queryParams).then(response => {
+        this.steps = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 判断是否为今天
+    isToday(date) {
+      const today = new Date();
+      const formattedDate = `${today.getFullYear()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`;
+      return date === formattedDate;
+    },
+    getDetails(id) {
+      this.queryParams.qwExternalContactId=id;
+      this.getList();
+
+    },
+    handleTypeChange() {
+      this.queryParams.pageNum = 1;  // 重置分页为第一页
+      this.getList();  // 重新获取数据
+    }
+  },
+};
+</script>
+
+<style scoped>
+.behavior-track-container {
+  width: 100%;
+  padding: 12px;
+  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
+  background-color: #fff;
+  /* border-radius: 12px; */
+  /* box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); */
+}
+  .empty-tip {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 40px 0;
+    color: #909399;
+  }
+
+  .empty-tip i {
+    font-size: 48px;
+    margin-bottom: 10px;
+    color: #dcdfe6;
+  }
+.filter-section {
+  margin-bottom: 24px;
+  background: #ffffff;
+  border-radius: 8px;
+  padding: 16px 20px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+  display: flex;
+  align-items: center;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+}
+
+.filter-item label {
+  font-weight: 600;
+  color: #333;
+  margin-right: 12px;
+  font-size: 14px;
+}
+
+.type-select {
+  width: 240px;
+}
+
+.custom-steps {
+  /* padding: 0 16px; */
+}
+
+.step-title {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+}
+.el-step__title{
+  padding-bottom: 12px !important;
+}
+.step-detail p {
+  margin: 8px 0;
+  line-height: 1.6;
+  font-size: 14px;
+  color: #555;
+}
+
+/* 步骤条样式优化 - 移除图标边框 */
+.custom-steps >>> .el-step__head {
+  padding-right: 16px;
+}
+
+.custom-steps >>> .el-step__icon {
+  width: 24px;
+  height: 24px;
+  font-size: 18px;
+}
+
+.custom-steps >>> .el-step__icon.is-icon {
+  color: #52c41a;
+}
+
+.custom-steps >>> .el-step__line {
+  top: 36px;
+  left: 11px;
+  background-color: #e8e8e8;
+}
+
+/* 分页样式优化 */
+.pagination-wrapper {
+  margin-top: 32px;
+  text-align: center;
+  padding: 16px 0;
+}
+
+.custom-pagination >>> .el-pager li {
+  border-radius: 4px;
+  margin: 0 4px;
+}
+
+.custom-pagination >>> .el-pager li.active {
+  background-color: #52c41a;
+  color: #fff;
+}
+
+.custom-pagination >>> .el-pagination__jump {
+  margin-left: 12px;
+}
+
+.custom-pagination >>> .el-input__inner {
+  border-radius: 4px;
+}
+
+/* 新增和修改的样式 */
+.step-content {
+ /* margin-top: 16px; */
+  margin-bottom: 16px;
+  padding: 12px;
+  background: #F5F7FA;
+  border-radius: 8px;
+  /* border: 1px solid #ebeef5; */
+ /* box-shadow: 0 1px 6px rgba(0, 0, 0, 0.06); */
+  transition: all 0.3s ease;
+}
+
+.step-content:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+  transform: translateY(-1px);
+}
+
+.detail-label {
+  display: inline-flex;
+  align-items: center;
+  width: 100px;
+  color: #606266;
+  font-weight: 500;
+  flex-shrink: 0;
+}
+
+.detail-label i {
+  margin-right: 6px;
+  font-size: 14px;
+  color: #909399;
+}
+
+.detail-value {
+  flex: 1;
+  color: #303133;
+  word-break: break-word;
+  padding-left: 4px;
+}
+
+.step-row {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.first-row .detail-item {
+  flex: 1 1 30%; /* 平均分布 */
+  display: flex;
+  align-items: center;
+  margin-right: 16px;
+}
+
+.second-row .detail-item {
+  display: flex;
+  align-items: center;
+}
+
+.time-item {
+  margin-left: auto; /* 时间靠右 */
+}
+
+.detail-label {
+  font-weight: 500;
+  color: #606266;
+  margin-right: 6px;
+}
+
+.detail-value {
+  color: #303133;
+  word-break: break-word;
+}
+
+@media (max-width: 768px) {
+  .first-row,
+  .second-row {
+    flex-direction: column;
+  }
+  .time-item {
+    margin-left: 0;
+  }
+}
+/* 完全重置步骤描述区域的样式 */
+.custom-steps >>> .el-step__description {
+  width: 100% !important;
+  max-width: 100% !important;
+  padding: 0 !important;
+  margin: 0 !important;
+}
+
+/* 确保步骤内容使用弹性布局 */
+.step-content {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+}
+
+/* 调整时间项 */
+.time-item {
+  align-self: flex-end; /* 替代 margin-left: auto */
+  margin-top: 8px; /* 如果需要与上方的间距 */
+}
+/* 日期标题样式 */
+.date-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+  margin-top: 16px;
+}
+
+.date-divider {
+  height: 1px;
+  background: #e8e8e8;
+  margin: 4px 0 12px;
+}
+
+/* 在步骤条标题旁边显示时间 */
+.step-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-weight: 500;
+  font-size: 16px;
+  color: #202124;
+}
+
+.step-time {
+  font-weight: 400;
+  font-size: 12px;
+  color: #84868C;
+  margin-left: 10px;
+}
+
+/* 第二行的备注 */
+.second-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.second-row .detail-item {
+  display: flex;
+  align-items: center;
+}
+
+.no-data {
+  text-align: center;
+  color: #999;
+  font-size: 18px;
+  padding: 20px;
+}
+</style>

+ 85 - 12
src/views/qw/qwChat/userDetail/userCourseWatchLog.vue

@@ -1,6 +1,22 @@
 <template>
   <div class="apcontainer">
-    <el-table border v-loading="loading" :data="courseWatchLogList">
+    <div class="list">
+      <div class="item" v-for="(item,index) in courseWatchLogList" :key="index">
+        <div class="left">
+          <img :src="item.thumbnail" />
+          <div class="box">
+            <div class="title">{{item.courseName}}</div>
+            <div class="text">播放时长:{{item.duration}}</div>
+            <div class="text">创建时间:{{item.createTime}}</div>
+            <div class="text">记录编号:{{item.logId}}</div>
+          </div>
+        </div>
+        <div class="right">
+         {{type(item.logType)}}
+        </div>
+      </div>
+    </div>
+  <!--  <el-table border v-loading="loading" :data="courseWatchLogList">
       <el-table-column label="记录编号" align="center" prop="logId" />
       <el-table-column label="用户账号" align="center" prop="userName" />
       <el-table-column label="会员昵称" align="center" prop="fsNickName">
@@ -40,13 +56,14 @@
       :page.sync="queryParams.pageNum"
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
-    />
+    /> -->
 
   </div>
 </template>
 
 <script>
 import { listCourseWatchLog } from "@/api/course/courseWatchLog";
+import { getQwExternalContactWatchLogList } from "@/api/qw/im";
 import {mapState} from "vuex";
 export default {
   name: "CourseWatchLog",
@@ -63,17 +80,31 @@ export default {
       // 查询参数
       queryParams: {
         pageNum: 1,
-        pageSize: 10,
-        companyUserId: null,
-        userId: null,
-        logType: null,
+        pageSize: 3,
+       qwExternalContactId:null,
+        // logType: null,
       },
     };
   },
   computed: {
     ...mapState({
       companyUserId: state => state.user.user.userId,
-    })
+    }),
+
+    type(){
+       return (val) => { // 闭包函数,接收 val
+             if (val) {
+               if(val==3){
+                 return '待看课';
+               }else{
+                 let str=this.logTypeOptions.find(item => Number(item.dictValue) == val)
+                 return str?str.dictLabel:'-';
+               }
+             }else{
+               return '-';
+             }
+           }
+    }
   },
   created() {
     this.getDicts("sys_course_watch_log_type").then(response => {
@@ -83,7 +114,7 @@ export default {
   },
   methods: {
     getUserWatchLog(id) {
-      this.queryParams.userId = id
+      this.queryParams.qwExternalContactId = id
       this.getList()
     },
     /** 查询短链课程看课记录列表 */
@@ -93,7 +124,7 @@ export default {
         this.queryParams.logType = null;
       }
 
-      listCourseWatchLog(this.queryParams).then(response => {
+      getQwExternalContactWatchLogList(this.queryParams).then(response => {
         this.courseWatchLogList = response.rows;
         this.total = response.total;
         this.loading = false;
@@ -103,9 +134,51 @@ export default {
 };
 </script>
 
-<style scoped>
+<style scoped lang="scss">
 .apcontainer {
-  padding: 10px;
-  font-size: 12px;
+  /* padding: 10px; */
+  padding-top: 20px;
+  /* font-size: 12px; */
+  .list{
+    .item{
+      display: flex;
+      align-items:flex-start;
+      justify-content: space-between;
+      margin-bottom: 12px;
+      .left{
+        flex:1;
+        display: flex;
+        align-items: center;
+        img{
+          width: 86px;
+          height: 86px;
+          border-radius: 4px 4px 4px 4px;
+        }
+        .box{
+          height: 86px;
+          margin-left: 10px;
+          display: flex;
+          flex-direction: column;
+          align-items: flex-start;
+          justify-content: space-between;
+          .title{
+            font-weight: 400;
+            font-size: 14px;
+            color: #202124;
+          }
+          .text{
+            font-weight: 400;
+            font-size: 12px;
+            color: #84868C;
+          }
+        }
+      }
+      .right{
+        font-weight: 400;
+        font-size: 12px;
+        color: #FC4B0E;
+      }
+    }
+  }
 }
 </style>

+ 2 - 3
src/views/qw/qwChat/userDetail/userInfoEdit.vue

@@ -191,7 +191,6 @@ export default {
 
 <style scoped>
 .user-form {
-  margin-top: 10px;
   background: #fff;
 }
 .form-row {
@@ -234,8 +233,8 @@ export default {
   background: #1890ff;
   color: #fff;
   border: none;
-  border-radius: 22px;
-  font-size: 12px;
+  border-radius: 4px;
+  font-size: 14px;
   cursor: pointer;
   transition: background 0.2s;
 }

+ 175 - 22
src/views/qw/qwChat/userDetail/userStorerDetails.vue

@@ -1,6 +1,40 @@
 <template>
   <div class="aacontainer">
-    <el-table v-loading="loading" :data="orderList" border>
+    <div v-for="(item, index) in orderList" :key="index" class="product">
+      <div class="product-top">
+        <div class="orderCode">订单编号:{{item.orderCode}}</div>
+        <div class="status">{{type(item.status)}}</div>
+      </div>
+      <div class="product-box" v-for="(it, idx) in item.itemJson">
+        <div class="product-left">
+          <img class="mall-img" :src="it.jsonInfo.image"/>
+          <div class="detail">
+            <div class="title">{{it.jsonInfo.productName||'-'}}</div>
+            <div class="text">规格:{{it.jsonInfo.sku||'-'}}</div>
+            <div class="text">数量:{{it.jsonInfo.num||'-'}}</div>
+            <div class="text">单价:¥{{it.jsonInfo.price||'-'}}</div>
+          </div>
+        </div>
+       <div class="product-right">
+           <div class="price">
+             实付款
+             <div class="num">¥{{item.payPrice}}</div>
+           </div>
+           <div class="text">含运费:¥{{item.payDelivery}}</div>
+           <el-button
+             size="mini"
+             type="text"
+             @click="handleDetails(item)"
+             v-hasPermi="['store:storeOrder:query']"
+           >详情</el-button>
+       </div>
+      </div>
+    </div>
+    <div v-if="orderList.length === 0" class="empty-tip">
+      <img src="@/assets/image/no_page.png" width="280"/>
+      <p>暂无订单</p>
+    </div>
+ <!--   <el-table v-loading="loading" :data="orderList" border>
       <el-table-column label="订单号" align="center" prop="orderCode" width="180px"/>
       <el-table-column label="用户昵称" align="center" prop="nickName" show-overflow-tooltip width="100px"/>
       <el-table-column label="收货人" align="center" prop="userName" />
@@ -56,33 +90,35 @@
           >查看</el-button>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table> -->
 
-    <pagination
+<!--    <pagination
       v-show="total>0"
       :total="total"
       :page.sync="queryParams.pageNum"
       :limit.sync="queryParams.pageSize"
       @pagination="getList"
-    />
+    /> -->
 
     <el-drawer
       size="75%"
       :title="show.title" :visible.sync="show.open"
       :modal="false"
     >
-      <product-order  ref="order" />
+      <storeOrderDetails  ref="Details" />
     </el-drawer>
   </div>
 </template>
 
 <script>
 import {listOrder} from "@/api/store/storeOrder";
-import productOrder from "@/views/store/components/productOrder.vue";
+import { getQwExternalContactOrderList } from "@/api/qw/im";
 import {mapState} from "vuex";
+import storeOrderDetails from '@/views/store/components/storeOrderDetails.vue';
+// import { join } from "core-js/core/array";
 export default {
   name: "userInquir",
-  components: {productOrder},
+  components: {storeOrderDetails},
   props:["data"],
   data() {
     return {
@@ -92,14 +128,15 @@ export default {
       // 总条数
       total: 0,
       // 订单表格数据
-      orderList: [],
+      orderList: [
+      ],
       // 查询参数
       queryParams: {
         pageNum: 1,
-        pageSize: 10,
-        companyUserId: null,
-        userId: null,
-        status: null,
+        pageSize: 3,
+        //companyUserId: null,
+        qwExternalContactId: null,
+        //status: null,
       },
        PayOptions:[],
        orderOptions:[],
@@ -116,7 +153,17 @@ export default {
   computed: {
     ...mapState({
       companyUserId: state => state.user.user.userId,
-    })
+    }),
+    type(){
+       return (val) => { // 闭包函数,接收 val
+             if (val) {
+                 let str=this.orderOptions.find(item => Number(item.dictValue) == val)
+                 return str?str.dictLabel:'-';
+             }else{
+               return '-';
+             }
+           }
+    }
   },
   created() {
     this.getDicts("sys_inquiry_pay").then(response => {
@@ -137,18 +184,27 @@ export default {
     this.getDicts("sys_company_or").then(response => {
           this.orOptions = response.data;
         });
-    this.queryParams.companyUserId = this.companyUserId
+    //this.queryParams.companyUserId = this.companyUserId
   },
   methods: {
     getUserDetails(id) {
-      this.queryParams.userId = id
+      this.queryParams.qwExternalContactId = id
       this.getList();
     },
     /** 查询订单列表 */
     getList() {
       this.loading = true;
-      listOrder(this.queryParams).then(response => {
-        this.orderList = response.rows;
+      getQwExternalContactOrderList(this.queryParams).then(response => {
+        // this.orderList = response.rows;
+        this.orderList = response.rows.map(item => {
+            item.itemJson=JSON.parse(item.itemJson)
+            item.itemJson=item.itemJson.map(it => {
+              it.jsonInfo=JSON.parse(it.jsonInfo)
+               return it;
+            })
+            return item;
+        });
+        // console.log(this.orderList,'000')
         this.total = response.total;
         this.loading = false;
       });
@@ -156,18 +212,115 @@ export default {
     },
     handleDetails(row){
       this.show.open=true;
-      const orderId = row.id ;
       setTimeout(() => {
-        this.$refs.order.getOrder(orderId);
+        this.$refs.Details.getDetails(row.orderId,row.nickName,row.storeName);
       }, 500);
     },
   }
 };
 </script>
 
-<style scoped>
+<style scoped lang="scss">
+  .mall-img{
+    width: 86px;
+    height: 86px;
+    border-radius: 4px;
+    border: 1px solid #F5F7FA;
+    margin-right: 10px;
+  }
+  .empty-tip {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 40px 0;
+    color: #909399;
+  }
+
+  .empty-tip i {
+    font-size: 48px;
+    margin-bottom: 10px;
+    color: #dcdfe6;
+  }
 .aacontainer {
-  padding: 10px;
-  font-size: 12px;
+  padding: 12px;
+  /* font-size: 12px; */
+  .product{
+    margin-bottom: 12px;
+    width: 100%;
+    .product-top{
+      padding: 8px;
+      // height: 32px;
+      // line-height: 32px;
+      background: #F5F7FA;
+      border-radius: 4px 4px 4px 4px;
+      margin-bottom: 12px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .orderCode{
+        font-weight: 400;
+        font-size: 12px;
+        color: #5C5F66;
+      }
+      .status{
+         font-weight: 400;
+         font-size: 12px;
+         color: #409EFF;
+      }
+
+    }
+    .product-box{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .product-left{
+       flex:1;
+        display: flex;
+        align-items: center;
+        .detail{
+          height: 86px;
+          display: flex;
+          align-items: flex-start;
+          flex-direction: column;
+          justify-content: space-between;
+          .title{
+            font-weight: 400;
+            font-size: 14px;
+            color: #202124;
+}
+          .text{
+            font-weight: 400;
+            font-size: 12px;
+            color: #84868C;
+          }
+}
+      }
+      .product-right{
+        display: flex;
+        align-items: flex-end;
+        flex-direction: column;
+        justify-content: flex-start;
+        .price{
+          display: flex;
+          align-items: center;
+          font-weight: 400;
+          font-size: 12px;
+          color: #202124;
+          .num{
+            font-weight: 500;
+            font-size: 14px;
+          }
+
+        }
+        .text{
+          font-weight: 400;
+          font-size: 12px;
+          color: #84868C;
+        }
+      }
+    }
+  }
 }
 </style>