فهرست منبع

Merge remote-tracking branch 'origin/master'

caoliqin 21 ساعت پیش
والد
کامیت
91503482c8

+ 9 - 1
src/api/system/config.js

@@ -23,6 +23,7 @@ export function getConfigByKey(configKey) {
     method: 'get'
   })
 }
+
 // 根据参数键名查询参数值
 export function getConfigKey(configKey) {
   return request({
@@ -31,6 +32,13 @@ export function getConfigKey(configKey) {
   })
 }
 
+export function getCourseConfigByRewardType() {
+    return request({
+        url: '/system/config/getCourseConfigByRewardType',
+        method: 'get'
+    })
+}
+
 // 新增参数配置
 export function addConfig(data) {
   return request({
@@ -81,4 +89,4 @@ export function updateConfigByKey(data) {
     method: 'post',
     data: data
   })
-}
+}

+ 10 - 0
src/api/wx/wxContact.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 查询个微联系人列表
+export function listWxContact(query) {
+  return request({
+    url: '/company/wxContact/list',
+    method: 'get',
+    params: query
+  })
+}

+ 10 - 0
src/api/wx/wxMsgLog.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 查询聊天记录列表
+export function listWxMsgLog(query) {
+  return request({
+    url: '/company/wxMsgLog/list',
+    method: 'get',
+    params: query
+  })
+}

+ 10 - 1
src/api/wx/wxSop.js

@@ -58,4 +58,13 @@ export function updateWxStatus(ids) {
     url: '/qw/sop/updateWxStatus/' + ids,
     method: 'get'
   })
-}
+}
+
+// 个微SOP一键群发
+export function sendWxSopMsg(data) {
+  return request({
+    url: '/wx/wxSop/sendMsg',
+    method: 'post',
+    data: data
+  })
+}

+ 0 - 1
src/views/company/companyClient/index.vue

@@ -309,7 +309,6 @@ export default {
       })
     },
     getQueryData() {
-      let form = JSON.parse(JSON.stringify(this.queryParams));
       if (form.time && form.time.length === 2) {
         form.beginTime = form.time[0];
         form.endTime = form.time[1];

+ 348 - 73
src/views/company/wxAccount/index.vue

@@ -82,40 +82,76 @@
 	  <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-table v-loading="loading" :data="companyAccountList" @selection-change="handleSelectionChange">
-      <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="id" align="center" prop="id" />
-      <el-table-column label="微信昵称" align="center" prop="wxNickName" />
-      <el-table-column label="微信号" align="center" prop="wxNo" />
-      <el-table-column label="手机号" align="center" prop="phone" />
-      <el-table-column label="员工" align="center" prop="companyUserName" />
-      <el-table-column label="微信备注前缀" align="center" prop="wxRemark" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
-        <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['company:companyWx:edit']"
-          >修改</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-delete"
-            @click="handleDelete(scope.row)"
-            v-hasPermi="['company:companyWx:remove']"
-          >删除</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-refresh"
-            @click="handleSyncCustomer(scope.row)"
-            v-hasPermi="['company:companyWx:edit']"
-          >同步客户</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+        <el-table v-loading="loading" :data="companyAccountList" @selection-change="handleSelectionChange">
+            <el-table-column label="微信昵称" align="center" prop="wxNickName" />
+            <el-table-column label="头像" width="150" align="center">
+                <template slot-scope="scope">
+                    <img :src="scope.row.headImgUrl" style="height: 80px">
+                </template>
+            </el-table-column>
+            <el-table-column label="微信号" align="center" prop="wxNo" />
+            <el-table-column label="手机号" align="center" prop="phone" />
+            <el-table-column label="员工" align="center">
+                <template slot-scope="scope">
+                    <el-tag>{{scope.row.companyUserName}}</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="登录状态" align="center">
+                <template slot-scope="scope">
+                    <el-tag v-if="scope.row.loginStatus == 1" type="success">在线</el-tag>
+                    <el-tag v-else type="danger">离线</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="在线/离线时间" align="center">
+                <template slot-scope="scope">
+                    <el-tag type="danger" v-if="scope.row.loginStatus == 0">{{scope.row.outTime}}</el-tag>
+                    <el-tag type="success" v-if="scope.row.loginStatus == 1">{{scope.row.loginTime}}</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="离线备注" align="center" prop="outRemark">
+                <template slot-scope="scope">
+                    <p v-if="scope.row.loginStatus == 0">{{scope.row.outRemark}}</p>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                <template slot-scope="scope">
+                    <el-button
+                        size="mini"
+                        type="text"
+                        icon="el-icon-edit"
+                        @click="handleUpdate(scope.row)"
+                        v-hasPermi="['company:companyWx:edit']"
+                    >修改</el-button>
+                    <el-button
+                        v-if="scope.row.serverStatus == 0"
+                        size="mini"
+                        type="text"
+                        @click="bind(scope.row)"
+                    >获取PAD</el-button>
+                    <el-button
+                        v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 1"
+                        size="mini"
+                        type="text"
+                        @click="wxLoginOutFun(scope.row.id)"
+                    >退出微信</el-button>
+                    <el-button
+                        size="mini"
+                        type="text"
+                        icon="el-icon-refresh"
+                        @click="handleSyncCustomer(scope.row)"
+                        v-hasPermi="['company:companyWx:edit']"
+                    >同步客户</el-button>
+                    <el-button
+                        size="mini"
+                        type="text"
+                        icon="el-icon-delete"
+                        @click="handleDelete(scope.row)"
+                        v-hasPermi="['company:companyWx:remove']"
+                    >删除</el-button>
+                    <el-button size="mini" type="text" @click="handleContactList(scope.row)">联系人</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
 
     <pagination
       v-show="total>0"
@@ -126,8 +162,8 @@
     />
 
     <!-- 添加或修改个微账号对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
         <el-form-item label="微信昵称" prop="wxNickName">
           <el-input v-model="form.wxNickName" placeholder="请输入微信昵称" />
         </el-form-item>
@@ -142,9 +178,6 @@
             <el-option v-for="item in qwUserList" :label="item.nickName" :value="item.userId" />
           </el-select>
         </el-form-item>
-        <el-form-item label="微信备注前缀" prop="wxRemark">
-          <el-input :disabled="title=='修改个微账号'" v-model="form.wxRemark"  :maxlength="6" placeholder="请输入6位微信备注前缀(数字/字母/中文)" />
-        </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitForm">确 定</el-button>
@@ -153,10 +186,91 @@
     </el-dialog>
   </div>
 </template>
+<!-- 联系人列表弹窗 -->
+<el-drawer title="联系人列表" :visible.sync="contactOpen" size="70%" append-to-body>
+<el-table :data="contactList">
+    <el-table-column label="头像" align="center" width="70">
+        <template slot-scope="scope">
+            <img v-if="scope.row.headImgUrl" :src="scope.row.headImgUrl" style="width:40px;height:40px;border-radius:50%;object-fit:cover;" />
+            <el-avatar v-else :size="40" icon="el-icon-user-solid" />
+        </template>
+    </el-table-column>
+    <el-table-column label="微信昵称" align="center" prop="nickName" />
+    <el-table-column label="微信号" align="center" prop="alias" />
+    <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+    <el-table-column label="是否好友" align="center" width="90">
+        <template slot-scope="scope">
+            <el-tag v-if="scope.row.friends == 1" type="success" size="small">是</el-tag>
+            <el-tag v-else type="danger" size="small">否</el-tag>
+        </template>
+    </el-table-column>
+    <el-table-column label="创建时间" align="center" prop="createTime" width="160">
+        <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+    </el-table-column>
+    <el-table-column label="操作" align="center" width="100">
+        <template slot-scope="scope">
+            <el-button size="mini" type="text" @click="handleChatHistory(scope.row)">聊天记录</el-button>
+        </template>
+    </el-table-column>
+</el-table>
+<pagination
+    v-show="contactTotal > 0"
+    :total="contactTotal"
+    :page.sync="contactQueryParams.pageNum"
+    :limit.sync="contactQueryParams.pageSize"
+    @pagination="getContactList"
+/>
+</el-drawer>
 
+<!-- 聊天记录弹窗 -->
+<el-dialog :title="'聊天记录 - ' + (currentContact.nickName || '')" :visible.sync="chatOpen" width="60%" append-to-body>
+<div class="chat-container">
+    <div v-for="(msg, index) in chatList" :key="index">
+        <div class="chat-time">{{ parseTime(msg.createTime) }}</div>
+        <!-- 收到的消息 - 左侧 -->
+        <div v-if="msg.receiveType == 0" class="chat-item">
+            <el-avatar :size="36" icon="el-icon-user-solid" />
+            <div class="chat-bubble left">{{ msg.content }}</div>
+        </div>
+        <!-- 发出的消息 - 右侧 -->
+        <div v-else class="chat-item self">
+            <div class="chat-bubble right">{{ msg.content }}</div>
+            <el-avatar :size="36" icon="el-icon-user-solid" style="background: #95ec69;" />
+        </div>
+    </div>
+    <div v-if="chatList.length === 0" style="text-align:center; color:#999; padding: 40px 0;">暂无聊天记录</div>
+</div>
+<pagination
+    v-show="chatTotal > 0"
+    :total="chatTotal"
+    :page.sync="chatQueryParams.pageNum"
+    :limit.sync="chatQueryParams.pageSize"
+    @pagination="getChatList"
+/>
+</el-dialog>
 <script>
-import { listCompanyAccount, getCompanyAccount, delCompanyAccount, addCompanyAccount, updateCompanyAccount, exportCompanyAccount, companyListAll, syncWx } from "@/api/company/companyAccount";
+import {
+    listCompanyAccount,
+    getCompanyAccount,
+    delCompanyAccount,
+    addCompanyAccount,
+    updateCompanyAccount,
+    exportCompanyAccount,
+    companyListAll,
+    getWxQrCode,
+    getLoginStatus,
+    wakeUpLogin,
+    syncWx,
+    updateWxInfo,
+    wxLoginOut,
+    bindService
+} from '@/api/company/companyAccount'
 import {getAllUserlist} from "@/api/company/companyUser";
+import { qrCodeStatus } from '@/api/qw/user'
+import { listWxContact } from "@/api/wx/wxContact"
+import { listWxMsgLog } from "@/api/wx/wxMsgLog"
 
 
 export default {
@@ -182,6 +296,11 @@ export default {
       title: "",
       // 是否显示弹出层
       open: false,
+        loginQwInterval: null,
+        loginData:{
+            open: false,
+            url: null
+        },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -195,25 +314,41 @@ export default {
       form: {},
       // 表单校验
       rules: {
-        wxNickName: [
-          { required: true, message: '请输入微信昵称', trigger: 'blur' }
-        ],
-        wxNo: [
-          { required: true, message: '请输入微信号', trigger: 'blur' }
-        ],
-        phone: [
-          { required: true, message: '请输入手机号', trigger: 'blur' },
-          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
-        ],
-        companyUserId: [
-          { required: true, message: '请选择员工', trigger: 'change' }
-        ],
-        wxRemark: [
-          { required: true, message: '请输入微信备注前缀', trigger: 'blur' },
-          { len: 6, message: '微信备注前缀必须为6位', trigger: 'blur' },
-          { pattern: /^[0-9a-zA-Z\u4e00-\u9fa5]{6}$/, message: '微信备注前缀只能输入6位数字、字母或中文', trigger: 'blur' }
-        ]
-      }
+          wxNickName: [
+              { required: true, message: '请输入微信昵称', trigger: 'blur' }
+          ],
+          wxNo: [
+              { required: true, message: '请输入微信号', trigger: 'blur' }
+          ],
+          phone: [
+              { required: true, message: '请输入手机号', trigger: 'blur' },
+              { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
+          ],
+          companyUserId: [
+              { required: true, message: '请选择员工', trigger: 'change' }
+          ]
+      },
+        // 联系人弹窗
+        contactOpen: false,
+        contactList: [],
+        contactTotal: 0,
+        contactQueryParams: {
+            pageNum: 1,
+            pageSize: 10,
+            accountId: null
+        },
+        // 聊天记录弹窗
+        chatOpen: false,
+        chatList: [],
+        chatTotal: 0,
+        chatQueryParams: {
+            pageNum: 1,
+            pageSize: 20,
+            accountId: null,
+            fromUserName: null,
+            toUserName: null
+        },
+        currentContact: {}
     };
   },
   created() {
@@ -281,6 +416,59 @@ export default {
         this.title = "修改个微账号";
       });
     },
+
+      login(row, ipadOrMac){
+          getWxQrCode({accountId: row.id, ipadOrMac}).then(e => {
+              this.loginData.open = true;
+              this.loginData.url = e.data;
+              this.loginStatus(row.id);
+          });
+      },
+      wkUp(row){
+          wakeUpLogin({accountId: row.id}).then(e => {
+              this.loginStatus(row.id);
+          });
+      },
+      loginStatus(id){
+          this.loginQwInterval = setInterval(() => {
+              this.getLoginStatusFun(id);
+          }, 3000);
+      },
+      getLoginStatusFun(id){
+          getLoginStatus({accountId: id}).then(res => {
+              if(res.data){
+                  this.loginData.open = false;
+                  this.getList();
+                  clearInterval(this.loginQwInterval);
+              }
+          });
+      },
+      updateWxInfoFun(id){
+          updateWxInfo({accountId: id}).then(res => {
+              if(res.data){
+                  this.loginData.open = false;
+                  this.getList();
+                  clearInterval(this.loginQwInterval);
+              }
+          });
+      },
+      wxLoginOutFun(id){
+          wxLoginOut({accountId: id}).then(res => {
+              if(res.data){
+                  this.getList();
+              }
+          });
+      },
+      syncWxFun(id){
+          syncWx({accountId: id}).then(res => {
+              this.msgSuccess("正在同步......");
+          });
+      },
+      bind(row){
+          bindService({accountId: row.id}).then(e => {
+              this.getList();
+          })
+      },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {
@@ -332,20 +520,107 @@ export default {
           this.download(response.msg);
         }).catch(function() {});
     },
-    /** 同步客户按钮操作 */
-    handleSyncCustomer(row) {
-      this.$confirm('是否确认同步该个微账号的客户数据?', "提示", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          return syncWx({ accountId: row.id });
-        }).then(response => {
-          if (response.code === 200) {
-            this.msgSuccess("同步客户成功");
-          }
-        }).catch(function() {});
-    },
+      /** 同步客户按钮操作 */
+      handleSyncCustomer(row) {
+          this.$confirm('是否确认同步该个微账号的客户数据?', "提示", {
+              confirmButtonText: "确定",
+              cancelButtonText: "取消",
+              type: "warning"
+          }).then(() => {
+              return syncWx({ accountId: row.id });
+          }).then(response => {
+              if (response.code === 200) {
+                  this.msgSuccess("同步客户成功");
+              }
+          }).catch(function() {});
+      },
+      /** 打开联系人弹窗 */
+      handleContactList(row) {
+          this.contactQueryParams.pageNum = 1;
+          this.contactQueryParams.accountId = row.id;
+          this.contactOpen = true;
+          this.getContactList();
+      },
+      /** 查询联系人列表 */
+      getContactList() {
+          listWxContact(this.contactQueryParams).then(response => {
+              this.contactList = response.rows;
+              this.contactTotal = response.total;
+          });
+      },
+      /** 打开聊天记录弹窗 */
+      handleChatHistory(contactRow) {
+          this.currentContact = contactRow;
+          this.chatQueryParams.pageNum = 1;
+          this.chatQueryParams.accountId = this.contactQueryParams.accountId;
+          this.chatOpen = true;
+          this.getChatList();
+      },
+      /** 查询聊天记录列表 */
+      getChatList() {
+          listWxMsgLog(this.chatQueryParams).then(response => {
+              this.chatList = response.rows;
+              this.chatTotal = response.total;
+          });
+      },
   }
 };
 </script>
+
+<style scoped>
+.chat-container {
+    background: #f5f5f5;
+    padding: 16px;
+    max-height: 500px;
+    overflow-y: auto;
+}
+.chat-item {
+    display: flex;
+    margin-bottom: 16px;
+    align-items: flex-start;
+}
+.chat-item.self {
+    justify-content: flex-end;
+}
+.chat-bubble {
+    max-width: 60%;
+    padding: 10px 14px;
+    border-radius: 8px;
+    font-size: 14px;
+    line-height: 1.5;
+    word-break: break-all;
+    position: relative;
+}
+.chat-bubble.left {
+    background: #fff;
+    margin-left: 10px;
+}
+.chat-bubble.left::before {
+    content: '';
+    position: absolute;
+    left: -8px;
+    top: 10px;
+    border-width: 6px 8px 6px 0;
+    border-style: solid;
+    border-color: transparent #fff transparent transparent;
+}
+.chat-bubble.right {
+    background: #95ec69;
+    margin-right: 10px;
+}
+.chat-bubble.right::after {
+    content: '';
+    position: absolute;
+    right: -8px;
+    top: 10px;
+    border-width: 6px 0 6px 8px;
+    border-style: solid;
+    border-color: transparent transparent transparent #95ec69;
+}
+.chat-time {
+    text-align: center;
+    color: #999;
+    font-size: 12px;
+    margin-bottom: 10px;
+}
+</style>

+ 16 - 2
src/views/course/courseWatchLog/deptWatchLog.vue

@@ -467,7 +467,12 @@
 <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
 <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
 <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+          <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+              <template slot-scope="scope">
+                  <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+              </template>
+          </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -536,6 +541,7 @@ import {infoSop} from "@/api/qw/sop";
 import {myDeptTreeselect} from "../../../api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -545,6 +551,7 @@ export default {
   components: {Treeselect},
   data() {
     return {
+      RewardType: 1,
       companyName:process.env.VUE_APP_COURSE_COMPANY_NAME,
       sopSearchText: '', // SOP搜索框显示的文本
       selectedSopId: null, // 选中的SOP ID
@@ -698,6 +705,9 @@ export default {
     };
   },
   created() {
+
+    this.getCourseByRewardType();
+
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -716,7 +726,11 @@ export default {
     this.loading=false;
   },
   methods: {
-
+      getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
     showContentDialog(questionJson){
       // 解析 JSON 字符串为 JavaScript 对象
       // 替换非法换行符等控制字符

+ 14 - 1
src/views/course/courseWatchLog/index.vue

@@ -546,7 +546,12 @@
 <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
 <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
 <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+          <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+              <template slot-scope="scope">
+                  <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+              </template>
+          </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -717,6 +722,7 @@ import {getQwList} from "@/api/qw/qwUser";
 import {treeselect} from "@/api/company/companyDept";
 import Treeselect from "@riophae/vue-treeselect";
 import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -725,6 +731,7 @@ export default {
   components: {Treeselect },
   data() {
     return {
+      RewardType: 1,
       watchTypeList: [
         { dictLabel: 'app', dictValue: 1 },
         { dictLabel: '小程序', dictValue: 2 }
@@ -935,6 +942,7 @@ export default {
     };
   },
   created() {
+    this.getCourseByRewardType();
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -959,6 +967,11 @@ export default {
     this.loading=false;
   },
   methods: {
+    getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
 
     showContentDialog(questionJson){
       // 解析 JSON 字符串为 JavaScript 对象

+ 15 - 1
src/views/course/courseWatchLog/watchLog.vue

@@ -622,7 +622,12 @@
         <!--        <el-table-column label="会员电话" align="center" prop="phone" />-->
         <!--        <el-table-column label="所属销售" align="center" prop="companyUserName" />-->
         <!--        <el-table-column label="所属公司" align="center" prop="companyName" />-->
-        <el-table-column label="转账金额" align="center" prop="amount" />
+<!--        <el-table-column label="转账金额" align="center" prop="amount" />-->
+        <el-table-column :label="RewardType === 1 ? '转账金额' : '积分发放(红包转)'" align="center">
+          <template slot-scope="scope">
+              <span>{{ RewardType === 1 ? scope.row.amount : scope.row.amount * 1000 }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="状态" align="center" prop="status" >
           <template slot-scope="scope">
             <el-tag>
@@ -829,6 +834,7 @@ import {allListTagGroup} from "../../../api/qw/tagGroup";
 import Vue from 'vue'
 import Calendar from 'vue-mobile-calendar'
 import {infoSop} from "@/api/qw/sop";
+import {getCourseConfigByRewardType} from "../../../api/system/config";
 
 Vue.use(Calendar)
 
@@ -836,6 +842,7 @@ export default {
   name: "CourseWatchLog",
   data() {
     return {
+      RewardType: 1,
       watchTypeList: [
         { dictLabel: 'app', dictValue: 1 },
         { dictLabel: '小程序', dictValue: 2 }
@@ -1027,6 +1034,7 @@ export default {
     };
   },
   created() {
+    this.getCourseByRewardType();
     courseList().then(response => {
       this.courseLists = response.list;
     });
@@ -1058,6 +1066,12 @@ export default {
 
   },
   methods: {
+      getCourseByRewardType(){
+          getCourseConfigByRewardType().then(res=>{
+              this.RewardType=res.rewardType;
+          })
+      },
+
     copyText(text, event) {
       const clipboard = new ClipboardJS(event.currentTarget, {
         text: () => text,

+ 1 - 1
src/views/crm/components/addOrEditCustomer.vue

@@ -1,4 +1,4 @@
-<template>
+ <template>
     <div class="customer-dialog-content">
         <div class="dialog-header">
             <i class="el-icon-user header-icon"></i>

+ 194 - 18
src/views/gw/gwAccount/index.vue

@@ -100,23 +100,23 @@
             @click="bind(scope.row)"
           >获取PAD</el-button>
           <el-button
-            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"
-            size="mini"
-            type="text"
-            @click="login(scope.row, 'ipad')"
-          >登录IPAD</el-button>
-          <el-button
-            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"
-            size="mini"
-            type="text"
-            @click="login(scope.row, 'mac')"
-          >登录Mac</el-button>
-          <el-button
-            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"
-            size="mini"
-            type="text"
-            @click="wkUp(scope.row)"
-          >唤醒登录</el-button>
+<!--            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="login(scope.row, 'ipad')"-->
+<!--          >登录IPAD</el-button>-->
+<!--          <el-button-->
+<!--            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="login(scope.row, 'mac')"-->
+<!--          >登录Mac</el-button>-->
+<!--          <el-button-->
+<!--            v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 0"-->
+<!--            size="mini"-->
+<!--            type="text"-->
+<!--            @click="wkUp(scope.row)"-->
+<!--          >唤醒登录</el-button>-->
           <el-button
             v-if="scope.row.serverStatus == 1 && scope.row.loginStatus == 1"
             size="mini"
@@ -142,6 +142,7 @@
             @click="handleDelete(scope.row)"
             v-hasPermi="['company:companyWx:remove']"
           >删除</el-button>
+          <el-button size="mini" type="text" @click="handleContactList(scope.row)">联系人</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -196,6 +197,71 @@
         <p class="qr-login-instructions">使用微信扫码授权登录</p>
       </div>
     </el-dialog>
+
+    <!-- 联系人列表弹窗 -->
+    <el-dialog title="联系人列表" :visible.sync="contactOpen" width="800px" append-to-body>
+      <el-table :data="contactList">
+        <el-table-column label="头像" align="center" width="70">
+          <template slot-scope="scope">
+            <img v-if="scope.row.headImgUrl" :src="scope.row.headImgUrl" style="width:40px;height:40px;border-radius:50%;object-fit:cover;" />
+            <el-avatar v-else :size="40" icon="el-icon-user-solid" />
+          </template>
+        </el-table-column>
+        <el-table-column label="微信昵称" align="center" prop="nickName" />
+        <el-table-column label="微信号" align="center" prop="alias" />
+        <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
+        <el-table-column label="是否好友" align="center" width="90">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.friends == 1" type="success" size="small">是</el-tag>
+            <el-tag v-else type="danger" size="small">否</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="创建时间" align="center" prop="createTime" width="160">
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.createTime) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="100">
+          <template slot-scope="scope">
+            <el-button size="mini" type="text" @click="handleChatHistory(scope.row)">聊天记录</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination
+        v-show="contactTotal > 0"
+        :total="contactTotal"
+        :page.sync="contactQueryParams.pageNum"
+        :limit.sync="contactQueryParams.pageSize"
+        @pagination="getContactList"
+      />
+    </el-dialog>
+
+    <!-- 聊天记录弹窗 -->
+    <el-dialog :title="'聊天记录 - ' + (currentContact.nickName || '')" :visible.sync="chatOpen" width="700px" append-to-body>
+      <div class="chat-container">
+        <div v-for="(msg, index) in chatList" :key="index">
+          <div class="chat-time">{{ parseTime(msg.createTime) }}</div>
+          <!-- 收到的消息 - 左侧 -->
+          <div v-if="msg.receiveType == 0" class="chat-item">
+            <el-avatar :size="36" icon="el-icon-user-solid" />
+            <div class="chat-bubble left">{{ msg.content }}</div>
+          </div>
+          <!-- 发出的消息 - 右侧 -->
+          <div v-else class="chat-item self">
+            <div class="chat-bubble right">{{ msg.content }}</div>
+            <el-avatar :size="36" icon="el-icon-user-solid" style="background: #95ec69;" />
+          </div>
+        </div>
+        <div v-if="chatList.length === 0" style="text-align:center; color:#999; padding: 40px 0;">暂无聊天记录</div>
+      </div>
+      <pagination
+        v-show="chatTotal > 0"
+        :total="chatTotal"
+        :page.sync="chatQueryParams.pageNum"
+        :limit.sync="chatQueryParams.pageSize"
+        @pagination="getChatList"
+      />
+    </el-dialog>
   </div>
 </template>
 
@@ -217,6 +283,8 @@ import {
   bindService
 } from '@/api/company/companyAccount'
 import { qrCodeStatus } from '@/api/qw/user'
+import { listWxContact } from "@/api/wx/wxContact"
+import { listWxMsgLog } from "@/api/wx/wxMsgLog"
 
 
 export default {
@@ -260,7 +328,28 @@ export default {
       form: {},
       // 表单校验
       rules: {
-      }
+      },
+      // 联系人弹窗
+      contactOpen: false,
+      contactList: [],
+      contactTotal: 0,
+      contactQueryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        accountId: null
+      },
+      // 聊天记录弹窗
+      chatOpen: false,
+      chatList: [],
+      chatTotal: 0,
+      chatQueryParams: {
+        pageNum: 1,
+        pageSize: 20,
+        accountId: null,
+        fromUserName: null,
+        toUserName: null
+      },
+      currentContact: {}
     };
   },
   created() {
@@ -431,6 +520,93 @@ export default {
           this.download(response.msg);
         }).catch(function() {});
     },
+    /** 打开联系人弹窗 */
+    handleContactList(row) {
+      this.contactQueryParams.pageNum = 1;
+      this.contactQueryParams.accountId = row.id;
+      this.contactOpen = true;
+      this.getContactList();
+    },
+    /** 查询联系人列表 */
+    getContactList() {
+      listWxContact(this.contactQueryParams).then(response => {
+        this.contactList = response.rows;
+        this.contactTotal = response.total;
+      });
+    },
+    /** 打开聊天记录弹窗 */
+    handleChatHistory(contactRow) {
+      this.currentContact = contactRow;
+      this.chatQueryParams.pageNum = 1;
+      this.chatQueryParams.accountId = this.contactQueryParams.accountId;
+      this.chatOpen = true;
+      this.getChatList();
+    },
+    /** 查询聊天记录列表 */
+    getChatList() {
+      listWxMsgLog(this.chatQueryParams).then(response => {
+        this.chatList = response.rows;
+        this.chatTotal = response.total;
+      });
+    },
   }
 };
 </script>
+
+<style scoped>
+.chat-container {
+  background: #f5f5f5;
+  padding: 16px;
+  max-height: 500px;
+  overflow-y: auto;
+}
+.chat-item {
+  display: flex;
+  margin-bottom: 16px;
+  align-items: flex-start;
+}
+.chat-item.self {
+  justify-content: flex-end;
+}
+.chat-bubble {
+  max-width: 60%;
+  padding: 10px 14px;
+  border-radius: 8px;
+  font-size: 14px;
+  line-height: 1.5;
+  word-break: break-all;
+  position: relative;
+}
+.chat-bubble.left {
+  background: #fff;
+  margin-left: 10px;
+}
+.chat-bubble.left::before {
+  content: '';
+  position: absolute;
+  left: -8px;
+  top: 10px;
+  border-width: 6px 8px 6px 0;
+  border-style: solid;
+  border-color: transparent #fff transparent transparent;
+}
+.chat-bubble.right {
+  background: #95ec69;
+  margin-right: 10px;
+}
+.chat-bubble.right::after {
+  content: '';
+  position: absolute;
+  right: -8px;
+  top: 10px;
+  border-width: 6px 0 6px 8px;
+  border-style: solid;
+  border-color: transparent transparent transparent #95ec69;
+}
+.chat-time {
+  text-align: center;
+  color: #999;
+  font-size: 12px;
+  margin-bottom: 10px;
+}
+</style>

+ 135 - 15
src/views/wx/wxSop/index.vue

@@ -93,6 +93,16 @@
           v-hasPermi="['qw:sop:batchExecuteWx']"
         >批量执行个微SOP</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-s-promotion"
+          size="mini"
+          :disabled="multiple"
+          @click="handleSendMsg"
+        >SOP营期一键群发</el-button>
+      </el-col>
     </el-row>
 
     <el-table border v-loading="loading" :data="wxSopList" @selection-change="handleSelectionChange">
@@ -361,18 +371,87 @@
 
       <qw-user-select @success="selectQwUserFun" ref="qwUserSelect" />
     </el-dialog>
+
+    <!-- 一键群发弹窗 -->
+    <el-dialog title="一键批量群发" :visible.sync="sendMsgOpen" width="600px" append-to-body>
+      <el-alert
+        title="此功能用于给 选中的 SOP营期 内【所有的】客户 发送 消息"
+        type="error"
+        :closable="false"
+        show-icon
+        style="margin-bottom: 20px;"
+      />
+
+      <el-form label-width="80px">
+        <el-form-item label="规则">
+          <div style="border: 1px solid #DCDFE6; border-radius: 4px; padding: 15px;">
+            <div style="margin-bottom: 10px;">
+              <span style="margin-right: 10px;">内容类别</span>
+              <el-radio :value="'1'" :label="'1'" disabled>文本</el-radio>
+            </div>
+            <el-form-item label="内容" label-width="60px" style="margin-bottom: 0;">
+              <el-input
+                v-model="sendMsgForm.content"
+                type="textarea"
+                :rows="4"
+                placeholder="内容"
+                maxlength="500"
+                show-word-limit
+              />
+              <div style="margin-top: 8px;">
+                <el-link type="primary" :underline="false" @click="addPlaceholder('#销售称呼#')">添加#销售称呼#</el-link>
+                <span style="margin: 0 10px;"></span>
+                <el-link type="primary" :underline="false" @click="addPlaceholder('#客户称呼#')">添加#客户称呼#</el-link>
+              </div>
+            </el-form-item>
+          </div>
+        </el-form-item>
+
+        <el-form-item label="发送时间">
+          <el-time-picker
+            v-model="sendMsgForm.sendTime"
+            format="HH:mm"
+            value-format="HH:mm"
+            placeholder="时间"
+            size="small"
+            style="width: 120px;"
+          />
+          <span style="margin-left: 10px; color: #909399; font-size: 13px;">不填时,默认为系统当前时间(立即发送)</span>
+        </el-form-item>
+      </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitSendMsg">确 定</el-button>
+        <el-button @click="sendMsgOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 执行详情抽屉 -->
+    <el-drawer
+      :title="'执行详情 - ' + (detailRow ? detailRow.name : '')"
+      :visible.sync="detailDrawerVisible"
+      direction="rtl"
+      size="75%"
+      destroy-on-close
+    >
+      <sop-logs-list
+        v-if="detailDrawerVisible && detailRow"
+        :row-detail-from="detailRow"
+      />
+    </el-drawer>
   </div>
 </template>
 
 <script>
-import { listWxSop, getWxSop, delWxSop, addWxSop, updateWxSop, exportWxSop, updateWxStatus } from "@/api/wx/wxSop";
+import { listWxSop, getWxSop, delWxSop, addWxSop, updateWxSop, exportWxSop, updateWxStatus, sendWxSopMsg } from "@/api/wx/wxSop";
 import { listSopTemp } from "@/api/qw/sopTemp";
 import Tip from "@/components/Tip";
 import QwUserSelect from "@/views/components/QwUserSelect.vue";
+import SopLogsList from "./sopLogsList.vue";
 
 export default {
   name: "WxSop",
-  components: { Tip, QwUserSelect },
+  components: { Tip, QwUserSelect, SopLogsList },
   data() {
     return {
       // 遮罩层
@@ -426,6 +505,15 @@ export default {
         { dictValue: "0", dictLabel: "否" },
         { dictValue: "1", dictLabel: "是" },
       ],
+      // 执行详情抽屉
+      detailDrawerVisible: false,
+      detailRow: null,
+      // 一键群发
+      sendMsgOpen: false,
+      sendMsgForm: {
+        content: '',
+        sendTime: null
+      },
       // 表单参数
       form: {},
       // 表单校验
@@ -613,13 +701,8 @@ export default {
     },
     /** 查看执行详情按钮操作 */
     handleViewDetail(row) {
-      this.$router.push({
-        path: '/wxSop/wxSop/sopLogsList/' + row.id,
-        query: {
-          name: row.name,
-          tempId: row.tempId
-        }
-      });
+      this.detailRow = row;
+      this.detailDrawerVisible = true;
     },
     /** 删除按钮操作 */
     handleDelete(row) {
@@ -643,7 +726,7 @@ export default {
       }
 
       // 过滤出启用状态的SOP
-      const enabledSops = this.wxSopList.filter(item => 
+      const enabledSops = this.wxSopList.filter(item =>
         this.ids.includes(item.id) && item.status === 1
       );
 
@@ -670,25 +753,25 @@ export default {
         return updateWxStatus(enabledIds.join(","));
       }).then(response => {
         this.loading = false;
-        
+
         console.log('批量执行个微SOP接口返回数据:', response);
-        
+
         // 解析后端返回的统计信息
         if (response && response.msg) {
           const successCount = response.data?.successCount || enabledIds.length;
           const failCount = response.data?.failCount || 0;
-          
+
           let msg = `批量执行完成!`;
           msg += `\n成功:${successCount}条`;
           if (failCount > 0) {
             msg += `\n失败:${failCount}条`;
           }
-          
+
           this.msgSuccess(msg);
         } else {
           this.msgSuccess(`已成功执行${enabledIds.length}条个微SOP`);
         }
-        
+
         // 刷新列表
         this.getList();
       }).catch(error => {
@@ -699,6 +782,43 @@ export default {
         }
       });
     },
+    /** 一键群发按钮操作 */
+    handleSendMsg() {
+      if (this.ids.length === 0) {
+        this.$message.warning("请至少选择一条SOP记录");
+        return;
+      }
+      this.sendMsgForm.content = '';
+      this.sendMsgForm.sendTime = null;
+      this.sendMsgOpen = true;
+    },
+    /** 添加占位符 */
+    addPlaceholder(text) {
+      this.sendMsgForm.content += text;
+    },
+    /** 提交一键群发 */
+    submitSendMsg() {
+      if (!this.sendMsgForm.content || !this.sendMsgForm.content.trim()) {
+        this.$message.warning("请输入发送内容");
+        return;
+      }
+
+      this.$confirm('确认对选中的SOP营期内所有客户发送消息吗?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        const param = {
+          sopIds: this.ids,
+          setting: JSON.stringify([{ contentType: '1', value: this.sendMsgForm.content }]),
+          sendTime: this.sendMsgForm.sendTime || ''
+        };
+        sendWxSopMsg(param).then(response => {
+          this.msgSuccess("一键群发成功");
+          this.sendMsgOpen = false;
+        });
+      }).catch(() => {});
+    },
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有个微SOP数据项?', "警告", {

+ 381 - 272
src/views/wx/wxSop/sopLogsList.vue

@@ -1,295 +1,404 @@
 <template>
-  <div class="app-container">
-    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
-      <el-form-item label="个微账号昵称" prop="accountName">
-        <el-input
-          v-model="queryParams.accountName"
-          placeholder="请输入个微账号昵称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="客户昵称" prop="wxContactName">
-        <el-input
-          v-model="queryParams.wxContactName"
-          placeholder="请输入客户昵称"
-          clearable
-          size="small"
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
-      <el-form-item label="发送状态" prop="sendStatus">
-        <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable size="small">
-          <el-option
-            v-for="dict in sendStatusOptions"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="发送类型" prop="sendType">
-        <el-select v-model="queryParams.sendType" placeholder="请选择发送类型" clearable size="small">
-          <el-option
-            v-for="dict in sysQwSopType"
-            :key="dict.dictValue"
-            :label="dict.dictLabel"
-            :value="dict.dictValue"
-          />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="消息类型" prop="type">
-        <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
-          <el-option label="个人" :value="0" />
-          <el-option label="群" :value="1" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="预计发送时间" prop="scheduleTime">
-        <el-date-picker
-          clearable
-          size="small"
-          v-model="scheduleTime"
-          type="datetimerange"
-          range-separator="至"
-          start-placeholder="开始日期"
-          end-placeholder="结束日期"
-          placeholder="选择预计发送时间"
-          @change="handleScheduleTimeChange"
-        >
-        </el-date-picker>
-      </el-form-item>
-      <el-form-item>
-        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
-        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
-      </el-form-item>
-    </el-form>
+    <div class="app-container">
+        <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
+            <el-form-item label="个微账号昵称" prop="accountName">
+                <el-input
+                    v-model="queryParams.accountName"
+                    placeholder="请输入个微账号昵称"
+                    clearable
+                    size="small"
+                    @keyup.enter.native="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="客户昵称" prop="wxContactName">
+                <el-input
+                    v-model="queryParams.wxContactName"
+                    placeholder="请输入客户昵称"
+                    clearable
+                    size="small"
+                    @keyup.enter.native="handleQuery"
+                />
+            </el-form-item>
+            <el-form-item label="发送状态" prop="sendStatus">
+                <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态" clearable size="small">
+                    <el-option
+                        v-for="dict in sendStatusOptions"
+                        :key="dict.dictValue"
+                        :label="dict.dictLabel"
+                        :value="dict.dictValue"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="发送类型" prop="sendType">
+                <el-select v-model="queryParams.sendType" placeholder="请选择发送类型" clearable size="small">
+                    <el-option
+                        v-for="dict in sysQwSopType"
+                        :key="dict.dictValue"
+                        :label="dict.dictLabel"
+                        :value="dict.dictValue"
+                    />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="消息类型" prop="type">
+                <el-select v-model="queryParams.type" placeholder="请选择消息类型" clearable size="small">
+                    <el-option label="个人" :value="0"/>
+                    <el-option label="群" :value="1"/>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="预计发送时间" prop="scheduleTime">
+                <el-date-picker
+                    clearable
+                    size="small"
+                    v-model="scheduleTime"
+                    type="datetimerange"
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期"
+                    placeholder="选择预计发送时间"
+                    @change="handleScheduleTimeChange"
+                >
+                </el-date-picker>
+            </el-form-item>
+            <el-form-item>
+                <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+                <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+            </el-form-item>
+        </el-form>
+
+        <el-row :gutter="10" class="mb8">
+            <el-col :span="1.5">
+                <el-button
+                    type="warning"
+                    plain
+                    icon="el-icon-download"
+                    size="mini"
+                    :loading="exportLoading"
+                    @click="handleExport"
+                    v-hasPermi="['wx:wxSopLogs:export']"
+                >导出
+                </el-button>
+            </el-col>
+            <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
 
-    <el-row :gutter="10" class="mb8">
-      <el-col :span="1.5">
-        <el-button
-          type="warning"
-          plain
-          icon="el-icon-download"
-          size="mini"
-          :loading="exportLoading"
-          @click="handleExport"
-          v-hasPermi="['wx:wxSopLogs:export']"
-        >导出</el-button>
-      </el-col>
-      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
-    </el-row>
+        <el-table border v-loading="loading" :data="wxSopLogsList">
+            <el-table-column label="编号" align="center" prop="id" width="80"/>
+            <el-table-column label="个微账号昵称" align="center" prop="accountName"/>
+            <el-table-column label="客户昵称" align="center" prop="wxContactName"/>
+            <el-table-column label="客户标签" align="center" prop="tagNames" show-overflow-tooltip>
+                <template slot-scope="scope">
+                    <span v-if="scope.row.tagNames">{{ scope.row.tagNames }}</span>
+                    <span v-else style="color: #909399;">-</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="发送状态" align="center" prop="sendStatus" width="100">
+                <template slot-scope="scope">
+                    <el-tag type="info" v-if="scope.row.sendStatus === 0">待发送</el-tag>
+                    <el-tag type="success" v-else-if="scope.row.sendStatus === 1">发送成功</el-tag>
+                    <el-tag type="danger" v-else-if="scope.row.sendStatus === 2">发送失败</el-tag>
+                    <el-tag type="warning" v-else-if="scope.row.sendStatus === 3">消息作废</el-tag>
+                    <el-tag v-else>未知</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="发送类型" align="center" prop="sendType" width="100">
+                <template slot-scope="scope">
+                    <dict-tag :options="sysQwSopType" :value="scope.row.sendType"/>
+                </template>
+            </el-table-column>
+            <el-table-column label="生成类型" align="center" prop="generateType" width="100">
+                <template slot-scope="scope">
+                    <el-tag type="success" v-if="scope.row.generateType === 0">自动</el-tag>
+                    <el-tag type="primary" v-else-if="scope.row.generateType === 1">手动</el-tag>
+                    <el-tag v-else>未知</el-tag>
+                </template>
+            </el-table-column>
+            <el-table-column label="生成时间" align="center" prop="createTime" width="165"/>
+            <el-table-column label="预计发送时间" align="center" prop="sendTime" width="165">
+                <template slot-scope="scope">
+                    <span v-if="scope.row.sendTime">{{ scope.row.sendTime }}</span>
+                    <span v-else style="color: #909399;">-</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="实际发送时间" align="center" prop="realSendTime" width="165">
+                <template slot-scope="scope">
+                    <span v-if="scope.row.realSendTime">{{ scope.row.realSendTime }}</span>
+                    <span v-else style="color: #909399;">-</span>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="100">
+                <template slot-scope="scope">
+                    <el-button
+                        v-if="scope.row.contentJson"
+                        type="text"
+                        size="mini"
+                        @click="showContentDialog(scope.row.contentJson)"
+                    >查看详情
+                    </el-button>
+                    <span v-else style="color: #909399;">-</span>
+                </template>
+            </el-table-column>
+        </el-table>
 
-    <el-table border v-loading="loading" :data="wxSopLogsList">
-      <el-table-column label="编号" align="center" prop="id" width="80" />
-      <el-table-column label="个微账号昵称" align="center" prop="accountName" />
-      <el-table-column label="客户昵称" align="center" prop="wxContactName" />
-      <el-table-column label="客户标签" align="center" prop="tagNames" show-overflow-tooltip>
-        <template slot-scope="scope">
-          <span v-if="scope.row.tagNames">{{ scope.row.tagNames }}</span>
-          <span v-else style="color: #909399;">-</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="发送状态" align="center" prop="sendStatus" width="100">
-        <template slot-scope="scope">
-          <el-tag type="info" v-if="scope.row.sendStatus === 0">待发送</el-tag>
-          <el-tag type="success" v-else-if="scope.row.sendStatus === 1">发送成功</el-tag>
-          <el-tag type="danger" v-else-if="scope.row.sendStatus === 2">发送失败</el-tag>
-          <el-tag type="warning" v-else-if="scope.row.sendStatus === 3">消息作废</el-tag>
-          <el-tag v-else>未知</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="发送类型" align="center" prop="sendType" width="100">
-        <template slot-scope="scope">
-          <dict-tag :options="sysQwSopType" :value="scope.row.sendType"/>
-        </template>
-      </el-table-column>
-      <el-table-column label="生成类型" align="center" prop="generateType" width="100">
-        <template slot-scope="scope">
-          <el-tag type="success" v-if="scope.row.generateType === 0">自动</el-tag>
-          <el-tag type="primary" v-else-if="scope.row.generateType === 1">手动</el-tag>
-          <el-tag v-else>未知</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column label="生成时间" align="center" prop="createTime" width="165" />
-      <el-table-column label="实际发送时间" align="center" prop="realSendTime" width="165">
-        <template slot-scope="scope">
-          <span v-if="scope.row.realSendTime">{{ scope.row.realSendTime }}</span>
-          <span v-else style="color: #909399;">-</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="消息过期时间" align="center" prop="expirationTime" width="165">
-        <template slot-scope="scope">
-          <span v-if="scope.row.expirationTime">{{ scope.row.expirationTime }}</span>
-          <span v-else style="color: #909399;">-</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="发送备注" align="center" prop="sendRemark" show-overflow-tooltip>
-        <template slot-scope="scope">
-          <span v-if="scope.row.sendRemark">{{ scope.row.sendRemark }}</span>
-          <span v-else style="color: #909399;">-</span>
-        </template>
-      </el-table-column>
-    </el-table>
+        <!-- 消息详情弹窗 -->
+        <el-dialog title="发送消息详情" :visible.sync="contentDialogVisible" width="800px" append-to-body>
+            <div v-if="contentDialogData && contentDialogData.length > 0" class="content-detail">
+                <div v-for="(item, index) in contentDialogData" :key="index" class="content-item">
+                    <div class="content-type-tag">
+                        <el-tag size="small" :type="getContentTypeTag(item.contentType).type">
+                            {{ getContentTypeTag(item.contentType).label }}
+                        </el-tag>
+                        <el-tag
+                            v-if="item.sendStatus !== undefined && item.sendStatus !== null"
+                            size="small"
+                            :type="item.sendStatus === 1 ? 'success' : item.sendStatus === 2 ? 'danger' : 'warning'"
+                            style="margin-left: 8px;"
+                        >
+                            {{ item.sendStatus === 0 ? '待发送' : item.sendStatus === 1 ? '发送成功' : '发送失败' }}
+                        </el-tag>
+                    </div>
+                    <div v-if="item.sendStatus === 2 && item.sendRemarks" style="color: #F56C6C; font-size: 13px; margin-bottom: 8px;">
+                        <i class="el-icon-warning-outline"></i> {{ item.sendRemarks }}
+                    </div>
+                    <div class="content-body">
+                        <!-- 文本内容 -->
+                        <div v-if="item.contentType === '1' || item.contentType === 1" class="text-content">
+                            {{ item.value }}
+                        </div>
+                        <!-- 图片 -->
+                        <div v-else-if="item.contentType === '2' || item.contentType === 2">
+                            <el-image :src="item.imgUrl || item.value" style="max-width: 200px; max-height: 200px;"
+                                      fit="contain"
+                            />
+                        </div>
+                        <!-- 其他类型显示JSON -->
+                        <div v-else class="text-content">
+                            <pre style="white-space: pre-wrap; word-break: break-all; margin: 0;"
+                            >{{ JSON.stringify(item, null, 2) }}</pre>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div v-else style="text-align: center; color: #909399; padding: 20px;">暂无消息内容</div>
+        </el-dialog>
 
-    <pagination
-      v-show="total>0"
-      :total="total"
-      :page.sync="queryParams.pageNum"
-      :limit.sync="queryParams.pageSize"
-      @pagination="getList"
-    />
-  </div>
+        <pagination
+            v-show="total>0"
+            :total="total"
+            :page.sync="queryParams.pageNum"
+            :limit.sync="queryParams.pageSize"
+            @pagination="getList"
+        />
+    </div>
 </template>
 
 <script>
-import { listWxSopLogsCVO, exportWxSopLogsCVO } from "@/api/wx/wxSopLogs";
+import { listWxSopLogsCVO, exportWxSopLogsCVO } from '@/api/wx/wxSopLogs'
 
 export default {
-  name: "WxSopLogsList",
-  props: {
-    rowDetailFrom: {
-      type: Object,
-      default: () => ({})
-    }
-  },
-  watch: {
-    rowDetailFrom: {
-      handler(newVal) {
-        // 当formData变化时重新查询
-        this.getList(newVal);
-      },
-      deep: true
-    }
-  },
-  data() {
-    return {
-      // 时间选择
-      scheduleTime: null,
-      // 遮罩层
-      loading: true,
-      // 导出遮罩层
-      exportLoading: false,
-      // 显示搜索条件
-      showSearch: true,
-      // 总条数
-      total: 0,
-      // 个微SOP执行详情表格数据
-      wxSopLogsList: [],
-      // 发送状态字典
-      sendStatusOptions: [
-        { dictValue: '0', dictLabel: '待发送', listClass: 'default' },
-        { dictValue: '1', dictLabel: '发送成功', listClass: 'success' },
-        { dictValue: '2', dictLabel: '发送失败', listClass: 'danger' },
-        { dictValue: '3', dictLabel: '消息作废', listClass: 'warning' }
-      ],
-      // 企微SOP发送类型
-      sysQwSopType: [],
-      // 查询参数
-      queryParams: {
-        pageNum: 1,
-        pageSize: 10,
-        sopId: null,
-        accountName: null,
-        wxContactName: null,
-        sendStatus: null,
-        sendType: null,
-        type: null,
-        scheduleStartTime: null,
-        scheduleEndTime: null
-      }
-    };
-  },
-  created() {
-    this.getList(this.rowDetailFrom);
-    
-    // 发送消息类型
-    this.getDicts("sys_qw_sop_course_type").then(response => {
-      this.sysQwSopType = response.data;
-    });
-  },
-  methods: {
-    /** 查询个微SOP执行详情列表 */
-    getList(val) {
-      this.queryParams.sopId = val?.id || this.rowDetailFrom?.id;
-      this.loading = true;
-
-      listWxSopLogsCVO(this.queryParams).then(response => {
-        console.log('个微SOP执行详情接口返回数据:', response);
-        this.wxSopLogsList = response.rows;
-        this.total = response.total;
-        this.loading = false;
-      }).catch(error => {
-        console.error('查询个微SOP执行详情失败:', error);
-        this.loading = false;
-      });
+    name: 'WxSopLogsList',
+    props: {
+        rowDetailFrom: {
+            type: Object,
+            default: () => ({})
+        }
+    },
+    watch: {
+        rowDetailFrom: {
+            handler(newVal) {
+                // 当formData变化时重新查询
+                this.getList(newVal)
+            },
+            deep: true
+        }
+    },
+    data() {
+        return {
+            // 时间选择
+            scheduleTime: null,
+            // 遮罩层
+            loading: true,
+            // 导出遮罩层
+            exportLoading: false,
+            // 显示搜索条件
+            showSearch: true,
+            // 总条数
+            total: 0,
+            // 个微SOP执行详情表格数据
+            wxSopLogsList: [],
+            // 发送状态字典
+            sendStatusOptions: [
+                { dictValue: '0', dictLabel: '待发送', listClass: 'default' },
+                { dictValue: '1', dictLabel: '发送成功', listClass: 'success' },
+                { dictValue: '2', dictLabel: '发送失败', listClass: 'danger' },
+                { dictValue: '3', dictLabel: '消息作废', listClass: 'warning' }
+            ],
+            // 企微SOP发送类型
+            sysQwSopType: [],
+            // 消息详情弹窗
+            contentDialogVisible: false,
+            contentDialogData: [],
+            // 查询参数
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                sopId: null,
+                accountName: null,
+                wxContactName: null,
+                sendStatus: null,
+                sendType: null,
+                type: null,
+                scheduleStartTime: null,
+                scheduleEndTime: null
+            }
+        }
     },
+    created() {
+        this.getList(this.rowDetailFrom)
 
-    handleScheduleTimeChange(val) {
-      if (val) {
-        this.queryParams.scheduleStartTime = this.formatDateTime(val[0]);
-        this.queryParams.scheduleEndTime = this.formatDateTime(val[1]);
-      } else {
-        this.queryParams.scheduleStartTime = null;
-        this.queryParams.scheduleEndTime = null;
-      }
+        // 发送消息类型
+        this.getDicts('sys_qw_sop_course_type').then(response => {
+            this.sysQwSopType = response.data
+        })
     },
+    methods: {
+        /** 查询个微SOP执行详情列表 */
+        getList(val) {
+            this.queryParams.sopId = val?.id || this.rowDetailFrom?.id
+            this.loading = true
 
-    // 格式化日期为 yyyy-MM-dd HH:mm:ss 的北京时间
-    formatDateTime(date) {
-      if (!date) return null;
+            listWxSopLogsCVO(this.queryParams).then(response => {
+                console.log('个微SOP执行详情接口返回数据:', response)
+                this.wxSopLogsList = response.rows
+                this.total = response.total
+                this.loading = false
+            }).catch(error => {
+                console.error('查询个微SOP执行详情失败:', error)
+                this.loading = false
+            })
+        },
 
-      const options = {
-        timeZone: 'Asia/Shanghai',
-        year: 'numeric',
-        month: '2-digit',
-        day: '2-digit',
-        hour: '2-digit',
-        minute: '2-digit',
-        second: '2-digit',
-        hour12: false,
-      };
+        handleScheduleTimeChange(val) {
+            if (val) {
+                this.queryParams.scheduleStartTime = this.formatDateTime(val[0])
+                this.queryParams.scheduleEndTime = this.formatDateTime(val[1])
+            } else {
+                this.queryParams.scheduleStartTime = null
+                this.queryParams.scheduleEndTime = null
+            }
+        },
 
-      const formattedDate = new Intl.DateTimeFormat('zh-CN', options).format(new Date(date));
-      const [datePart, timePart] = formattedDate.replace(',', '').split(' ');
-      return `${datePart} ${timePart}`;
-    },
+        // 格式化日期为 yyyy-MM-dd HH:mm:ss 的北京时间
+        formatDateTime(date) {
+            if (!date) return null
 
-    /** 搜索按钮操作 */
-    handleQuery() {
-      this.queryParams.pageNum = 1;
-      this.getList(this.rowDetailFrom);
-    },
+            const options = {
+                timeZone: 'Asia/Shanghai',
+                year: 'numeric',
+                month: '2-digit',
+                day: '2-digit',
+                hour: '2-digit',
+                minute: '2-digit',
+                second: '2-digit',
+                hour12: false
+            }
 
-    /** 重置按钮操作 */
-    resetQuery() {
-      this.resetForm("queryForm");
-      this.queryParams.scheduleStartTime = null;
-      this.queryParams.scheduleEndTime = null;
-      this.scheduleTime = null;
-      this.handleQuery();
-    },
+            const formattedDate = new Intl.DateTimeFormat('zh-CN', options).format(new Date(date))
+            const [datePart, timePart] = formattedDate.replace(',', '').split(' ')
+            return `${datePart} ${timePart}`
+        },
+
+        /** 搜索按钮操作 */
+        handleQuery() {
+            this.queryParams.pageNum = 1
+            this.getList(this.rowDetailFrom)
+        },
+
+        /** 重置按钮操作 */
+        resetQuery() {
+            this.resetForm('queryForm')
+            this.queryParams.scheduleStartTime = null
+            this.queryParams.scheduleEndTime = null
+            this.scheduleTime = null
+            this.handleQuery()
+        },
 
-    /** 导出按钮操作 */
-    handleExport() {
-      const queryParams = this.queryParams;
-      this.$confirm('是否确认导出所有个微SOP执行详情数据项?', "警告", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning"
-      }).then(() => {
-        this.exportLoading = true;
-        return exportWxSopLogsCVO(queryParams);
-      }).then(response => {
-        this.download(response.msg);
-        this.exportLoading = false;
-      }).catch(() => {
-        this.exportLoading = false;
-      });
+        /** 显示消息详情弹窗 */
+        showContentDialog(contentJson) {
+            if (!contentJson) return
+            try {
+                const sanitizedJson = contentJson.replace(/[\u0000-\u001F\u007F]/g, '')
+                const parsedData = JSON.parse(sanitizedJson)
+                if (Array.isArray(parsedData)) {
+                    this.contentDialogData = parsedData
+                } else if (parsedData.setting) {
+                    this.contentDialogData = parsedData.setting
+                } else {
+                    this.contentDialogData = [parsedData]
+                }
+                this.contentDialogVisible = true
+            } catch (e) {
+                this.$message.error('消息内容解析失败')
+                console.error('解析contentJson失败:', e)
+            }
+        },
+        /** 获取内容类型标签 */
+        getContentTypeTag(contentType) {
+            const typeMap = {
+                '1': { label: '文本', type: '' },
+                '2': { label: '图片', type: 'success' },
+                '3': { label: '链接', type: 'warning' },
+                '5': { label: '文件', type: 'info' },
+                '6': { label: '视频', type: 'danger' }
+            }
+            return typeMap[String(contentType)] || { label: '其他(' + contentType + ')', type: 'info' }
+        },
+        /** 导出按钮操作 */
+        handleExport() {
+            const queryParams = this.queryParams
+            this.$confirm('是否确认导出所有个微SOP执行详情数据项?', '警告', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'warning'
+            }).then(() => {
+                this.exportLoading = true
+                return exportWxSopLogsCVO(queryParams)
+            }).then(response => {
+                this.download(response.msg)
+                this.exportLoading = false
+            }).catch(() => {
+                this.exportLoading = false
+            })
+        }
     }
-  }
-};
+}
 </script>
 
 <style scoped>
+.content-detail {
+    max-height: 400px;
+    overflow-y: auto;
+}
+
+.content-item {
+    padding: 12px 16px;
+    margin-bottom: 12px;
+    background: #f8f9fa;
+    border-radius: 8px;
+    border: 1px solid #ebeef5;
+}
+
+.content-type-tag {
+    margin-bottom: 8px;
+}
+
+.content-body {
+    font-size: 14px;
+    line-height: 1.6;
+    color: #303133;
+}
+
+.text-content {
+    white-space: pre-wrap;
+    word-break: break-all;
+}
 </style>