浏览代码

Merge remote-tracking branch 'origin/master'

yuhongqi 2 天之前
父节点
当前提交
b0281116d9

+ 1 - 1
.env.prod-heyantang

@@ -16,7 +16,7 @@ ENV = 'production'
 VUE_APP_BASE_API = '/prod-api'
 
 #默认 1、会员 2、企微
-VUE_APP_COURSE_DEFAULT = 2
+VUE_APP_COURSE_DEFAULT = 1
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 60 - 0
src/api/statistics/statistics.js

@@ -324,3 +324,63 @@ export function getWatchCourseStatisticsData(param){
     data: safeParam
   })
 }
+
+/**
+ * 日综合统计(按照天分组)
+ * @returns {*}
+ */
+export function getStatisticsData(data) {
+  return request({
+    url: '/crm/ComprehensiveStatistics/statisticMain',
+    method: 'post',
+    data: data  // 使用 data 而不是 params
+  })
+}
+
+/**
+ * 获取公司下拉
+ * @returns {*}
+ */
+export function getSearchCompanyInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchCompanyInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 获取部门下拉
+ * @returns {*}
+ */
+export function getSearchDeptInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchDeptInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 获取用户下拉
+ * @returns {*}
+ */
+export function getSearchUserInfo(param){
+  return request({
+    url: '/crm/ComprehensiveStatistics/getSearchUserInfo',
+    method: 'get',
+    params: param
+  })
+}
+
+/**
+ * 综合统计
+ * @returns {*}
+ */
+export function getStatisticsDataN(data) {
+  return request({
+    url: '/crm/ComprehensiveStatistics/statisticMainN',
+    method: 'post',
+    data: data  // 使用 data 而不是 params
+  })
+}

+ 1 - 1
src/views/course/courseUserStatistics/qw/index.vue

@@ -84,7 +84,7 @@
           size="mini"
           :loading="exportLoading"
           @click="handleExport"
-          v-hasPermi="['course:courseRedPacketLog:export']"
+          v-hasPermi="['course:courseWatchLog:export']"
           >导出</el-button
         >
       </el-col>

+ 634 - 0
src/views/crm/statistics/comprehensiveStatistics.vue

@@ -0,0 +1,634 @@
+<template>
+  <div class="app-container">
+    <div class="app-content">
+      <div class="title">日综合统计看板</div>
+
+      <el-form class="search-form" :inline="true" label-width="90px">
+        <el-row :gutter="10">
+          <!-- 时间范围选择 -->
+          <el-col :span="6">
+            <el-form-item label="时间范围">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+
+          <!-- 统计维度选择 -->
+          <el-col :span="4">
+            <el-form-item label="统计维度">
+              <el-select
+                v-model="queryParams.dimension"
+                placeholder="请选择统计维度"
+                @change="handleDimensionChange"
+                clearable
+                style="width: 100%">
+                <el-option label="个人" :value="1"></el-option>
+                <el-option label="公司" :value="2"></el-option>
+                <el-option label="部门" :value="3"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 公司选择 -->
+          <el-col :span="4" v-if="showCompanySelect">
+            <el-form-item label="选择公司">
+              <el-select
+                v-model="queryParams.companyId"
+                placeholder="请选择公司"
+                @change="handleCompanyChange"
+                clearable
+                :disabled="!queryParams.dimension"
+                style="width: 100%">
+                <el-option
+                  v-for="company in companyList"
+                  :key="company.companyId"
+                  :label="company.companyName"
+                  :value="company.companyId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 部门选择 -->
+          <el-col :span="4" v-if="showDepartmentSelect">
+            <el-form-item label="选择部门">
+              <el-cascader
+                v-model="selectedDeptIds"
+                :options="deptTreeData"
+                :props="deptCascaderProps"
+                placeholder="请选择部门"
+                clearable
+                style="width: 100%"
+                @change="handleDeptChange">
+              </el-cascader>
+            </el-form-item>
+          </el-col>
+
+          <!-- 操作按钮 -->
+          <el-col :span="6">
+            <el-form-item label-width="0" style="padding-top: 35px;">
+              <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-button type="success" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading">导出</el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-section">
+      <el-table v-loading="loading" :data="paginatedTableData" border style="width: 100%" height="600">
+        <!-- 添加时间列 -->
+        <el-table-column prop="companyName" label="公司名称"/>
+        <el-table-column prop="deptName" label="部门名称" v-if="showDeptNameColumn" />
+        <!-- 根据维度决定是否显示人员姓名 -->
+        <el-table-column prop="companyUserName" label="人员名称" v-if="showUserNameColumn" />
+        <!-- 更新以下列为新的字段名 -->
+        <el-table-column prop="sendCount" label="发课数">
+          <template slot-scope="scope">
+            {{ scope.row.sendCount || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="completeNum" label="完课数">
+          <template slot-scope="scope">
+            {{ scope.row.completeNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 完播率 -->
+        <el-table-column prop="completeRate" label="完播率" width="100">
+          <template slot-scope="scope">
+            {{ calculateCompleteRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="answerNum" label="答题数">
+          <template slot-scope="scope">
+            {{ scope.row.answerNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 答题率 -->
+        <el-table-column prop="answerRate" label="答题率" width="100">
+          <template slot-scope="scope">
+            {{ calculateAnswerRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketNum" label="红包领取数">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketNum || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketAmount" label="红包金额(元)">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketAmount || 0 }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pageSize"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.length"        style="margin-top: 20px; text-align: right;">
+      </el-pagination>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { getStatisticsDataN, getSearchUserInfo, getSearchCompanyInfo, getSearchDeptInfo } from "@/api/statistics/statistics";
+
+export default {
+  data() {
+    return {
+      exportLoading: false, // 添加导出加载状态
+      loading: false,
+      lastQueryDimension: null, // 记录上次查询的维度
+      companyList: [],
+      deptList: [], // 原始部门数据
+      deptTreeData: [], // 树形部门数据
+      selectedDeptIds: [], // 选中的部门ID路径
+      userList: [],
+      rawData: [],
+      tableData: [],
+      currentPage: 1,
+      pageSize: 20,
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+      deptCascaderProps: {
+        value: 'deptId',
+        label: 'deptName',
+        children: 'children',
+        expandTrigger: 'hover',
+        checkStrictly: true, // 允许选择任意一级
+        emitPath: false // 只返回选中节点的值,而不是整个路径
+      },
+      queryParams: {
+        dimension: null,
+        companyId: null,
+        deptId: null,
+        userId: null
+      }
+    };
+  },
+
+  computed: {
+    paginatedTableData() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      const end = start + this.pageSize;
+      return this.tableData.slice(start, end);
+    },
+
+    showCompanySelect() {
+      // 公司维度不需要显示公司选择,其他维度都需要
+      return this.queryParams.dimension && this.queryParams.dimension !== 2;
+    },
+
+    showDepartmentSelect() {
+      // 部门维度不需要显示部门选择,个人维度需要显示
+      return this.queryParams.dimension === 1 && this.queryParams.companyId;
+    },
+
+
+
+    // 控制部门名称列显示
+    showDeptNameColumn() {
+      // 基于上次查询的维度来决定是否显示部门名称列
+      return this.lastQueryDimension && this.lastQueryDimension !== 2;
+    },
+
+    // 控制人员姓名列显示
+    showUserNameColumn() {
+      // 只有个人维度显示人员姓名列
+      return this.lastQueryDimension === 1;
+    }
+  },
+
+  mounted() {
+    // 设置默认时间为当天
+    const today = new Date();
+    this.dateRange = [today, today];
+    // 默认设置统计维度为公司
+    this.queryParams.dimension = 2;
+    this.loadData();
+    // 页面初始化时查询一次数据
+    this.fetchStatisticsData();
+  },
+
+  methods: {
+    // 在 loadData 方法中也移除自动查询
+    loadData() {
+      // 使用 getSearchCompanyInfo 获取公司下拉数据
+      getSearchCompanyInfo().then(response => {
+        this.companyList = response.data || [];
+
+        // 默认选择第一个公司(如果存在)
+        if (this.companyList.length > 0 && this.queryParams.dimension !== 2) {
+          this.queryParams.companyId = this.companyList[0].companyId;
+          // 加载该公司的部门数据
+          return this.loadDeptData(this.queryParams.companyId);
+        }
+      }).then(() => {
+        // 移除自动查询调用,只保留初始加载
+        // return this.fetchStatisticsData();
+      }).catch(error => {
+        console.error('数据加载失败:', error);
+        this.$message.error('数据加载失败');
+      });
+    },
+
+    loadDeptData(companyId) {
+      if (!companyId) return Promise.resolve();
+
+      return getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+    },
+
+    // 构建部门树形结构
+    buildDeptTree(deptList) {
+      if (!deptList || deptList.length === 0) return [];
+
+      // 创建部门映射
+      const deptMap = {};
+      deptList.forEach(dept => {
+        // 如果后端返回的数据中已经有children字段且有值,则使用后端的children
+        // 否则初始化为空数组
+        deptMap[dept.deptId] = {
+          ...dept,
+          children: (dept.children && dept.children.length > 0) ? dept.children : []
+        };
+      });
+
+      // 构建树形结构
+      const tree = [];
+      deptList.forEach(dept => {
+        if (dept.parentId === 0 || dept.parentId === null) {
+          // 顶级部门
+          tree.push(deptMap[dept.deptId]);
+        } else {
+          // 子部门 - 只有在后端没有返回children的情况下才手动构建
+          if (deptMap[dept.parentId] && (!dept.children || dept.children.length === 0)) {
+            deptMap[dept.parentId].children.push(deptMap[dept.deptId]);
+          }
+        }
+      });
+
+      // 清理空的children数组,保持数据结构整洁
+      const cleanEmptyChildren = (nodes) => {
+        nodes.forEach(node => {
+          if (node.children && node.children.length === 0) {
+            delete node.children;
+          } else if (node.children && node.children.length > 0) {
+            cleanEmptyChildren(node.children);
+          }
+        });
+      };
+      cleanEmptyChildren(tree);
+
+      return tree;
+    },
+
+    // 添加时间格式化方法
+    formatDateTime(dateString) {
+      if (!dateString) return '';
+      // 移除时区信息并格式化日期时间
+      const date = new Date(dateString.replace(/\.\d{3}\+\d{4}$/, ''));
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    formatDate(date) {
+      if (!date) return '';
+      const d = new Date(date);
+      const year = d.getFullYear();
+      const month = String(d.getMonth() + 1).padStart(2, '0');
+      const day = String(d.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    fetchStatisticsData() {
+      this.loading = true; // 开始请求时设置 loading 为 true
+      // 构造请求参数对象
+      const params = {
+        dimension: this.queryParams.dimension,
+        startTime: this.formatDate(this.dateRange[0]),
+        endTime: this.formatDate(this.dateRange[1])
+      };
+
+      // 根据新规则设置id参数
+      if (this.queryParams.dimension === 1 && this.queryParams.deptId) {
+        // 个人维度传递部门ID
+        params.id = this.queryParams.deptId;
+      } else if (this.queryParams.dimension === 3 && this.queryParams.companyId) {
+        // 部门维度传递公司ID
+        params.id = this.queryParams.companyId;
+      }
+      // 公司维度不传递ID参数
+
+      // 以POST方式发送请求体
+      return getStatisticsDataN(params).then(response => {
+        this.rawData = response.data || [];
+        this.tableData = [...this.rawData];
+        this.currentPage = 1;
+      }).catch(error => {
+        console.error('统计数据加载失败:', error);
+        this.$message.error('统计数据加载失败');
+      }).finally(() => {
+        this.loading = false; // 请求完成后设置 loading 为 false
+      });
+    },
+
+    handleCompanyChange(companyId) {
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+
+      if (!companyId) return;
+
+      // 使用 getSearchDeptInfo 获取部门下拉数据
+      getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        // 仅在个人维度下默认选择第一个部门
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+      // 移除.then()后面的自动查询调用
+    },
+    handleDeptChange(selectedDeptId) {
+      // 更新选中的部门ID
+      this.queryParams.deptId = selectedDeptId;
+    },
+
+    handleQuery() {
+      // 记录当前查询的维度
+      this.lastQueryDimension = this.queryParams.dimension;
+      // 触发统计数据请求
+      this.fetchStatisticsData();
+    },
+
+    handleDimensionChange() {
+      // 重置后续选择项
+      this.queryParams.companyId = null;
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+    },
+
+    resetQuery() {
+      this.queryParams = {
+        dimension: 2, // 重置为公司维度
+        companyId: null,
+        deptId: null,
+        userId: null
+      };
+      this.selectedDeptIds = []; // 重置部门选择
+      // 重置时间为当天
+      const today = new Date();
+      this.dateRange = [today, today];
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+      this.tableData = [...this.rawData];
+      this.currentPage = 1;
+      // 重置后立即发送请求
+      this.lastQueryDimension = 2; // 同时更新lastQueryDimension
+      this.fetchStatisticsData();
+    },
+
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+    },
+
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+
+    // 计算完播率:完课数 / 发送数
+    calculateCompleteRate(row) {
+      const sendCount = row.sendCount || 0;
+      const completeNum = row.completeNum || 0;
+
+      if (sendCount === 0) {
+        return '0%';
+      }
+
+      const rate = (completeNum / sendCount * 100).toFixed(2);
+      return rate + '%';
+    },
+
+    // 计算答题率:答题数 / 完课数
+    calculateAnswerRate(row) {
+      const completeNum = row.completeNum || 0;
+      const answerNum = row.answerNum || 0;
+
+      if (completeNum === 0) {
+        return '0%';
+      }
+
+      const rate = (answerNum / completeNum * 100).toFixed(2);
+      return rate + '%';
+    },
+    // 添加导出方法
+    handleExport() {
+      if (this.tableData.length === 0) {
+        this.$message.warning('没有可导出的数据');
+        return;
+      }
+
+      this.exportLoading = true;
+
+      try {
+        // 创建 CSV 内容
+        const headers = [
+         '公司名称', '部门名称', '人员名称',
+          '发课数', '完课数', '完播率', '答题数', '答题率',
+          '红包领取数', '红包金额(元)'
+        ];
+
+        const csvContent = [
+          headers.join(','),
+          ...this.tableData.map(row => [
+            row.companyName || '',
+            row.deptName || '',
+            row.companyUserName || '',
+            row.sendCount || 0,
+            row.completeNum || 0,
+            this.calculateCompleteRate(row),
+            row.answerNum || 0,
+            this.calculateAnswerRate(row),
+            row.redPacketNum || 0,
+            row.redPacketAmount || 0
+          ].map(field => `"${field}"`).join(','))
+        ].join('\n');
+
+        // 创建下载链接
+        const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
+        const link = document.createElement('a');
+        const url = URL.createObjectURL(blob);
+
+        link.setAttribute('href', url);
+
+        // link.setAttribute('download', `综合统计看板_${new Date().toISOString().slice(0, 10)}.csv`);
+        // 修改为:
+        const now = new Date();
+        const timestamp = now.getFullYear() +
+          String(now.getMonth() + 1).padStart(2, '0') +
+          String(now.getDate()).padStart(2, '0') +
+          String(now.getHours()).padStart(2, '0') +
+          String(now.getMinutes()).padStart(2, '0') +
+          String(now.getSeconds()).padStart(2, '0');
+        link.setAttribute('download', `综合统计看板_${timestamp}.csv`);
+        link.style.visibility = 'hidden';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+
+        this.$message.success('导出成功');
+      } catch (error) {
+        console.error('导出失败:', error);
+        this.$message.error('导出失败');
+      } finally {
+        this.exportLoading = false;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.app-container {
+  border: 1px solid #e6e6e6;
+  padding: 12px;
+  background-color: #f5f7fa;
+}
+
+.app-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+
+.title {
+  text-align: center;
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20px;
+}
+
+.search-form {
+  margin-bottom: 0;
+}
+
+.search-form .el-form-item {
+  margin-bottom: 18px;
+  width: 100%;
+}
+
+.search-form .el-row {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}
+
+.table-section {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-section .el-table {
+  width: 100% !important;
+}
+
+@media (min-width: 1200px) {
+  .table-section {
+    padding: 20px 50px;
+  }
+}
+</style>

+ 640 - 0
src/views/crm/statistics/dayComprehensiveStatistics.vue

@@ -0,0 +1,640 @@
+<template>
+  <div class="app-container">
+    <div class="app-content">
+      <div class="title">综合统计看板</div>
+
+      <el-form class="search-form" :inline="true" label-width="90px">
+        <el-row :gutter="10">
+          <!-- 时间范围选择 -->
+          <el-col :span="6">
+            <el-form-item label="时间范围">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                value-format="yyyy-MM-dd"
+                :picker-options="pickerOptions"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+
+          <!-- 统计维度选择 -->
+          <el-col :span="4">
+            <el-form-item label="统计维度">
+              <el-select
+                v-model="queryParams.dimension"
+                placeholder="请选择统计维度"
+                @change="handleDimensionChange"
+                clearable
+                style="width: 100%">
+                <el-option label="个人" :value="1"></el-option>
+                <el-option label="公司" :value="2"></el-option>
+                <el-option label="部门" :value="3"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 公司选择 -->
+          <el-col :span="4" v-if="showCompanySelect">
+            <el-form-item label="选择公司">
+              <el-select
+                v-model="queryParams.companyId"
+                placeholder="请选择公司"
+                @change="handleCompanyChange"
+                clearable
+                :disabled="!queryParams.dimension"
+                style="width: 100%">
+                <el-option
+                  v-for="company in companyList"
+                  :key="company.companyId"
+                  :label="company.companyName"
+                  :value="company.companyId">
+                </el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+
+          <!-- 部门选择 -->
+          <el-col :span="4" v-if="showDepartmentSelect">
+            <el-form-item label="选择部门">
+              <el-cascader
+                v-model="selectedDeptIds"
+                :options="deptTreeData"
+                :props="deptCascaderProps"
+                placeholder="请选择部门"
+                clearable
+                style="width: 100%"
+                @change="handleDeptChange">
+              </el-cascader>
+            </el-form-item>
+          </el-col>
+
+          <!-- 操作按钮 -->
+          <el-col :span="6">
+            <el-form-item label-width="0" style="padding-top: 35px;">
+              <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-button type="success" icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading">导出</el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-section">
+      <el-table v-loading="loading" :data="paginatedTableData" border style="width: 100%" height="600">
+        <!-- 添加时间列 -->
+        <el-table-column prop="statisticsTime" label="统计时间" width="180">
+          <template slot-scope="scope">
+            {{ formatDateTime(scope.row.statisticsTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="companyName" label="公司名称"/>
+        <el-table-column prop="deptName" label="部门名称" v-if="showDeptNameColumn" />
+        <!-- 根据维度决定是否显示人员姓名 -->
+        <el-table-column prop="companyUserName" label="人员名称" v-if="showUserNameColumn" />
+        <!-- 更新以下列为新的字段名 -->
+        <el-table-column prop="sendCount" label="发课数">
+          <template slot-scope="scope">
+            {{ scope.row.sendCount || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="completeNum" label="完课数">
+          <template slot-scope="scope">
+            {{ scope.row.completeNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 完播率 -->
+        <el-table-column prop="completeRate" label="完播率" width="100">
+          <template slot-scope="scope">
+            {{ calculateCompleteRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="answerNum" label="答题数">
+          <template slot-scope="scope">
+            {{ scope.row.answerNum || 0 }}
+          </template>
+        </el-table-column>
+        <!-- 答题率 -->
+        <el-table-column prop="answerRate" label="答题率" width="100">
+          <template slot-scope="scope">
+            {{ calculateAnswerRate(scope.row) }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketNum" label="红包领取数">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketNum || 0 }}
+          </template>
+        </el-table-column>
+        <el-table-column prop="redPacketAmount" label="红包金额(元)">
+          <template slot-scope="scope">
+            {{ scope.row.redPacketAmount || 0 }}
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="currentPage"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pageSize"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="tableData.length"        style="margin-top: 20px; text-align: right;">
+      </el-pagination>
+    </div>
+
+  </div>
+</template>
+
+<script>
+import { getStatisticsData, getSearchUserInfo, getSearchCompanyInfo, getSearchDeptInfo } from "@/api/statistics/statistics";
+
+export default {
+  data() {
+    return {
+      exportLoading: false, // 添加导出加载状态
+      loading: false,
+      lastQueryDimension: null, // 记录上次查询的维度
+      companyList: [],
+      deptList: [], // 原始部门数据
+      deptTreeData: [], // 树形部门数据
+      selectedDeptIds: [], // 选中的部门ID路径
+      userList: [],
+      rawData: [],
+      tableData: [],
+      currentPage: 1,
+      pageSize: 20,
+      dateRange: [],
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
+            picker.$emit('pick', [start, end]);
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date();
+            const start = new Date();
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
+            picker.$emit('pick', [start, end]);
+          }
+        }]
+      },
+      deptCascaderProps: {
+        value: 'deptId',
+        label: 'deptName',
+        children: 'children',
+        expandTrigger: 'hover',
+        checkStrictly: true, // 允许选择任意一级
+        emitPath: false // 只返回选中节点的值,而不是整个路径
+      },
+      queryParams: {
+        dimension: null,
+        companyId: null,
+        deptId: null,
+        userId: null
+      }
+    };
+  },
+
+  computed: {
+    paginatedTableData() {
+      const start = (this.currentPage - 1) * this.pageSize;
+      const end = start + this.pageSize;
+      return this.tableData.slice(start, end);
+    },
+
+    showCompanySelect() {
+      // 公司维度不需要显示公司选择,其他维度都需要
+      return this.queryParams.dimension && this.queryParams.dimension !== 2;
+    },
+
+    showDepartmentSelect() {
+      // 部门维度不需要显示部门选择,个人维度需要显示
+      return this.queryParams.dimension === 1 && this.queryParams.companyId;
+    },
+
+
+
+    // 控制部门名称列显示
+    showDeptNameColumn() {
+      // 基于上次查询的维度来决定是否显示部门名称列
+      return this.lastQueryDimension && this.lastQueryDimension !== 2;
+    },
+
+    // 控制人员姓名列显示
+    showUserNameColumn() {
+      // 只有个人维度显示人员姓名列
+      return this.lastQueryDimension === 1;
+    }
+  },
+
+  mounted() {
+    // 设置默认时间为当天
+    const today = new Date();
+    this.dateRange = [today, today];
+    // 默认设置统计维度为公司
+    this.queryParams.dimension = 2;
+    this.loadData();
+    // 页面初始化时查询一次数据
+    this.fetchStatisticsData();
+  },
+
+  methods: {
+    // 在 loadData 方法中也移除自动查询
+    loadData() {
+      // 使用 getSearchCompanyInfo 获取公司下拉数据
+      getSearchCompanyInfo().then(response => {
+        this.companyList = response.data || [];
+
+        // 默认选择第一个公司(如果存在)
+        if (this.companyList.length > 0 && this.queryParams.dimension !== 2) {
+          this.queryParams.companyId = this.companyList[0].companyId;
+          // 加载该公司的部门数据
+          return this.loadDeptData(this.queryParams.companyId);
+        }
+      }).then(() => {
+        // 移除自动查询调用,只保留初始加载
+        // return this.fetchStatisticsData();
+      }).catch(error => {
+        console.error('数据加载失败:', error);
+        this.$message.error('数据加载失败');
+      });
+    },
+
+    loadDeptData(companyId) {
+      if (!companyId) return Promise.resolve();
+
+      return getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+    },
+
+    // 构建部门树形结构
+    buildDeptTree(deptList) {
+      if (!deptList || deptList.length === 0) return [];
+
+      // 创建部门映射
+      const deptMap = {};
+      deptList.forEach(dept => {
+        // 如果后端返回的数据中已经有children字段且有值,则使用后端的children
+        // 否则初始化为空数组
+        deptMap[dept.deptId] = {
+          ...dept,
+          children: (dept.children && dept.children.length > 0) ? dept.children : []
+        };
+      });
+
+      // 构建树形结构
+      const tree = [];
+      deptList.forEach(dept => {
+        if (dept.parentId === 0 || dept.parentId === null) {
+          // 顶级部门
+          tree.push(deptMap[dept.deptId]);
+        } else {
+          // 子部门 - 只有在后端没有返回children的情况下才手动构建
+          if (deptMap[dept.parentId] && (!dept.children || dept.children.length === 0)) {
+            deptMap[dept.parentId].children.push(deptMap[dept.deptId]);
+          }
+        }
+      });
+
+      // 清理空的children数组,保持数据结构整洁
+      const cleanEmptyChildren = (nodes) => {
+        nodes.forEach(node => {
+          if (node.children && node.children.length === 0) {
+            delete node.children;
+          } else if (node.children && node.children.length > 0) {
+            cleanEmptyChildren(node.children);
+          }
+        });
+      };
+      cleanEmptyChildren(tree);
+
+      return tree;
+    },
+
+    // 添加时间格式化方法
+    formatDateTime(dateString) {
+      if (!dateString) return '';
+      // 移除时区信息并格式化日期时间
+      const date = new Date(dateString.replace(/\.\d{3}\+\d{4}$/, ''));
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    formatDate(date) {
+      if (!date) return '';
+      const d = new Date(date);
+      const year = d.getFullYear();
+      const month = String(d.getMonth() + 1).padStart(2, '0');
+      const day = String(d.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+
+    fetchStatisticsData() {
+      this.loading = true; // 开始请求时设置 loading 为 true
+      // 构造请求参数对象
+      const params = {
+        dimension: this.queryParams.dimension,
+        startTime: this.formatDate(this.dateRange[0]),
+        endTime: this.formatDate(this.dateRange[1])
+      };
+
+      // 根据新规则设置id参数
+      if (this.queryParams.dimension === 1 && this.queryParams.deptId) {
+        // 个人维度传递部门ID
+        params.id = this.queryParams.deptId;
+      } else if (this.queryParams.dimension === 3 && this.queryParams.companyId) {
+        // 部门维度传递公司ID
+        params.id = this.queryParams.companyId;
+      }
+      // 公司维度不传递ID参数
+
+      // 以POST方式发送请求体
+      return getStatisticsData(params).then(response => {
+        this.rawData = response.data || [];
+        this.tableData = [...this.rawData];
+        this.currentPage = 1;
+      }).catch(error => {
+        console.error('统计数据加载失败:', error);
+        this.$message.error('统计数据加载失败');
+      }).finally(() => {
+        this.loading = false; // 请求完成后设置 loading 为 false
+      });
+    },
+
+    handleCompanyChange(companyId) {
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+
+      if (!companyId) return;
+
+      // 使用 getSearchDeptInfo 获取部门下拉数据
+      getSearchDeptInfo({ id: companyId }).then(response => {
+        const deptData = response.data || [];
+        this.deptList = deptData;
+
+        // 如果后端直接返回树形结构(第一层数据有children),直接使用
+        // 否则通过buildDeptTree构建树形结构
+        const hasTreeStructure = deptData.some(dept => dept.children && dept.children.length > 0);
+        if (hasTreeStructure) {
+          this.deptTreeData = deptData.filter(dept => dept.parentId === 0 || dept.parentId === null);
+        } else {
+          this.deptTreeData = this.buildDeptTree(deptData);
+        }
+
+        // 默认选择第一个部门(如果存在)
+        // 仅在个人维度下默认选择第一个部门
+        if (this.deptTreeData.length > 0 && this.queryParams.dimension === 1) {
+          // 选择第一个顶级部门
+          this.queryParams.deptId = this.deptTreeData[0].deptId;
+          this.selectedDeptIds = [this.queryParams.deptId];
+        }
+      }).catch(error => {
+        console.error('部门数据加载失败:', error);
+        this.$message.error('部门数据加载失败');
+      });
+      // 移除.then()后面的自动查询调用
+    },
+    handleDeptChange(selectedDeptId) {
+      // 更新选中的部门ID
+      this.queryParams.deptId = selectedDeptId;
+    },
+
+    handleQuery() {
+      // 记录当前查询的维度
+      this.lastQueryDimension = this.queryParams.dimension;
+      // 触发统计数据请求
+      this.fetchStatisticsData();
+    },
+
+    handleDimensionChange() {
+      // 重置后续选择项
+      this.queryParams.companyId = null;
+      this.queryParams.deptId = null;
+      this.selectedDeptIds = [];
+      this.queryParams.userId = null;
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+    },
+
+    resetQuery() {
+      this.queryParams = {
+        dimension: 2, // 重置为公司维度
+        companyId: null,
+        deptId: null,
+        userId: null
+      };
+      this.selectedDeptIds = []; // 重置部门选择
+      // 重置时间为当天
+      const today = new Date();
+      this.dateRange = [today, today];
+      this.deptList = [];
+      this.deptTreeData = [];
+      this.userList = [];
+      this.tableData = [...this.rawData];
+      this.currentPage = 1;
+      // 重置后立即发送请求
+      this.lastQueryDimension = 2; // 同时更新lastQueryDimension
+      this.fetchStatisticsData();
+    },
+
+    handleSizeChange(val) {
+      this.pageSize = val;
+      this.currentPage = 1;
+    },
+
+    handleCurrentChange(val) {
+      this.currentPage = val;
+    },
+
+    // 计算完播率:完课数 / 发送数
+    calculateCompleteRate(row) {
+      const sendCount = row.sendCount || 0;
+      const completeNum = row.completeNum || 0;
+
+      if (sendCount === 0) {
+        return '0%';
+      }
+
+      const rate = (completeNum / sendCount * 100).toFixed(2);
+      return rate + '%';
+    },
+
+    // 计算答题率:答题数 / 完课数
+    calculateAnswerRate(row) {
+      const completeNum = row.completeNum || 0;
+      const answerNum = row.answerNum || 0;
+
+      if (completeNum === 0) {
+        return '0%';
+      }
+
+      const rate = (answerNum / completeNum * 100).toFixed(2);
+      return rate + '%';
+    },
+    // 添加导出方法
+    handleExport() {
+      if (this.tableData.length === 0) {
+        this.$message.warning('没有可导出的数据');
+        return;
+      }
+
+      this.exportLoading = true;
+
+      try {
+        // 创建 CSV 内容
+        const headers = [
+          '统计时间', '公司名称', '部门名称', '人员名称',
+          '发课数', '完课数', '完播率', '答题数', '答题率',
+          '红包领取数', '红包金额(元)'
+        ];
+
+        const csvContent = [
+          headers.join(','),
+          ...this.tableData.map(row => [
+            this.formatDateTime(row.statisticsTime),
+            row.companyName || '',
+            row.deptName || '',
+            row.companyUserName || '',
+            row.sendCount || 0,
+            row.completeNum || 0,
+            this.calculateCompleteRate(row),
+            row.answerNum || 0,
+            this.calculateAnswerRate(row),
+            row.redPacketNum || 0,
+            row.redPacketAmount || 0
+          ].map(field => `"${field}"`).join(','))
+        ].join('\n');
+
+        // 创建下载链接
+        const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
+        const link = document.createElement('a');
+        const url = URL.createObjectURL(blob);
+
+        link.setAttribute('href', url);
+
+        // link.setAttribute('download', `综合统计看板_${new Date().toISOString().slice(0, 10)}.csv`);
+        // 修改为:
+        const now = new Date();
+        const timestamp = now.getFullYear() +
+          String(now.getMonth() + 1).padStart(2, '0') +
+          String(now.getDate()).padStart(2, '0') +
+          String(now.getHours()).padStart(2, '0') +
+          String(now.getMinutes()).padStart(2, '0') +
+          String(now.getSeconds()).padStart(2, '0');
+        link.setAttribute('download', `综合统计看板_${timestamp}.csv`);
+        link.style.visibility = 'hidden';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+
+        this.$message.success('导出成功');
+      } catch (error) {
+        console.error('导出失败:', error);
+        this.$message.error('导出失败');
+      } finally {
+        this.exportLoading = false;
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.app-container {
+  border: 1px solid #e6e6e6;
+  padding: 12px;
+  background-color: #f5f7fa;
+}
+
+.app-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+  margin-bottom: 20px;
+}
+
+.title {
+  text-align: center;
+  font-size: 24px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20px;
+}
+
+.search-form {
+  margin-bottom: 0;
+}
+
+.search-form .el-form-item {
+  margin-bottom: 18px;
+  width: 100%;
+}
+
+.search-form .el-row {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}
+
+.table-section {
+  background-color: white;
+  padding: 20px;
+  border-radius: 4px;
+}
+
+.table-section .el-table {
+  width: 100% !important;
+}
+
+@media (min-width: 1200px) {
+  .table-section {
+    padding: 20px 50px;
+  }
+}
+</style>

+ 190 - 150
src/views/fastGpt/fastGptRole/fastGptRoleUpdate.vue

@@ -2,73 +2,119 @@
   <div class="app-container">
 
 
-      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-              <el-form-item label="客服名称" prop="roleName">
-                <el-input v-model="form.roleName" placeholder="请输入角色名" />
-              </el-form-item>
-              <el-form-item label="角色类型" prop="roleType">
-                <el-select v-model="form.roleType" placeholder="请选择类型" clearable size="small" style="width: 150px" >
-                  <el-option
-                    v-for="item in typeOptions"
-                    :key="item.dictValue"
-                    :label="item.dictLabel"
-                    :value="String(item.dictValue)"
-                  />
-                </el-select>
-              </el-form-item>
-
-              <el-form-item label="客服头像" prop="avatar">
-                <ImageUpload v-model="form.avatar" type="image" :num="1" :width="150" :height="150" style="margin-top: 1%;" />
-              </el-form-item>
-              <el-form-item label="APPKey"  >
-                <el-input type="textarea" v-model="form.modeConfigJson.APPKey" placeholder="请输入FastGPT的APPKey(特定key)" />
-              </el-form-item>
-              <el-form-item  label="提示词"  >
-                <el-input type="textarea" :rows="3" v-model="form.reminderWords" placeholder="请输入FastGPT的提示词" />
-              </el-form-item>
-<!--              <el-form-item  label="标签人设"  >
-                <div v-if="tagsFormList.length > 0" style="display: flex; align-items: center; flex-wrap: wrap; width: 100%;">
-                  <div style="min-height: 40px; max-height: 200px; overflow-y: auto; width: 100%;">
-                    <div style="display: flex; flex-wrap: wrap; width: 100%;">
-                      <div v-for="(tagsForm, index) in tagsFormList" :key="tagsForm.id">
-                        <el-tag type="success"
-                                closable
-                                :disable-transitions="false"
-                                @click="handleEditRoleTag(tagsForm, index)"
-                                @close="handleCloseRoleTag(tagsForm, index)"
-                                style="margin: 3px;">
-                          {{ getTagNames(tagsForm.tagIds) }}
-                        </el-tag>
+    <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+      <el-form-item label="客服名称" prop="roleName">
+        <el-input v-model="form.roleName" placeholder="请输入角色名" />
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          AI角色名字
+        </div>
+      </el-form-item>
+      <el-form-item label="角色类型" prop="roleType">
+        <el-select v-model="form.roleType" placeholder="请选择类型" clearable size="small" style="width: 150px" >
+          <el-option
+            v-for="item in typeOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="String(item.dictValue)"
+          />
+        </el-select>
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          选择角色类型:一般选择伴学助手,医生工作室,伴学助手无性别,无性别版本只会叫用户同学,其余的会叫用户先生女士
+        </div>
+      </el-form-item>
+
+      <el-form-item label="渠道类型" prop="channelType">
+        <el-select v-model="form.channelType" placeholder="请选择类型" clearable size="small" style="width: 150px" >
+          <el-option
+            v-for="item in channelOptions"
+            :key="item.dictValue"
+            :label="item.dictLabel"
+            :value="String(item.dictLabel)"
+          />
+        </el-select>
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          选择进线渠道:只影响新客户进来的首次对话
+        </div>
+      </el-form-item>
+
+      <el-form-item label="物流提醒" prop="logistics">
+        <el-select v-model="form.logistics" placeholder="请选择类型" clearable size="small" style="width: 150px" >
+          <el-option
+            v-for="item in logisticsOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
+        </el-select>
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          AI是否主动发送物流信息,发货和到货的时候会提醒用户
+        </div>
+      </el-form-item>
+
+      <el-form-item label="客服头像" prop="avatar">
+        <ImageUpload v-model="form.avatar" type="image" :num="1" :width="150" :height="150" style="margin-top: 1%;" />
+      </el-form-item>
+      <el-form-item label="APPKey"  >
+        <el-input type="textarea" v-model="form.modeConfigJson.APPKey" placeholder="请输入APPKey(特定key)" />
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          根据管理员发送的APPKey填写
+        </div>
+      </el-form-item>
+      <el-form-item  label="提示词"  >
+        <el-input type="textarea" :rows="3" v-model="form.reminderWords" placeholder="请输入提示词" />
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          输入个人的介绍,并且在最后增加一个自我介绍(用于首次交流触发),模板可以找管理员获取
+        </div>
+      </el-form-item>
+      <!--              <el-form-item  label="标签人设"  >
+                      <div v-if="tagsFormList.length > 0" style="display: flex; align-items: center; flex-wrap: wrap; width: 100%;">
+                        <div style="min-height: 40px; max-height: 200px; overflow-y: auto; width: 100%;">
+                          <div style="display: flex; flex-wrap: wrap; width: 100%;">
+                            <div v-for="(tagsForm, index) in tagsFormList" :key="tagsForm.id">
+                              <el-tag type="success"
+                                      closable
+                                      :disable-transitions="false"
+                                      @click="handleEditRoleTag(tagsForm, index)"
+                                      @close="handleCloseRoleTag(tagsForm, index)"
+                                      style="margin: 3px;">
+                                {{ getTagNames(tagsForm.tagIds) }}
+                              </el-tag>
+                            </div>
+                          </div>
+                        </div>
                       </div>
-                    </div>
-                  </div>
-                </div>
-                  <el-button
-                    size="mini"
-                    type="primary" plain
-                    icon="el-icon-circle-plus-outline"
-                    @click="handleAddTags(form.roleId,form.bindCorpId)"
-                    v-hasPermi="['fastGptRole:fastGptRole:edit']"
-                  >添加标签人设</el-button>
-              </el-form-item> -->
-
-<!--      		<el-form-item label="修改栏目" prop="contactInfo">
-      		  <el-select v-model="contactInfo"  multiple filterable placeholder="请选择修改栏目" clearable size="small" style="width: 50%" >
-      		    <el-option
-      		      v-for="item in externalInfoOptions"
-      		      :key="item.dictValue"
-      		      :label="item.dictLabel"
-      		      :value="item.dictValue"
-      		    />
-      		  </el-select>
-      		</el-form-item> -->
-		 </el-form>
-      <div slot="footer" class="dialog-footer" style="padding-bottom: 40px;">
-        <el-button type="primary" @click="submitForm" style="float: right;margin-right: 20px;">确 定</el-button>
-        <el-button @click="cancel" style="float: right;margin-right: 20px;">取 消</el-button>
-      </div>
-
-<!--    修改-->
+                        <el-button
+                          size="mini"
+                          type="primary" plain
+                          icon="el-icon-circle-plus-outline"
+                          @click="handleAddTags(form.roleId,form.bindCorpId)"
+                          v-hasPermi="['fastGptRole:fastGptRole:edit']"
+                        >添加标签人设</el-button>
+                    </el-form-item> -->
+
+      <!--      		<el-form-item label="修改栏目" prop="contactInfo">
+                  <el-select v-model="contactInfo"  multiple filterable placeholder="请选择修改栏目" clearable size="small" style="width: 50%" >
+                    <el-option
+                      v-for="item in externalInfoOptions"
+                      :key="item.dictValue"
+                      :label="item.dictLabel"
+                      :value="item.dictValue"
+                    />
+                  </el-select>
+                </el-form-item> -->
+    </el-form>
+    <div slot="footer" class="dialog-footer" style="padding-bottom: 40px;">
+      <el-button type="primary" @click="submitForm" style="float: right;margin-right: 20px;">确 定</el-button>
+      <el-button @click="cancel" style="float: right;margin-right: 20px;">取 消</el-button>
+    </div>
+
+    <!--    修改-->
     <el-dialog :title="changeTagsOpen.title" :visible.sync="changeTagsOpen.open" width="800px" append-to-body>
 
       <el-form ref="tagsForm" :model="tagsForm"  :rules="tagsFormRules">
@@ -102,18 +148,15 @@
         <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
           <span class="name-background">{{ item.name }}</span>
         </div>
-        <!-- 添加外层滚动容器 -->
-        <div class="scroll-wrapper">
-          <div class="tag-container">
-            <a
-              v-for="tagItem in item.tag"
-              class="tag-box"
-              @click="tagSelection(tagItem)"
-              :class="{ 'tag-selected': tagItem.isSelected }"
-            >
-              {{ tagItem.name }}
-            </a>
-          </div>
+        <div class="tag-container">
+          <a
+            v-for="tagItem in item.tag"
+            class="tag-box"
+            @click="tagSelection(tagItem)"
+            :class="{ 'tag-selected': tagItem.isSelected }"
+          >
+            {{ tagItem.name }}
+          </a>
         </div>
       </div>
       <div slot="footer" class="dialog-footer">
@@ -122,7 +165,7 @@
       </div>
     </el-dialog>
 
-<!--    新增-->
+    <!--    新增-->
     <el-dialog :title="changeTagsOpenAdd.title" :visible.sync="changeTagsOpenAdd.open" width="800px" append-to-body>
 
       <el-form ref="tagsFormAdd" :model="tagsFormAdd"  :rules="tagsFormAddRules">
@@ -156,18 +199,15 @@
         <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
           <span class="name-background">{{ item.name }}</span>
         </div>
-        <!-- 添加外层滚动容器 -->
-        <div class="scroll-wrapper">
-          <div class="tag-container">
-            <a
-              v-for="tagItem in item.tag"
-              class="tag-box"
-              @click="tagSelection(tagItem)"
-              :class="{ 'tag-selected': tagItem.isSelected }"
-            >
-              {{ tagItem.name }}
-            </a>
-          </div>
+        <div class="tag-container">
+          <a
+            v-for="tagItem in item.tag"
+            class="tag-box"
+            @click="tagSelection(tagItem)"
+            :class="{ 'tag-selected': tagItem.isSelected }"
+          >
+            {{ tagItem.name }}
+          </a>
         </div>
       </div>
       <div slot="footer" class="dialog-footer">
@@ -197,6 +237,10 @@ export default {
   components: {ImageUpload},
   data() {
     return {
+      logisticsOptions: [
+        { label: '是', value: 1 },
+        { label: '否', value: 0 }
+      ],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -205,6 +249,8 @@ export default {
       typeOptions: [],
       //AI模型
       modeOptions: [],
+      //渠道类型
+      channelOptions: [],
       changeTagsOpen: {
         title : "",
         open :false,
@@ -234,7 +280,7 @@ export default {
 
       //所有的标签
       tagList:[],
-		contactInfo:[],
+      contactInfo:[],
       //已经选择的标签
       tagListFormIndex:[],
 
@@ -244,7 +290,7 @@ export default {
       uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
       // 选中数组
       ids: [],
-	  externalInfoOptions : [],
+      externalInfoOptions : [],
       // 非单个禁用
       single: true,
       // 非多个禁用
@@ -270,7 +316,9 @@ export default {
         kfId: null,
         kfUrl: null,
         avatar: null,
-        kfMediaId: null
+        kfMediaId: null,
+        channelType: null,
+        logistics: null
       },
       // 表单参数
       form: {
@@ -289,6 +337,9 @@ export default {
         roleType: [
           { required: true, message: "角色类型不能为空", trigger: "change" }
         ],
+        logistics: [
+          { required: true, message: "物流提醒不能为空", trigger: "change" }
+        ],
       },
       tagsFormRules:{
         tagIds:[
@@ -364,7 +415,7 @@ export default {
   },
   created() {
 
-	 this.handleUpdate();
+    this.handleUpdate();
     //客服类型
     // this.getDicts("chat_role_type").then((response) => {
     //   this.typeOptions = response.data;
@@ -374,12 +425,16 @@ export default {
       this.modeOptions = response.data;
     });
     this.getDicts("sys_fastgpt_role_external_info").then((response) => {
-    	  this.externalInfoOptions = response.data;
+      this.externalInfoOptions = response.data;
+    });
+    //渠道类型
+    this.getDicts("sys_fastgpt_channel_type").then((response) => {
+      this.channelOptions = response.data;
     });
 
-	getAllRoleType().then(response => {
-        this.typeOptions = response.data;
-      });
+    getAllRoleType().then(response => {
+      this.typeOptions = response.data;
+    });
   },
   methods: {
     /** 查询应用列表 */
@@ -395,7 +450,7 @@ export default {
     cancel() {
 
       this.reset();
-	  window.location.replace('/fastGpt/fastGptRole')
+      window.location.replace('/fastGpt/fastGptRole')
     },
     cancelTags(){
       this.changeTagsOpen.open=false;
@@ -482,7 +537,7 @@ export default {
 
     /** 修改按钮操作 */
     handleUpdate() {
-		var id=this.$route.params.command
+      var id=this.$route.params.command
       this.reset();
       getFastGptRole(id).then(response => {
         this.form = response.role;
@@ -490,11 +545,11 @@ export default {
         if(this.form.modeConfigJson!=null&&this.form.modeConfigJson!=""){
           this.form.modeConfigJson=JSON.parse(this.form.modeConfigJson)
         }else{
-			 this.form.modeConfigJson={APPKey:null}
-		}
-		if(this.form.contactInfo!=null){
-			  this.contactInfo = (this.form.contactInfo).split(",");
-			}
+          this.form.modeConfigJson={APPKey:null}
+        }
+        if(this.form.contactInfo!=null){
+          this.contactInfo = (this.form.contactInfo).split(",");
+        }
         //含标签吗
         getListByRoleId(id).then(res => {
           this.tagsFormList=res.rows;
@@ -668,9 +723,9 @@ export default {
     },
     /** 提交按钮 */
     submitForm() {
-	if(this.contactInfo!=null){
-		   this.form.contactInfo= (this.contactInfo).toString()
-		}
+      if(this.contactInfo!=null){
+        this.form.contactInfo= (this.contactInfo).toString()
+      }
       this.$refs["form"].validate(valid => {
         if (valid) {
           this.form.modeConfigJson=JSON.stringify(this.form.modeConfigJson)
@@ -701,7 +756,7 @@ export default {
               this.changeTagsOpen = false;
               this.getList();
             });
-            }
+          }
         }
       });
     },
@@ -711,12 +766,12 @@ export default {
         if (valid) {
           this.tagsFormAdd.roleId=roleId
           this.tagsFormAdd.tagIds=this.tagsFormAdd.tagIds.join(",")
-            addFastGptRoleTag(this.tagsFormAdd).then(response => {
-              this.msgSuccess("新增成功");
-              this.changeTagsOpenAdd = false;
-              this.open=false;
-              this.handleUpdate();
-            });
+          addFastGptRoleTag(this.tagsFormAdd).then(response => {
+            this.msgSuccess("新增成功");
+            this.changeTagsOpenAdd = false;
+            this.open=false;
+            this.handleUpdate();
+          });
 
         }
       });
@@ -725,30 +780,30 @@ export default {
     handleDelete(row) {
       const roleIds = row.roleId || this.ids;
       this.$confirm('是否确认删除应用编号为"' + roleIds + '"的数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(function() {
-          return delFastGptRole(roleIds);
-        }).then(() => {
-          this.getList();
-          this.msgSuccess("删除成功");
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(function() {
+        return delFastGptRole(roleIds);
+      }).then(() => {
+        this.getList();
+        this.msgSuccess("删除成功");
+      }).catch(() => {});
     },
     /** 导出按钮操作 */
     handleExport() {
       const queryParams = this.queryParams;
       this.$confirm('是否确认导出所有应用数据项?', "警告", {
-          confirmButtonText: "确定",
-          cancelButtonText: "取消",
-          type: "warning"
-        }).then(() => {
-          this.exportLoading = true;
-          return exportFastGptRole(queryParams);
-        }).then(response => {
-          this.download(response.msg);
-          this.exportLoading = false;
-        }).catch(() => {});
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        this.exportLoading = true;
+        return exportFastGptRole(queryParams);
+      }).then(response => {
+        this.download(response.msg);
+        this.exportLoading = false;
+      }).catch(() => {});
     }
   }
 };
@@ -802,19 +857,4 @@ export default {
   overflow-y: auto; /* 内容超出时显示滚动条 */
   line-height: 1.5em; /* 行高设置,确保每行高度一致 */
 }
-/* 新增的滚动容器样式(不影响原有样式) */
-.scroll-wrapper {
-  max-height: 130px; /* 大约三行的高度 */
-  overflow-y: auto;  /* 垂直滚动 */
-  padding-right: 5px; /* 为滚动条留出空间 */
-}
-
-/* 美化滚动条(可选) */
-.scroll-wrapper::-webkit-scrollbar {
-  width: 6px;
-}
-.scroll-wrapper::-webkit-scrollbar-thumb {
-  background: rgba(0, 0, 0, 0.2);
-  border-radius: 3px;
-}
 </style>