吴树波 пре 2 недеља
родитељ
комит
304ec67529

+ 9 - 0
src/api/qw/externalContact.js

@@ -25,6 +25,15 @@ export function getWatchLogList(query) {
   })
 }
 
+// 查询重粉看课历史综合信息(关联销售 + 课程进度)
+export function getRepeatCourseHistory(fsUserId) {
+  return request({
+    url: '/qw/externalContact/getRepeatCourseHistory',
+    method: 'get',
+    params: { fsUserId }
+  })
+}
+
 // 查询企业微信客户列表
 export function getRepeat(query) {
   return request({

+ 467 - 81
src/views/qw/externalContact/index.vue

@@ -83,10 +83,10 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="是否重粉" prop="userRepeat">
-        <el-select v-model="queryParams.userRepeat" placeholder="重粉" clearable size="small">
-          <el-option label="" :value="0"/>
-          <el-option label="" :value="1"/>
+      <el-form-item label="是否重粉" prop="isRepeat">
+        <el-select v-model="queryParams.isRepeat" placeholder="重粉" clearable size="small">
+          <el-option label="正常" :value="0"/>
+          <el-option label="重粉" :value="1"/>
         </el-select>
       </el-form-item>
       <el-form-item label="客户等级" prop="level">
@@ -509,16 +509,16 @@
       </el-table-column>
       <el-table-column label="企业id" align="center" prop="corpId" />
       <el-table-column label="广告业务ID" align="center" prop="traceId" />
-      <el-table-column label="重粉看课历史" width="100px" align="center" fixed="right">
+      <el-table-column label="重粉看课历史" width="140px" align="center" fixed="right">
         <template slot-scope="scope">
           <div v-if="scope.row.fsUserId">
-            <el-tag type="success" v-if="scope.row.userRepeat == 0">正常</el-tag>
-            <el-tag type="danger" v-if="scope.row.userRepeat == 1">重粉</el-tag>
+            <el-tag type="success" v-if="scope.row.isRepeat == 0" size="mini">正常</el-tag>
+            <el-tag type="danger" v-if="scope.row.isRepeat == 1" size="mini">重粉</el-tag>
             <el-button
               size="mini"
               type="text"
               @click="showLog(scope.row)"
-            >重粉看课历史
+            >看课历史
             </el-button>
           </div>
         </template>
@@ -899,73 +899,161 @@
       <mycustomer ref="mycustomer"  @bindCustomerId="bindCustomerId"></mycustomer>
     </el-dialog>
 
-    <!-- 重粉看课记录   -->
-    <el-drawer title="重粉看课历史" :visible.sync="log.open" size="75%" append-to-body>
-      <div style="padding: 10px">
-        <el-form :model="log.queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-          <el-form-item label="所属项目" prop="project">
-            <el-select v-model="log.queryParams.project" placeholder="请选择项目" filterable clearable size="small">
-              <el-option
-                v-for="dict in projectOptions"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
+    <!-- 重粉看课记录 - 卡片式弹窗   -->
+    <el-dialog
+      title="重粉看课历史"
+      :visible.sync="log.open"
+      width="900px"
+      append-to-body
+      custom-class="repeat-course-history-dialog"
+      :close-on-click-modal="false"
+    >
+      <div class="repeat-history-content" v-loading="log.loading">
+        <!-- 客户基本信息 -->
+        <div class="customer-info-card">
+          <div class="customer-avatar">
+            <img :src="log.currentRow.avatar || require('@/assets/image/profile.jpg')" alt="" />
+          </div>
+          <div class="customer-detail">
+            <div class="customer-name-row">
+              <span class="customer-name">{{ log.currentRow.name || '-' }}</span>
+              <el-tag v-if="log.currentRow.isRepeat == 0" type="success" size="small">正常</el-tag>
+              <el-tag v-if="log.currentRow.isRepeat == 1" type="danger" size="small">重粉</el-tag>
+            </div>
+            <div class="customer-meta">
+              <span v-if="log.currentRow.remark">备注:{{ log.currentRow.remark }}</span>
+            </div>
+          </div>
+        </div>
 
-          <el-form-item label="课程" prop="courseId">
-            <el-select filterable v-model="log.queryParams.courseId" placeholder="请选择课程" clearable size="small"
-                       @change="courseChange(log.queryParams.courseId)">
-              <el-option
-                v-for="dict in courseLists"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="小节" prop="videoId">
-            <el-select filterable v-model="log.queryParams.videoId" placeholder="请选择小节" clearable size="small">
-              <el-option
-                v-for="dict in videoList"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQueryWatchLog">搜索</el-button>
-          </el-form-item>
-        </el-form>
-        <el-table v-loading="log.loading" :data="log.list">
-          <el-table-column label="编号" align="center" prop="id"/>
-          <el-table-column label="所属企微主体" align="center" prop="corpName"/>
-          <el-table-column label="所属企微" align="center" prop="qwUserName"/>
-          <!--          <el-table-column label="企微" align="center" prop="qwUserName"/>-->
-          <el-table-column label="项目" align="center" prop="projectName"/>
-          <el-table-column label="课程" align="center" prop="courseName"/>
-          <el-table-column label="小节" align="aligner" prop="videoName"/>
-          <el-table-column label="记录时间" align="center" prop="createTime"/>
-          <el-table-column label="是否完课" align="center" prop="logType">
-            <template slot-scope="scope">
-              <el-tag v-if="scope.row.logType == 2" type="success">已完课</el-tag>
-              <el-tag v-else type="success">未完课</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="完课时间" align="center" prop="finishTime"/>
-        </el-table>
-
-        <pagination
-          v-show="log.total>0"
-          :total="log.total"
-          :page.sync="log.queryParams.pageNum"
-          :limit.sync="log.queryParams.pageSize"
-          @pagination="logList"
-        />
+        <!-- 关联销售 -->
+        <div class="section-block">
+          <div class="section-title">
+            <i class="el-icon-user"></i>
+            <span>关联销售</span>
+            <el-tag size="mini" type="info">{{ log.mockSales.length }}人</el-tag>
+          </div>
+          <div class="sales-cards" v-if="log.mockSales.length > 0">
+            <div class="sales-card" v-for="(item, idx) in log.mockSales" :key="idx">
+              <div class="sales-card-top">
+                <span class="sales-name">{{ item.qwUserName }}</span>
+                <el-tag size="mini" v-if="item.status != null" :type="item.status === 0 ? 'success' : item.status === 1 ? 'warning' : item.status === 2 ? '' : 'danger'">{{ {0:'正常',1:'离职待接替',2:'正在接替',3:'流失',4:'删除'}[item.status] || '未知' }}</el-tag>
+                <el-tag size="mini" type="info" v-if="item.hasPermission === false">无权查看</el-tag>
+              </div>
+              <div class="sales-card-body">
+                <div class="sales-card-row">
+                  <span class="label">所属主体</span>
+                  <span class="value">{{ item.corpName }}</span>
+                </div>
+                <div class="sales-card-row">
+                  <span class="label">添加时间</span>
+                  <span class="value">{{ item.addTime }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="empty-tip" v-else>暂无关联销售数据</div>
+        </div>
+
+        <!-- 课程学习进度 -->
+        <div class="section-block">
+          <div class="section-title">
+            <i class="el-icon-reading"></i>
+            <span>课程学习进度</span>
+            <el-tag size="mini" type="info">{{ log.mockCourses.length }}门</el-tag>
+          </div>
+          <div class="course-list" v-if="log.mockCourses.length > 0">
+            <div class="course-item" v-for="(course, idx) in log.mockCourses" :key="idx">
+              <div class="course-header">
+                <div class="course-name-wrap">
+                  <span class="course-index">{{ idx + 1 }}</span>
+                  <span class="course-name">{{ course.courseName }}</span>
+                </div>
+                <div class="course-status">
+                  <el-tag v-if="course.finished" type="success" size="mini" effect="plain">已完课</el-tag>
+                  <el-tag v-else type="warning" size="mini" effect="plain">学习中</el-tag>
+                </div>
+              </div>
+              <div class="course-progress">
+                <el-progress
+                  :percentage="course.percentage"
+                  :stroke-width="10"
+                  :color="course.finished ? '#67C23A' : '#409EFF'"
+                ></el-progress>
+                <span class="progress-text">已学 <b>{{ course.watchedCount }}</b>/{{ course.totalCount }}节</span>
+              </div>
+              <div class="course-latest">
+                <span class="latest-label">最新学习:</span>
+                <span class="latest-section">{{ course.latestSection }}</span>
+                <span class="latest-time" v-if="course.latestTime">{{ course.latestTime }}</span>
+              </div>
+              <div class="course-source" v-if="course.qwUserName || course.corpName">
+                <span class="source-item" v-if="course.qwUserName"><i class="el-icon-user"></i> {{ course.qwUserName }}</span>
+                <span class="source-item" v-if="course.corpName"><i class="el-icon-office-building"></i> {{ course.corpName }}</span>
+                <el-tag size="mini" type="info" v-if="course.hasPermission === false" style="margin-left: 4px;">无权查看</el-tag>
+              </div>
+            </div>
+          </div>
+          <div class="empty-tip" v-else>暂无课程学习记录</div>
+        </div>
+
+        <!-- 详细看课记录 -->
+        <div class="section-block">
+          <div class="section-title" @click="log.detailExpanded = !log.detailExpanded" style="cursor: pointer;">
+            <i class="el-icon-document"></i>
+            <span>详细看课记录</span>
+            <i :class="log.detailExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" style="margin-left: 4px;"></i>
+          </div>
+          <div v-show="log.detailExpanded">
+            <el-form :model="log.queryParams" ref="queryForm" :inline="true" label-width="80px" size="small">
+              <el-form-item label="课程" prop="courseId">
+                <el-select filterable v-model="log.queryParams.courseId" placeholder="请选择课程" clearable>
+                  <el-option
+                    v-for="c in log.mockCourses"
+                    :key="c.courseId"
+                    :label="c.courseName"
+                    :value="c.courseId"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQueryWatchLog">搜索</el-button>
+              </el-form-item>
+            </el-form>
+            <el-table :data="log.list" size="small" border>
+              <el-table-column label="所属企微主体" align="center" prop="corpName" min-width="120">
+                <template slot-scope="scope">
+                  <span :class="{'no-permission': scope.row.hasPermission === false}">{{ scope.row.corpName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="所属企微" align="center" prop="qwUserName" min-width="100">
+                <template slot-scope="scope">
+                  <span :class="{'no-permission': scope.row.hasPermission === false}">{{ scope.row.qwUserName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="项目" align="center" prop="projectName" min-width="100"/>
+              <el-table-column label="课程" align="center" prop="courseName" min-width="120"/>
+              <el-table-column label="小节" align="center" prop="videoName" min-width="100"/>
+              <el-table-column label="记录时间" align="center" prop="createTime" min-width="140"/>
+              <el-table-column label="是否完课" align="center" prop="logType" width="80">
+                <template slot-scope="scope">
+                  <el-tag v-if="scope.row.logType == 2" type="success" size="mini">已完课</el-tag>
+                  <el-tag v-else type="info" size="mini">未完课</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="完课时间" align="center" prop="finishTime" min-width="140"/>
+            </el-table>
+            <pagination
+              v-show="log.total>0"
+              :total="log.total"
+              :page.sync="log.queryParams.pageNum"
+              :limit.sync="log.queryParams.pageSize"
+              @pagination="logList"
+            />
+          </div>
+        </div>
       </div>
-    </el-drawer>
+    </el-dialog>
 
 <!--    设置一个课程sop-->
     <el-dialog :title="setSop.title" :visible.sync="setSop.open"  width="1200px" append-to-body>
@@ -1047,7 +1135,8 @@ import {
   getCustomerCourseSop,
   setCustomerCourseSopList,
   unBindUserId, updateExternalContactCall,updateExternalContactStatus,getWatchLogList,
-  updateFirstLoginRewardAddress
+  updateFirstLoginRewardAddress,
+  getRepeatCourseHistory
 } from '@/api/qw/externalContact'
 import {getMyQwUserList, getMyQwCompanyList, updateUser,getQwUserListLikeName} from "@/api/qw/user";
 import {listTag, getTag, searchTags} from "@/api/qw/tag";
@@ -1078,6 +1167,12 @@ export default {
         loading: true,
         list: [],
         total: 0,
+        detailExpanded: false,
+        currentRow: {},
+        // 模拟数据 - 关联销售
+        mockSales: [],
+        // 模拟数据 - 课程学习进度
+        mockCourses: [],
         queryParams: {
           pageNum: 1,
           pageSize: 10,
@@ -1306,7 +1401,7 @@ export default {
         wayId:null,
         levelType:null,
         companyUser:null,
-        userRepeat: null
+        isRepeat: null
       },
       //选择的标签
       selectTags:[],
@@ -1378,10 +1473,50 @@ export default {
       this.log.queryParams.fsUserId = row.fsUserId;
       this.log.open = true;
       this.log.loading = true;
-      courseList().then(response => {
-        this.courseLists = response.list;
-        this.logList();
-      })
+      this.log.detailExpanded = false;
+      this.log.currentRow = {
+        name: row.name,
+        avatar: row.avatar,
+        remark: row.remark,
+        isRepeat: row.isRepeat,
+      };
+      // 调用后端接口获取关联销售 + 课程进度
+      getRepeatCourseHistory(row.fsUserId).then(res => {
+        const data = res.data;
+        if (data) {
+          this.log.currentRow = {
+            name: data.name || row.name,
+            avatar: data.avatar || row.avatar,
+            remark: data.remark || row.remark,
+            isRepeat: data.userRepeat != null ? data.userRepeat : row.isRepeat,
+          };
+          this.log.mockSales = (data.salesList || []).map(s => ({
+            qwUserName: s.qwUserName,
+            corpName: s.corpName,
+            addTime: s.addTime,
+            status: s.status,
+            hasPermission: s.hasPermission,
+          }));
+          this.log.mockCourses = (data.courseList || []).map(c => ({
+            courseId: c.courseId,
+            courseName: c.courseName,
+            watchedCount: c.watchedCount,
+            totalCount: c.totalCount,
+            percentage: c.percentage,
+            latestSection: c.latestSection,
+            latestTime: c.latestTime,
+            finished: c.finished,
+            qwUserName: c.qwUserName,
+            corpName: c.corpName,
+            hasPermission: c.hasPermission,
+          }));
+        }
+        this.log.loading = false;
+      }).catch(() => {
+        this.log.loading = false;
+      });
+      // 同时加载详细看课记录
+      this.logList();
     },
     handleQueryWatchLog() {
       this.log.queryParams.pageNum = 1;
@@ -1389,8 +1524,14 @@ export default {
       this.logList();
     },
     logList() {
-      getWatchLogList(this.log.queryParams).then(e => {
-        this.log.loading = false;
+      // 只传 fsUserId 和 courseId,确保查询有效
+      const params = {
+        pageNum: this.log.queryParams.pageNum,
+        pageSize: this.log.queryParams.pageSize,
+        fsUserId: this.log.queryParams.fsUserId,
+        courseId: this.log.queryParams.courseId || undefined,
+      };
+      getWatchLogList(params).then(e => {
         this.log.list = e.rows;
         this.log.total = e.total;
       });
@@ -2794,3 +2935,248 @@ export default {
   margin-bottom: 20px;
 }
 </style>
+
+<!-- 重粉看课历史弹窗样式(不能加 scoped,否则 custom-class 不生效) -->
+<style>
+.repeat-course-history-dialog {
+  border-radius: 8px;
+}
+.repeat-course-history-dialog .el-dialog__header {
+  border-bottom: 1px solid #ebeef5;
+  padding-bottom: 15px;
+}
+.repeat-course-history-dialog .el-dialog__body {
+  padding: 16px 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+.repeat-history-content {
+  font-size: 14px;
+  color: #303133;
+}
+
+/* 客户信息卡片 */
+.customer-info-card {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 20px;
+}
+.customer-info-card .customer-avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  overflow: hidden;
+  margin-right: 14px;
+  flex-shrink: 0;
+  border: 2px solid #e4e7ed;
+}
+.customer-info-card .customer-avatar img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+.customer-info-card .customer-detail {
+  flex: 1;
+}
+.customer-info-card .customer-name-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 4px;
+}
+.customer-info-card .customer-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+.customer-info-card .customer-meta {
+  	color: #909399;
+  font-size: 12px;
+}
+
+/* 区块样式 */
+.section-block {
+  margin-bottom: 20px;
+}
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #ebeef5;
+  margin-bottom: 12px;
+}
+.section-title i:first-child {
+  color: #409EFF;
+  font-size: 16px;
+}
+.section-title .el-tag {
+  margin-left: 4px;
+}
+
+/* 销售卡片 */
+.sales-cards {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+.sales-card {
+  flex: 1;
+  min-width: 220px;
+  max-width: 280px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 12px;
+  background: #fff;
+  transition: box-shadow 0.2s;
+}
+.sales-card:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+.sales-card-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+.sales-card-top .sales-name {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+.sales-card-body {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.sales-card-row {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+}
+.sales-card-row .label {
+  color: #909399;
+  width: 60px;
+  flex-shrink: 0;
+}
+.sales-card-row .value {
+  color: #606266;
+}
+
+/* 课程列表 */
+.course-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.course-item {
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 12px 14px;
+  background: #fff;
+  transition: box-shadow 0.2s;
+}
+.course-item:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+.course-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+.course-name-wrap {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.course-index {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 22px;
+  height: 22px;
+  border-radius: 50%;
+  background: #409EFF;
+  color: #fff;
+  font-size: 12px;
+  font-weight: 600;
+}
+.course-name {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+.course-progress {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 6px;
+}
+.course-progress .el-progress {
+  flex: 1;
+}
+.course-progress .progress-text {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+}
+.course-progress .progress-text b {
+  color: #303133;
+  font-size: 13px;
+}
+.course-latest {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+}
+.course-latest .latest-label {
+  color: #909399;
+}
+.course-latest .latest-section {
+  color: #409EFF;
+  font-weight: 500;
+}
+.course-latest .latest-time {
+  color: #c0c4cc;
+  margin-left: 8px;
+}
+.course-source {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 12px;
+  margin-top: 4px;
+}
+.course-source .source-item {
+  color: #909399;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  line-height: 1;
+}
+.course-source .source-item i {
+  font-size: 13px;
+  line-height: 1;
+  vertical-align: middle;
+}
+.no-permission {
+  color: #c0c4cc;
+  font-style: italic;
+}
+
+/* 空提示 */
+.empty-tip {
+  text-align: center;
+  color: #c0c4cc;
+  padding: 20px 0;
+  font-size: 13px;
+}
+</style>

+ 469 - 81
src/views/qw/externalContact/myExternalContact.vue

@@ -85,10 +85,10 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="是否重粉" prop="userRepeat">
-        <el-select v-model="queryParams.userRepeat" placeholder="重粉" clearable size="small">
-          <el-option label="" :value="0"/>
-          <el-option label="" :value="1"/>
+      <el-form-item label="是否重粉" prop="isRepeat">
+        <el-select v-model="queryParams.isRepeat" placeholder="重粉" clearable size="small">
+          <el-option label="正常" :value="0"/>
+          <el-option label="重粉" :value="1"/>
         </el-select>
       </el-form-item>
       <el-form-item label="客户等级" prop="level">
@@ -469,16 +469,16 @@
         </template>
       </el-table-column>
       <el-table-column label="企业id" align="center" prop="corpId" />
-      <el-table-column label="重粉看课历史" width="100px" align="center" fixed="right">
+      <el-table-column label="重粉看课历史" width="140px" align="center" fixed="right">
         <template slot-scope="scope">
           <div v-if="scope.row.fsUserId">
-            <el-tag type="success" v-if="scope.row.userRepeat == 0">正常</el-tag>
-            <el-tag type="danger" v-if="scope.row.userRepeat == 1">重粉</el-tag>
+            <el-tag type="success" v-if="scope.row.isRepeat == 0" size="mini">正常</el-tag>
+            <el-tag type="danger" v-if="scope.row.isRepeat == 1" size="mini">重粉</el-tag>
             <el-button
               size="mini"
               type="text"
               @click="showLog(scope.row)"
-            >重粉看课历史
+            >看课历史
             </el-button>
           </div>
         </template>
@@ -948,71 +948,161 @@
       </div>
     </el-dialog>
 
-    <!-- 重粉看课记录   -->
-    <el-drawer title="重粉看课历史" :visible.sync="log.open" size="75%" append-to-body>
-      <div style="padding: 10px">
-        <el-form :model="log.queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
-          <el-form-item label="所属项目" prop="project">
-            <el-select v-model="log.queryParams.project" placeholder="请选择项目" filterable clearable size="small">
-              <el-option
-                v-for="dict in projectOptions"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="课程" prop="courseId">
-            <el-select filterable v-model="log.queryParams.courseId" placeholder="请选择课程" clearable size="small"
-                       @change="courseChange(log.queryParams.courseId)">
-              <el-option
-                v-for="dict in courseLists"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="小节" prop="videoId">
-            <el-select filterable v-model="log.queryParams.videoId" placeholder="请选择小节" clearable size="small">
-              <el-option
-                v-for="dict in videoList"
-                :key="dict.dictValue"
-                :label="dict.dictLabel"
-                :value="dict.dictValue"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item>
-            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQueryWatchLog">搜索</el-button>
-          </el-form-item>
-        </el-form>
-        <el-table v-loading="log.loading" :data="log.list">
-          <el-table-column label="编号" align="center" prop="id"/>
-          <el-table-column label="所属企微主体" align="center" prop="corpName"/>
-          <el-table-column label="所属企微" align="center" prop="qwUserName"/>
-          <el-table-column label="项目" align="center" prop="projectName"/>
-          <el-table-column label="课程" align="center" prop="courseName"/>
-          <el-table-column label="小节" align="aligner" prop="videoName"/>
-          <el-table-column label="记录时间" align="center" prop="createTime"/>
-          <el-table-column label="是否完课" align="center" prop="logType">
-            <template slot-scope="scope">
-              <el-tag v-if="scope.row.logType == 2" type="success">已完课</el-tag>
-              <el-tag v-else type="success">未完课</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="完课时间" align="center" prop="finishTime"/>
-        </el-table>
-
-        <pagination
-          v-show="log.total>0"
-          :total="log.total"
-          :page.sync="log.queryParams.pageNum"
-          :limit.sync="log.queryParams.pageSize"
-          @pagination="logList"
-        />
+    <!-- 重粉看课记录 - 卡片式弹窗   -->
+    <el-dialog
+      title="重粉看课历史"
+      :visible.sync="log.open"
+      width="900px"
+      append-to-body
+      custom-class="repeat-course-history-dialog"
+      :close-on-click-modal="false"
+    >
+      <div class="repeat-history-content" v-loading="log.loading">
+        <!-- 客户基本信息 -->
+        <div class="customer-info-card">
+          <div class="customer-avatar">
+            <img :src="log.currentRow.avatar || require('@/assets/image/profile.jpg')" alt="" />
+          </div>
+          <div class="customer-detail">
+            <div class="customer-name-row">
+              <span class="customer-name">{{ log.currentRow.name || '-' }}</span>
+              <el-tag v-if="log.currentRow.isRepeat == 0" type="success" size="small">正常</el-tag>
+              <el-tag v-if="log.currentRow.isRepeat == 1" type="danger" size="small">重粉</el-tag>
+            </div>
+            <div class="customer-meta">
+              <span v-if="log.currentRow.remark">备注:{{ log.currentRow.remark }}</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 关联销售 -->
+        <div class="section-block">
+          <div class="section-title">
+            <i class="el-icon-user"></i>
+            <span>关联销售</span>
+            <el-tag size="mini" type="info">{{ log.mockSales.length }}人</el-tag>
+          </div>
+          <div class="sales-cards" v-if="log.mockSales.length > 0">
+            <div class="sales-card" v-for="(item, idx) in log.mockSales" :key="idx">
+              <div class="sales-card-top">
+                <span class="sales-name">{{ item.qwUserName }}</span>
+                <el-tag size="mini" v-if="item.status != null" :type="item.status === 0 ? 'success' : item.status === 1 ? 'warning' : item.status === 2 ? '' : 'danger'">{{ {0:'正常',1:'离职待接替',2:'正在接替',3:'流失',4:'删除'}[item.status] || '未知' }}</el-tag>
+                <el-tag size="mini" type="info" v-if="item.hasPermission === false">无权查看</el-tag>
+              </div>
+              <div class="sales-card-body">
+                <div class="sales-card-row">
+                  <span class="label">所属主体</span>
+                  <span class="value">{{ item.corpName }}</span>
+                </div>
+                <div class="sales-card-row">
+                  <span class="label">添加时间</span>
+                  <span class="value">{{ item.addTime }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="empty-tip" v-else>暂无关联销售数据</div>
+        </div>
+
+        <!-- 课程学习进度 -->
+        <div class="section-block">
+          <div class="section-title">
+            <i class="el-icon-reading"></i>
+            <span>课程学习进度</span>
+            <el-tag size="mini" type="info">{{ log.mockCourses.length }}门</el-tag>
+          </div>
+          <div class="course-list" v-if="log.mockCourses.length > 0">
+            <div class="course-item" v-for="(course, idx) in log.mockCourses" :key="idx">
+              <div class="course-header">
+                <div class="course-name-wrap">
+                  <span class="course-index">{{ idx + 1 }}</span>
+                  <span class="course-name">{{ course.courseName }}</span>
+                </div>
+                <div class="course-status">
+                  <el-tag v-if="course.finished" type="success" size="mini" effect="plain">已完课</el-tag>
+                  <el-tag v-else type="warning" size="mini" effect="plain">学习中</el-tag>
+                </div>
+              </div>
+              <div class="course-progress">
+                <el-progress
+                  :percentage="course.percentage"
+                  :stroke-width="10"
+                  :color="course.finished ? '#67C23A' : '#409EFF'"
+                ></el-progress>
+                <span class="progress-text">已学 <b>{{ course.watchedCount }}</b>/{{ course.totalCount }}节</span>
+              </div>
+              <div class="course-latest">
+                <span class="latest-label">最新学习:</span>
+                <span class="latest-section">{{ course.latestSection }}</span>
+                <span class="latest-time" v-if="course.latestTime">{{ course.latestTime }}</span>
+              </div>
+              <div class="course-source" v-if="course.qwUserName || course.corpName">
+                <span class="source-item" v-if="course.qwUserName"><i class="el-icon-user"></i> {{ course.qwUserName }}</span>
+                <span class="source-item" v-if="course.corpName"><i class="el-icon-office-building"></i> {{ course.corpName }}</span>
+                <el-tag size="mini" type="info" v-if="course.hasPermission === false" style="margin-left: 4px;">无权查看</el-tag>
+              </div>
+            </div>
+          </div>
+          <div class="empty-tip" v-else>暂无课程学习记录</div>
+        </div>
+
+        <!-- 详细看课记录 -->
+        <div class="section-block">
+          <div class="section-title" @click="log.detailExpanded = !log.detailExpanded" style="cursor: pointer;">
+            <i class="el-icon-document"></i>
+            <span>详细看课记录</span>
+            <i :class="log.detailExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" style="margin-left: 4px;"></i>
+          </div>
+          <div v-show="log.detailExpanded">
+            <el-form :model="log.queryParams" ref="queryForm" :inline="true" label-width="80px" size="small">
+              <el-form-item label="课程" prop="courseId">
+                <el-select filterable v-model="log.queryParams.courseId" placeholder="请选择课程" clearable>
+                  <el-option
+                    v-for="c in log.mockCourses"
+                    :key="c.courseId"
+                    :label="c.courseName"
+                    :value="c.courseId"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQueryWatchLog">搜索</el-button>
+              </el-form-item>
+            </el-form>
+            <el-table :data="log.list" size="small" border>
+              <el-table-column label="所属企微主体" align="center" prop="corpName" min-width="120">
+                <template slot-scope="scope">
+                  <span :class="{'no-permission': scope.row.hasPermission === false}">{{ scope.row.corpName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="所属企微" align="center" prop="qwUserName" min-width="100">
+                <template slot-scope="scope">
+                  <span :class="{'no-permission': scope.row.hasPermission === false}">{{ scope.row.qwUserName }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="项目" align="center" prop="projectName" min-width="100"/>
+              <el-table-column label="课程" align="center" prop="courseName" min-width="120"/>
+              <el-table-column label="小节" align="center" prop="videoName" min-width="100"/>
+              <el-table-column label="记录时间" align="center" prop="createTime" min-width="140"/>
+              <el-table-column label="是否完课" align="center" prop="logType" width="80">
+                <template slot-scope="scope">
+                  <el-tag v-if="scope.row.logType == 2" type="success" size="mini">已完课</el-tag>
+                  <el-tag v-else type="info" size="mini">未完课</el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="完课时间" align="center" prop="finishTime" min-width="140"/>
+            </el-table>
+            <pagination
+              v-show="log.total>0"
+              :total="log.total"
+              :page.sync="log.queryParams.pageNum"
+              :limit.sync="log.queryParams.pageSize"
+              @pagination="logList"
+            />
+          </div>
+        </div>
       </div>
-    </el-drawer>
+    </el-dialog>
 
     <el-drawer
         :with-header="false"
@@ -1107,7 +1197,8 @@ import {
   getCustomerCourseSop,
   setCustomerCourseSopList,
   syncMyExternalContact, unBindUserId, updateExternalContactCall,exportMyExternalContact,updateExternalContactStatus,getWatchLogList,
-  updateFirstLoginRewardAddress
+  updateFirstLoginRewardAddress,
+  getRepeatCourseHistory
 } from '@/api/qw/externalContact'
 import info from "@/views/qw/externalContact/info.vue";
 import {getMyQwUserList, getMyQwCompanyList, handleInputAuthAppKey, updateUser} from "@/api/qw/user";
@@ -1144,6 +1235,12 @@ export default {
         loading: true,
         list: [],
         total: 0,
+        detailExpanded: false,
+        currentRow: {},
+        // 模拟数据 - 关联销售
+        mockSales: [],
+        // 模拟数据 - 课程学习进度
+        mockCourses: [],
         queryParams: {
           pageNum: 1,
           pageSize: 10,
@@ -1352,7 +1449,7 @@ export default {
         level:null,
         levelType:null,
         companyUser:null,
-        userRepeat: null
+        isRepeat: null
       },
 
       queryTagParams:{
@@ -1429,11 +1526,51 @@ export default {
       this.log.queryParams.fsUserId = row.fsUserId;
       this.log.open = true;
       this.log.loading = true;
-      courseList().then(response => {
-        this.courseLists = response.list;
-        this.log.queryParams.externalUserId = row.id;
-        this.logList();
-      })
+      this.log.detailExpanded = false;
+      this.log.currentRow = {
+        name: row.name,
+        avatar: row.avatar,
+        remark: row.remark,
+        isRepeat: row.isRepeat,
+      };
+      // 调用后端接口获取关联销售 + 课程进度
+      getRepeatCourseHistory(row.fsUserId).then(res => {
+        const data = res.data;
+        if (data) {
+          // 覆盖客户信息(以后端返回为准)
+          this.log.currentRow = {
+            name: data.name || row.name,
+            avatar: data.avatar || row.avatar,
+            remark: data.remark || row.remark,
+            isRepeat: data.userRepeat != null ? data.userRepeat : row.isRepeat,
+          };
+          this.log.mockSales = (data.salesList || []).map(s => ({
+            qwUserName: s.qwUserName,
+            corpName: s.corpName,
+            addTime: s.addTime,
+            status: s.status,
+            hasPermission: s.hasPermission,
+          }));
+          this.log.mockCourses = (data.courseList || []).map(c => ({
+            courseId: c.courseId,
+            courseName: c.courseName,
+            watchedCount: c.watchedCount,
+            totalCount: c.totalCount,
+            percentage: c.percentage,
+            latestSection: c.latestSection,
+            latestTime: c.latestTime,
+            finished: c.finished,
+            qwUserName: c.qwUserName,
+            corpName: c.corpName,
+            hasPermission: c.hasPermission,
+          }));
+        }
+        this.log.loading = false;
+      }).catch(() => {
+        this.log.loading = false;
+      });
+      // 同时加载详细看课记录
+      this.logList();
     },
     handleQueryWatchLog() {
       this.log.queryParams.pageNum = 1;
@@ -1441,8 +1578,14 @@ export default {
       this.logList();
     },
     logList() {
-      getWatchLogList(this.log.queryParams).then(e => {
-        this.log.loading = false;
+      // 只传 fsUserId 和 courseId,确保查询有效
+      const params = {
+        pageNum: this.log.queryParams.pageNum,
+        pageSize: this.log.queryParams.pageSize,
+        fsUserId: this.log.queryParams.fsUserId,
+        courseId: this.log.queryParams.courseId || undefined,
+      };
+      getWatchLogList(params).then(e => {
         this.log.list = e.rows;
         this.log.total = e.total;
       });
@@ -2884,3 +3027,248 @@ export default {
   margin-bottom: 20px;
 }
 </style>
+
+<!-- 重粉看课历史弹窗样式(不能加 scoped,否则 custom-class 不生效) -->
+<style>
+.repeat-course-history-dialog {
+  border-radius: 8px;
+}
+.repeat-course-history-dialog .el-dialog__header {
+  border-bottom: 1px solid #ebeef5;
+  padding-bottom: 15px;
+}
+.repeat-course-history-dialog .el-dialog__body {
+  padding: 16px 20px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+.repeat-history-content {
+  font-size: 14px;
+  color: #303133;
+}
+
+/* 客户信息卡片 */
+.customer-info-card {
+  display: flex;
+  align-items: center;
+  padding: 12px 16px;
+  background: #f5f7fa;
+  border-radius: 8px;
+  margin-bottom: 20px;
+}
+.customer-info-card .customer-avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  overflow: hidden;
+  margin-right: 14px;
+  flex-shrink: 0;
+  border: 2px solid #e4e7ed;
+}
+.customer-info-card .customer-avatar img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+.customer-info-card .customer-detail {
+  flex: 1;
+}
+.customer-info-card .customer-name-row {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 4px;
+}
+.customer-info-card .customer-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+.customer-info-card .customer-meta {
+  	color: #909399;
+  font-size: 12px;
+}
+
+/* 区块样式 */
+.section-block {
+  margin-bottom: 20px;
+}
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #ebeef5;
+  margin-bottom: 12px;
+}
+.section-title i:first-child {
+  color: #409EFF;
+  font-size: 16px;
+}
+.section-title .el-tag {
+  margin-left: 4px;
+}
+
+/* 销售卡片 */
+.sales-cards {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+.sales-card {
+  flex: 1;
+  min-width: 220px;
+  max-width: 280px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 12px;
+  background: #fff;
+  transition: box-shadow 0.2s;
+}
+.sales-card:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+.sales-card-top {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+.sales-card-top .sales-name {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+.sales-card-body {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+.sales-card-row {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+}
+.sales-card-row .label {
+  color: #909399;
+  width: 60px;
+  flex-shrink: 0;
+}
+.sales-card-row .value {
+  color: #606266;
+}
+
+/* 课程列表 */
+.course-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+.course-item {
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 12px 14px;
+  background: #fff;
+  transition: box-shadow 0.2s;
+}
+.course-item:hover {
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+.course-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+.course-name-wrap {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+.course-index {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 22px;
+  height: 22px;
+  border-radius: 50%;
+  background: #409EFF;
+  color: #fff;
+  font-size: 12px;
+  font-weight: 600;
+}
+.course-name {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+.course-progress {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 6px;
+}
+.course-progress .el-progress {
+  flex: 1;
+}
+.course-progress .progress-text {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+}
+.course-progress .progress-text b {
+  color: #303133;
+  font-size: 13px;
+}
+.course-latest {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+}
+.course-latest .latest-label {
+  color: #909399;
+}
+.course-latest .latest-section {
+  color: #409EFF;
+  font-weight: 500;
+}
+.course-latest .latest-time {
+  color: #c0c4cc;
+  margin-left: 8px;
+}
+.course-source {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 12px;
+  margin-top: 4px;
+}
+.course-source .source-item {
+  color: #909399;
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  line-height: 1;
+}
+.course-source .source-item i {
+  font-size: 13px;
+  line-height: 1;
+  vertical-align: middle;
+}
+.no-permission {
+  color: #c0c4cc;
+  font-style: italic;
+}
+
+/* 空提示 */
+.empty-tip {
+  text-align: center;
+  color: #c0c4cc;
+  padding: 20px 0;
+  font-size: 13px;
+}
+</style>