Kaynağa Gözat

个微sop:批量执行sop,查看营期,打标签进营期 ,查看执行记录

xw 4 gün önce
ebeveyn
işleme
5f7d07887d

+ 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
+  })
+}

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

@@ -50,4 +50,12 @@ export function exportWxSop(query) {
     method: 'get',
     params: query
   })
+}
+
+// 批量执行个微SOP
+export function updateWxStatus(ids) {
+  return request({
+    url: '/qw/sop/updateWxStatus/' + ids,
+    method: 'get'
+  })
 }

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

@@ -50,4 +50,22 @@ export function exportWxSopLogs(query) {
     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
+  })
 }

+ 34 - 0
src/router/index.js

@@ -249,6 +249,40 @@ 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: '/watch/deviceInfo/details',
     component: Layout,

+ 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>

+ 121 - 3
src/views/wx/wxSop/index.vue

@@ -82,10 +82,30 @@
           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">
@@ -119,8 +139,23 @@
         </template>
       </el-table-column>
       <el-table-column label="备注" align="center" prop="remark" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <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"
@@ -330,7 +365,7 @@
 </template>
 
 <script>
-import { listWxSop, getWxSop, delWxSop, addWxSop, updateWxSop, exportWxSop } from "@/api/wx/wxSop";
+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";
@@ -566,6 +601,26 @@ export default {
         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;
@@ -580,7 +635,70 @@ export default {
           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数据项?', "警告", {

+ 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>