lmx 1 месяц назад
Родитель
Сommit
e390de6ed2

+ 9 - 0
src/api/company/companyUser.js

@@ -368,3 +368,12 @@ export function unBind(userId) {
     data: { userId: userId }
   })
 }
+
+// 查询我的企微列表
+export function myQwList(query) {
+  return request({
+    url: '/company/user/myQwList',
+    method: 'get',
+    params: query
+  })
+}

+ 8 - 0
src/api/company/companyVoiceRobotic.js

@@ -8,6 +8,14 @@ export function listRobotic(query) {
     params: query
   })
 }
+
+export function myListRobotic(query) {
+    return request({
+        url: '/company/companyVoiceRobotic/myList',
+        method: 'get',
+        params: query
+    })
+}
 // 查询机器人外呼任务列表
 export function listAll(query) {
   return request({

+ 8 - 0
src/api/company/companyWorkflow.js

@@ -9,6 +9,14 @@ export function listWorkflow(query) {
   })
 }
 
+export function myListWorkflow(query) {
+    return request({
+        url: '/company/companyWorkflow/myList',
+        method: 'get',
+        params: query
+    })
+}
+
 // 查询AI工作流详细
 export function getWorkflow(workflowId) {
   return request({

+ 142 - 0
src/api/company/inboundCallManage.js

@@ -0,0 +1,142 @@
+import request from '@/utils/request'
+
+/**
+ * 查询呼入大模型配置列表
+ * @param {Object} query 查询参数
+ */
+export function listInboundLlm(query) {
+  return request({
+    url: '/company/inboundCallManage/list',
+    method: 'get',
+    params: query
+  })
+}
+
+/**
+ * 新增呼入大模型配置
+ * @param {Object} data 配置数据
+ */
+export function addInboundLlm(data) {
+  return request({
+    url: '/company/inboundCallManage',
+    method: 'post',
+    data: data
+  })
+}
+
+/**
+ * 修改呼入大模型配置
+ * @param {Object} data 配置数据
+ */
+export function updateInboundLlm(data) {
+  return request({
+    url: '/company/inboundCallManage',
+    method: 'put',
+    data: data
+  })
+}
+
+/**
+ * 删除呼入大模型配置
+ * @param {String} ids 配置ID,多个用逗号分隔
+ */
+export function delInboundLlm(ids) {
+  return request({
+    url: '/company/inboundCallManage/' + ids,
+    method: 'delete'
+  })
+}
+
+/**
+ * 获取大模型账户下拉列表
+ */
+export function listLlmAccount() {
+  return request({
+    url: '/company/inboundCallManage/llmAccountList',
+    method: 'get'
+  })
+}
+
+/**
+ * 校验被叫号码是否唯一
+ */
+export function checkCallee(id, callee) {
+  return request({
+    url: '/company/inboundCallManage/checkCallee',
+    method: 'get',
+    params: { id, callee }
+  })
+}
+
+/**
+ * 获取ASR提供商列表
+ */
+export function listAsrProvider() {
+  return request({
+    url: '/company/inboundCallManage/asrProviderList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取TTS音色来源列表
+ */
+export function listVoiceSource() {
+  return request({
+    url: '/company/inboundCallManage/voiceSourceList',
+    method: 'get'
+  })
+}
+
+/**
+ * 根据音色来源获取音色列表
+ */
+export function listVoiceBySource(voiceSource) {
+  return request({
+    url: '/company/inboundCallManage/voiceList',
+    method: 'get',
+    params: { voiceSource }
+  })
+}
+
+/**
+ * 获取业务组列表
+ */
+export function listBizGroup() {
+  return request({
+    url: '/company/inboundCallManage/bizGroupList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取出局网关列表
+ */
+export function listGateway() {
+  return request({
+    url: '/company/inboundCallManage/gatewayList',
+    method: 'get'
+  })
+}
+
+/**
+ * 获取IVR列表
+ */
+export function listIvr() {
+  return request({
+    url: '/company/inboundCallManage/ivrList',
+    method: 'get'
+  })
+}
+
+/**
+ * 查询呼入通话记录列表
+ * @param {Object} query 查询参数
+ */
+export function listInboundCdr(query) {
+  return request({
+    url: '/company/inboundCallManage/inboundCdrList',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/wx/sopUserLogsWx.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询个微SOP营期列表
+export function listSopUserLogsWx(query) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微SOP营期详细
+export function getSopUserLogsWx(id) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/' + id,
+    method: 'get'
+  })
+}
+
+// 删除个微SOP营期
+export function delSopUserLogsWx(ids) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/' + ids,
+    method: 'delete'
+  })
+}
+
+// 导出个微SOP营期
+export function exportSopUserLogsWx(query) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 批量修改个微SOP营期时间
+export function updateLogDate(data) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/updateLogDate',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询个微SOP营期详情(客户列表)
+export function getSopUserLogsDetail(sopUserId, query) {
+  return request({
+    url: '/wxSop/sopUserLogsWx/detail/' + sopUserId,
+    method: 'get',
+    params: query
+  })
+}

+ 61 - 0
src/api/wx/wxSop.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询个微SOP列表
+export function listWxSop(query) {
+  return request({
+    url: '/wx/wxSop/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微SOP详细
+export function getWxSop(id) {
+  return request({
+    url: '/wx/wxSop/' + id,
+    method: 'get'
+  })
+}
+
+// 新增个微SOP
+export function addWxSop(data) {
+  return request({
+    url: '/wx/wxSop',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改个微SOP
+export function updateWxSop(data) {
+  return request({
+    url: '/wx/wxSop',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除个微SOP
+export function delWxSop(id) {
+  return request({
+    url: '/wx/wxSop/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出个微SOP
+export function exportWxSop(query) {
+  return request({
+    url: '/wx/wxSop/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 批量执行个微SOP
+export function updateWxStatus(ids) {
+  return request({
+    url: '/qw/sop/updateWxStatus/' + ids,
+    method: 'get'
+  })
+}

+ 71 - 0
src/api/wx/wxSopLogs.js

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询个微发送记录列表
+export function listWxSopLogs(query) {
+  return request({
+    url: '/wx/wxSopLogs/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微发送记录详细
+export function getWxSopLogs(id) {
+  return request({
+    url: '/wx/wxSopLogs/' + id,
+    method: 'get'
+  })
+}
+
+// 新增个微发送记录
+export function addWxSopLogs(data) {
+  return request({
+    url: '/wx/wxSopLogs',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改个微发送记录
+export function updateWxSopLogs(data) {
+  return request({
+    url: '/wx/wxSopLogs',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除个微发送记录
+export function delWxSopLogs(id) {
+  return request({
+    url: '/wx/wxSopLogs/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出个微发送记录
+export function exportWxSopLogs(query) {
+  return request({
+    url: '/wx/wxSopLogs/export',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微SOP执行详情列表
+export function listWxSopLogsCVO(query) {
+  return request({
+    url: '/wx/wxSopLogs/listCVO',
+    method: 'get',
+    params: query
+  })
+}
+
+// 导出个微SOP执行详情
+export function exportWxSopLogsCVO(query) {
+  return request({
+    url: '/wx/wxSopLogs/exportCVO',
+    method: 'post',
+    params: query
+  })
+}

+ 53 - 0
src/api/wx/wxSopUser.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询个微营期列表
+export function listWxSopUser(query) {
+  return request({
+    url: '/wx/wxSopUser/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微营期详细
+export function getWxSopUser(id) {
+  return request({
+    url: '/wx/wxSopUser/' + id,
+    method: 'get'
+  })
+}
+
+// 新增个微营期
+export function addWxSopUser(data) {
+  return request({
+    url: '/wx/wxSopUser',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改个微营期
+export function updateWxSopUser(data) {
+  return request({
+    url: '/wx/wxSopUser',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除个微营期
+export function delWxSopUser(id) {
+  return request({
+    url: '/wx/wxSopUser/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出个微营期
+export function exportWxSopUser(query) {
+  return request({
+    url: '/wx/wxSopUser/export',
+    method: 'get',
+    params: query
+  })
+}

+ 53 - 0
src/api/wx/wxSopUserInfo.js

@@ -0,0 +1,53 @@
+import request from '@/utils/request'
+
+// 查询个微营期详情列表
+export function listWxSopUserInfo(query) {
+  return request({
+    url: '/wx/wxSopUserInfo/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询个微营期详情详细
+export function getWxSopUserInfo(id) {
+  return request({
+    url: '/wx/wxSopUserInfo/' + id,
+    method: 'get'
+  })
+}
+
+// 新增个微营期详情
+export function addWxSopUserInfo(data) {
+  return request({
+    url: '/wx/wxSopUserInfo',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改个微营期详情
+export function updateWxSopUserInfo(data) {
+  return request({
+    url: '/wx/wxSopUserInfo',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除个微营期详情
+export function delWxSopUserInfo(id) {
+  return request({
+    url: '/wx/wxSopUserInfo/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出个微营期详情
+export function exportWxSopUserInfo(query) {
+  return request({
+    url: '/wx/wxSopUserInfo/export',
+    method: 'get',
+    params: query
+  })
+}

+ 55 - 1
src/router/index.js

@@ -296,6 +296,12 @@ export const constantRoutes = [
       name: 'AiWorkflow',
       meta: { title: 'AI外呼工作流', icon: 'workflow' }
     },
+    {
+      path: 'myList',
+      component: () => import('@/views/company/companyWorkflow/myIndex'),
+      name: 'MyCompanyWorkflow',
+      meta: { title: '我的工作流', activeMenu: '/companyWx/companyWorkflow' }
+    },
     {
       path: 'design',
       component: () => import('@/views/company/companyWorkflow/design'),
@@ -388,7 +394,55 @@ export const constantRoutes = [
                 }
             }
         ]
-    }
+    },
+
+  {
+    path: '/wxSop/sopUserLogsWx',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'sopUserLogsScheduleWx/:id',
+        component: () => import('@/views/wx/sopUserLogsWx/sopUserLogsScheduleWx.vue'),
+        name: 'sopUserLogsScheduleWx',
+        meta: { title: '个微SOP营期', activeMenu: '/wx/wxSop' }
+      },
+      {
+        path: 'detail',
+        component: () => import('@/views/wx/sopUserLogsWx/detail.vue'),
+        name: 'sopUserLogsDetailWx',
+        meta: { title: '个微SOP营期详情', activeMenu: '/wx/wxSop' }
+      }
+    ]
+  },
+
+  {
+    path: '/wxSop/wxSop',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'sopLogsList/:id',
+        component: () => import('@/views/wx/wxSop/sopLogsList.vue'),
+        name: 'WxSopLogsList',
+        meta: { title: '个微SOP执行详情', activeMenu: '/wx/wxSop' }
+      }
+    ]
+  },
+
+  {
+    path: '/companyVoiceRobotic',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'myIndex',
+        component: () => import('@/views/company/companyVoiceRobotic/myIndex.vue'),
+        name: 'MyCompanyVoiceRobotic',
+        meta: { title: '我的音色任务', activeMenu: '/companyWx/companyVoiceRobotic' }
+      }
+    ]
+  }
 
 ]
 

+ 672 - 0
src/views/company/aiModel/inboundCallManage/inboundCallRecord.vue

@@ -0,0 +1,672 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <el-form
+      v-show="showSearch"
+      ref="queryForm"
+      :inline="true"
+      :model="queryParams"
+      label-width="100px"
+    >
+      <el-form-item label="通话UUID" prop="uuid">
+        <el-input
+          v-model="queryParams.uuid"
+          clearable
+          placeholder="请输入通话UUID"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="主叫号码" prop="caller">
+        <el-input
+          v-model="queryParams.caller"
+          clearable
+          placeholder="请输入主叫号码"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="接听分机号" prop="extnum">
+        <el-input
+          v-model="queryParams.extnum"
+          clearable
+          placeholder="请输入接听分机号"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="通话时长(秒)">
+        <el-input-number
+          v-model="queryParams.params.timeLenStart"
+          :min="0"
+          size="small"
+          style="width: 120px"
+          placeholder="最小值"
+          controls-position="right"
+        />
+        <span class="range-separator">-</span>
+        <el-input-number
+          v-model="queryParams.params.timeLenEnd"
+          :min="0"
+          size="small"
+          style="width: 120px"
+          placeholder="最大值"
+          controls-position="right"
+        />
+      </el-form-item>
+      <el-form-item label="呼入时间">
+        <el-date-picker
+          v-model="inboundTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item label="接听时间">
+        <el-date-picker
+          v-model="answeredTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item label="挂机时间">
+        <el-date-picker
+          v-model="hangupTimeRange"
+          type="datetimerange"
+          size="small"
+          style="width: 340px"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button 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">
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table
+      v-loading="loading"
+      :data="tableData"
+      border
+      style="width: 100%"
+    >
+      <el-table-column align="center" label="UUID" prop="uuid" min-width="120" show-overflow-tooltip />
+      <el-table-column align="center" label="主叫号码" prop="caller" min-width="110" show-overflow-tooltip />
+       <el-table-column align="center" label="被叫号码" prop="callee" min-width="110" show-overflow-tooltip />
+      <el-table-column align="center" label="录音文件" min-width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="getMediaType(scope.row.wavFile) === 'audio'" type="primary" size="small">音频</el-tag>
+          <el-tag v-else-if="getMediaType(scope.row.wavFile) === 'video'" type="success" size="small">视频</el-tag>
+          <span v-else>无</span>
+        </template>
+      </el-table-column>
+      <el-table-column align="center" label="呼入时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.inboundTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="接听时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.answeredTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="接听分机" prop="extnum" min-width="90" />
+      <el-table-column align="center" label="接听坐席" prop="opnum" min-width="90" />
+      <el-table-column align="center" label="挂机时间" min-width="150">
+        <template slot-scope="scope">{{ formatTimestamp(scope.row.hangupTime) }}</template>
+      </el-table-column>
+      <el-table-column align="center" label="业务组" prop="groupName" min-width="100" show-overflow-tooltip />
+      <el-table-column align="center" label="通话时长" min-width="90">
+        <template slot-scope="scope">{{ formatDuration(scope.row.timeLen) }}</template>
+      </el-table-column>
+      <!-- <el-table-column align="center" label="挂机原因" min-width="120" show-overflow-tooltip>
+        <template slot-scope="scope">
+          <span
+            class="hangup-cause-cell"
+            :title="scope.row.hangupCause"
+            @dblclick="copyText(formatHangupCause(scope.row.hangupCause))"
+          >{{ formatHangupCause(scope.row.hangupCause) }}</span>
+        </template>
+      </el-table-column> -->
+      <el-table-column
+        align="center"
+        class-name="small-padding fixed-width"
+        label="操作"
+        width="160"
+      >
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.wavFileUrl"
+            size="mini"
+            type="text"
+            icon="el-icon-video-play"
+            @click="handlePlay(scope.row)"
+          >播放</el-button>
+          <el-button
+            v-if="scope.row.wavFileUrl"
+            size="mini"
+            type="text"
+            icon="el-icon-download"
+            @click="handleDownload(scope.row)"
+          >下载</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :limit.sync="queryParams.pageSize"
+      :page.sync="queryParams.pageNum"
+      :total="total"
+      @pagination="getList"
+    />
+
+    <!-- 播放弹窗 -->
+    <el-dialog
+      title="录音播放"
+      :visible.sync="playDialogVisible"
+      width="700px"
+      append-to-body
+      @close="handlePlayDialogClose"
+    >
+      <!-- 音频/视频播放器 -->
+      <div v-if="currentMediaType === 'audio'" class="player-wrapper">
+        <audio
+          ref="audioPlayer"
+          :src="currentWavFileUrl"
+          controls
+          style="width: 100%"
+        />
+      </div>
+      <div v-else-if="currentMediaType === 'video'" class="player-wrapper">
+        <video
+          ref="videoPlayer"
+          :src="currentWavFileUrl"
+          controls
+          style="width: 100%"
+        />
+      </div>
+
+      <!-- AI对话内容 -->
+      <div v-if="chatDialogList.length > 0" class="chat-container">
+        <div class="chat-title">AI对话内容</div>
+        <div class="chat-list">
+          <div
+            v-for="(item, index) in chatDialogList"
+            :key="index"
+            class="dialog-item"
+            :class="item.role"
+          >
+            <!-- 左侧角色(assistant / agent / kb) -->
+            <template v-if="item.role !== 'user'">
+              <span class="role-icon">
+                <i :class="getRoleIcon(item.role)" />
+              </span>
+              <span class="role-label">{{ getRoleLabel(item.role) }}</span>
+              <div class="bubble">
+                <span class="content-text">
+                  {{ item.expanded ? item.content : getTruncatedContent(item.content) }}
+                  <span
+                    v-if="item.content.length > 200"
+                    class="more-text"
+                    @click="toggleExpand(index)"
+                  >{{ item.expanded ? '[收起]' : '[更多]' }}</span>
+                </span>
+                <el-tag v-if="item.isKb" size="mini" type="warning" class="kb-tag">知识库来源</el-tag>
+              </div>
+            </template>
+            <!-- 右侧角色(user) -->
+            <template v-else>
+              <div class="bubble">
+                <span class="content-text">
+                  {{ item.expanded ? item.content : getTruncatedContent(item.content) }}
+                  <span
+                    v-if="item.content.length > 200"
+                    class="more-text"
+                    @click="toggleExpand(index)"
+                  >{{ item.expanded ? '[收起]' : '[更多]' }}</span>
+                </span>
+              </div>
+              <span class="role-icon">
+                <i class="el-icon-user" />
+              </span>
+              <span class="role-label">客户</span>
+            </template>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listInboundCdr } from '@/api/company/inboundCallManage'
+
+export default {
+  name: 'InboundCallRecord',
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      tableData: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        uuid: undefined,
+        caller: undefined,
+        extnum: undefined,
+        params: {
+          timeLenStart: undefined,
+          timeLenEnd: undefined,
+          inboundTimeStart: undefined,
+          inboundTimeEnd: undefined,
+          answeredTimeStart: undefined,
+          answeredTimeEnd: undefined,
+          hangupTimeStart: undefined,
+          hangupTimeEnd: undefined
+        }
+      },
+      // 时间范围
+      inboundTimeRange: [],
+      answeredTimeRange: [],
+      hangupTimeRange: [],
+      // 播放弹窗
+      playDialogVisible: false,
+      currentWavFileUrl: '',
+      currentMediaType: '',
+      // AI对话列表
+      chatDialogList: []
+    }
+  },
+  created() {
+    this.getList()
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true
+      // 处理时间范围参数(转为epoch毫秒时间戳,与后端Long类型字段匹配)
+      if (this.inboundTimeRange && this.inboundTimeRange.length === 2) {
+        this.queryParams.params.inboundTimeStart = new Date(this.inboundTimeRange[0]).getTime()
+        this.queryParams.params.inboundTimeEnd = new Date(this.inboundTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.inboundTimeStart = undefined
+        this.queryParams.params.inboundTimeEnd = undefined
+      }
+      if (this.answeredTimeRange && this.answeredTimeRange.length === 2) {
+        this.queryParams.params.answeredTimeStart = new Date(this.answeredTimeRange[0]).getTime()
+        this.queryParams.params.answeredTimeEnd = new Date(this.answeredTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.answeredTimeStart = undefined
+        this.queryParams.params.answeredTimeEnd = undefined
+      }
+      if (this.hangupTimeRange && this.hangupTimeRange.length === 2) {
+        this.queryParams.params.hangupTimeStart = new Date(this.hangupTimeRange[0]).getTime()
+        this.queryParams.params.hangupTimeEnd = new Date(this.hangupTimeRange[1]).getTime()
+      } else {
+        this.queryParams.params.hangupTimeStart = undefined
+        this.queryParams.params.hangupTimeEnd = undefined
+      }
+      // 通话时长:前端输入秒,数据库存毫秒,显示用Math.ceil(ms/1000)
+      // Math.ceil(v/1000)==N 等价于 (N-1)*1000 < v <= N*1000,即 v∈[(N-1)*1000+1, N*1000]
+      const params = { ...this.queryParams }
+      params.params = { ...this.queryParams.params }
+      if (params.params.timeLenStart != null && params.params.timeLenStart !== undefined) {
+        const n = params.params.timeLenStart
+        params.params.timeLenStart = n > 0 ? (n - 1) * 1000 + 1 : 0
+      }
+      if (params.params.timeLenEnd != null && params.params.timeLenEnd !== undefined) {
+        params.params.timeLenEnd = params.params.timeLenEnd * 1000
+      }
+      listInboundCdr(params).then(response => {
+        this.tableData = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.inboundTimeRange = []
+      this.answeredTimeRange = []
+      this.hangupTimeRange = []
+      this.resetForm('queryForm')
+      this.queryParams = {
+        pageNum: 1,
+        pageSize: 10,
+        uuid: undefined,
+        caller: undefined,
+        extnum: undefined,
+        params: {
+          timeLenStart: undefined,
+          timeLenEnd: undefined,
+          inboundTimeStart: undefined,
+          inboundTimeEnd: undefined,
+          answeredTimeStart: undefined,
+          answeredTimeEnd: undefined,
+          hangupTimeStart: undefined,
+          hangupTimeEnd: undefined
+        }
+      }
+      this.handleQuery()
+    },
+    /** 格式化时间戳 - 毫秒级时间戳转 yyyy-MM-dd HH:mm:ss */
+    formatTimestamp(value) {
+      if (!value || value <= 0) return '-'
+      const date = new Date(Number(value))
+      const year = date.getFullYear()
+      const month = (date.getMonth() + 1).toString().padStart(2, '0')
+      const day = date.getDate().toString().padStart(2, '0')
+      const hour = date.getHours().toString().padStart(2, '0')
+      const minute = date.getMinutes().toString().padStart(2, '0')
+      const second = date.getSeconds().toString().padStart(2, '0')
+      return `${year}-${month}-${day} ${hour}:${minute}:${second}`
+    },
+    /** 格式化通话时长 - 毫秒转 mm分ss秒 */
+    formatDuration(value) {
+      if (!value || value <= 0) return '0秒'
+      const totalSeconds = Math.ceil(value / 1000)
+      const minutes = Math.floor(totalSeconds / 60)
+      const seconds = totalSeconds % 60
+      if (minutes > 0) {
+        return `${minutes}分${seconds.toString().padStart(2, '0')}秒`
+      }
+      return `${seconds}秒`
+    },
+    /** 判断媒体类型 */
+    getMediaType(wavFile) {
+      if (!wavFile) return 'none'
+      const ext = wavFile.split('.').pop().toLowerCase()
+      if (['wav', 'mp3', 'aac'].includes(ext)) return 'audio'
+      if (['mp4', 'avi', 'mov'].includes(ext)) return 'video'
+      return 'none'
+    },
+    /** 格式化挂机原因 */
+    formatHangupCause(value) {
+      if (!value) return '-'
+      try {
+        const obj = JSON.parse(value)
+        if (obj && obj.code !== undefined) {
+          return `${obj.code}: ${obj.details || ''}`
+        }
+        return value
+      } catch (e) {
+        return value
+      }
+    },
+    /** 双击复制文本 */
+    copyText(text) {
+      if (!text || text === '-') return
+      if (navigator.clipboard && window.isSecureContext) {
+        navigator.clipboard.writeText(text).then(() => {
+          this.$message.success('复制成功')
+        }).catch(() => {
+          this.fallbackCopy(text)
+        })
+      } else {
+        this.fallbackCopy(text)
+      }
+    },
+    /** 降级复制方案 */
+    fallbackCopy(text) {
+      const textArea = document.createElement('textarea')
+      textArea.value = text
+      textArea.style.position = 'fixed'
+      textArea.style.left = '-9999px'
+      document.body.appendChild(textArea)
+      textArea.focus()
+      textArea.select()
+      try {
+        document.execCommand('copy')
+        this.$message.success('复制成功')
+      } catch (err) {
+        this.$message.error('复制失败')
+      }
+      document.body.removeChild(textArea)
+    },
+    /** 播放按钮操作 */
+    handlePlay(row) {
+      this.currentWavFileUrl = row.wavFileUrl
+      this.currentMediaType = this.getMediaType(row.wavFileUrl || row.wavFile)
+      // 解析AI对话内容
+      this.chatDialogList = this.parseChatContent(row.chatContent)
+      this.playDialogVisible = true
+    },
+    /** 解析聊天内容 */
+    parseChatContent(chatContent) {
+      if (!chatContent) return []
+      try {
+        let items = chatContent
+        // 如果是字符串,尝试解析
+        if (typeof items === 'string') {
+          items = JSON.parse(items)
+        }
+        // 可能是双重JSON字符串
+        if (typeof items === 'string') {
+          items = JSON.parse(items)
+        }
+        if (!Array.isArray(items)) return []
+        return items.filter(item => ['user', 'assistant', 'agent'].includes(item.role)).map(item => {
+          let content = item.content || ''
+          let isKb = false
+          // 检查是否包含JSON(知识库返回结果)
+          if (this.containsJson(content)) {
+            isKb = true
+            // 移除JSON部分
+            let cleaned = content
+            while (this.containsJson(cleaned)) {
+              cleaned = cleaned.replace(/\{[^{}]*\}/s, '').trim()
+            }
+            content = cleaned || content
+          }
+          return {
+            role: isKb ? 'kb' : item.role,
+            content: content,
+            isKb: isKb,
+            expanded: false
+          }
+        }).filter(item => item.content)
+      } catch (e) {
+        return []
+      }
+    },
+    /** 检查字符串是否包含JSON */
+    containsJson(input) {
+      if (!input) return false
+      return /\{.*?\}/s.test(input)
+    },
+    /** 获取截断内容 */
+    getTruncatedContent(content) {
+      if (!content || content.length <= 200) return content
+      return content.substring(0, 200) + '...'
+    },
+    /** 切换展开/收起 */
+    toggleExpand(index) {
+      this.$set(this.chatDialogList[index], 'expanded', !this.chatDialogList[index].expanded)
+    },
+    /** 获取角色图标 */
+    getRoleIcon(role) {
+      const iconMap = {
+        assistant: 'el-icon-monitor',
+        agent: 'el-icon-phone-outline',
+        kb: 'el-icon-folder-opened',
+        user: 'el-icon-user'
+      }
+      return iconMap[role] || 'el-icon-user'
+    },
+    /** 获取角色标签 */
+    getRoleLabel(role) {
+      const labelMap = {
+        assistant: 'AI',
+        agent: '坐席',
+        kb: '知识库',
+        user: '客户'
+      }
+      return labelMap[role] || role
+    },
+    /** 下载按钮操作 */
+    handleDownload(row) {
+      if (row.wavFileUrl) {
+        const link = document.createElement('a')
+        link.href = row.wavFileUrl
+        link.target = '_blank'
+        link.download = ''
+        document.body.appendChild(link)
+        link.click()
+        document.body.removeChild(link)
+      }
+    },
+    /** 播放弹窗关闭 */
+    handlePlayDialogClose() {
+      // 停止音频/视频播放
+      if (this.$refs.audioPlayer) {
+        this.$refs.audioPlayer.pause()
+        this.$refs.audioPlayer.currentTime = 0
+      }
+      if (this.$refs.videoPlayer) {
+        this.$refs.videoPlayer.pause()
+        this.$refs.videoPlayer.currentTime = 0
+      }
+      this.currentWavFileUrl = ''
+      this.currentMediaType = ''
+      this.chatDialogList = []
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+.range-separator {
+  padding: 0 5px;
+}
+.player-wrapper {
+  margin-bottom: 16px;
+}
+/* 对话容器 */
+.chat-container {
+  margin-top: 16px;
+  border-top: 1px solid #ebeef5;
+  padding-top: 12px;
+}
+.chat-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 12px;
+  color: #303133;
+}
+.chat-list {
+  max-height: 400px;
+  overflow-y: auto;
+  padding-right: 6px;
+}
+/* 对话气泡 */
+.dialog-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 12px;
+}
+.dialog-item.user {
+  justify-content: flex-end;
+}
+.dialog-item.assistant,
+.dialog-item.agent,
+.dialog-item.kb {
+  justify-content: flex-start;
+}
+.bubble {
+  display: inline-block;
+  max-width: 70%;
+  padding: 10px 14px;
+  border-radius: 12px;
+  word-break: break-word;
+  line-height: 1.6;
+  font-size: 14px;
+  color: #263238;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+}
+.dialog-item.user .bubble {
+  background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+  text-align: right;
+}
+.dialog-item.assistant .bubble {
+  background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
+}
+.dialog-item.agent .bubble {
+  background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
+}
+.dialog-item.kb .bubble {
+  background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
+}
+.role-icon {
+  display: flex;
+  align-items: center;
+  padding: 0 8px;
+  font-size: 18px;
+  color: #606266;
+}
+.role-label {
+  font-size: 12px;
+  color: #909399;
+  white-space: nowrap;
+  padding: 0 4px;
+  line-height: 32px;
+}
+.content-text {
+  word-break: break-word;
+}
+.more-text {
+  color: #1976d2;
+  cursor: pointer;
+  font-size: 13px;
+  margin-left: 4px;
+  white-space: nowrap;
+}
+.more-text:hover {
+  text-decoration: underline;
+}
+.kb-tag {
+  margin-top: 6px;
+  display: inline-block;
+}
+.hangup-cause-cell {
+  cursor: pointer;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  max-width: 100%;
+  display: inline-block;
+}
+</style>

+ 709 - 0
src/views/company/aiModel/inboundCallManage/index.vue

@@ -0,0 +1,709 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <el-form
+      v-show="showSearch"
+      ref="queryForm"
+      :inline="true"
+      :model="queryParams"
+      label-width="100px"
+    >
+      <el-form-item label="大模型底座" prop="llmAccountId">
+        <el-select
+          v-model="queryParams.llmAccountId"
+          clearable
+          placeholder="全部"
+          size="small"
+          style="width: 200px"
+        >
+          <el-option label="全部" value="" />
+          <el-option
+            v-for="item in llmAccountList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="被叫号码" prop="callee">
+        <el-input
+          v-model="queryParams.callee"
+          clearable
+          placeholder="请输入被叫号码"
+          size="small"
+          style="width: 200px"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button 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
+          v-hasPermi="['inboundCallManage:add']"
+          icon="el-icon-plus"
+          plain
+          size="mini"
+          type="success"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <!-- <el-col :span="1.5">
+        <el-button
+          icon="el-icon-edit"
+          plain
+          size="mini"
+          type="primary"
+          :disabled="single"
+          @click="handleEdit()"
+        >修改</el-button>
+      </el-col> -->
+      <!-- <el-col :span="1.5">
+        <el-button
+          icon="el-icon-delete"
+          plain
+          size="mini"
+          type="danger"
+          :disabled="multiple"
+          @click="handleDelete()"
+        >删除</el-button>
+      </el-col> -->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
+    </el-row>
+
+    <!-- 数据表格 -->
+    <el-table
+      v-loading="loading"
+      :data="inboundList"
+      border
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column align="center" label="名称" prop="inboundAlias" />
+      <el-table-column align="center" label="大模型底座" prop="llmAccountName" />
+      <el-table-column align="center" label="音色" prop="voiceName" />
+      <el-table-column align="center" label="被叫号码" prop="callee" />
+      <el-table-column align="center" label="服务类型" prop="serviceType" width="200">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.serviceType === 'ai'" type="primary">AI</el-tag>
+          <el-tag v-else-if="scope.row.serviceType === 'acd'" type="success">技能组</el-tag>
+          <el-tag v-else-if="scope.row.serviceType === 'ivr'" type="info">IVR</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column
+        align="center"
+        class-name="small-padding fixed-width"
+        label="操作"
+        width="180"
+      >
+        <template slot-scope="scope">
+          <el-button
+            v-hasPermi="['inboundCallManage:edit']"
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleEdit(scope.row)"
+          >修改</el-button>
+          <el-button
+          v-hasPermi="['inboundCallManage:delete']"
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :limit.sync="queryParams.pageSize"
+      :page.sync="queryParams.pageNum"
+      :total="total"
+      @pagination="getList"
+    />
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog
+      :title="dialogTitle"
+      :visible.sync="dialogVisible"
+      width="650px"
+      append-to-body
+      @close="handleDialogClose"
+    >
+      <el-form
+        ref="form"
+        :model="form"
+        :rules="rules"
+        label-width="120px"
+      >
+        <!-- 名称 -->
+        <el-form-item label="名称" prop="inboundAlias">
+          <el-input
+            v-model="form.inboundAlias"
+            placeholder="请输入名称"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 被叫号码 -->
+        <el-form-item label="被叫号码" prop="callee">
+          <el-input
+            v-model="form.callee"
+            placeholder="请输入被叫号码"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 服务类型 -->
+        <el-form-item label="服务类型" prop="serviceType">
+          <el-select
+            v-model="form.serviceType"
+            placeholder="请选择服务类型"
+            style="width: 400px;"
+            @change="handleServiceTypeChange"
+          >
+            <el-option label="AI" value="ai" />
+            <el-option label="技能组" value="acd" />
+            <el-option label="IVR" value="ivr" />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 大模型底座 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="大模型底座" prop="llmAccountId">
+          <el-select
+            v-model="form.llmAccountId"
+            placeholder="请选择大模型底座"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in llmAccountList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- ASR提供商 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="ASR提供商" prop="asrProvider">
+          <el-select
+            v-model="form.asrProvider"
+            placeholder="请选择ASR提供商"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="(value, key) in asrProviderList"
+              :key="key"
+              :label="value"
+              :value="key"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 音色来源 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="音色来源" prop="voiceSource">
+          <el-select
+            v-model="form.voiceSource"
+            placeholder="请选择音色来源"
+            style="width: 400px;"
+            @change="handleVoiceSourceChange"
+          >
+            <el-option
+              v-for="(value, key) in voiceSourceList"
+              :key="key"
+              :label="value"
+              :value="key"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 音色 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="音色" prop="voiceCode">
+          <el-select
+            v-model="form.voiceCode"
+            placeholder="请选择音色"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in voiceList"
+              :key="item.voiceCode"
+              :label="item.voiceName"
+              :value="item.voiceCode"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- AI转接类型 (AI可见) -->
+        <el-form-item v-if="form.serviceType === 'ai'" label="AI转接类型" prop="aiTransferType">
+          <el-select
+            v-model="form.aiTransferType"
+            placeholder="请选择AI转接类型"
+            style="width: 400px;"
+            @change="handleAiTransferTypeChange"
+          >
+            <el-option label="技能组" value="acd" />
+            <el-option label="分机号" value="extension" />
+            <el-option label="网关" value="gateway" />
+          </el-select>
+        </el-form-item>
+        
+        <!-- IVR下拉 (IVR可见) -->
+        <el-form-item v-if="form.serviceType === 'ivr'" label="IVR" prop="ivrId">
+          <el-select
+            v-model="form.ivrId"
+            placeholder="请选择IVR"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in ivrList"
+              :key="item.id"
+              :label="item.ivrNodeName"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 业务组 (ACD/AI+ACD可见) -->
+        <el-form-item v-if="showBizGroup" label="业务组" prop="aiTransferGroupId">
+          <el-select
+            v-model="form.aiTransferGroupId"
+            placeholder="请选择业务组"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in bizGroupList"
+              :key="item.groupId"
+              :label="item.bizGroupName"
+              :value="String(item.groupId)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 网关 (AI+Gateway可见) -->
+        <el-form-item v-if="showGateway" label="网关" prop="aiTransferGatewayId">
+          <el-select
+            v-model="form.aiTransferGatewayId"
+            placeholder="请选择网关"
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in gatewayList"
+              :key="item.id"
+              :label="item.gwDesc"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <!-- 网关目标号码 (AI+Gateway可见) -->
+        <el-form-item v-if="showGateway" label="网关目标号码" prop="aiTransferGatewayDestNumber">
+          <el-input
+            v-model="form.aiTransferGatewayDestNumber"
+            placeholder="请输入网关目标号码"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 分机号 (AI+Extension可见) -->
+        <el-form-item v-if="showExtension" label="分机号" prop="aiTransferExtNumber">
+          <el-input
+            v-model="form.aiTransferExtNumber"
+            placeholder="请输入分机号"
+            style="width: 400px;"
+          />
+        </el-form-item>
+        
+        <!-- 服务评价 -->
+        <el-form-item label="服务评价" prop="satisfSurveyIvrId">
+          <el-select
+            v-model="form.satisfSurveyIvrId"
+            placeholder="请选择服务评价IVR"
+            clearable
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in ivrList"
+              :key="item.id"
+              :label="item.ivrNodeName"
+              :value="String(item.id)"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="呼入场景" prop="fsSceneType">
+          <el-select
+            v-model="form.fsSceneType"
+            placeholder="请选择呼入场景"
+            clearable
+            style="width: 400px;"
+          >
+            <el-option
+              v-for="item in sceneList"
+              :key="item.dictValue"
+              :label="item.dictLabel"
+              :value="parseInt(item.dictValue)"
+            />
+          </el-select>
+        </el-form-item>
+        <!-- <el-form-item label="线路回调地址" prop="callBackUrl">
+         <el-input
+            v-model="form.callBackUrl"
+            placeholder="请输入线路回调地址"
+            style="width: 400px;"
+          />
+        </el-form-item> -->
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  listInboundLlm,
+  addInboundLlm,
+  updateInboundLlm,
+  delInboundLlm,
+  listLlmAccount,
+  listAsrProvider,
+  listVoiceSource,
+  listVoiceBySource,
+  listBizGroup,
+  listGateway,
+  listIvr
+} from '@/api/company/inboundCallManage'
+
+export default {
+  name: 'InboundCallManage',
+  data() {
+    return {
+      // 遮罩层
+      loading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 表格数据
+      inboundList: [],
+      // 选中的数据
+      selectedRows: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        llmAccountId: undefined,
+        callee: undefined
+      },
+      // 大模型账户列表
+      llmAccountList: [],
+      // ASR提供商列表
+      asrProviderList: {},
+      // 音色来源列表
+      voiceSourceList: {},
+      // 音色列表
+      voiceList: [],
+      // 业务组列表
+      bizGroupList: [],
+      // 网关列表
+      gatewayList: [],
+      // IVR列表
+      ivrList: [],
+      // 弹窗控制
+      dialogVisible: false,
+      dialogTitle: '新增呼入大模型配置',
+      // 表单数据
+      form: {
+        id: undefined,
+        inboundAlias: undefined,
+        callee: undefined,
+        serviceType: 'ai',
+        llmAccountId: undefined,
+        asrProvider: undefined,
+        voiceSource: undefined,
+        voiceCode: undefined,
+        aiTransferType: 'acd',
+        aiTransferGroupId: undefined,
+        aiTransferGatewayId: undefined,
+        aiTransferGatewayDestNumber: undefined,
+        aiTransferExtNumber: undefined,
+        ivrId: undefined,
+        satisfSurveyIvrId: undefined,
+        fsSceneType:null,
+        callBackUrl:null
+      },
+      // 表单校验规则
+      rules: {
+        inboundAlias: [
+          { required: true, message: '请输入名称', trigger: 'blur' }
+        ],
+        callee: [
+          { required: true, message: '请输入被叫号码', trigger: 'blur' }
+        ],
+        serviceType: [
+          { required: true, message: '请选择服务类型', trigger: 'change' }
+        ],
+        fsSceneType: [
+          { required: true, message: '请选择呼入场景', trigger: 'change' }
+        ],
+        callBackUrl: [
+          { required: true, message: '请输入线路回调地址', trigger: 'blur' }
+        ],
+      },
+      //场景下拉
+      sceneList:[]
+    }
+  },
+  computed: {
+    // 是否显示业务组选择
+    showBizGroup() {
+      return this.form.serviceType === 'acd' ||
+             (this.form.serviceType === 'ai' && this.form.aiTransferType === 'acd')
+    },
+    // 是否显示网关选择
+    showGateway() {
+      return this.form.serviceType === 'ai' && this.form.aiTransferType === 'gateway'
+    },
+    // 是否显示分机号输入
+    showExtension() {
+      return this.form.serviceType === 'ai' && this.form.aiTransferType === 'extension'
+    }
+  },
+  created() {
+    this.getList()
+    this.loadDropdownData()
+    this.getDicts("task_scene_type").then((response) => {
+        this.sceneList = response.data;
+        console.log(this.sceneList);
+    });
+  },
+  methods: {
+    /** 加载下拉数据 */
+    loadDropdownData() {
+      // 大模型账户
+      listLlmAccount().then(response => {
+        this.llmAccountList = response.data || []
+      })
+      // ASR提供商
+      listAsrProvider().then(response => {
+        this.asrProviderList = response.data || {}
+      })
+      // 音色来源
+      listVoiceSource().then(response => {
+        this.voiceSourceList = response.data || {}
+      })
+      // 业务组
+      listBizGroup().then(response => {
+        this.bizGroupList = response.data || []
+      })
+      // 网关
+      listGateway().then(response => {
+        this.gatewayList = response.data || []
+      })
+      // IVR
+      listIvr().then(response => {
+        this.ivrList = response.data || []
+      })
+    },
+    /** 查询列表 */
+    getList() {
+      this.loading = true
+      listInboundLlm(this.queryParams).then(response => {
+        this.inboundList = response.rows || []
+        this.total = response.total || 0
+        this.loading = false
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    /** 服务类型改变 */
+    handleServiceTypeChange() {
+      // 清空关联字段
+      this.form.llmAccountId = undefined
+      this.form.asrProvider = undefined
+      this.form.voiceSource = undefined
+      this.form.voiceCode = undefined
+      this.form.aiTransferType = 'acd'
+      this.form.ivrId = undefined
+      this.voiceList = []
+    },
+    /** 音色来源改变 */
+    handleVoiceSourceChange(val) {
+      this.form.voiceCode = undefined
+      if (val) {
+        listVoiceBySource(val).then(response => {
+          this.voiceList = response.data || []
+        })
+      } else {
+        this.voiceList = []
+      }
+    },
+    /** AI转接类型改变 */
+    handleAiTransferTypeChange() {
+      this.form.aiTransferGroupId = undefined
+      this.form.aiTransferGatewayId = undefined
+      this.form.aiTransferGatewayDestNumber = undefined
+      this.form.aiTransferExtNumber = undefined
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    /** 表格多选选中事件 */
+    handleSelectionChange(selection) {
+      this.selectedRows = selection
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset()
+      this.dialogTitle = '新增呼入大模型配置'
+      this.dialogVisible = true
+    },
+    /** 修改按钮操作 */
+    handleEdit(row) {
+      this.reset()
+      let editRow = row
+      if (!editRow) {
+        if (this.selectedRows.length !== 1) {
+          this.$message.warning('请选择一条要修改的数据')
+          return
+        }
+        editRow = this.selectedRows[0]
+      }
+      this.dialogTitle = '修改呼入大模型配置'
+      this.form = { ...editRow }
+      // 如果有音色来源,加载音色列表
+      if (this.form.voiceSource) {
+        listVoiceBySource(this.form.voiceSource).then(response => {
+          this.voiceList = response.data || []
+        })
+      }
+      this.dialogVisible = true
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      let ids = []
+      if (row) {
+        ids = [row.id]
+      } else {
+        if (this.selectedRows.length === 0) {
+          this.$message.warning('请至少选择一条要删除的数据')
+          return
+        }
+        ids = this.selectedRows.map(item => item.id)
+      }
+      this.$confirm(`此操作将永久删除${ids.length > 1 ? '这些' : '该'}记录,是否继续?`, '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        delInboundLlm(ids.join(',')).then(response => {
+          if (response.code === 200) {
+            this.$message.success('删除成功')
+            this.getList()
+          } else {
+            this.$message.error(response.msg || '删除失败')
+          }
+        })
+      }).catch(() => {})
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        id: undefined,
+        inboundAlias: undefined,
+        callee: undefined,
+        serviceType: 'ai',
+        llmAccountId: undefined,
+        asrProvider: undefined,
+        voiceSource: undefined,
+        voiceCode: undefined,
+        aiTransferType: 'acd',
+        aiTransferGroupId: undefined,
+        aiTransferGatewayId: undefined,
+        aiTransferGatewayDestNumber: undefined,
+        aiTransferExtNumber: undefined,
+        ivrId: undefined,
+        satisfSurveyIvrId: undefined
+      }
+      this.voiceList = []
+      this.resetForm('form')
+    },
+    /** 弹窗关闭时清理 */
+    handleDialogClose() {
+      this.reset()
+    },
+    /** 提交表单 */
+    submitForm() {
+      this.$refs.form.validate(valid => {
+        if (valid) {
+          // 处理转接数据
+          const submitData = { ...this.form }
+          if (submitData.serviceType === 'ai') {
+            if (submitData.aiTransferType === 'acd') {
+              submitData.aiTransferData = submitData.aiTransferGroupId
+            } else if (submitData.aiTransferType === 'extension') {
+              submitData.aiTransferData = submitData.aiTransferExtNumber
+            } else if (submitData.aiTransferType === 'gateway') {
+              submitData.aiTransferData = JSON.stringify({
+                gatewayId: submitData.aiTransferGatewayId,
+                destNumber: submitData.aiTransferGatewayDestNumber
+              })
+            }
+          } else if (submitData.serviceType === 'acd') {
+            submitData.aiTransferData = submitData.aiTransferGroupId
+          }
+          
+          if (submitData.id) {
+            updateInboundLlm(submitData).then(response => {
+              if (response.code === 200) {
+                this.$message.success('修改成功')
+                this.dialogVisible = false
+                this.getList()
+              } else {
+                this.$message.error(response.msg || '修改失败')
+              }
+            })
+          } else {
+            addInboundLlm(submitData).then(response => {
+              if (response.code === 200) {
+                this.$message.success('新增成功')
+                this.dialogVisible = false
+                this.getList()
+              } else {
+                this.$message.error(response.msg || '新增失败')
+              }
+            })
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-container {
+  padding: 20px;
+}
+</style>

+ 169 - 4
src/views/company/companyVoiceRobotic/index.vue

@@ -87,7 +87,7 @@
       </el-table-column>
       <el-table-column label="添加类型" align="center" prop="isWeCom">
         <template slot-scope="scope">
-          <!-- <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag> -->
+          <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag>
           <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
         </template>
       </el-table-column>
@@ -381,10 +381,10 @@
             </div>
             <el-form-item label="添加类型" prop="isWeCom">
               <el-radio-group v-model="form.isWeCom">
-                <!-- <el-radio :label="1" border>
+                <el-radio :label="1" border>
                   <i class="el-icon-pie-chart"></i>
                   个微
-                </el-radio> -->
+                </el-radio>
                 <el-radio :label="2" border>
                   <i class="el-icon-star-on"></i>
                   企微
@@ -474,6 +474,49 @@
             <el-tag v-for="item in levelList" v-if="scope.row.intention == item.dictValue">{{item.dictLabel}}</el-tag>
           </template>
         </el-table-column>
+        <el-table-column label="呼出次数" align="right" prop="roboticCallOutCount" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.roboticCallOutCount == null ? 0 : scope.row.roboticCallOutCount }}
+          </template>
+        </el-table-column>
+          <el-table-column label="AI标签" align="right" prop="customerId" width="250">
+              <template slot-scope="scope">
+                  <div v-if="scope.row.tagList && scope.row.tagList.length" class="ai-tags-container">
+                      <div v-for="tag in scope.row.tagList" :key="tag.id" class="ai-tag-item">
+                          <div class="tag-main-content">
+                              <span class="tag-property-name">{{ tag.propertyName }}</span>
+                              <span class="tag-property-value">
+                                  <span v-if="Array.isArray(tag.propertyValue)">{{ tag.propertyValue.join('、') }}</span>
+                                  <span v-else>{{ tag.propertyValue }}</span>
+                              </span>
+                          </div>
+                          <div v-if="tag.intention || (tag.likeRatio !== null && tag.likeRatio !== undefined)" class="tag-meta-info">
+                              <el-tag v-if="tag.intention" size="mini" effect="plain" class="meta-tag intention-tag">
+                                  <i class="el-icon-star-on"></i> {{ getIntentionText(tag.intention) }}
+                              </el-tag>
+                              <el-tag v-if="tag.likeRatio !== null && tag.likeRatio !== undefined" size="mini" type="success" effect="plain" class="meta-tag ratio-tag">
+                                  <i class="el-icon-trend-chart"></i> {{ tag.likeRatio }}%
+                              </el-tag>
+                          </div>
+                      </div>
+                  </div>
+                  <span v-else class="no-tags">暂无标签</span>
+              </template>
+          </el-table-column>
+          <el-table-column label="是否添加客服" align="center" prop="isAdd">
+              <template slot-scope="scope">
+                  <el-tag
+                      :type="scope.row.isAdd === 1 ? 'success' : 'info'"
+                      :style="{
+                  backgroundColor: scope.row.isAdd === 1 ? '#67C23A' : '#909399',
+                  color: '#fff',
+                  border: 'none'
+                }"
+                  >
+                      {{ scope.row.isAdd === 1 ? '已添加' : '未添加' }}
+                  </el-tag>
+              </template>
+          </el-table-column>
         <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
           <template slot-scope="scope">
             <el-button
@@ -852,10 +895,11 @@ import {getDicts} from "@/api/system/dict/data";
 import { optionList } from '@/api/company/companyWorkflow'
 import {wxListQw} from "../../../api/company/companyVoiceRobotic";
 import CallCenterPhoneBar from '../../aiSipCall/aiSipCallManualOutbound.vue'
+import AiTagPanel from "../../crm/components/AiTagPanel.vue";
 
 export default {
   name: "Robotic",
-  components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
+  components: {AiTagPanel, draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
   data() {
     return {
       submitFormLoading:false,
@@ -1109,6 +1153,17 @@ export default {
     }
   },
   methods: {
+
+      getIntentionText(intention) {
+          const intentionMap = {
+              high: "高意向",
+              medium: "中意向",
+              low: "低意向",
+              none: "无意向"
+          };
+          return intentionMap[intention] || intention;
+      },
+
     getSmsTempDropList(){
       getSmsTempList().then(res=>{
         this.smsTempList = res.data;
@@ -2145,4 +2200,114 @@ export default {
     font-size: 14px;
     color: #606266;
 }
+
+/* AI标签样式优化 */
+.ai-tags-container {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    padding: 4px 0;
+    max-height: 270px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scroll-behavior: smooth;
+}
+
+.ai-tags-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.ai-tags-container::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb {
+    background: #c0c4cc;
+    border-radius: 3px;
+    transition: background 0.3s ease;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb:hover {
+    background: #909399;
+}
+
+.ai-tag-item {
+    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 10px 12px;
+    transition: all 0.3s ease;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+    flex-shrink: 0;
+}
+
+.ai-tag-item:hover {
+    border-color: #c0c4cc;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    transform: translateY(-1px);
+}
+
+.tag-main-content {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-bottom: 6px;
+}
+
+.tag-property-name {
+    font-size: 13px;
+    color: #909399;
+    font-weight: 500;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.tag-property-value {
+    font-size: 13px;
+    color: #303133;
+    font-weight: 600;
+    flex: 1;
+    word-break: break-all;
+}
+
+.tag-meta-info {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding-top: 6px;
+    border-top: 1px dashed #e4e7ed;
+}
+
+.meta-tag {
+    font-size: 12px;
+    padding: 0 8px;
+    height: 22px;
+    line-height: 20px;
+    border-radius: 4px;
+    font-weight: 500;
+}
+
+.meta-tag i {
+    margin-right: 3px;
+    font-size: 12px;
+}
+
+.intention-tag {
+    background: linear-gradient(135deg, #fff1f0 0%, #ffffff 100%);
+    border-color: #ffa39e;
+    color: #cf1322;
+}
+
+.ratio-tag {
+    background: linear-gradient(135deg, #f6ffed 0%, #ffffff 100%);
+    border-color: #b7eb8f;
+    color: #389e0d;
+}
+
+.no-tags {
+    color: #c0c4cc;
+    font-size: 13px;
+    font-style: italic;
+}
 </style>

+ 2303 - 0
src/views/company/companyVoiceRobotic/myIndex.vue

@@ -0,0 +1,2303 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="任务名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入任务名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <!-- <el-form-item label="机器人" prop="robot">
+        <el-select v-model="queryParams.robot" filterable clearable>
+          <el-option v-for="item in robotList" :label="item.name + '('+item.num+')'" :value="item.id"/>
+        </el-select>
+      </el-form-item> -->
+      <el-form-item label="任务类型" prop="taskType">
+        <el-select v-model="queryParams.taskType" filterable clearable>
+          <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="cyan" 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="primary"
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['system:companyVoiceRobotic:add']"
+        >新增
+        </el-button>
+      </el-col>
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="danger"-->
+<!--          icon="el-icon-delete"-->
+<!--          size="mini"-->
+<!--          :disabled="multiple"-->
+<!--          @click="handleDelete"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:remove']"-->
+<!--        >删除-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="warning"-->
+<!--          icon="el-icon-download"-->
+<!--          size="mini"-->
+<!--          @click="handleExport"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:export']"-->
+<!--        >导出-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+<!--      <el-col :span="1.5">-->
+<!--        <el-button-->
+<!--          type="success"-->
+<!--          icon="el-icon-refresh"-->
+<!--          size="mini"-->
+<!--          @click="updateStatusFun"-->
+<!--          v-hasPermi="['system:companyVoiceRobotic:list']"-->
+<!--        >更新状态-->
+<!--        </el-button>-->
+<!--      </el-col>-->
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="roboticList">
+      <el-table-column label="ID" align="center" prop="id"/>
+      <el-table-column label="任务名称" align="center" prop="name"/>
+      <el-table-column label="任务流程" align="center" prop="name">
+        <template slot-scope="scope">
+          <el-tag v-for="item in workflowList" v-if="scope.row.companyAiWorkflowId == item.value">{{item.label}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="加微方式" align="center" prop="dialogId">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.addType == 0">平均</el-tag>
+          <el-tag v-if="scope.row.addType == 1">意向</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="添加类型" align="center" prop="isWeCom">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag>
+          <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
+        </template>
+      </el-table-column>
+       <el-table-column label="任务类型" align="center" prop="taskType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.taskType == 1">普通任务</el-tag>
+          <el-tag v-if="scope.row.taskType == 2">场景任务 - {{scope.row.sceneTypeName}}</el-tag>
+        </template>
+      </el-table-column>
+       <el-table-column label="生效时间段" align="center" prop="taskType" width="250">
+        <template slot-scope="scope">
+          <div v-if="scope.row.taskType == 1">
+            {{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+          </div>
+          <div v-if="scope.row.taskType == 2" >
+            <div v-if="!!scope.row.availableStartTime && !!scope.row.availableEndTime ">
+            适用:{{scope.row.availableStartTime}} ~ {{scope.row.availableEndTime}}
+            </div>
+            <div v-if="!!scope.row.runtimeRangeStart && !!scope.row.runtimeRangeEnd">
+            运行:{{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+            </div>
+          </div>
+        </template>
+      </el-table-column>
+      <el-table-column label="任务状态" align="center">
+        <template slot-scope="scope">
+            <el-tag v-if="scope.row.taskStatus == 0">未启动</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 1" type="warning">执行中</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 2" type="danger">执行中断</el-tag>
+            <el-tag v-if="scope.row.taskStatus == 3" type="success">执行完成</el-tag>
+        </template>
+      </el-table-column>
+
+        <el-table-column label="创建人" align="center" prop="createByName"/>
+        <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
+<!--      <el-table-column label="外呼状态" align="center">-->
+<!--        <template slot-scope="scope">-->
+<!--          <div v-loading="loadingStatus">-->
+<!--            <p v-if="statusObj.hasOwnProperty(scope.row.taskId)">-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 0">未启动</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 1">运行中</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 2">已暂停</el-tag>-->
+<!--              <el-tag v-if="statusObj[scope.row.taskId].runningStatus == 3">已停止</el-tag>-->
+<!--            </p>-->
+<!--            <p v-if="!statusObj.hasOwnProperty(scope.row.taskId)"><el-tag>空</el-tag></p>-->
+<!--          </div>-->
+<!--        </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"
+            @click="calleesOpen(scope.row.id)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >客户列表</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="wxOpen(scope.row.id,scope.row.isWeCom)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >加微管理</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document"
+            @click="showExecLogs(scope.row)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >执行日志</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            v-if="scope.row.taskStatus == 0"
+            @click="taskRunFun(scope.row.id)"
+          >启动任务</el-button>
+          <!-- <el-button
+            size="mini"
+            type="text"
+            v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 0 || statusObj[scope.row.taskId].runningStatus == 3)"
+            @click="startRoboticFun(scope.row.taskId)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >开启外呼任务</el-button> -->
+          <el-button
+            size="mini"
+            type="text"
+            v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 1 || statusObj[scope.row.taskId].runningStatus == 2)"
+            @click="stopRoboticFun(scope.row.taskId)"
+            v-hasPermi="['system:companyVoiceRobotic:list']"
+          >停止任务</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['system:companyVoiceRobotic:remove']"
+          >删除
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改机器人外呼任务对话框 -->
+    <el-drawer size="45%" :title="title" :visible.sync="open" width="500px" append-to-body class="task-form-drawer">
+      <div class="drawer-content">
+        <el-form ref="form" :model="form" :rules="rules" label-width="90px" class="task-form">
+            <div class="form-section" >
+               <div class="section-title">
+                <i class="el-icon-document"></i>
+                <span>任务类型</span>
+              </div>
+             <el-form-item label="任务类型" prop="taskType">
+              <el-select v-model="form.taskType" filterable placeholder="请选择任务类型" @change="taskTypeChange()">
+                <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
+              </el-select>
+            </el-form-item>
+             </div>
+
+          <div class="form-section" v-if="form.taskType === 1" >
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入任务名称" clearable/>
+            </el-form-item>
+            <el-form-item label="拨打客户" prop="userIds">
+              <el-button icon="el-icon-user" @click="openSelect">
+                选择客户
+                <el-tag v-if="form.userIds && form.userIds.length" type="primary" size="small" style="margin-left: 8px;">
+                  {{ form.userIds.length }}
+                </el-tag>
+              </el-button>
+            </el-form-item>
+            <el-form-item label="任务流程" prop="companyAiWorkflowId">
+              <el-select v-model="form.companyAiWorkflowId" filterable placeholder="请选择任务流程">
+                <el-option v-for="item in workflowList" :key="item.value" :label="item.label" :value="item.value"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="运行时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeStart">
+                     <el-time-select
+                  placeholder="任务运行开始时间"
+                   style="width: 100%;"
+                  v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeEnd">
+                     <el-time-select
+                      style="width: 100%;"
+                  placeholder="任务运行结束时间"
+                  v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
+                    minTime: form.runtimeRangeStart
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+          </div>
+          <!-- 场景任务 -->
+          <div class="form-section" v-if="form.taskType === 2" >
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入任务名称" clearable/>
+            </el-form-item>
+            <el-form-item label="任务流程" prop="companyAiWorkflowId">
+              <el-select v-model="form.companyAiWorkflowId" filterable placeholder="请选择任务流程">
+                <el-option v-for="item in workflowList" :key="item.value" :label="item.label" :value="item.value"/>
+              </el-select>
+            </el-form-item>
+             <el-form-item label="场景类型" prop="sceneType">
+              <el-select v-model="form.sceneType" filterable placeholder="请选择场景类型">
+                <el-option v-for="opt in sceneList" :key="opt.dictValue" :label="opt.dictLabel" :value="opt.dictValue"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="运行时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeStart">
+                     <el-time-select
+                  placeholder="任务运行开始时间"
+                   style="width: 100%;"
+                  v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="runtimeRangeEnd">
+                     <el-time-select
+                      style="width: 100%;"
+                  placeholder="任务运行结束时间"
+                  v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
+                    minTime: form.runtimeRangeStart
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+            <el-form-item label="适用时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="availableStartTime">
+                     <el-time-select
+                  placeholder="场景适用开始时间"
+                   style="width: 100%;"
+                  v-model="form.availableStartTime"
+                  key="availableStartTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '23:30'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="availableEndTime">
+                   <el-time-select
+                  style="width: 100%;"
+                  placeholder="场景适用结束时间"
+                  v-model="form.availableEndTime"
+                  key="availableEndTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '23:30'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-setting"></i>
+              <span>加微设置</span>
+            </div>
+            <el-form-item label="加微方式" prop="addType">
+              <el-radio-group v-model="form.addType">
+                <el-radio :label="0" border>
+                  <i class="el-icon-pie-chart"></i>
+                  平均分配
+                </el-radio>
+                <el-radio :label="1" border>
+                  <i class="el-icon-star-on"></i>
+                  意向分配
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </div>
+
+          <div class="form-section account-section">
+            <div class="section-title">
+              <i class="el-icon-user-solid"></i>
+              <span>分配账号</span>
+            </div>
+            <el-form-item label="添加类型" prop="isWeCom">
+              <el-radio-group v-model="form.isWeCom">
+                <el-radio :label="1" border>
+                  <i class="el-icon-pie-chart"></i>
+                  个微
+                </el-radio>
+                <el-radio :label="2" border>
+                  <i class="el-icon-star-on"></i>
+                  企微
+                </el-radio>
+              </el-radio-group>
+            </el-form-item>
+            <el-button type="primary" size="mini" icon="el-icon-plus" @click="addQwUser" plain>添加</el-button>
+
+            <div class="account-list" v-if="form.qwUser && form.qwUser.length">
+              <div v-for="(item, index) in form.qwUser" :key="index" class="account-item">
+                <el-row :gutter="12">
+                  <el-col :span="form.addType == 1 ? 6 : 0" v-if="form.addType == 1">
+                    <el-select v-model="item.intention" placeholder="意向等级" filterable clearable size="small">
+                      <el-option v-for="opt in levelList" :key="opt.dictValue" :label="opt.dictLabel" :value="opt.dictValue"/>
+                    </el-select>
+                  </el-col>
+                  <el-col :span="form.addType == 1 ? 7 : 10">
+                    <el-button icon="el-icon-user" @click="openQwUserSelect(index)" size="small" style="width: 100%;">
+                      选择账号
+                      <el-tag v-if="item.companyUserId && item.companyUserId.length" type="success" size="mini" style="margin-left: 6px;">
+                        {{ item.companyUserId.length }}
+                      </el-tag>
+                    </el-button>
+                  </el-col>
+                  <!-- <el-col :span="form.addType == 1 ? 9 : 12">
+                    <el-select v-model="item.wxDialogId" placeholder="选择话术" filterable size="small">
+                      <el-option v-for="dialog in wxDialogList" :key="dialog.id" :label="dialog.name" :value="dialog.id"/>
+                    </el-select>
+                  </el-col> -->
+                  <el-col :span="2">
+                    <el-button type="danger" icon="el-icon-delete" circle @click="removeQwUser(index)" size="small"></el-button>
+                  </el-col>
+                </el-row>
+              </div>
+            </div>
+            <el-empty v-else description="暂无分配账号" :image-size="80"></el-empty>
+          </div>
+          <!-- <el-form-item label="模式" prop="mode">
+            <el-select v-model="form.mode">
+              <el-option label="呼叫机器人后挂断" :value="7"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="呼叫倍率" prop="multiplier">
+            <el-select v-model="form.multiplier">
+              <el-option :value="1"/>
+              <el-option :value="2"/>
+              <el-option :value="3"/>
+              <el-option :value="4"/>
+              <el-option :value="5"/>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="自动重呼" prop="autoRecall">
+            <el-radio v-model="form.autoRecall" :label="0">否</el-radio>
+            <el-radio v-model="form.autoRecall" :label="1">是</el-radio>
+          </el-form-item>
+          <el-form-item label="重呼次数" prop="recallTimes" v-if="form.autoRecall == 1">
+            <el-select v-model="form.recallTimes">
+              <el-option label="不自动重呼" :value="0"/>
+              <el-option :value="1"/>
+              <el-option :value="2"/>
+              <el-option :value="3"/>
+              <el-option :value="4"/>
+              <el-option :value="5"/>
+            </el-select>
+          </el-form-item> -->
+        </el-form>
+        <div slot="footer" class="dialog-footer" style="text-align:right">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </div>
+    </el-drawer>
+    <customer-select @success="selectFun" ref="customer"/>
+    <component
+      :is="getCurrentComponent()"
+      @success="selectQwUserFun"
+      ref="dynamicQwUserSelect"
+    />
+
+    <el-drawer title="呼叫客户列表" size="60%" :visible.sync="callees.show" width="800px" append-to-body>
+      <el-table v-loading="callees.loading" :data="callees.list">
+        <el-table-column label="电话号码" align="center" prop="phone"/>
+        <el-table-column label="客户名称" align="center" prop="userName"/>
+        <el-table-column label="客户ID" align="center" prop="userId"/>
+        <el-table-column label="客户意向度" align="center">
+          <template slot-scope="scope">
+            <el-tag v-for="item in levelList" v-if="scope.row.intention == item.dictValue">{{item.dictLabel}}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="呼出次数" align="right" prop="roboticCallOutCount" width="100">
+          <template slot-scope="scope">
+            {{ scope.row.roboticCallOutCount == null ? 0 : scope.row.roboticCallOutCount }}
+          </template>
+        </el-table-column>
+          <el-table-column label="AI标签" align="right" prop="customerId" width="250">
+              <template slot-scope="scope">
+                  <div v-if="scope.row.tagList && scope.row.tagList.length" class="ai-tags-container">
+                      <div v-for="tag in scope.row.tagList" :key="tag.id" class="ai-tag-item">
+                          <div class="tag-main-content">
+                              <span class="tag-property-name">{{ tag.propertyName }}</span>
+                              <span class="tag-property-value">
+                                  <span v-if="Array.isArray(tag.propertyValue)">{{ tag.propertyValue.join('、') }}</span>
+                                  <span v-else>{{ tag.propertyValue }}</span>
+                              </span>
+                          </div>
+                          <div v-if="tag.intention || (tag.likeRatio !== null && tag.likeRatio !== undefined)" class="tag-meta-info">
+                              <el-tag v-if="tag.intention" size="mini" effect="plain" class="meta-tag intention-tag">
+                                  <i class="el-icon-star-on"></i> {{ getIntentionText(tag.intention) }}
+                              </el-tag>
+                              <el-tag v-if="tag.likeRatio !== null && tag.likeRatio !== undefined" size="mini" type="success" effect="plain" class="meta-tag ratio-tag">
+                                  <i class="el-icon-trend-chart"></i> {{ tag.likeRatio }}%
+                              </el-tag>
+                          </div>
+                      </div>
+                  </div>
+                  <span v-else class="no-tags">暂无标签</span>
+              </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"
+              @click="openCustomer(scope.row.userId,scope.row.idToString,scope.row.roboticId)"
+            >客户信息详情</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination
+        v-show="callees.total>0"
+        :total="callees.total"
+        :page.sync="callees.queryParams.pageNum"
+        :limit.sync="callees.queryParams.pageSize"
+        @pagination="getCalleesList"
+      />
+    </el-drawer>
+    <el-drawer title="加个微详情" size="60%" :visible.sync="wx.show" append-to-body>
+      <el-table v-loading="wx.loading" :data="wx.list">
+        <el-table-column label="意向等级" align="center" prop="intention"/>
+        <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="dialogName"/>
+        <el-table-column label="分配数量" align="center" prop="num"/>
+        <el-table-column label="添加数量" align="center" prop="addNum"/>
+      </el-table>
+
+      <pagination
+        v-show="wx.total>0"
+        :total="wx.total"
+        :page.sync="wx.queryParams.pageNum"
+        :limit.sync="wx.queryParams.pageSize"
+        @pagination="getWxList"
+      />
+    </el-drawer>
+    <el-drawer title="加企微详情" size="60%" :visible.sync="qw.show" append-to-body>
+      <el-table v-loading="qw.loading" :data="qw.list">
+        <el-table-column label="意向等级" align="center" prop="intention"/>
+        <el-table-column label="企微昵称" align="center" prop="wxNickName"/>
+        <el-table-column label="企微id" align="center" prop="wxNo"/>
+        <el-table-column label="手机号" align="center" prop="phone"/>
+        <el-table-column label="话术" align="center" prop="dialogName"/>
+        <el-table-column label="分配数量" align="center" prop="num"/>
+        <el-table-column label="添加数量" align="center" prop="addNum"/>
+      </el-table>
+
+      <pagination
+        v-show="qw.total>0"
+        :total="qw.total"
+        :page.sync="qw.queryParams.pageNum"
+        :limit.sync="qw.queryParams.pageSize"
+        @pagination="getWxListQw"
+      />
+    </el-drawer>
+
+    <!-- 执行日志对话框 -->
+    <el-drawer
+      title="任务执行日志"
+      size="75%"
+      :visible.sync="execLogs.show"
+      append-to-body
+      class="exec-logs-drawer">
+      <div class="exec-logs-container" v-loading="execLogs.loading">
+        <el-card class="filter-card" shadow="never">
+          <el-form :inline="true" size="small" class="filter-form" style="margin-top: 20px;">
+            <el-form-item label="客户姓名">
+              <el-input
+                v-model="execLogs.customerName"
+                placeholder="请输入客户姓名"
+                clearable
+                @keyup.enter.native="handleSearch"
+                style="width: 180px">
+              </el-input>
+            </el-form-item>
+
+            <el-form-item label="手机号">
+              <el-input
+                v-model="execLogs.customerPhone"
+                placeholder="请输入手机号"
+                clearable
+                @keyup.enter.native="handleSearch"
+                style="width: 200px">
+              </el-input>
+            </el-form-item>
+
+            <el-form-item>
+              <span style="margin-right: 8px;">只看待执行外呼任务</span>
+              <el-checkbox v-model="execLogs.onlyCallNode"></el-checkbox>
+            </el-form-item>
+
+            <div style="margin-right:auto;">
+              <el-button type="primary" icon="el-icon-search" @click="handleSearch">
+                查询
+              </el-button>
+              <el-button icon="el-icon-refresh" @click="handleReset">
+                重置
+              </el-button>
+            </div>
+          </el-form>
+        </el-card>
+
+        <div v-if="execLogs.list.length === 0" class="empty-logs">
+          <i class="el-icon-document"></i>
+          <p>暂无执行记录</p>
+        </div>
+
+        <div v-else>
+          <!-- 统计信息卡片 -->
+          <el-card class="stats-card" shadow="never">
+            <div class="stats-grid">
+              <div class="stat-item">
+                <i class="el-icon-user" style="color: #1890ff;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">客户总数</div>
+<!--                  <div class="stat-value">{{ execLogs.list.length }}</div>-->
+                  <div class="stat-value">{{ execLogs.total }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-phone" style="color: #52c41a;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已打电话</div>
+                  <div class="stat-value">{{ execLogs.stats.callDone }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-chat-line-square" style="color: #faad14;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已加微信</div>
+                  <div class="stat-value">{{ execLogs.stats.addWxDone }}</div>
+                </div>
+              </div>
+              <div class="stat-item">
+                <i class="el-icon-message" style="color: #722ed1;"></i>
+                <div class="stat-content">
+                  <div class="stat-label">已发短信</div>
+                  <div class="stat-value">{{ execLogs.stats.sendMsgDone }}</div>
+                </div>
+              </div>
+            </div>
+          </el-card>
+
+          <!-- 执行记录列表 -->
+          <div class="logs-list">
+            <el-collapse v-model="execLogs.activeNames" accordion>
+              <el-collapse-item
+                v-for="(record, index) in execLogs.list"
+                :key="index"
+                :name="index">
+                <template slot="title">
+                  <div class="record-header">
+                    <div class="record-left">
+                      <el-avatar :size="48" icon="el-icon-user-solid" :style="{background: getAvatarColor(index)}"></el-avatar>
+                      <div class="customer-detail">
+                        <div class="customer-name-row">
+                          <span class="customer-name">{{ record.customerName }}</span>
+
+                          <el-tag
+                            v-if="record.intention"
+                            size="small"
+                            type="info"
+                            class="status-tag">
+                            意向度:{{ getIntentionLabel(record.intention) }}
+                          </el-tag>
+
+                          <el-tag
+                            :type="getWorkflowStatusType(record.workflowStatus)"
+                            size="small"
+                            class="status-tag">
+                            {{ record.workflowStatusName }}
+                          </el-tag>
+                        </div>
+                        <div class="customer-meta">
+                          <span class="meta-item phone">
+                            <i class="el-icon-phone-outline"></i>
+                            {{ record.customerPhone }}
+                          </span>
+                          <span class="meta-divider">|</span>
+                          <span class="meta-item node">
+                            <i class="el-icon-s-operation"></i>
+                            当前节点:<strong>{{ record.currentNodeTypeName }}</strong>
+
+                            <!-- <el-button
+                              v-if="hasContent(record)"
+                              size="mini"
+                              type="primary"
+                              plain
+                              icon="el-icon-chat-dot-round"
+                              style="margin-left: 8px;"
+                              @click.stop="handleShowContent(record)">
+                                查看对话内容
+                            </el-button> -->
+                            <!-- 外呼未执行标识 -->
+                            <el-button
+                                v-if="record.waitCallNode"
+                                size="mini"
+                                type="primary"
+                                plain
+                                icon="el-icon-phone"
+                                style="margin-left: 8px;"
+                                @click.stop="handleManualCall(record)">
+                                开始人工外呼
+                            </el-button>
+                          </span>
+                        </div>
+                      </div>
+                    </div>
+                    <!-- <div class="record-right">
+                      <div class="progress-info">
+                        <span class="progress-label">执行进度</span>
+                        <span class="progress-value">{{ getProgress(record) }}%</span>
+                      </div>
+                      <el-progress
+                        :percentage="getProgress(record)"
+                        :color="getProgressColor(record)"
+                        :stroke-width="10"
+                        :show-text="false"></el-progress>
+                    </div> -->
+                  </div>
+                </template>
+
+                <!-- 节点执行日志 -->
+                <div class="node-logs">
+                  <el-timeline>
+                    <el-timeline-item
+                      v-for="(log, logIndex) in record.nodeLogs"
+                      :key="logIndex"
+                      :timestamp="log.startTime"
+                      :color="getNodeStatusColor(log.statusName)"
+                      placement="top">
+                      <el-card class="node-log-card">
+                        <div class="node-log-header">
+                          <div class="node-info">
+                            <i :class="getNodeIcon(log.nodeKey)" class="node-icon"></i>
+                            <span class="node-name">{{ log.nodeName }}</span>
+                            <el-button
+                              v-if="hasContent(log)"
+                              size="mini"
+                              type="primary"
+                              plain
+                              icon="el-icon-chat-dot-round"
+                              style="margin-left: 8px;"
+                              @click.stop="handleShowContent(record,log)">
+                                查看对话内容
+                            </el-button>
+                          </div>
+                          <el-tag
+                            :type="getStatusTagType(log.statusName)"
+                            size="mini">
+                            {{ log.statusName }}
+                          </el-tag>
+                        </div>
+                        <div class="node-log-body" v-if="log.errorMsg">
+                          <div class="error-msg">
+                            <i class="el-icon-warning"></i>
+                            {{ log.errorMsg }}
+                          </div>
+                        </div>
+                        <div class="node-log-footer">
+                          <span class="duration" v-if="log.duration">
+                            <i class="el-icon-time"></i>
+                            耗时: {{ formatDuration(log.duration) }}
+                          </span>
+                        </div>
+                      </el-card>
+                    </el-timeline-item>
+                  </el-timeline>
+                </div>
+              </el-collapse-item>
+            </el-collapse>
+          </div>
+          <!-- 分页 -->
+          <div class="pagination-wrapper" style="margin-top: 20px;">
+            <el-pagination
+              background
+              layout="total, sizes, prev, pager, next, jumper"
+              :total="execLogs.total"
+              :page-size="execLogs.pageSize"
+              :current-page.sync="execLogs.pageNum"
+              :page-sizes="[5, 10, 20, 30, 50]"
+              @current-change="handlePageChange"
+              @size-change="handleSizeChange">
+            </el-pagination>
+          </div>
+        </div>
+      </div>
+    </el-drawer>
+
+    <el-drawer size="75%" title="客户详情" :visible.sync="customerDetailShow" append-to-body>
+      <customer-details ref="customerDetails" />
+    </el-drawer>
+    <el-dialog
+      :title="manualCallDialog.title"
+      :visible.sync="manualCallDialog.visible"
+      width="1500px"
+      append-to-body
+      destroy-on-close
+      class="manual-call-dialog"
+      @close="handleManualCallDialogClose"
+    >
+      <call-center-phone-bar
+          :key="manualCallDialog.key"
+          ref="callCenterPhoneBar"
+          :init-phone-number="manualCallDialog.phone"
+          :robotic-id="manualCallDialog.roboticId"
+          :company-id="manualCallDialog.companyId"
+          :company-user-id="manualCallDialog.companyUserId"
+          :workflow-instance-id="manualCallDialog.workflowInstanceId"
+      />
+    </el-dialog>
+
+    <el-dialog
+      title="对话内容"
+      :visible.sync="contentDialog.visible"
+      width="900px"
+      append-to-body
+      class="content-dialog"
+    >
+      <div class="content-dialog-wrapper">
+          <div class="content-dialog-header" >
+              <span>客户姓名:{{ contentDialog.customerName || '-' }}</span>
+              <span>手机号:{{ contentDialog.customerPhone || '-' }}</span>
+          </div>
+
+          <div v-if="!contentDialog.content" class="content-empty">
+              暂无对话内容
+          </div>
+
+          <div class="chat-container">
+              <div
+                  v-for="(msg, index) in parseContentList(contentDialog.content)"
+                  :key="index"
+                  :class="['chat-item', msg.role === 'user' ? 'chat-right' : 'chat-left']"
+              >
+                  <div class="chat-bubble-wrapper">
+                      <div class="chat-role">
+                          {{ msg.role === 'user' ? '客户' : '客服' }}
+                      </div>
+                      <div class="chat-bubble">
+                          {{ msg.content }}
+                      </div>
+                  </div>
+              </div>
+          </div>
+
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import {
+    myListRobotic,
+  getRobotic,
+  delRobotic,
+  addRobotic,
+  updateRobotic,
+  exportRobotic,
+  calleesList,
+  statusList,
+  startRobotic,
+  stopRobotic,
+  companyUserList,
+  wxList,
+  taskRun,
+    wxListQw,
+  getSmsTempList,
+  // getCIDGroupList,
+  getExecRecords,
+  getCurrentCompanyId
+} from "@/api/company/companyVoiceRobotic";
+import draggable from 'vuedraggable'
+// import { listAll } from '@/api/company/wxDialog';
+import customerSelect from '@/views/crm/components/CustomerSelect.vue';
+import qwUserSelect from '@/views/components/QwUserSelect.vue';
+import qwUserSelectTwo from '@/views/components/QwUserSelectTwo.vue';
+import customerDetails from "@/views/crm/components/customerDetails.vue";
+import {getDicts} from "@/api/system/dict/data";
+import { optionList } from '@/api/company/companyWorkflow'
+import CallCenterPhoneBar from '../../aiSipCall/aiSipCallManualOutbound.vue'
+
+export default {
+  name: "myRobotic",
+  components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo,CallCenterPhoneBar},
+  data() {
+    return {
+      taskType:1,
+      taskTypeList:[{id:1,name:"普通任务"},{id:2,name:"场景任务"}],
+      currentCompanyId:null,
+      // 遮罩层
+      loading: true,
+      // CIDGroupList:[],
+      // 选中数组
+      ids: [],
+      weekList: [
+        {label: "星期一", value: 1},
+        {label: "星期二", value: 2},
+        {label: "星期三", value: 3},
+        {label: "星期四", value: 4},
+        {label: "星期五", value: 5},
+        {label: "星期六", value: 6},
+        {label: "星期日", value: 0},
+      ],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      loadingStatus: true,
+      // 总条数
+      total: 0,
+      // 机器人外呼任务表格数据
+      roboticList: [],
+      userTableList: [],
+      workflowList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        taskName: null,
+        taskId: null,
+        robot: null,
+        dialogId: null,
+        mode: null,
+        multiplier: null,
+        autoRecall: null,
+        recallTimes: null,
+        cidGroupId: null,
+        weekDay1: null,
+        startTime1: null,
+        endTime1: null,
+        weekDay2: null,
+        startTime2: null,
+        endTime2: null,
+        createUser: null,
+        taskType:null
+      },
+      // 表单参数
+      form: {
+        taskType:1
+      },
+      taskFlowList: [{key: "cellPhone", value: "外呼"}, {key: "qwAddWx", value: "企微加微"}, {key: "sendMsg", value: "发短信"}, {key: "addWx", value: "加微"}],
+      taskFlowMap: {cellPhone: "外呼", sendMsg: "发短信", addWx: "加微", qwAddWx: "企微加微"},
+      statusObj: {},
+      levelList: {},
+      robotList: [],
+      dialogList: [],
+      wxDialogList: [],
+      qwUserList: [],
+      selectQwUserList: [],
+      thisQwUserIndex: 0,
+      updateTime: null,
+      customer: {
+        show: false,
+      },
+      customerDetailShow: false,
+      callees: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams:{
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      wx: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams:{
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      qw: {
+        show: false,
+        list: [],
+        loading: false,
+        total: 0,
+        queryParams: {
+          id: null,
+          pageNum: 1,
+          pageSize: 10,
+        },
+      },
+      execLogs: {
+        show: false,
+        loading: false,
+        list: [],
+        activeNames: [],
+        stats: {
+          callDone: 0,
+          addWxDone: 0,
+          sendMsgDone: 0
+        },
+        pageNum: 1,
+        pageSize: 10,
+        total: 0,
+        currentTaskId: null,
+        customerName: '',
+        customerPhone: '',
+        onlyCallNode: false
+      },
+      manualCallDialog: {
+        visible: false,
+        phone: '',
+        title: "人工外呼",
+        record: null,
+        key: 0,
+        roboticId: null,
+        companyId: null,
+        companyUserId: null,
+        workflowInstanceId: null
+      },
+      contentDialog: {
+        visible: false,
+        content: '',
+        customerName: '',
+        customerPhone: ''
+      },
+      // 表单校验
+      rules: {
+        taskType:[
+           { required: true, message: '请选择任务类型', trigger: 'change' },
+        ],
+        name: [
+            { required: true, message: '请输入任务名称', trigger: 'blur' },
+          ],
+        companyAiWorkflowId: [
+            { required: true, message: '请选择流程', trigger: 'change' }
+          ],
+        runtimeRangeStart:[
+             { required: true, message: '请选择任务运行开始时间', trigger: 'change' }
+          ],
+        runtimeRangeEnd:[
+             { required: true, message: '请选择任务运行结束时间', trigger: 'change' }
+          ]
+      },
+      smsTempList:[],
+      sceneList:[]
+    };
+  },
+  created() {
+    getCurrentCompanyId().then(res=>{
+        this.currentCompanyId = res.companyId;
+    }).catch(res=>{
+      console.log(res);
+    })
+    //getTypes().then(e => {
+      //this.robotList = e.robot;
+      //this.dialogList = e.dialog;
+    //})
+    // listAll().then(e => {
+    //   this.wxDialogList = e.data;
+    // })
+    companyUserList().then(e => {
+      this.qwUserList = e.data;
+    })
+    optionList().then(e => {
+      this.workflowList = e.data;
+    })
+    getDicts("customer_intention_level").then(e => {
+      this.levelList = e.data;
+    })
+    getDicts("task_scene_type").then(e => {
+      console.log(e);
+      this.sceneList = e.data;
+    })
+    this.getList();
+    this.getSmsTempDropList();
+
+    // getCIDGroupList().then(res=>{
+    //   console.log("----------------------")
+    //   console.log(res);
+    //   this.CIDGroupList = res.data;
+    // }).catch(res=>{
+    //   console.log("catch_____+++++++")
+    //   console.log(res);
+    // });
+  },
+  watch: {
+    // 监听添加类型的切换,清空选择器数据
+    'form.isWeCom': {
+      handler(newVal, oldVal) {
+        // 只有当值真正发生变化时才执行清空操作
+        if (newVal !== oldVal && oldVal !== undefined) {
+          // 清空 qwUser 数组中的选择数据
+          if (this.form.qwUser && this.form.qwUser.length > 0) {
+            this.form.qwUser.forEach(item => {
+              item.companyUserId = []; // 清空已选择的账号ID
+            });
+          }
+
+          // 清空选择器列表数据
+          this.selectQwUserList = [];
+
+          // 重置当前选择索引
+          this.thisQwUserIndex = 0;
+
+          // 强制更新视图
+          this.$forceUpdate();
+        }
+      },
+      immediate: false
+    },
+    'execLogs.currentTaskId': {
+      handler(newVal, oldVal) {
+        // 只有当任务ID真的变化时才重置搜索条件
+        if (newVal !== oldVal && oldVal !== undefined) {
+          // 清空搜索条件
+          this.execLogs.customerName = ''
+          this.execLogs.customerPhone = ''
+          this.execLogs.pageNum = 1
+          this.execLogs.onlyCallNode = false
+
+          // 可选:重置分页
+          this.execLogs.pageSize = 10
+
+          // 重新获取执行记录
+          this.getExecLogs()
+        }
+      },
+      immediate: false
+    }
+  },
+  methods: {
+
+      getIntentionText(intention) {
+          const intentionMap = {
+              high: "高意向",
+              medium: "中意向",
+              low: "低意向",
+              none: "无意向"
+          };
+          return intentionMap[intention] || intention;
+      },
+    getSmsTempDropList(){
+      getSmsTempList().then(res=>{
+        this.smsTempList = res.data;
+        console.log(this.smsTempList);
+      }).catch(res=>{
+        console.log(res);
+      })
+    },
+    /** 查询机器人外呼任务列表 */
+    getList() {
+      this.loading = true;
+        myListRobotic(this.queryParams).then(response => {
+        this.roboticList = response.rows;
+        this.roboticList.forEach(e => {
+          if(e.weekDay1){
+            e.weekDay = e.weekDay1.split(",")
+          }
+        })
+        this.total = response.total;
+        this.loading = false;
+        this.updateStatusFun();
+      });
+    },
+    updateStatusFun(){
+      if(!this.roboticList){
+        return;
+      }
+      this.loadingStatus = true;
+      statusList(this.roboticList.map(e => e.taskId).join()).then(e => {
+        this.loadingStatus = false;
+        this.statusObj = e.data;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        taskName: null,
+        taskId: null,
+        robot: null,
+        dialogId: null,
+        mode: 7,
+        multiplier: 3,
+        autoRecall: 0,
+        recallTimes: null,
+        cidGroupId: null,
+        weekDay1: null,
+        startTime1: null,
+        addType: 0,
+        endTime1: null,
+        weekDay2: null,
+        startTime2: null,
+        endTime2: null,
+        createTime: null,
+        qwUser: [],
+        createUser: null,
+        userIds: [],
+        userNames: [],
+        userTableList: [],
+        companyAiWorkflowId: null,
+        runtimeRangeStart:null,
+        runtimeRangeEnd:null,
+        isWeCom: 1,
+        taskType:1,
+        availableStartTime :null,
+        availableEndTime: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      optionList().then(e => {
+      this.workflowList = e.data;
+      })
+      this.open = true;
+      this.title = "添加任务";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getRobotic(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改机器人外呼任务";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.weekDay && this.form.weekDay.length > 0) {
+            this.form.weekDay1 = this.form.weekDay.join(",")
+          }
+
+          // 验证加微方案
+          if(!this.form.qwUser || this.form.qwUser.length == 0){
+            this.msgError("请添加分配账号");
+            return;
+          }
+
+          // 验证每个账号是否选择了企微和话术
+          for(let i = 0; i < this.form.qwUser.length; i++) {
+            const account = this.form.qwUser[i];
+            if(!account.companyUserId || account.companyUserId.length == 0) {
+              this.msgError(`第 ${i + 1} 个账号请选择企微`);
+              return;
+            }
+            // if(!account.wxDialogId) {
+            //   this.msgError(`第 ${i + 1} 个账号请选择话术`);
+            //   return;
+            // }
+          }
+
+          let list = [];
+          this.form.qwUser.forEach(l => {
+            list = list.concat(l.companyUserId.map(e => {return {intention: l.intention, companyUserId: e,wxDialogId: l.wxDialogId,smsTempId:l.smsTempId}}))
+          })
+          this.form.qwUserList = list;
+          console.log(this.form);
+          // if(this.form.addType != 0 ){
+          //  let firstTask = this.taskFlowList[0];
+          //   if(firstTask.key != "cellPhone"){
+          //     this.msgError("【意向】加微方式下,任务流程第一步必须为外呼!");
+          //     return;
+          //   }
+          // }
+          if(!this.form.qwUserList || this.form.qwUserList.length == 0){
+            this.msgError("请选者加微方案");
+            return;
+          }
+          this.form.taskFlow = this.taskFlowList.map(e => e.key).join();
+          if (this.form.id != null) {
+            updateRobotic(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("修改成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          } else {
+            addRobotic(this.form).then(response => {
+              if (response.code === 200) {
+                this.msgSuccess("新增成功");
+                this.open = false;
+                this.getList();
+              }
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除机器人外呼任务编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return delRobotic(ids);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(function () {
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有机器人外呼任务数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function () {
+        return exportRobotic(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+      }).catch(function () {
+      });
+    },
+    openSelect() {
+      if(!!this.currentCompanyId){
+      this.$refs.customer.setRowsDesignatedCompany(this.form.userTableList || [],this.currentCompanyId);
+      }else{
+      this.$refs.customer.setRows(this.form.userTableList || []);
+      }
+    },
+    // openQwUserSelect(index) {
+    //   this.thisQwUserIndex = index;
+    //   this.$nextTick(() => {
+    //     this.$refs.qwUserSelect.setRows(this.selectQwUserList[index]);
+    //   })
+    // },
+    selectFun(e) {
+      this.form.userIds = e.ids;
+      this.form.userNames = e.names;
+      this.form.userTableList = e.rows;
+      this.$forceUpdate()
+    },
+    selectQwUserFun(e) {
+      this.form.qwUser[this.thisQwUserIndex].companyUserId = e.ids;
+      this.selectQwUserList[this.thisQwUserIndex] = e.rows;
+      this.$forceUpdate()
+    },
+    calleesOpen(id){
+      this.callees.show = true;
+      this.callees.queryParams.id = id;
+      this.getCalleesList();
+    },
+    getCalleesList() {
+      this.callees.loading = true;
+      calleesList(this.callees.queryParams).then(response => {
+        this.callees.list = response.rows;
+        this.callees.total = response.total;
+        this.callees.loading = false;
+      });
+    },
+    wxOpen(id, isWeCom) {
+      console.log("isWeCom="+isWeCom)
+      if (isWeCom === 2) {
+        this.qw.show = true;
+        this.qw.queryParams.id = id;
+        this.getWxListQw();
+      } else {
+        this.wx.show = true;
+        this.wx.queryParams.id = id;
+        this.getWxList();
+      }
+    },
+    getWxListQw() {
+      this.qw.loading = true;
+      wxListQw(this.qw.queryParams).then(response => {
+        this.qw.list = response.rows;
+        this.qw.total = response.total;
+        this.qw.loading = false;
+      });
+    },
+    getWxList() {
+      this.wx.loading = true;
+      wxList(this.wx.queryParams).then(response => {
+        this.wx.list = response.rows;
+        this.wx.total = response.total;
+        this.wx.loading = false;
+      });
+    },
+    openCustomer(id,calleesId,roboticId) {
+      this.customerDetailShow=true;
+      this.$nextTick(() => {
+        this.$refs.customerDetails.getDetails(id,calleesId,roboticId);
+      })
+    },
+    startRoboticFun(id){
+      startRobotic(id).then(e => {
+        this.updateStatusFun();
+      })
+    },
+    taskRunFun(id){
+      taskRun({id}).then(e => {
+        this.getList();
+      })
+    },
+    stopRoboticFun(id){
+      stopRobotic(id).then(e => {
+        this.updateStatusFun();
+      })
+    },
+    addQwUser(){
+      this.form.qwUser.push({});
+    },
+    removeQwUser(index){
+      this.form.qwUser.splice(index, 1)
+    },
+    // 打开执行日志
+    async showExecLogs(row) {
+      this.execLogs.show = true;
+      // this.execLogs.loading = true;
+      // this.execLogs.list = [];
+      this.execLogs.pageNum = 1;
+      this.execLogs.pageSize = 10;
+      this.execLogs.currentTaskId = row.id;
+
+      try {
+        // const res = await getExecRecords(row.id);
+        // this.execLogs.list = res.data || [];
+        this.getExecLogs();
+
+        // 计算统计数据
+        // this.execLogs.stats = {
+        //   callDone: this.execLogs.list.reduce((sum, r) => sum + (r.callPhoneDone || 0), 0),
+        //   addWxDone: this.execLogs.list.reduce((sum, r) => sum + (r.addWxDone || 0), 0),
+        //   sendMsgDone: this.execLogs.list.reduce((sum, r) => sum + (r.sendMsgDone || 0), 0)
+        // };
+      } catch (error) {
+        console.error('获取执行日志失败:', error);
+        this.$message.error('获取执行日志失败');
+      } finally {
+        this.execLogs.loading = false;
+      }
+    },
+    // 获取工作流状态类型
+    getWorkflowStatusType(status) {
+      const typeMap = {
+        1: '',
+        2: 'success',
+        3: 'warning',
+        4: 'danger'
+      };
+      return typeMap[status] || 'info';
+    },
+    // 获取进度
+    getProgress(record) {
+      if (!record.nodeLogs || record.nodeLogs.length === 0) return 0;
+      const total = record.nodeLogs.length;
+      const completed = record.nodeLogs.filter(log => log.statusName === '执行成功').length;
+      return Math.round((completed / total) * 100);
+    },
+    // 获取进度条颜色
+    getProgressColor(record) {
+      const progress = this.getProgress(record);
+      if (progress === 100) return '#52c41a';
+      if (progress >= 50) return '#1890ff';
+      return '#faad14';
+    },
+    // 获取节点状态颜色
+    getNodeStatusColor(statusName) {
+      const colorMap = {
+        '执行成功': '#52c41a',
+        '执行中': '#1890ff',
+        '执行失败': '#f5222d',
+        '等待执行': '#d9d9d9',
+        '执行超时':'#f5222d'
+      };
+      return colorMap[statusName] || '#d9d9d9';
+    },
+    // 获取状态标签类型
+    getStatusTagType(statusName) {
+      const typeMap = {
+        '执行成功': 'success',
+        '执行中': 'warning',
+        '执行失败': 'danger',
+        '等待执行': 'info',
+        '执行超时':'danger'
+      };
+      return typeMap[statusName] || 'info';
+    },
+    // 获取节点图标
+    getNodeIcon(nodeKey) {
+      const iconMap = {
+        'node_start': 'el-icon-video-play',
+        'node_addwx': 'el-icon-chat-line-square',
+        'node_call': 'el-icon-phone',
+        'node_sms': 'el-icon-message',
+        'node_end': 'el-icon-circle-check'
+      };
+      return iconMap[nodeKey] || 'el-icon-document';
+    },
+    // 格式化时长
+    formatDuration(ms) {
+      if (!ms) return '-';
+      if (ms < 1000) return ms + 'ms';
+      const seconds = Math.floor(ms / 1000);
+      if (seconds < 60) return seconds + 's';
+      const minutes = Math.floor(seconds / 60);
+      const remainSeconds = seconds % 60;
+      return minutes + 'm' + remainSeconds + 's';
+    },
+    // 获取当前应该使用的组件
+    getCurrentComponent() {
+      return this.form.isWeCom === 2 ? 'qwUserSelectTwo' : 'qwUserSelect';
+    },
+
+    openQwUserSelect(index) {
+      this.thisQwUserIndex = index;
+      this.$nextTick(() => {
+        // 根据 isWeCom 的值选择对应的组件引用
+        const componentRef = this.form.isWeCom === 2 ? this.$refs.dynamicQwUserSelect : this.$refs.dynamicQwUserSelect;
+        if (componentRef && typeof componentRef.setRows === 'function') {
+          componentRef.setRows(this.selectQwUserList[index]);
+        }
+      })
+    },
+    // 获取头像颜色
+    getAvatarColor(index) {
+      const colors = ['#1890ff', '#52c41a', '#faad14', '#722ed1', '#eb2f96'];
+      return colors[index % colors.length];
+    },
+    /**
+     * 选择任务类型
+     */
+    taskTypeChange(){
+      console.log(this.form.taskType);
+      this.form.runtimeRangeStart=null;
+      this.form.runtimeRangeEnd= null;
+      this.form.userIds= [];
+      this.form.userNames= [];
+      this.form.userTableList= [];
+      this.form.companyAiWorkflowId = null;
+      this.form.name = null;
+      this.form.availableStartTime = null;
+      this.form.availableEndTime = null;
+      this.$nextTick(() => {
+      this.$refs.form.clearValidate();
+      })
+    },
+    async getExecLogs() {
+      this.execLogs.loading = true
+      try {
+        const res = await getExecRecords({
+          roboticId: this.execLogs.currentTaskId,
+          pageNum: this.execLogs.pageNum,
+          pageSize: this.execLogs.pageSize,
+          customerName: this.execLogs.customerName,
+          customerPhone: this.execLogs.customerPhone,
+          onlyCallNode: this.execLogs.onlyCallNode
+        })
+        this.execLogs.list = (res.rows || []).map(item => ({
+          ...item,
+          contentList: item.contentList || '',
+          intention: item.intention || ''
+        }))
+        this.execLogs.total = res.total || 0
+        // this.execLogs.stats = res.stats || {
+        //   callDone: 0,
+        //   addWxDone: 0,
+        //   sendMsgDone: 0
+        // }
+        // 前端计算统计数据
+        this.execLogs.stats = {
+          callDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.callPhoneDone) || 0), 0),
+          addWxDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.addWxDone) || 0), 0),
+          sendMsgDone: this.execLogs.list.reduce((sum, r) => sum + (Number(r.sendMsgDone) || 0), 0)
+        }
+
+      } catch (e) {
+        this.$message.error("获取执行日志失败")
+      } finally {
+        this.execLogs.loading = false
+      }
+    },
+
+    handlePageChange(page) {
+      this.execLogs.pageNum = page
+      this.getExecLogs()
+    },
+
+    handleSizeChange(size) {
+      this.execLogs.pageSize = size
+      this.execLogs.pageNum = 1
+      this.getExecLogs()
+    },
+    handleSearch() {
+      // 查询时回到第一页
+      this.execLogs.pageNum = 1
+      this.getExecLogs()
+    },
+
+    handleReset() {
+      this.execLogs.customerName = ''
+      this.execLogs.customerPhone = ''
+      this.execLogs.pageNum = 1
+      this.execLogs.onlyCallNode = false
+      this.getExecLogs()
+    },
+    handleManualCall(record) {
+      const phone = record.customerPhone || '';
+
+      if (!phone) {
+          this.$message.warning('当前客户没有可外呼的手机号');
+          return;
+      }
+
+      this.manualCallDialog.record = record;
+      this.manualCallDialog.phone = phone;
+      this.manualCallDialog.title = `人工外呼 - ${record.customerName || '未知客户'}`;
+
+      const currentTask = this.roboticList.find(
+        item => item.id === this.execLogs.currentTaskId
+      );
+      // 带过去的额外参数
+      this.manualCallDialog.roboticId = record.roboticId || null;
+      this.manualCallDialog.companyId = (currentTask && currentTask.companyId) || null;
+      this.manualCallDialog.companyUserId = (currentTask && currentTask.companyUserId) || null;
+      this.manualCallDialog.workflowInstanceId = record.workflowInstanceId || null;
+      this.manualCallDialog.key += 1;
+      this.manualCallDialog.visible = true;
+    },
+    handleManualCallDialogClose() {
+      this.manualCallDialog.phone = '';
+      this.manualCallDialog.record = null;
+      this.manualCallDialog.roboticId = null;
+      this.manualCallDialog.companyId = null;
+      this.manualCallDialog.companyUserId = null;
+      this.manualCallDialog.workflowInstanceId = null;
+    },
+     handleShowContent(record,log) {
+      this.contentDialog.customerName = record.customerName || '';
+      this.contentDialog.customerPhone = record.customerPhone || '';
+      this.contentDialog.content = log.nodeContentList || '';
+      this.contentDialog.visible = true;
+    },
+
+    parseContentList(content) {
+      if (!content) return []
+
+      try {
+          const parsed = typeof content === 'string' ? JSON.parse(content) : content
+          if (!Array.isArray(parsed)) return []
+
+          return parsed.filter(item => {
+              // 过滤 system 提示词
+              if (!item) return false
+              if (item.role === 'system') return false
+
+              // 过滤空内容
+              const text = item.content || ''
+              if (!String(text).trim()) return false
+
+              return true
+          })
+      } catch (e) {
+          console.error("解析 contentList 失败", e)
+          return []
+      }
+    },
+    hasContent(record) {
+      if (!record || !record.nodeContentList) return false
+
+      try {
+          const parsed = typeof record.nodeContentList === 'string'
+              ? JSON.parse(record.nodeContentList)
+              : record.nodeContentList
+
+          if (!Array.isArray(parsed)) return false
+
+          // 过滤 system 和空内容
+          const validList = parsed.filter(item => {
+              if (!item) return false
+              if (item.role === 'system') return false
+
+              const text = String(item.content || '').trim()
+              if (!text) return false
+
+              return true
+          })
+
+          return validList.length > 0
+      } catch (e) {
+          return false
+      }
+    },
+    getIntentionLabel(val) {
+      if (val === null || val === undefined || val === '') {
+          return '-'
+      }
+
+      const item = (this.levelList || []).find(e => String(e.dictValue) === String(val))
+      return item ? item.dictLabel : val
+    },
+  }
+};
+</script>
+<style>
+.flow-parent{
+  display: flex;
+  flex-wrap: nowrap;
+}
+.flow-child{
+  position: relative;
+  width: 150px;
+  cursor: move;
+}
+.flow-child:last-child i{
+  display: none;
+}
+.flow-child:last-child::after{
+  display: none;
+}
+.flow-child i{
+  right: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  position: absolute;
+  font-weight: bold;
+}
+.flow-child::after{
+  content: "";
+  border-top: 1px solid #000;
+  height: 1px;
+  width: 50px;
+  right: 26px;
+  top: 50%;
+  transform: translateY(-50%);
+  position: absolute;
+}
+.sortable-ghost{
+  background: rgb(217, 236, 255) !important;
+}
+
+.exec-logs-container {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: calc(100vh - 60px);
+}
+
+.exec-logs-container ::v-deep .el-card {
+  border: none;
+  box-shadow: none;
+}
+
+.empty-logs {
+  text-align: center;
+  padding: 80px 20px;
+  color: #909399;
+}
+
+.empty-logs i {
+  font-size: 64px;
+  margin-bottom: 16px;
+  color: #dcdfe6;
+}
+
+.empty-logs p {
+  font-size: 16px;
+}
+
+.stats-card {
+  margin-bottom: 20px;
+  border-radius: 8px;
+  border: none;
+}
+
+.stats-card ::v-deep .el-card__body {
+  padding: 20px;
+}
+
+.stats-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 20px;
+}
+
+.stat-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px;
+  background: #f9fafb;
+  border-radius: 6px;
+}
+
+.stat-item i {
+  font-size: 32px;
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-label {
+  font-size: 13px;
+  color: #909399;
+  margin-bottom: 4px;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.logs-list {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px;
+  border: none;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+.el-collapse-item__header{
+  height: auto !important;
+}
+
+.record-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  width: 100%;
+  padding: 20px 16px;
+  gap: 32px;
+}
+
+.record-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  flex: 1;
+  min-width: 0;
+}
+
+.customer-detail {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.customer-name-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.customer-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  line-height: 1.4;
+}
+
+.status-tag {
+  flex-shrink: 0;
+}
+
+.customer-meta {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  font-size: 13px;
+  color: #606266;
+  line-height: 1.4;
+}
+
+.meta-item {
+  display: inline-flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.meta-item i {
+  font-size: 14px;
+  color: #909399;
+}
+
+.meta-item.node strong {
+  color: #1890ff;
+  font-weight: 600;
+}
+
+.meta-divider {
+  color: #dcdfe6;
+}
+
+.record-right {
+  width: 200px;
+  flex-shrink: 0;
+}
+
+.progress-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.progress-label {
+  font-size: 13px;
+  color: #909399;
+}
+
+.progress-value {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.node-logs {
+  padding: 20px;
+  background: #fafafa;
+  border-radius: 6px;
+}
+
+.node-log-card {
+  margin-bottom: 0;
+}
+
+.node-log-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.node-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.node-icon {
+  font-size: 18px;
+  color: #1890ff;
+}
+
+.node-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.node-log-body {
+  margin: 12px 0;
+}
+
+.error-msg {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 8px 12px;
+  background: #fff2e8;
+  border-left: 3px solid #faad14;
+  border-radius: 4px;
+  font-size: 13px;
+  color: #d46b08;
+}
+
+.error-msg i {
+  font-size: 16px;
+}
+
+.node-log-footer {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  font-size: 13px;
+  color: #909399;
+}
+
+.duration {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.duration i {
+  font-size: 14px;
+}
+
+.task-form-drawer ::v-deep .el-drawer__body {
+  padding: 0;
+}
+
+.drawer-content {
+  padding: 16px;
+  background: #f5f7fa;
+  min-height: 100%;
+}
+
+.task-form {
+  max-width: 750px;
+  margin: 0 auto;
+}
+
+.form-section {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px 20px;
+  margin-bottom: 16px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 16px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #f0f2f5;
+}
+
+.section-title i {
+  font-size: 16px;
+  color: #1890ff;
+}
+
+.task-form ::v-deep .el-form-item {
+  margin-bottom: 16px;
+}
+
+.task-form ::v-deep .el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+.task-form ::v-deep .el-input,
+.task-form ::v-deep .el-select {
+  width: 100%;
+}
+
+.task-form ::v-deep .el-radio-group {
+  width: 100%;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+}
+
+.task-form ::v-deep .el-radio.is-bordered {
+  margin: 0;
+  padding: 10px 16px;
+  height: auto;
+}
+
+.task-form ::v-deep .el-radio.is-bordered i {
+  margin-right: 6px;
+  font-size: 16px;
+}
+
+.account-list {
+  margin-top: 12px;
+}
+
+.account-item {
+  margin-bottom: 10px;
+  padding: 12px;
+  background: #f9fafb;
+  border-radius: 6px;
+  border: 1px solid #e8e8e8;
+}
+
+.account-item:hover {
+  border-color: #1890ff;
+  background: #f0f9ff;
+}
+
+.account-item ::v-deep .el-select {
+  width: 100%;
+}
+
+.chat-container {
+    max-height: 600px;
+    overflow-y: auto;
+    padding: 16px;
+    background: #f5f7fa;
+    border-radius: 8px;
+}
+
+.chat-item {
+    display: flex;
+    margin-bottom: 14px;
+}
+
+.chat-left {
+    justify-content: flex-start;
+}
+
+.chat-right {
+    justify-content: flex-end;
+}
+
+.chat-bubble-wrapper {
+    max-width: 75%;
+}
+
+.chat-role {
+    font-size: 12px;
+    color: #909399;
+    margin-bottom: 4px;
+}
+
+.chat-left .chat-role {
+    text-align: left;
+}
+
+.chat-right .chat-role {
+    text-align: right;
+}
+
+.chat-bubble {
+    padding: 10px 14px;
+    border-radius: 12px;
+    font-size: 14px;
+    line-height: 1.6;
+    word-break: break-word;
+    white-space: pre-wrap;
+}
+
+.chat-left .chat-bubble {
+    background: #fff;
+    border: 1px solid #ebeef5;
+    color: #303133;
+}
+
+.chat-right .chat-bubble {
+    background: #409eff;
+    color: #fff;
+}
+
+.content-dialog-header {
+    margin-top: -12px;
+    margin-bottom: 24px;
+    padding-bottom: 8px;
+    border-bottom: 1px solid #ebeef5;
+    display: flex;
+    align-items: center;
+    gap: 20px;
+    font-size: 14px;
+    color: #606266;
+}
+
+.ai-tags-container {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    padding: 4px 0;
+    max-height: 270px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    scroll-behavior: smooth;
+}
+
+.ai-tags-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.ai-tags-container::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb {
+    background: #c0c4cc;
+    border-radius: 3px;
+    transition: background 0.3s ease;
+}
+
+.ai-tags-container::-webkit-scrollbar-thumb:hover {
+    background: #909399;
+}
+
+.ai-tag-item {
+    background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    padding: 10px 12px;
+    transition: all 0.3s ease;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
+    flex-shrink: 0;
+}
+
+.ai-tag-item:hover {
+    border-color: #c0c4cc;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    transform: translateY(-1px);
+}
+
+.tag-main-content {
+    display: flex;
+    align-items: baseline;
+    gap: 8px;
+    margin-bottom: 6px;
+}
+
+.tag-property-name {
+    font-size: 13px;
+    color: #909399;
+    font-weight: 500;
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.tag-property-value {
+    font-size: 13px;
+    color: #303133;
+    font-weight: 600;
+    flex: 1;
+    word-break: break-all;
+}
+
+.tag-meta-info {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding-top: 6px;
+    border-top: 1px dashed #e4e7ed;
+}
+
+.meta-tag {
+    font-size: 12px;
+    padding: 0 8px;
+    height: 22px;
+    line-height: 20px;
+    border-radius: 4px;
+    font-weight: 500;
+}
+
+.meta-tag i {
+    margin-right: 3px;
+    font-size: 12px;
+}
+
+.intention-tag {
+    background: linear-gradient(135deg, #fff1f0 0%, #ffffff 100%);
+    border-color: #ffa39e;
+    color: #cf1322;
+}
+
+.ratio-tag {
+    background: linear-gradient(135deg, #f6ffed 0%, #ffffff 100%);
+    border-color: #b7eb8f;
+    color: #389e0d;
+}
+
+.no-tags {
+    color: #c0c4cc;
+    font-size: 13px;
+    font-style: italic;
+}
+
+</style>

+ 61 - 6
src/views/company/companyWorkflow/design.vue

@@ -282,7 +282,7 @@
               </el-form-item>
             </div>
              <!-- AI加微配置 -->
-            <div v-if="selectedNode.nodeType == 'AI_ADD_WX_TASK'" class="property-section">
+            <div v-if="selectedNode.nodeType == 'AI_ADD_WX_TASK_NEW'" class="property-section">
               <div class="section-title">
                 <i class="el-icon-chat-dot-round"></i>加微配置
               </div>
@@ -401,13 +401,28 @@
                   />
                 </el-select>
               </el-form-item>
+              <el-form-item label="转人工业务组">
+                  <el-select
+                      v-model="selectedNode.nodeConfig.busiGroupId"
+                      filterable
+                      placeholder="请选择技能组"
+                      @change="handleConfigChange"
+                  >
+                      <el-option
+                          v-for="item in easyCallBusiGroupList"
+                          :key="item.groupId"
+                          :label="item.bizGroupName"
+                          :value="item.groupId"
+                      />
+                  </el-select>
+              </el-form-item>
               <el-form-item label="外呼线路">
                 <el-select
                   :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.gatewayId"
                   filterable
                   placeholder="请选择外呼线路"
-                  @change="handleConfigChange"
+                  @change="handleConfigChange('handleGateway')"
                 >
                   <el-option
                     v-for="item in easyCallGatewayList"
@@ -433,6 +448,22 @@
                   />
                 </el-select>
               </el-form-item>
+              <el-form-item label="音色来源">
+                <el-select
+                    :disabled="editAiDisable"
+                    v-model="selectedNode.nodeConfig.voiceSource"
+                    placeholder="请选择音色来源"
+                    @change="handleConfigChange('handleVoiceSource')"
+                >
+                    <el-option
+                        v-for="item in voiceSourceOptions"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value"
+                    />
+                </el-select>
+              </el-form-item>
+
               <el-form-item label="音色">
                 <el-select
                   :disabled="editAiDisable"
@@ -521,7 +552,7 @@
             </div>
 
             <!-- 条件判断 -->
-            <div v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK' || edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK' || edgeSourceNode.nodeType == 'AI_ADD_WX_TASK' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'"  class="property-section">
+            <div v-if="edgeSourceNode.nodeType == 'AI_CALL_TASK' || edgeSourceNode.nodeType == 'AI_SEND_MSG_TASK' || edgeSourceNode.nodeType == 'AI_ADD_WX_TASK_NEW' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'"  class="property-section">
               <div class="section-title">
                 <i class="el-icon-set-up"></i>条件判断
                 <el-button type="success" size="mini" icon="el-icon-plus" @click="addCondition" class="add-condition-btn">新增条件</el-button>
@@ -560,7 +591,7 @@
               </div>
 
               <!-- 加微信任务条件 -->
-              <div v-if="edgeSourceNode.nodeType == 'AI_ADD_WX_TASK' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'" class="conditions-container">
+              <div v-if="edgeSourceNode.nodeType == 'AI_ADD_WX_TASK_NEW' || edgeSourceNode.nodeType == 'AI_QW_ADD_WX_TASK'" class="conditions-container">
                 <div v-for="(item, index) in selectedEdge.conditionExprObj" :key="index" class="condition-item">
                   <div class="condition-header">
                     <span class="condition-number">条件 {{ index + 1 }}</span>
@@ -632,7 +663,7 @@ import {
 import { getWorkflow, addWorkflow, updateWorkflow, getNodeTypes, getWorkflowVersionDetail } from '@/api/company/companyWorkflow'
 import {getDicts} from "@/api/system/dict/data";
 import { getGatewayList, getLlmAccountList, getVoiceCodeList, getBusiGroupList } from '@/api/company/easyCall'
-// import { listAll } from '@/api/company/wxDialog';
+import { listAll } from '@/api/company/wxDialog';
 export default {
   name: 'WorkflowDesign',
   data() {
@@ -728,7 +759,13 @@ export default {
       easyCallGatewayList: [],     // 外呼线路(网关)列表
       easyCallLlmAccountList: [],  // 大模型底座列表
       easyCallVoiceCodeList: [],   // 音色列表
+      allEasyCallVoiceCodeList: [],   // 接口原始数据
       easyCallBusiGroupList: [],   // tts厂商(技能组)列表
+      voiceSourceOptions: [
+        { label: '阿里云TTS', value: 'aliyun_tts' },
+        { label: '豆包语音', value: 'doubao_vcl_tts' },
+        { label: '电信语音', value: 'chinatelecom_tts' }
+      ],
     }
   },
   created() {
@@ -751,6 +788,11 @@ export default {
     //   console.log("------")
     //   console.log(this.wxDialogList)
     // })
+    listAll().then(e => {
+      this.wxDialogList = e.data;
+      console.log("------")
+      console.log(this.wxDialogList)
+    })
 
     this.getDicts("sys_qw_qw_wx_add_way").then(response => {
       this.qwWxAddWayOptions = response.data;
@@ -787,9 +829,18 @@ export default {
 
     // 处理配置变化,强制更新视图
     handleConfigChange(v) {
+      if( !!v && v === "handleVoiceSource" ){
+        let voiceSource = this.selectedNode.nodeConfig.voiceSource;
+
+        this.easyCallVoiceCodeList = this.allEasyCallVoiceCodeList.filter(e => e.voiceSource == voiceSource);
+
+        this.selectedNode.nodeConfig.voiceCode = null;
+        this.selectedNode.nodeConfig.ttsModels = null;
+      }
       if( !!v && v === "handleVoice" ){
        let voice =  this.easyCallVoiceCodeList.filter(e=>e.voiceCode == this.selectedNode.nodeConfig.voiceCode);
        this.selectedNode.nodeConfig.voiceSource = voice[0].voiceSource;
+       this.selectedNode.nodeConfig.ttsModels =  voice[0].ttsModels;
       }
       else if( !!v && v === "handleGateway" ){
        let gateway =  this.easyCallGatewayList.filter(e=>e.id == this.selectedNode.nodeConfig.gatewayId);
@@ -843,6 +894,9 @@ export default {
         if (!this.selectedNode.nodeConfig.recallTimes && this.selectedNode.nodeConfig.recallTimes !== 0) {
           this.$set(this.selectedNode.nodeConfig, 'recallTimes', 0)
         }
+          if (this.selectedNode.nodeConfig.busiGroupId === undefined) {
+              this.$set(this.selectedNode.nodeConfig, 'busiGroupId', null)
+          }
         // 初始化外呼模式,默认为人工外呼(1)
         if (!this.selectedNode.nodeConfig.callMode) {
           this.$set(this.selectedNode.nodeConfig, 'callMode', 1)
@@ -867,6 +921,7 @@ export default {
           this.easyCallLlmAccountList = res.data || []
         })
         getVoiceCodeList().then(res => {
+          this.allEasyCallVoiceCodeList = res.data || []
           this.easyCallVoiceCodeList = res.data || []
         })
         getBusiGroupList().then(res => {
@@ -991,7 +1046,7 @@ export default {
         end: '#ff4d4f',
         condition: '#faad14',
         AI_CALL_TASK: '#1890ff',
-        AI_ADD_WX_TASK: '#722ed1',
+        AI_ADD_WX_TASK_NEW: '#722ed1',
         AI_SEND_MSG_TASK: '#eb2f96',
         DELAY_TASK: '#13c2c2'
       }

+ 2 - 0
src/views/company/companyWorkflow/index.vue

@@ -89,6 +89,8 @@
           />
         </template>
       </el-table-column>
+      <el-table-column label="创建人" align="center" prop="createByName"/>
+      <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
       <el-table-column label="版本" align="center" prop="version" width="60" />
       <el-table-column label="创建时间" align="center" prop="createTime" width="160">
         <template slot-scope="scope">

+ 657 - 0
src/views/company/companyWorkflow/myIndex.vue

@@ -0,0 +1,657 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="90px">
+      <el-form-item label="工作流名称" prop="workflowName">
+        <el-input
+          v-model="queryParams.workflowName"
+          placeholder="请输入工作流名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="工作流类型" prop="workflowType">
+        <el-select v-model="queryParams.workflowType" placeholder="请选择类型" clearable size="small">
+          <el-option
+            v-for="dict in workflowTypeOptions"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option
+            v-for="dict in statusOptions"
+            :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="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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" border :data="workflowList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="ID" align="center" prop="workflowId" width="80" />
+      <el-table-column label="工作流名称" align="center" prop="workflowName" min-width="150" show-overflow-tooltip />
+      <el-table-column label="描述" align="center" prop="workflowDesc" min-width="200" show-overflow-tooltip />
+      <el-table-column label="类型" align="center" prop="workflowType" width="100">
+        <template slot-scope="scope">
+          <dict-tag :options="workflowTypeOptions" :value="scope.row.workflowType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-switch
+            v-model="scope.row.status"
+            :active-value="1"
+            :inactive-value="0"
+            @change="handleStatusChange(scope.row)"
+          />
+        </template>
+      </el-table-column>
+      <el-table-column label="创建人" align="center" prop="createByName"/>
+      <el-table-column label="创建部门" align="center" prop="createByDeptName"/>
+      <el-table-column label="版本" align="center" prop="version" width="60" />
+      <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" class-name="small-padding fixed-width" fixed="right" width="200">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleEdit(scope.row)"
+          >设计</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-user"
+            @click="handleBindSales(scope.row)"
+          >绑定销售</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-document-copy"
+            @click="handleCopy(scope.row)"
+          >复制</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+          >删除</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleVersion(scope.row)"
+          >历史版本</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 绑定销售弹窗 -->
+    <el-dialog title="绑定销售" :visible.sync="bindSalesOpen" width="500px" append-to-body>
+      <!-- 当前绑定的销售 -->
+      <div class="current-sales">
+        <div class="section-title">当前绑定销售</div>
+        <div v-if="currentBindUserList && currentBindUserList.length > 0" class="bind-info">
+          <el-tag
+            v-for="(user, index) in currentBindUserList"
+            :key="index"
+            type="success"
+            style="margin-right: 8px; margin-bottom: 8px;"
+          >
+            {{ user.userName || user.user_name }}({{ user.nickName || user.nick_name }})
+          </el-tag>
+        </div>
+        <div v-else class="no-bind">
+          <el-tag type="info">暂未绑定销售</el-tag>
+        </div>
+      </div>
+
+      <!-- 可选销售列表 -->
+      <div class="sales-list">
+        <div class="section-title">选择销售</div>
+        <el-input
+          v-model="salesSearchKey"
+          placeholder="搜索昵称"
+          size="small"
+          prefix-icon="el-icon-search"
+          clearable
+          style="margin-bottom: 10px"
+        />
+        <el-table
+          :data="filteredCompanyUserList"
+          v-loading="salesLoading"
+          border
+          height="300"
+          :row-key="getSalesRowKey"
+          :row-class-name="salesRowClassName"
+          @selection-change="handleSalesSelectionChange"
+          ref="salesTable"
+        >
+          <el-table-column type="selection" width="55" align="center" :selectable="checkSelectable" :reserve-selection="true" />
+          <el-table-column label="用户名" align="center" prop="userName" />
+          <el-table-column label="昵称" align="center" prop="nickName" />
+        </el-table>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="handleBindSalesCancel">取 消</el-button>
+        <el-button type="primary" :disabled="selectedSalesList.length === 0" @click="confirmBindSales">确 定</el-button>
+      </div>
+    </el-dialog>
+
+
+    <!-- 历史版本弹窗 -->
+    <el-dialog
+      title="历史版本"
+      :visible.sync="versionOpen"
+      width="60%"
+      custom-class="version-dialog"
+      append-to-body
+    >
+      <el-table
+          v-loading="versionLoading"
+          :data="versionList"
+          border
+          size="small"
+      >
+          <el-table-column label="版本号" align="center" width="120">
+              <template slot-scope="scope">
+                  <el-tag v-if="scope.row.versionNo === currentVersionNo" type="warning">
+                      v{{ scope.row.versionNo }}(当前)
+                  </el-tag>
+                  <el-tag v-else type="success">
+                      v{{ scope.row.versionNo }}
+                  </el-tag>
+              </template>
+          </el-table-column>
+
+          <el-table-column label="工作流名称" align="center" prop="workflowName" min-width="60" />
+
+          <el-table-column label="快照时间" align="center" width="170">
+              <template slot-scope="scope">
+                  <span>{{scope.row.snapshotTime }}</span>
+              </template>
+          </el-table-column>
+
+          <el-table-column label="操作人" align="center" prop="snapshotBy" width="120" />
+
+          <el-table-column label="操作" align="center" width="180">
+              <template slot-scope="scope">
+                  <el-button
+                      type="text"
+                      size="mini"
+                      @click="handleViewVersion(scope.row)"
+                  >查看</el-button>
+                  <el-button
+                      v-if="versionList.length > 1 && scope.row.versionNo !== currentVersionNo"
+                      type="text"
+                      size="mini"
+                      style="color: #f56c6c"
+                      @click="handleRollbackVersion(scope.row)"
+                  >回退</el-button>
+              </template>
+          </el-table-column>
+      </el-table>
+
+      <div slot="footer" class="dialog-footer">
+          <el-button @click="versionOpen = false">关闭</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import { myListWorkflow, delWorkflow, updateWorkflowStatus, copyWorkflow, exportWorkflow, getBindCompanyUserByWorkflowId,
+         listCompanyUser, updateWorkflowBindCompanyUser, getWorkflowVersionList, getWorkflowVersionDetail, rollbackWorkflowVersion} from '@/api/company/companyWorkflow'
+
+export default {
+  name: 'myCompanyWorkflow',
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 工作流表格数据
+      workflowList: [],
+      // 工作流类型字典
+      workflowTypeOptions: [
+        { dictValue: '1', dictLabel: '对话流程' },
+        { dictValue: '2', dictLabel: '任务流程' },
+        { dictValue: '3', dictLabel: '审批流程' }
+      ],
+      // 状态字典
+      statusOptions: [
+        { dictValue: '1', dictLabel: '启用' },
+        { dictValue: '0', dictLabel: '禁用' }
+      ],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        workflowName: null,
+        workflowType: null,
+        status: null
+      },
+      // 绑定销售弹窗
+      bindSalesOpen: false,
+      // 销售列表加载
+      salesLoading: false,
+      // 当前操作的工作流
+      currentWorkflow: null,
+      // 当前绑定的用户列表
+      currentBindUserList: [],
+      // 销售列表
+      companyUserList: [],
+      // 选中的销售列表
+      selectedSalesList: [],
+      // 销售搜索关键字
+      salesSearchKey: '',
+      // 历史版本弹窗
+      versionOpen: false,
+      // 版本列表
+      versionList: [],
+      // 加载状态
+      versionLoading: false,
+      // 当前版本详情弹窗
+      versionDetailOpen: false,
+      // 当前版本详情加载
+      versionDetailLoading: false,
+      // 当前版本详情
+      versionDetail: {
+        workflowVersion: {},
+        nodes: [],
+        edges: []
+      },
+      currentVersionNo: null,
+    }
+  },
+  computed: {
+    // 过滤后的销售列表
+    filteredCompanyUserList() {
+      if (!this.salesSearchKey) {
+        return this.companyUserList
+      }
+      const key = this.salesSearchKey.toLowerCase()
+      return this.companyUserList.filter(item => {
+        return (item.nickName && item.nickName.toLowerCase().includes(key))
+      })
+    }
+  },
+  created() {
+    this.getList()
+    // 如果有字典配置,可以使用以下方式获取
+    // this.getDicts("ai_workflow_type").then(response => {
+    //   this.workflowTypeOptions = response.data
+    // })
+  },
+  watch: {
+    '$route.query.openVersionWorkflowId': {
+        handler(val) {
+            if (val) {
+                this.$nextTick(() => {
+                    this.autoOpenVersionDialog(val)
+                })
+            }
+        },
+        immediate: true
+    }
+  },
+  methods: {
+    /** 查询工作流列表 */
+    getList() {
+        this.loading = true
+        myListWorkflow(this.queryParams).then(response => {
+            this.workflowList = response.rows
+            this.total = response.total
+            this.loading = false
+
+            const workflowId = this.$route.query.openVersionWorkflowId
+            if (workflowId) {
+                this.$nextTick(() => {
+                    this.autoOpenVersionDialog(workflowId)
+                })
+            }
+        }).catch(() => {
+            this.loading = false
+        })
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      this.handleQuery()
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.workflowId)
+      this.single = selection.length !== 1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.$router.push('/companyWx/companyWorkflow/design')
+    },
+    /** 设计按钮操作 */
+    handleEdit(row) {
+      this.$router.push('/companyWx/companyWorkflow/design/' + row.workflowId)
+    },
+    /** 复制按钮操作 */
+    handleCopy(row) {
+      this.$confirm('是否确认复制该工作流?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return copyWorkflow(row.workflowId)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('复制成功')
+      }).catch(() => {})
+    },
+    /** 状态修改 */
+    handleStatusChange(row) {
+      const text = row.status === 1 ? '启用' : '停用'
+      this.$confirm('确认要"' + text + '"该工作流吗?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return updateWorkflowStatus(row.workflowId, row.status)
+      }).then(() => {
+        this.msgSuccess(text + '成功')
+      }).catch(() => {
+        row.status = row.status === 1 ? 0 : 1
+      })
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const workflowIds = row.workflowId || this.ids
+      this.$confirm('是否确认删除工作流编号为"' + workflowIds + '"的数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        return delWorkflow(workflowIds)
+      }).then(() => {
+        this.getList()
+        this.msgSuccess('删除成功')
+      }).catch(() => {})
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams
+      this.$confirm('是否确认导出所有工作流数据项?', '警告', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        this.exportLoading = true
+        return exportWorkflow(queryParams)
+      }).then(response => {
+        this.download(response.msg)
+        this.exportLoading = false
+      }).catch(() => {})
+    },
+    /** 绑定销售按钮操作 */
+    handleBindSales(row) {
+      this.currentWorkflow = row
+      this.currentBindUserList = []
+      this.selectedSalesList = []
+      this.salesSearchKey = ''
+      this.bindSalesOpen = true
+      this.salesLoading = true
+      console.log(11111)
+      // 获取当前绑定的销售列表
+      getBindCompanyUserByWorkflowId(row.workflowId).then(res => {
+        this.currentBindUserList = res.data.data || []
+      }).catch(() => {})
+      console.log(this.currentBindUserList)
+      // 获取销售列表
+      listCompanyUser().then(res => {
+        this.companyUserList = res.data || res.rows || []
+        this.salesLoading = false
+      }).catch(() => {
+        this.salesLoading = false
+      })
+    },
+    /** 销售勾选变化 */
+    handleSalesSelectionChange(selection) {
+      this.selectedSalesList = selection
+    },
+    /** 获取销售行key */
+    getSalesRowKey(row) {
+      return row.userId || row.companyUserId || row.id
+    },
+    /** 取消绑定销售弹窗 */
+    handleBindSalesCancel() {
+      this.bindSalesOpen = false
+      this.selectedSalesList = []
+      this.$refs.salesTable && this.$refs.salesTable.clearSelection()
+    },
+    /** 销售行样式 */
+    salesRowClassName({ row }) {
+      if (this.isCurrentBindUser(row)) {
+        return 'disabled-row'
+      }
+      return ''
+    },
+    /** 判断是否为当前绑定用户 */
+    isCurrentBindUser(row) {
+      if (!this.currentBindUserList || this.currentBindUserList.length === 0) {
+        return false
+      }
+      const rowUserId = row.userId || row.companyUserId || row.user_id
+      return this.currentBindUserList.some(user => {
+        const bindUserId = user.userId || user.companyUserId || user.user_id
+        return bindUserId == rowUserId
+      })
+    },
+    /** 检查是否可选 */
+    checkSelectable(row) {
+      return !this.isCurrentBindUser(row)
+    },
+    /** 确认绑定销售 */
+    confirmBindSales() {
+      if (this.selectedSalesList.length === 0) {
+        this.msgWarning('请选择要绑定的销售')
+        return
+      }
+
+      const workflowId = this.currentWorkflow.workflowId
+      const companyUserIds = this.selectedSalesList.map(item => item.userId || item.companyUserId)
+
+      this.doBindSales(workflowId, companyUserIds)
+    },
+    /** 执行绑定销售 */
+    doBindSales(workflowId, companyUserIds) {
+      updateWorkflowBindCompanyUser({
+        workflowId: workflowId,
+        companyUserIds: companyUserIds
+      }).then(() => {
+        this.msgSuccess('绑定成功')
+        this.bindSalesOpen = false
+        this.getList()
+      })
+    },
+    /** 打开历史版本弹窗 */
+    handleVersion(row) {
+      this.currentWorkflow = row
+      this.currentVersionNo = row.version
+      this.versionOpen = true
+      this.versionLoading = true
+      this.versionList = []
+
+      getWorkflowVersionList(row.workflowId).then(res => {
+          this.versionList = res.data || []
+          this.versionLoading = false
+      }).catch(() => {
+          this.versionLoading = false
+      })
+    },
+
+    /** 查看版本详情 */
+    handleViewVersion(row) {
+        this.$router.push({
+            path: '/companyWorkflow/version-preview/' + row.versionId,
+            query: {
+                workflowId: this.currentWorkflow.workflowId
+            }
+        })
+    },
+
+    /** 一键回退 */
+    handleRollbackVersion(row) {
+      this.$confirm('是否确认回退到版本 v' + row.versionNo + ' ?', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+      }).then(() => {
+          return rollbackWorkflowVersion(row.versionId)
+      }).then(() => {
+          this.msgSuccess('回退成功')
+          this.versionOpen = false
+          this.versionDetailOpen = false
+          this.getList()
+      }).catch(() => {})
+    },
+    /** 自动打开历史版本弹窗 */
+    autoOpenVersionDialog(workflowId) {
+      const row = this.workflowList.find(item => String(item.workflowId) === String(workflowId))
+      if (!row) {
+          return
+      }
+      this.handleVersion(row)
+
+      // 清掉 query,避免重复触发
+      const query = { ...this.$route.query }
+      delete query.openVersionWorkflowId
+      this.$router.replace({
+          path: this.$route.path,
+          query
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.current-sales {
+  margin-bottom: 20px;
+  padding-bottom: 15px;
+  border-bottom: 1px solid #eee;
+
+  .section-title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    color: #333;
+  }
+
+  .bind-info {
+    .el-tag {
+      font-size: 14px;
+      padding: 8px 15px;
+      height: auto;
+      line-height: 1.5;
+      white-space: normal;
+    }
+  }
+
+  .no-bind {
+    .el-tag {
+      font-size: 14px;
+      padding: 8px 15px;
+      height: auto;
+    }
+  }
+}
+
+.sales-list {
+  .section-title {
+    font-weight: 600;
+    margin-bottom: 10px;
+    color: #333;
+  }
+}
+</style>
+
+<style lang="scss">
+.el-table .disabled-row {
+  background-color: #f5f5f5;
+  color: #999;
+
+  &:hover > td {
+    background-color: #f5f5f5 !important;
+  }
+}
+</style>

+ 46 - 3
src/views/company/wxAccount/index.vue

@@ -89,6 +89,7 @@
       <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
@@ -105,6 +106,13 @@
             @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>
@@ -118,8 +126,8 @@
     />
 
     <!-- 添加或修改个微账号对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
         <el-form-item label="微信昵称" prop="wxNickName">
           <el-input v-model="form.wxNickName" placeholder="请输入微信昵称" />
         </el-form-item>
@@ -134,6 +142,9 @@
             <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>
@@ -144,7 +155,7 @@
 </template>
 
 <script>
-import { listCompanyAccount, getCompanyAccount, delCompanyAccount, addCompanyAccount, updateCompanyAccount, exportCompanyAccount, companyListAll } from "@/api/company/companyAccount";
+import { listCompanyAccount, getCompanyAccount, delCompanyAccount, addCompanyAccount, updateCompanyAccount, exportCompanyAccount, companyListAll, syncWx } from "@/api/company/companyAccount";
 import {getAllUserlist} from "@/api/company/companyUser";
 
 
@@ -184,6 +195,24 @@ 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' }
+        ]
       }
     };
   },
@@ -303,6 +332,20 @@ 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() {});
+    },
   }
 };
 </script>

+ 188 - 0
src/views/wx/sopUserLogsWx/detail.vue

@@ -0,0 +1,188 @@
+<template>
+  <div class="app-container">
+    <!-- SOP信息卡片 -->
+    <div style="margin-bottom: 10px">
+      <el-card>
+        <span class="custom-style" style="display: block; margin-bottom: 10px">SOP编号:{{ queryParams.sopId }}</span>
+        <span class="custom-style" style="display: block;">营期编号:{{ queryParams.sopUserId }}</span>
+      </el-card>
+    </div>
+
+    <!-- 搜索表单 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="联系人ID" prop="wxContactId">
+        <el-input
+          v-model="queryParams.wxContactId"
+          placeholder="请输入联系人ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="小程序ID" prop="fsUserId">
+        <el-input
+          v-model="queryParams.fsUserId"
+          placeholder="请输入小程序ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="客户评级" prop="grade">
+        <el-select v-model="queryParams.grade" placeholder="请选择客户评级" clearable size="small">
+          <el-option label="无" :value="0" />
+          <el-option label="A" :value="1" />
+          <el-option label="B" :value="2" />
+          <el-option label="C" :value="3" />
+          <el-option label="D" :value="4" />
+          <el-option label="E" :value="5" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="正常" :value="0" />
+          <el-option label="禁用" :value="1" />
+        </el-select>
+      </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>
+
+    <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+
+    <!-- 客户列表表格 -->
+    <el-table border v-loading="loading" :data="customerList">
+      <el-table-column type="index" label="序号" width="60" align="center" />
+      <el-table-column label="用户ID" align="center" prop="customerId" width="180" />
+      <el-table-column label="总完课天数" align="center" prop="finishCout" width="110" />
+      <el-table-column label="最近完课时间" align="center" prop="finishTime" width="160">
+        <template slot-scope="scope">
+          <span v-if="scope.row.finishTime">{{ parseTime(scope.row.finishTime) }}</span>
+          <span v-else style="color: #999;">-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="连续完课天数" align="center" prop="finishCourseDays" width="110" />
+      <el-table-column label="客户标签" align="center" prop="tagNames" min-width="150" show-overflow-tooltip />
+      <el-table-column label="客户评级" align="center" prop="grade" width="90">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.grade === 0" type="info">无</el-tag>
+          <el-tag v-else-if="scope.row.grade === 1" type="success">A</el-tag>
+          <el-tag v-else-if="scope.row.grade === 2" type="primary">B</el-tag>
+          <el-tag v-else-if="scope.row.grade === 3" type="warning">C</el-tag>
+          <el-tag v-else-if="scope.row.grade === 4" type="danger">D</el-tag>
+          <el-tag v-else-if="scope.row.grade === 5" type="danger">E</el-tag>
+          <el-tag v-else type="info">-</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="7天未看课" align="center" prop="isDaysNotStudy" width="100">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.isDaysNotStudy === 0" type="success">否</el-tag>
+          <el-tag v-else-if="scope.row.isDaysNotStudy === 1" type="warning">是</el-tag>
+          <el-tag v-else type="info">-</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" align="center" prop="status" width="80">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 0" type="success">正常</el-tag>
+          <el-tag v-else-if="scope.row.status === 1" type="warning">禁用</el-tag>
+          <el-tag v-else type="info">未知</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>
+
+    <!-- 分页组件 -->
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import { getSopUserLogsDetail } from "@/api/wx/sopUserLogsWx";
+
+export default {
+  name: "SopUserLogsDetailWx",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 客户列表数据
+      customerList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        sopId: null,
+        sopUserId: null,
+        wxContactId: null,
+        fsUserId: null,
+        grade: null,
+        status: null
+      }
+    };
+  },
+  created() {
+    // 从路由参数获取sopUserId和sopId
+    const sopUserId = this.$route.params.sopUserId || this.$route.query.sopUserId;
+    const sopId = this.$route.params.sopId || this.$route.query.sopId;
+    
+    if (sopUserId) {
+      this.queryParams.sopUserId = sopUserId;
+    }
+    if (sopId) {
+      this.queryParams.sopId = sopId;
+    }
+    
+    // 加载数据
+    this.getList();
+  },
+  methods: {
+    /** 查询客户列表 */
+    getList() {
+      this.loading = true;
+      getSopUserLogsDetail(this.queryParams.sopUserId, this.queryParams).then(response => {
+        console.log('个微SOP营期详情接口返回数据:', response);
+        this.customerList = response.rows || [];
+        this.total = response.total || 0;
+        this.loading = false;
+      }).catch(error => {
+        console.error('查询个微SOP营期详情失败:', error);
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-style {
+  font-size: 14px;
+  color: #606266;
+}
+</style>

+ 351 - 0
src/views/wx/sopUserLogsWx/sopUserLogsScheduleWx.vue

@@ -0,0 +1,351 @@
+<template>
+  <div class="app-container">
+    <!-- SOP信息卡片 -->
+    <div style="margin-bottom: 10px">
+      <el-card>
+        <span class="custom-style" style="display: block; margin-bottom: 10px">SOP规则名称:{{ sopName }}</span>
+        <span class="custom-style" style="display: block; margin-bottom: 10px">SOP规则编号:{{ queryParams.sopId }}</span>
+        <span class="custom-style" style="display: block;">模板编号:{{ tempId }}</span>
+      </el-card>
+    </div>
+
+    <!-- 搜索表单 -->
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="100px">
+      <el-form-item label="执行账号" prop="accountId">
+        <el-input
+          v-model="queryParams.accountId"
+          placeholder="请输入执行账号ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期时间" prop="startTime">
+        <el-date-picker 
+          clearable 
+          size="small"
+          v-model="queryParams.startTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择营期时间"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="正常" :value="0" />
+          <el-option label="暂停" :value="1" />
+        </el-select>
+      </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-tooltip class="item" effect="dark" content="删除营期后将不再给该营期发送消息,删除后不可恢复" placement="top">
+          <el-button
+            type="danger"
+            icon="el-icon-delete"
+            size="mini"
+            :disabled="multiple"
+            @click="handleDelete"
+            v-hasPermi="['wx:sopUserLogsWx:remove']"
+          >批量删除营期</el-button>
+        </el-tooltip>
+      </el-col>
+      <el-col :span="1.5">
+        <el-tooltip class="item" effect="dark" content="修改选择的营期时间" placement="top">
+          <el-button
+            type="primary"
+            icon="el-icon-edit"
+            size="mini"
+            :disabled="multiple"
+            @click="handleUpdateTime"
+          >批量修改营期时间</el-button>
+        </el-tooltip>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 提示信息 -->
+    <div style="color: #999;font-size: 14px;display: flex;align-items: center;margin-bottom: 5px">
+      <i class="el-icon-info"></i>
+      【天数】:列表中的天数代表插件助手会发送任务模板里的第几天的消息
+    </div>
+
+    <!-- 营期数据表格 -->
+    <el-table border v-loading="loading" :data="sopUserLogsWxList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="营期编号" align="center" prop="id" width="100" />
+      <el-table-column label="SOP任务ID" align="center" prop="sopId" width="120" />
+      <el-table-column label="执行账号ID" align="center" prop="accountId" width="150" />
+      <el-table-column label="执行账号名称" align="center" prop="accountName" width="150">
+        <template slot-scope="scope">
+          <span v-if="scope.row.accountName">{{ scope.row.accountName }}</span>
+          <span v-else style="color: #999;">未匹配</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="营期时间" align="center" prop="startTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="天数" align="center" prop="countDays" width="100" />
+      <el-table-column label="状态" align="center" prop="status" width="120">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.status === 0" type="success">正常</el-tag>
+          <el-tag v-else-if="scope.row.status === 1" type="warning">暂停</el-tag>
+          <el-tag v-else type="info">未知</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleDetail(scope.row)"
+          >详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['wx:sopUserLogsWx:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页组件 -->
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 批量修改营期时间对话框 -->
+    <el-dialog title="批量修改营期时间" :visible.sync="updateTimeOpen" width="500px" append-to-body>
+      <el-form ref="updateTimeForm" :model="updateTimeForm" :rules="updateTimeRules" label-width="120px">
+        <el-form-item label="选择营期时间" prop="newStartTime">
+          <el-date-picker 
+            clearable 
+            size="small"
+            v-model="updateTimeForm.newStartTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择新的营期时间"
+            style="width: 100%;"
+          />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitUpdateTime">确 定</el-button>
+        <el-button @click="updateTimeOpen = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { 
+  listSopUserLogsWx, 
+  getSopUserLogsWx, 
+  delSopUserLogsWx, 
+  exportSopUserLogsWx,
+  updateLogDate
+} from "@/api/wx/sopUserLogsWx";
+
+export default {
+  name: "SopUserLogsScheduleWx",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 营期表格数据
+      sopUserLogsWxList: [],
+      // SOP名称
+      sopName: '',
+      // 模板ID
+      tempId: '',
+      // 批量修改营期时间对话框
+      updateTimeOpen: false,
+      // 批量修改营期时间表单
+      updateTimeForm: {
+        ids: [],
+        newStartTime: null
+      },
+      // 批量修改营期时间表单校验
+      updateTimeRules: {
+        newStartTime: [
+          { required: true, message: "请选择新的营期时间", trigger: "change" }
+        ]
+      },
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        sopId: null,
+        accountId: null,
+        startTime: null,
+        status: null
+      }
+    };
+  },
+  created() {
+    // 从路由参数获取SOP ID
+    const sopId = this.$route.params.id;
+    if (sopId) {
+      this.queryParams.sopId = sopId;
+      this.sopName = this.$route.query.name || '';
+      this.tempId = this.$route.query.tempId || '';
+    }
+    
+    // 加载数据
+    this.getList();
+  },
+  methods: {
+    /** 查询营期列表 */
+    getList() {
+      this.loading = true;
+      listSopUserLogsWx(this.queryParams).then(response => {
+        console.log('个微SOP营期接口返回数据:', response);
+        this.sopUserLogsWxList = response.rows || [];
+        this.total = response.total || 0;
+        this.loading = false;
+      }).catch(error => {
+        console.error('查询个微SOP营期失败:', error);
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 多选框选中数据 */
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    /** 打开批量修改营期时间对话框 */
+    handleUpdateTime() {
+      this.updateTimeForm = {
+        ids: this.ids,
+        newStartTime: null
+      };
+      this.updateTimeOpen = true;
+      this.$nextTick(() => {
+        this.$refs["updateTimeForm"].clearValidate();
+      });
+    },
+    /** 提交批量修改营期时间 */
+    submitUpdateTime() {
+      this.$refs["updateTimeForm"].validate(valid => {
+        if (valid) {
+          const loading = this.$loading({
+            lock: true,
+            text: '正在修改中,请稍候...',
+            spinner: 'el-icon-loading',
+            background: 'rgba(0, 0, 0, 0.7)'
+          });
+          
+          updateLogDate({
+            ids: this.updateTimeForm.ids.join(','),
+            newStartTime: this.updateTimeForm.newStartTime
+          }).then(response => {
+            console.log('批量修改个微SOP营期时间返回数据:', response);
+            this.msgSuccess("修改成功");
+            this.updateTimeOpen = false;
+            this.getList();
+          }).catch(error => {
+            console.error('批量修改个微SOP营期时间失败:', error);
+            this.msgError("修改失败");
+          }).finally(() => {
+            loading.close();
+          });
+        }
+      });
+    },
+    /** 跳转到营期详情页面 */
+    handleDetail(row) {
+      this.$router.push({
+        path: '/wxSop/sopUserLogsWx/detail',
+        query: {
+          sopUserId: row.id,
+          sopId: row.sopId
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids.join(',');
+      this.$confirm('删除营期后将不再给该营期发送消息,删除后不可恢复。是否确认删除营期编号为"' + ids + '"的数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return delSopUserLogsWx(ids);
+      }).then(response => {
+        console.log('删除个微SOP营期返回数据:', response);
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(error => {
+        if (error !== 'cancel') {
+          console.error('删除个微SOP营期失败:', error);
+        }
+      });
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.$confirm('是否确认导出所有个微SOP营期数据项?', "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportSopUserLogsWx(this.queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {
+        this.exportLoading = false;
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+.custom-style {
+  font-size: 14px;
+  color: #606266;
+}
+</style>

+ 796 - 0
src/views/wx/wxSop/index.vue

@@ -0,0 +1,796 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="名称" prop="name">
+        <el-input
+          v-model="queryParams.name"
+          placeholder="请输入名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="筛选方式" prop="filterType">
+        <el-select
+          v-model="queryParams.filterType"
+          placeholder="请选择筛选方式"
+          clearable
+          size="small"
+        >
+          <el-option
+            v-for="item in filterTypeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="模板" prop="tempId">
+        <el-select
+          v-model="queryParams.tempId"
+          placeholder="请选择模板"
+          clearable
+          filterable
+          size="small"
+          style="width: 200px;"
+        >
+          <el-option
+            v-for="item in tempList"
+            :key="item.id"
+            :label="item.name"
+            :value="item.id"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="固定营期" prop="isFixed">
+        <el-select
+          v-model="queryParams.isFixed"
+          placeholder="请选择固定营期"
+          clearable
+          size="small"
+        >
+          <el-option
+            v-for="item in isFixedOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="item.dictValue"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="营期时间" prop="startTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.startTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择营期时间">
+        </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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['wx:wxSop:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-video-play"
+          size="mini"
+          :disabled="multiple"
+          @click="handleBatchExecute"
+          v-hasPermi="['qw:sop:batchExecuteWx']"
+        >批量执行个微SOP</el-button>
+      </el-col>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="wxSopList" @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="status" width="120">
+        <template slot-scope="scope">
+          <el-tag type="info" v-if="scope.row.status === 0">停止</el-tag>
+          <el-tag type="success" v-else-if="scope.row.status === 1">启用</el-tag>
+          <el-tag type="warning" v-else-if="scope.row.status === 2">执行中</el-tag>
+          <el-tag v-else>未知</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="名称" align="center" prop="name" />
+      <el-table-column label="筛选方式" align="center" prop="filterType">
+        <template slot-scope="scope">
+          <dict-tag :options="filterTypeOptions" :value="scope.row.filterType" />
+        </template>
+      </el-table-column>
+      <el-table-column label="模板" align="center" prop="tempId">
+        <template slot-scope="scope">
+          <span
+            v-for="item in tempList"
+            :key="item.id"
+            v-if="String(item.id) === String(scope.row.tempId)"
+          >
+            {{ item.name }}
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="是否固定营期" align="center" prop="isFixed">
+        <template slot-scope="scope">
+          <dict-tag :options="isFixedOptions" :value="scope.row.isFixed" />
+        </template>
+      </el-table-column>
+      <el-table-column label="过期时间(小时)" align="center" prop="expiryTime">
+        <template slot-scope="scope">
+          {{ scope.row.expiryTime }} 小时
+        </template>
+      </el-table-column>
+      <el-table-column label="营期开始时间" align="center" prop="startTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
+        <template slot-scope="scope">
+          <el-button
+            v-if="scope.row.status === 2 || scope.row.status === 0"
+            size="mini"
+            type="text"
+            icon="el-icon-tickets"
+            @click="handleViewSchedule(scope.row)"
+            v-hasPermi="['wx:wxSop:list']"
+          >营期</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleViewDetail(scope.row)"
+            v-hasPermi="['wx:wxSop:list']"
+          >执行详情</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['wx:wxSop:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['wx:wxSop:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改个微SOP对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="70%" append-to-body class="wx-sop-dialog">
+      <div class="wx-sop-dialog-content">
+        <el-form ref="form" :model="form" :rules="rules" label-width="130px" class="wx-sop-form">
+          <!-- 基本信息 -->
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="规则名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入规则名称" clearable maxlength="50" show-word-limit />
+            </el-form-item>
+            <el-form-item label="模板" prop="tempId">
+              <el-select
+                v-model="form.tempId"
+                placeholder="请选择模板"
+                filterable
+                clearable
+                style="width: 100%;"
+                v-loading="tempListLoading"
+                @focus="loadTempList"
+              >
+                <el-option
+                  v-for="item in tempList"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+              <Tip title="选择想要发送的模板规则" />
+            </el-form-item>
+          </div>
+
+          <!-- 筛选规则 -->
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-s-operation"></i>
+              <span>筛选规则</span>
+            </div>
+            <el-form-item label="筛选方式" prop="filterType">
+              <el-radio-group v-model="form.filterType">
+                <el-radio :label="'0'">按标签</el-radio>
+                <el-radio :label="'1'">按群聊</el-radio>
+              </el-radio-group>
+              <Tip :title="'标签:按标签筛选客户进入SOP;群聊:按群聊筛选进入SOP'" />
+            </el-form-item>
+            <template v-if="form.filterType === '0'">
+              <el-form-item label="选择的标签" prop="selectTags">
+                <el-select
+                  v-model="selectTagsList"
+                  multiple
+                  filterable
+                  clearable
+                  placeholder="请选择进入SOP的标签"
+                  style="width: 100%;"
+                  @change="onSelectTagsChange"
+                >
+                  <el-option
+                    v-for="item in tagsOptions"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  />
+                </el-select>
+              </el-form-item>
+              <el-form-item label="排除的标签" prop="excludeTags">
+                <el-select
+                  v-model="excludeTagsList"
+                  multiple
+                  filterable
+                  clearable
+                  placeholder="请选择不想进入SOP的标签"
+                  style="width: 100%;"
+                  @change="onExcludeTagsChange"
+                >
+                  <el-option
+                    v-for="item in tagsOptions"
+                    :key="item.dictValue"
+                    :label="item.dictLabel"
+                    :value="item.dictValue"
+                  />
+                </el-select>
+              </el-form-item>
+            </template>
+            <el-form-item label="执行账号" prop="accountIds">
+              <el-button
+                type="primary"
+                plain
+                icon="el-icon-user"
+                size="small"
+                @click="openQwUserSelect"
+              >
+                选择账号
+                <el-tag v-if="selectedQwUsers && selectedQwUsers.length" type="success" size="mini" style="margin-left: 6px;">
+                  {{ selectedQwUsers.length }}
+                </el-tag>
+              </el-button>
+              <div v-if="selectedQwUsers && selectedQwUsers.length" class="selected-tags">
+                <el-tag
+                  v-for="item in selectedQwUsers"
+                  :key="item.id"
+                  size="mini"
+                  closable
+                  @close="removeQwUser(item)"
+                >
+                  {{ item.wxNickName || item.wxNo || item.companyUserName }}
+                </el-tag>
+              </div>
+              <Tip title="从企微账号列表中选择用于执行个微SOP的账号" />
+            </el-form-item>
+          </div>
+
+          <!-- 时间与营期 -->
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-time"></i>
+              <span>时间与营期</span>
+            </div>
+            <el-row :gutter="24">
+              <el-col :span="12">
+                <el-form-item label="开始时间" prop="startTime">
+                  <el-date-picker
+                    clearable
+                    size="small"
+                    v-model="form.startTime"
+                    type="date"
+                    value-format="yyyy-MM-dd"
+                    placeholder="选择开始时间"
+                    style="width: 100%;"
+                  />
+                  <Tip title="SOP开始发送时间" />
+                </el-form-item>
+              </el-col>
+              <el-col :span="12">
+                <el-form-item label="过期时间" prop="expiryTime">
+                  <div class="expiry-time-wrap">
+                    <el-input-number
+                      v-model="form.expiryTime"
+                      :min="1"
+                      :max="100"
+                      controls-position="right"
+                      style="flex: 1;"
+                    />
+                    <span class="unit-text">小时</span>
+                  </div>
+                  <Tip title="待发送消息超过此时间未发送将自动作废" />
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <el-form-item label="固定营期" prop="isFixed">
+              <el-radio-group v-model="form.isFixed">
+                <el-radio :label="'0'">否</el-radio>
+                <el-radio :label="'1'">是</el-radio>
+              </el-radio-group>
+              <Tip title="固定营期:不管什么时候进入SOP的客户,营期时间都为任务开始时间" />
+            </el-form-item>
+          </div>
+
+          <!-- 其他 -->
+          <div class="form-section">
+            <div class="section-title">
+              <i class="el-icon-edit-outline"></i>
+              <span>其他</span>
+            </div>
+            <el-form-item label="备注" prop="remark">
+              <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注(选填)" maxlength="200" show-word-limit />
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="cancel">取 消</el-button>
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+      </div>
+
+      <qw-user-select @success="selectQwUserFun" ref="qwUserSelect" />
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listWxSop, getWxSop, delWxSop, addWxSop, updateWxSop, exportWxSop, updateWxStatus } from "@/api/wx/wxSop";
+import { listSopTemp } from "@/api/qw/sopTemp";
+import Tip from "@/components/Tip";
+import QwUserSelect from "@/views/components/QwUserSelect.vue";
+
+export default {
+  name: "WxSop",
+  components: { Tip, QwUserSelect },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 模板下拉数据
+      tempList: [],
+      tempListLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 个微SOP表格数据
+      wxSopList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 选择的企微账号
+      selectedQwUsers: [],
+      // 标签库(与线索客户打标签共用 crm_customer_tag)
+      tagsOptions: [],
+      selectTagsList: [],
+      excludeTagsList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        name: null,
+        filterType: null,
+        selectTags: null,
+        excludeTags: null,
+        tempId: null,
+        isFixed: null,
+        expiryTime: null,
+        startTime: null,
+      },
+      // 本地字典(筛选方式、是否固定营期)
+      filterTypeOptions: [
+        { dictValue: "0", dictLabel: "按标签筛选" },
+        { dictValue: "1", dictLabel: "按群聊筛选" },
+      ],
+      isFixedOptions: [
+        { dictValue: "0", dictLabel: "否" },
+        { dictValue: "1", dictLabel: "是" },
+      ],
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        name: [{ required: true, message: "请输入规则名称", trigger: "blur" }],
+        tempId: [{ required: true, message: "请选择模板", trigger: "change" }]
+      }
+    };
+  },
+  created() {
+    this.getList();
+    this.loadTempList();
+    this.getDicts("crm_customer_tag").then((response) => {
+      this.tagsOptions = response.data || [];
+    });
+  },
+  methods: {
+    loadTempList() {
+      this.tempListLoading = true;
+      listSopTemp()
+        .then(response => {
+          this.tempList = response.rows || [];
+        })
+        .finally(() => {
+          this.tempListLoading = false;
+        });
+    },
+    onSelectTagsChange(val) {
+      this.form.selectTags = Array.isArray(val) ? val.join(",") : "";
+    },
+    onExcludeTagsChange(val) {
+      this.form.excludeTags = Array.isArray(val) ? val.join(",") : "";
+    },
+    syncTagsFromForm() {
+      this.selectTagsList = this.form.selectTags ? String(this.form.selectTags).split(",").filter(Boolean) : [];
+      this.excludeTagsList = this.form.excludeTags ? String(this.form.excludeTags).split(",").filter(Boolean) : [];
+    },
+    /** 查询个微SOP列表 */
+    getList() {
+      this.loading = true;
+      listWxSop(this.queryParams).then(response => {
+        this.wxSopList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        name: null,
+        filterType: "0",
+        selectTags: null,
+        excludeTags: null,
+        tempId: null,
+        tempName: null,
+        isFixed: "0",
+        accountIds: null,
+        expiryTime: 24,
+        startTime: null,
+        createTime: null,
+        createBy: null,
+        updateTime: null,
+        updateBy: null,
+        remark: null
+      };
+      this.selectTagsList = [];
+      this.excludeTagsList = [];
+      this.resetForm("form");
+    },
+    // 打开企微账号选择
+    openQwUserSelect() {
+      this.$nextTick(() => {
+        this.$refs.qwUserSelect && this.$refs.qwUserSelect.setRows(this.selectedQwUsers);
+      });
+    },
+    // 选择企微账号回调(数据源与外呼任务分配账号一致)
+    selectQwUserFun(e) {
+      this.selectedQwUsers = e.rows || [];
+      const ids = e.ids || [];
+      this.form.accountIds = ids.join(",");
+      this.$forceUpdate();
+    },
+    removeQwUser(item) {
+      this.selectedQwUsers = this.selectedQwUsers.filter((u) => u.id !== item.id);
+      this.form.accountIds = this.selectedQwUsers.map((u) => u.id).join(",");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加个微SOP";
+      this.loadTempList();
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getWxSop(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改个微SOP";
+        // 兼容后端返回 number,确保 radio 正常回显
+        this.form.filterType =
+          this.form.filterType === null || typeof this.form.filterType === "undefined"
+            ? "0"
+            : String(this.form.filterType);
+        this.form.isFixed =
+          this.form.isFixed === null || typeof this.form.isFixed === "undefined"
+            ? "0"
+            : String(this.form.isFixed);
+        this.syncTagsFromForm();
+        // 回显执行账号
+        this.selectedQwUsers = response.data.selectedQwUsers || [];
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          const payload = this.buildSubmitPayload();
+          if (this.form.id != null) {
+            updateWxSop(payload).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addWxSop(payload).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 构建提交数据,确保与后端 WxSop 字段一致 */
+    buildSubmitPayload() {
+      const f = this.form;
+      return {
+        id: f.id,
+        name: f.name,
+        filterType: f.filterType != null ? parseInt(f.filterType, 10) : 0,
+        selectTags: f.selectTags || null,
+        excludeTags: f.excludeTags || null,
+        tempId: f.tempId != null ? String(f.tempId) : null,
+        isFixed: f.isFixed != null ? parseInt(f.isFixed, 10) : 0,
+        expiryTime: f.expiryTime != null ? parseInt(f.expiryTime, 10) : 24,
+        startTime: f.startTime || null,
+        remark: f.remark || null,
+        accountIds: f.accountIds || null
+      };
+    },
+    /** 查看营期按钮操作 */
+    handleViewSchedule(row) {
+      this.$router.push({
+        path: '/wxSop/sopUserLogsWx/sopUserLogsScheduleWx/' + row.id,
+        query: {
+          name: row.name,
+          tempId: row.tempId
+        }
+      });
+    },
+    /** 查看执行详情按钮操作 */
+    handleViewDetail(row) {
+      this.$router.push({
+        path: '/wxSop/wxSop/sopLogsList/' + row.id,
+        query: {
+          name: row.name,
+          tempId: row.tempId
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除个微SOP编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delWxSop(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 批量执行个微SOP */
+    handleBatchExecute() {
+      if (this.ids.length === 0) {
+        this.$message.warning("请至少选择一条记录");
+        return;
+      }
+
+      // 过滤出启用状态的SOP
+      const enabledSops = this.wxSopList.filter(item => 
+        this.ids.includes(item.id) && item.status === 1
+      );
+
+      if (enabledSops.length === 0) {
+        this.$message.warning("所选SOP中没有启用状态的记录,只能执行启用状态的SOP");
+        return;
+      }
+
+      const enabledIds = enabledSops.map(item => item.id);
+      const notEnabledCount = this.ids.length - enabledIds.length;
+
+      let confirmMsg = `是否确认批量执行所选的${enabledIds.length}条个微SOP?`;
+      if (notEnabledCount > 0) {
+        confirmMsg += `\n注意:已自动过滤${notEnabledCount}条非启用状态的记录`;
+      }
+
+      this.$confirm(confirmMsg, "批量执行确认", {
+        confirmButtonText: "确定执行",
+        cancelButtonText: "取消",
+        type: "warning",
+        distinguishCancelAndClose: true
+      }).then(() => {
+        this.loading = true;
+        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 => {
+        this.loading = false;
+        if (error !== 'cancel' && error !== 'close') {
+          console.error('批量执行个微SOP失败:', error);
+          this.msgError("批量执行失败,请稍后重试");
+        }
+      });
+    },
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有个微SOP数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportWxSop(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.wx-sop-dialog-content {
+  padding: 8px 16px 24px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+.wx-sop-form {
+  .form-section {
+    background: #fafbfc;
+    border-radius: 10px;
+    padding: 24px 28px;
+    margin-bottom: 24px;
+    border: 1px solid #ebeef5;
+  }
+
+  .form-section:last-of-type {
+    margin-bottom: 0;
+  }
+
+  .section-title {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    font-size: 15px;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 20px;
+    padding-bottom: 14px;
+    border-bottom: 1px solid #e4e7ed;
+  }
+
+  .section-title i {
+    font-size: 18px;
+    color: #409eff;
+  }
+
+  ::v-deep .el-form-item {
+    margin-bottom: 22px;
+  }
+
+  ::v-deep .el-form-item:last-child {
+    margin-bottom: 0;
+  }
+
+  .expiry-time-wrap {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    width: 100%;
+  }
+
+  .unit-text {
+    color: #909399;
+    font-size: 13px;
+    flex-shrink: 0;
+  }
+
+  .selected-tags {
+    margin-top: 12px;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .selected-tags .el-tag {
+    margin: 0;
+  }
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+  padding: 16px 0 8px;
+}
+</style>

+ 295 - 0
src/views/wx/wxSop/sopLogsList.vue

@@ -0,0 +1,295 @@
+<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>
+
+    <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="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>
+
+    <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";
+
+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;
+      });
+    },
+
+    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;
+      }
+    },
+
+    // 格式化日期为 yyyy-MM-dd HH:mm:ss 的北京时间
+    formatDateTime(date) {
+      if (!date) return null;
+
+      const options = {
+        timeZone: 'Asia/Shanghai',
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit',
+        second: '2-digit',
+        hour12: false,
+      };
+
+      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;
+      });
+    }
+  }
+};
+</script>
+
+<style scoped>
+</style>

+ 481 - 0
src/views/wx/wxSopLogs/index.vue

@@ -0,0 +1,481 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="消息类型0个人1群" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择消息类型0个人1群" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="任务ID" prop="sopId">
+        <el-input
+          v-model="queryParams.sopId"
+          placeholder="请输入任务ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期ID" prop="sopUserId">
+        <el-input
+          v-model="queryParams.sopUserId"
+          placeholder="请输入营期ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="发送类型" prop="sendType">
+        <el-select v-model="queryParams.sendType" placeholder="请选择发送类型" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="生成类型(0自动1手动)" prop="generateType">
+        <el-select v-model="queryParams.generateType" placeholder="请选择生成类型(0自动1手动)" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="发送账号ID" prop="accountId">
+        <el-input
+          v-model="queryParams.accountId"
+          placeholder="请输入发送账号ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="发送对象ID" prop="wxContactId">
+        <el-input
+          v-model="queryParams.wxContactId"
+          placeholder="请输入发送对象ID"
+          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="发送群聊ID" prop="wxRoomId">
+        <el-input
+          v-model="queryParams.wxRoomId"
+          placeholder="请输入发送群聊ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="发送群聊名称" prop="wxRoomName">
+        <el-input
+          v-model="queryParams.wxRoomName"
+          placeholder="请输入发送群聊名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="小程序ID" prop="fsUserId">
+        <el-input
+          v-model="queryParams.fsUserId"
+          placeholder="请输入小程序ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="发送状态0待发送1发送成功2发送失败3消息作废" prop="sendStatus">
+        <el-select v-model="queryParams.sendStatus" placeholder="请选择发送状态0待发送1发送成功2发送失败3消息作废" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="发送备注" prop="sendRemark">
+        <el-input
+          v-model="queryParams.sendRemark"
+          placeholder="请输入发送备注"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="发送排序" prop="sendSort">
+        <el-input
+          v-model="queryParams.sendSort"
+          placeholder="请输入发送排序"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="消息过期时间" prop="expirationTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.expirationTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择消息过期时间">
+        </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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['wx:wxSopLogs:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['wx:wxSopLogs:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['wx:wxSopLogs:remove']"
+        >删除</el-button>
+      </el-col>
+      <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" @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="消息类型0个人1群" align="center" prop="type" />
+      <el-table-column label="任务ID" align="center" prop="sopId" />
+      <el-table-column label="营期ID" align="center" prop="sopUserId" />
+      <el-table-column label="发送类型" align="center" prop="sendType" />
+      <el-table-column label="生成类型(0自动1手动)" align="center" prop="generateType" />
+      <el-table-column label="发送账号ID" align="center" prop="accountId" />
+      <el-table-column label="发送对象ID" align="center" prop="wxContactId" />
+      <el-table-column label="发送对象名称" align="center" prop="wxContactName" />
+      <el-table-column label="发送群聊ID" align="center" prop="wxRoomId" />
+      <el-table-column label="发送群聊名称" align="center" prop="wxRoomName" />
+      <el-table-column label="小程序ID" align="center" prop="fsUserId" />
+      <el-table-column label="发送状态0待发送1发送成功2发送失败3消息作废" align="center" prop="sendStatus" />
+      <el-table-column label="发送备注" align="center" prop="sendRemark" />
+      <el-table-column label="发送排序" align="center" prop="sendSort" />
+      <el-table-column label="消息过期时间" align="center" prop="expirationTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.expirationTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="备注" align="center" prop="remark" />
+      <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="['wx:wxSopLogs:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['wx:wxSopLogs:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改个微发送记录对话框 -->
+    <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="消息类型0个人1群" prop="type">
+          <el-select v-model="form.type" placeholder="请选择消息类型0个人1群">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="任务ID" prop="sopId">
+          <el-input v-model="form.sopId" placeholder="请输入任务ID" />
+        </el-form-item>
+        <el-form-item label="营期ID" prop="sopUserId">
+          <el-input v-model="form.sopUserId" placeholder="请输入营期ID" />
+        </el-form-item>
+        <el-form-item label="发送类型" prop="sendType">
+          <el-select v-model="form.sendType" placeholder="请选择发送类型">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="生成类型(0自动1手动)" prop="generateType">
+          <el-select v-model="form.generateType" placeholder="请选择生成类型(0自动1手动)">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="发送账号ID" prop="accountId">
+          <el-input v-model="form.accountId" placeholder="请输入发送账号ID" />
+        </el-form-item>
+        <el-form-item label="发送对象ID" prop="wxContactId">
+          <el-input v-model="form.wxContactId" placeholder="请输入发送对象ID" />
+        </el-form-item>
+        <el-form-item label="发送对象名称" prop="wxContactName">
+          <el-input v-model="form.wxContactName" placeholder="请输入发送对象名称" />
+        </el-form-item>
+        <el-form-item label="发送群聊ID" prop="wxRoomId">
+          <el-input v-model="form.wxRoomId" placeholder="请输入发送群聊ID" />
+        </el-form-item>
+        <el-form-item label="发送群聊名称" prop="wxRoomName">
+          <el-input v-model="form.wxRoomName" placeholder="请输入发送群聊名称" />
+        </el-form-item>
+        <el-form-item label="小程序ID" prop="fsUserId">
+          <el-input v-model="form.fsUserId" placeholder="请输入小程序ID" />
+        </el-form-item>
+        <el-form-item label="发送状态0待发送1发送成功2发送失败3消息作废">
+          <el-radio-group v-model="form.sendStatus">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="发送备注" prop="sendRemark">
+          <el-input v-model="form.sendRemark" placeholder="请输入发送备注" />
+        </el-form-item>
+        <el-form-item label="发送排序" prop="sendSort">
+          <el-input v-model="form.sendSort" placeholder="请输入发送排序" />
+        </el-form-item>
+        <el-form-item label="消息过期时间" prop="expirationTime">
+          <el-date-picker clearable size="small"
+            v-model="form.expirationTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择消息过期时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listWxSopLogs, getWxSopLogs, delWxSopLogs, addWxSopLogs, updateWxSopLogs, exportWxSopLogs } from "@/api/wx/wxSopLogs";
+
+export default {
+  name: "WxSopLogs",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 个微发送记录表格数据
+      wxSopLogsList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        type: null,
+        sopId: null,
+        sopUserId: null,
+        sendType: null,
+        generateType: null,
+        accountId: null,
+        wxContactId: null,
+        wxContactName: null,
+        wxRoomId: null,
+        wxRoomName: null,
+        fsUserId: null,
+        sendStatus: null,
+        sendRemark: null,
+        sendSort: null,
+        expirationTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询个微发送记录列表 */
+    getList() {
+      this.loading = true;
+      listWxSopLogs(this.queryParams).then(response => {
+        this.wxSopLogsList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        type: null,
+        sopId: null,
+        sopUserId: null,
+        sendType: null,
+        generateType: null,
+        accountId: null,
+        wxContactId: null,
+        wxContactName: null,
+        wxRoomId: null,
+        wxRoomName: null,
+        fsUserId: null,
+        sendStatus: 0,
+        sendRemark: null,
+        sendSort: null,
+        expirationTime: null,
+        createTime: null,
+        createBy: null,
+        updateTime: null,
+        updateBy: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加个微发送记录";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getWxSopLogs(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改个微发送记录";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateWxSopLogs(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addWxSopLogs(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除个微发送记录编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delWxSopLogs(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有个微发送记录数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportWxSopLogs(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 350 - 0
src/views/wx/wxSopUser/index.vue

@@ -0,0 +1,350 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="类型(0个人1群聊)" prop="type">
+        <el-select v-model="queryParams.type" placeholder="请选择类型(0个人1群聊)" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="任务ID" prop="sopId">
+        <el-input
+          v-model="queryParams.sopId"
+          placeholder="请输入任务ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="个微账号ID" prop="accountId">
+        <el-input
+          v-model="queryParams.accountId"
+          placeholder="请输入个微账号ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期时间" prop="startTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.startTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择营期时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="群聊ID" prop="chatId">
+        <el-input
+          v-model="queryParams.chatId"
+          placeholder="请输入群聊ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="状态" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['wx:wxSopUser:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['wx:wxSopUser:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['wx:wxSopUser:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['wx:wxSopUser:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="wxSopUserList" @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="类型(0个人1群聊)" align="center" prop="type" />
+      <el-table-column label="任务ID" align="center" prop="sopId" />
+      <el-table-column label="个微账号ID" align="center" prop="accountId" />
+      <el-table-column label="营期时间" align="center" prop="startTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="群聊ID" align="center" prop="chatId" />
+      <el-table-column label="状态" align="center" prop="status" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <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="['wx:wxSopUser:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['wx:wxSopUser:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改个微营期对话框 -->
+    <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="类型(0个人1群聊)" prop="type">
+          <el-select v-model="form.type" placeholder="请选择类型(0个人1群聊)">
+            <el-option label="请选择字典生成" value="" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="任务ID" prop="sopId">
+          <el-input v-model="form.sopId" placeholder="请输入任务ID" />
+        </el-form-item>
+        <el-form-item label="个微账号ID" prop="accountId">
+          <el-input v-model="form.accountId" placeholder="请输入个微账号ID" />
+        </el-form-item>
+        <el-form-item label="营期时间" prop="startTime">
+          <el-date-picker clearable size="small"
+            v-model="form.startTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择营期时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="群聊ID" prop="chatId">
+          <el-input v-model="form.chatId" placeholder="请输入群聊ID" />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listWxSopUser, getWxSopUser, delWxSopUser, addWxSopUser, updateWxSopUser, exportWxSopUser } from "@/api/wx/wxSopUser";
+
+export default {
+  name: "WxSopUser",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 个微营期表格数据
+      wxSopUserList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        type: null,
+        sopId: null,
+        accountId: null,
+        startTime: null,
+        chatId: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询个微营期列表 */
+    getList() {
+      this.loading = true;
+      listWxSopUser(this.queryParams).then(response => {
+        this.wxSopUserList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        type: null,
+        sopId: null,
+        accountId: null,
+        startTime: null,
+        chatId: null,
+        status: 0,
+        createTime: null,
+        createBy: null,
+        updateTime: null,
+        updateBy: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加个微营期";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getWxSopUser(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改个微营期";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateWxSopUser(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addWxSopUser(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除个微营期编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delWxSopUser(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有个微营期数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportWxSopUser(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 412 - 0
src/views/wx/wxSopUserInfo/index.vue

@@ -0,0 +1,412 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="任务ID" prop="sopId">
+        <el-input
+          v-model="queryParams.sopId"
+          placeholder="请输入任务ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="营期ID" prop="sopUserId">
+        <el-input
+          v-model="queryParams.sopUserId"
+          placeholder="请输入营期ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="联系人ID" prop="wxContactId">
+        <el-input
+          v-model="queryParams.wxContactId"
+          placeholder="请输入联系人ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="小程序ID" prop="fsUserId">
+        <el-input
+          v-model="queryParams.fsUserId"
+          placeholder="请输入小程序ID"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="是否7天都没有看课 0否 1是" prop="isDaysNotStudy">
+        <el-input
+          v-model="queryParams.isDaysNotStudy"
+          placeholder="请输入是否7天都没有看课 0否 1是"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="总完课天数" prop="finishCout">
+        <el-input
+          v-model="queryParams.finishCout"
+          placeholder="请输入总完课天数"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="最近完课时间" prop="finishTime">
+        <el-date-picker clearable size="small"
+          v-model="queryParams.finishTime"
+          type="date"
+          value-format="yyyy-MM-dd"
+          placeholder="选择最近完课时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="连续完课天数" prop="finishCourseDays">
+        <el-input
+          v-model="queryParams.finishCourseDays"
+          placeholder="请输入连续完课天数"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="客户评级的等级" prop="grade">
+        <el-input
+          v-model="queryParams.grade"
+          placeholder="请输入客户评级的等级"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="禁用状态 0 正常 1禁用" prop="status">
+        <el-select v-model="queryParams.status" placeholder="请选择禁用状态 0 正常 1禁用" clearable size="small">
+          <el-option label="请选择字典生成" value="" />
+        </el-select>
+      </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="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['wx:wxSopUserInfo:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['wx:wxSopUserInfo:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['wx:wxSopUserInfo:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['wx:wxSopUserInfo:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table border v-loading="loading" :data="wxSopUserInfoList" @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="任务ID" align="center" prop="sopId" />
+      <el-table-column label="营期ID" align="center" prop="sopUserId" />
+      <el-table-column label="联系人ID" align="center" prop="wxContactId" />
+      <el-table-column label="小程序ID" align="center" prop="fsUserId" />
+      <el-table-column label="是否7天都没有看课 0否 1是" align="center" prop="isDaysNotStudy" />
+      <el-table-column label="总完课天数" align="center" prop="finishCout" />
+      <el-table-column label="最近完课时间" align="center" prop="finishTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.finishTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="连续完课天数" align="center" prop="finishCourseDays" />
+      <el-table-column label="客户评级的等级" align="center" prop="grade" />
+      <el-table-column label="禁用状态 0 正常 1禁用" align="center" prop="status" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <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="['wx:wxSopUserInfo:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['wx:wxSopUserInfo:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改个微营期详情对话框 -->
+    <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="任务ID" prop="sopId">
+          <el-input v-model="form.sopId" placeholder="请输入任务ID" />
+        </el-form-item>
+        <el-form-item label="营期ID" prop="sopUserId">
+          <el-input v-model="form.sopUserId" placeholder="请输入营期ID" />
+        </el-form-item>
+        <el-form-item label="联系人ID" prop="wxContactId">
+          <el-input v-model="form.wxContactId" placeholder="请输入联系人ID" />
+        </el-form-item>
+        <el-form-item label="小程序ID" prop="fsUserId">
+          <el-input v-model="form.fsUserId" placeholder="请输入小程序ID" />
+        </el-form-item>
+        <el-form-item label="是否7天都没有看课 0否 1是" prop="isDaysNotStudy">
+          <el-input v-model="form.isDaysNotStudy" placeholder="请输入是否7天都没有看课 0否 1是" />
+        </el-form-item>
+        <el-form-item label="总完课天数" prop="finishCout">
+          <el-input v-model="form.finishCout" placeholder="请输入总完课天数" />
+        </el-form-item>
+        <el-form-item label="最近完课时间" prop="finishTime">
+          <el-date-picker clearable size="small"
+            v-model="form.finishTime"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择最近完课时间">
+          </el-date-picker>
+        </el-form-item>
+        <el-form-item label="连续完课天数" prop="finishCourseDays">
+          <el-input v-model="form.finishCourseDays" placeholder="请输入连续完课天数" />
+        </el-form-item>
+        <el-form-item label="客户评级的等级" prop="grade">
+          <el-input v-model="form.grade" placeholder="请输入客户评级的等级" />
+        </el-form-item>
+        <el-form-item label="禁用状态 0 正常 1禁用">
+          <el-radio-group v-model="form.status">
+            <el-radio label="1">请选择字典生成</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input v-model="form.remark" placeholder="请输入备注" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listWxSopUserInfo, getWxSopUserInfo, delWxSopUserInfo, addWxSopUserInfo, updateWxSopUserInfo, exportWxSopUserInfo } from "@/api/wx/wxSopUserInfo";
+
+export default {
+  name: "WxSopUserInfo",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 个微营期详情表格数据
+      wxSopUserInfoList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        sopId: null,
+        sopUserId: null,
+        wxContactId: null,
+        fsUserId: null,
+        isDaysNotStudy: null,
+        finishCout: null,
+        finishTime: null,
+        finishCourseDays: null,
+        grade: null,
+        status: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询个微营期详情列表 */
+    getList() {
+      this.loading = true;
+      listWxSopUserInfo(this.queryParams).then(response => {
+        this.wxSopUserInfoList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        id: null,
+        sopId: null,
+        sopUserId: null,
+        wxContactId: null,
+        fsUserId: null,
+        isDaysNotStudy: null,
+        finishCout: null,
+        finishTime: null,
+        finishCourseDays: null,
+        grade: null,
+        status: 0,
+        createTime: null,
+        createBy: null,
+        updateTime: null,
+        updateBy: null,
+        remark: null
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加个微营期详情";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const id = row.id || this.ids
+      getWxSopUserInfo(id).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改个微营期详情";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.id != null) {
+            updateWxSopUserInfo(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            addWxSopUserInfo(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const ids = row.id || this.ids;
+      this.$confirm('是否确认删除个微营期详情编号为"' + ids + '"的数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(function() {
+          return delWxSopUserInfo(ids);
+        }).then(() => {
+          this.getList();
+          this.msgSuccess("删除成功");
+        }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有个微营期详情数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return exportWxSopUserInfo(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>