Jelajahi Sumber

客服角色迁移到总后台/解绑登录的微信

三七 1 hari lalu
induk
melakukan
8076b9977e

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

@@ -214,3 +214,11 @@ export function transferVoiceCompanyUserId(data) {
     data: data
   })
 }
+
+export function unBind(userId) {
+  return request({
+    url: '/company/CompanyUserAll/unBind',
+    method: 'post',
+    data: { userId: userId }
+  })
+}

+ 34 - 18
src/api/fastGpt/fastGptRole.js

@@ -3,12 +3,13 @@ import request from '@/utils/request'
 // 查询应用列表
 export function listFastGptRole(query) {
   return request({
-    url: '/fastGpt/role/list',
+    url: '/fastGpt/fastGptRole/list',
     method: 'get',
     params: query
   })
 }
 
+
 // 查询应用列表
 export function newListFastGptRole(query) {
   return request({
@@ -20,17 +21,41 @@ export function newListFastGptRole(query) {
 
 export function getAllRoleList(query) {
   return request({
-    url: '/fastGpt/role/getAllRoleList',
+    url: '/fastGpt/fastGptRole/getAllRoleList',
     method: 'get',
     params: query
   })
 }
 
+// 导出应用
+export function exportFastGptRole(query) {
+  return request({
+    url: '/fastGpt/fastGptRole/export',
+    method: 'get',
+    params: query
+  })
+}
+
+
+export function getAllRoleType() {
+  return request({
+    url: '/fastGpt/fastGptRole/getAllRoleType',
+    method: 'get'
+  })
+}
+
+export function getAllCourseList(companyId) {
+  return request({
+    url: '/fastGpt/fastGptRole/getAllCourseList/'+companyId,
+    method: 'get'
+  })
+}
+
 
-// 查询应用详细
+// 查询应用详细getAllRoleType
 export function getFastGptRole(roleId) {
   return request({
-    url: '/fastGpt/role/' + roleId,
+    url: '/fastGpt/fastGptRole/' + roleId,
     method: 'get'
   })
 }
@@ -38,14 +63,14 @@ export function getFastGptRole(roleId) {
 // 新增应用
 export function addFastGptRole(data) {
   return request({
-    url: '/fastGpt/role',
+    url: '/fastGpt/fastGptRole',
     method: 'post',
     data: data
   })
 }
 export function copyFastGptRole(data) {
   return request({
-    url: '/fastGpt/role/copy',
+    url: '/fastGpt/fastGptRole/copy',
     method: 'post',
     data: data
   })
@@ -53,7 +78,7 @@ export function copyFastGptRole(data) {
 // 修改应用
 export function updateFastGptRole(data) {
   return request({
-    url: '/fastGpt/role',
+    url: '/fastGpt/fastGptRole',
     method: 'put',
     data: data
   })
@@ -62,23 +87,14 @@ export function updateFastGptRole(data) {
 //解除绑定
 export function relieveFastGptRole(roleId) {
   return request({
-    url: '/fastGpt/role/relieveFastGptRole/'+roleId,
+    url: '/fastGpt/fastGptRole/relieveFastGptRole/'+roleId,
     method: 'get',
   })
 }
 // 删除应用
 export function delFastGptRole(roleId) {
   return request({
-    url: '/fastGpt/role/' + roleId,
+    url: '/fastGpt/fastGptRole/' + roleId,
     method: 'delete'
   })
 }
-
-// 导出应用
-export function exportFastGptRole(query) {
-  return request({
-    url: '/fastGpt/fastGptRole/export',
-    method: 'get',
-    params: query
-  })
-}

+ 9 - 0
src/api/his/user.js

@@ -167,3 +167,12 @@ export function disabledUsers(data){
     data: data
   })
 }
+
+// 绑定/修改手机号
+export function bindPhNum(data) {
+  return request({
+    url: '/his/user/bindPhNum',
+    method: 'post',
+    data: data
+  })
+}

+ 13 - 0
src/router/index.js

@@ -255,6 +255,19 @@ export const constantRoutes = [
       }
     ]
   },
+  {
+    path: '/fastGpt/fastGptRole2',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: 'updateFastGptRole2/:command',
+        component: (resolve) => require(['@/views/fastGpt/fastGptRole/fastGptRoleUpdate'], resolve),
+        name: 'updateAiRole2',
+        meta: { title: '修改ai角色', activeMenu: '/fastGpt/fastGptRole'}
+      }
+    ]
+  },
   {
     path: '/course',
     component: Layout,

+ 31 - 1
src/views/company/companyUser/indexAll.vue

@@ -223,6 +223,14 @@
           <el-table-column label="账号过期时间"  sortable align="center" prop="expirationTime" width="160"/>
           <el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
             <template slot-scope="scope">
+              <el-button
+                v-if="scope.row.unionId"
+                size="mini"
+                type="text"
+                icon="el-icon-edit"
+                @click="unBind(scope.row)"
+                v-hasPermi="['company:user:unBind']"
+              >解绑登录的微信</el-button>
               <el-button
                 size="mini"
                 type="text"
@@ -757,7 +765,8 @@ import {
   setIsRegisterMember,
   updateCompanyUserAreaList,
   isAllowedAllRegister, unBindDoctorId, bindDoctorId, updateBatchUserRoles, addCodeUrl, addInfo,
-  getAllVoiceList, getAllNotVoiceList, transferVoiceCompanyUserId
+  getAllVoiceList, getAllNotVoiceList, transferVoiceCompanyUserId,
+  unBind
 } from '@/api/company/companyUserAll'
 import { getToken } from "@/utils/auth";
 import { treeselect } from "@/api/company/companyDept";
@@ -1769,6 +1778,27 @@ export default {
       }
       return isPic && isLt2M
     },
+
+    unBind(row){
+      this.$confirm(
+        '确定要解绑吗?解绑后登录需要重新扫码绑定。',
+        '提示',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }
+      ).then(() => {
+        unBind(row.userId).then(response => {
+          if (response.code === 200) {
+            this.msgSuccess("解绑成功");
+          }
+        });
+      }).catch(() => {
+        this.msgInfo("已取消操作");
+      });
+    },
+
     handleUpdateDoctor(row) {
       this.doctor.title = "绑定医生"
       this.doctor.open = true;

+ 1290 - 0
src/views/fastGpt/fastGptRole/fastGptRoleUpdate.vue

@@ -0,0 +1,1290 @@
+<template>
+  <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="请输入角色名" />
+        <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-switch
+          v-model="form.logistics"
+          :active-value="1"
+          :inactive-value="0"
+          active-text="是"
+          inactive-text="否"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+        <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="forbidStatus">
+        <el-switch
+          v-model="form.forbidStatus"
+          :active-value="1"
+          :inactive-value="0"
+          active-text="是"
+          inactive-text="否"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+        <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+          <i class="el-icon-info"></i>
+          AI是否规定时间内不回复消息,默认为开启,在结束时间后再进行最后一条消息回复,关闭后一天24小时都进行回复
+        </div>
+      </el-form-item>
+
+      <el-form-item label="AI是否发送新客先导课" prop="sendCourseStatus">
+        <el-switch
+          v-model="form.sendCourseStatus"
+          :active-value="1"
+          :inactive-value="0"
+          active-text="是"
+          inactive-text="否"
+          active-color="#13ce66"
+          inactive-color="#ff4949"
+        />
+        <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="courseId">
+        <el-popover
+          placement="bottom"
+          width="300"
+          trigger="click"
+          :visible="coursePopoverVisible"
+          @show="onCoursePopoverShow"
+        >
+          <div class="course-select-popover">
+            <el-input
+              v-if="courseFilterable"
+              placeholder="输入关键字过滤"
+              v-model="courseFilterText"
+              size="mini"
+              clearable
+              class="course-filter-input"
+            >
+            </el-input>
+
+            <div class="course-list-container" :style="{ height: courseListHeight + 'px' }">
+              <div
+                ref="courseVirtualList"
+                class="course-virtual-list"
+                @scroll="handleCourseScroll"
+                :style="{ height: courseListHeight + 'px', overflow: 'auto' }"
+              >
+                <div
+                  class="course-virtual-content"
+                  :style="{
+                    height: courseTotalHeight + 'px',
+                    position: 'relative',
+                    paddingTop: courseOffsetY + 'px'
+                  }"
+                >
+                  <div
+                    v-for="(item, index) in courseVisibleItems"
+                    :key="`${item.videoId}-${index}`"
+                    :style="{
+                      height: courseItemHeight + 'px',
+                      display: 'flex',
+                      alignItems: 'center',
+                      padding: '0 10px'
+                    }"
+                    :class="[
+                      'course-virtual-item',
+                      {
+                        'is-selected': form.courseId === item.videoId,
+                        'is-disabled': item.disabled
+                      }
+                    ]"
+                    @click="handleCourseItemClick(item)"
+                  >
+                    <span class="course-label" :title="item.title">{{ item.title }}</span>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <el-button slot="reference" style="min-width: 150px; max-width: 300px;">
+            {{ form.courseId ? getCourseTitle(form.courseId) : '请选择课程' }}
+            <i class="el-icon-arrow-down el-icon--right"></i>
+          </el-button>
+        </el-popover>
+
+        <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="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>
+                        <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">
+        <el-form-item label="选择标签" prop="tagIds">
+          <div @click="handleChangeTags(changeTagsOpen.roleId)" style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 100%">
+            <div style="min-height: 40px; max-height: 200px; overflow-y: auto;">
+              <el-tag type="success"
+                      closable
+                      :disable-transitions="false"
+                      v-for="list in tagListFormIndex"
+                      :key="list.tagId"
+                      @close="handleCloseTag(list)"
+                      style="margin: 3px;"
+              >{{list.name}}
+              </el-tag>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="提示词" prop="reminderWords">
+          <el-input type="textarea" :rows="6" v-model="tagsForm.reminderWords" placeholder="请输入AI客服该标签的提示词" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitTagsForm(changeTagsOpen.roleId)">确 定</el-button>
+        <el-button @click="cancelTags">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="tagChange.title" :visible.sync="tagChange.open" width="800px" append-to-body>
+      <div v-for="item in tagGroupList" :key="item.id" >
+        <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
+          <span class="name-background">{{ item.name }}</span>
+        </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">
+        <el-button type="primary" @click="editTagSubmitForm(tagChange.roleId)">确 定</el-button>
+        <el-button @click="editTagCancel(tagChange.roleId)">取消</el-button>
+      </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">
+        <el-form-item label="选择标签" prop="tagIds">
+          <div @click="handleChangeTagsAdd(changeTagsOpenAdd.roleId)" style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 100%">
+            <div style="min-height: 40px; max-height: 200px; overflow-y: auto;">
+              <el-tag type="success"
+                      closable
+                      :disable-transitions="false"
+                      v-for="list in tagListFormIndexAdd"
+                      :key="list.tagId"
+                      @close="handleCloseTag(list)"
+                      style="margin: 3px;"
+              >{{list.name}}
+              </el-tag>
+            </div>
+          </div>
+        </el-form-item>
+        <el-form-item label="提示词" prop="reminderWords">
+          <el-input type="textarea" :rows="6" v-model="tagsFormAdd.reminderWords" placeholder="请输入AI客服该标签的提示词" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitAddTagsForm(changeTagsOpenAdd.roleId)">确 定</el-button>
+        <el-button @click="cancelAddTags">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <el-dialog :title="tagChangeAdd.title" :visible.sync="tagChangeAdd.open" width="800px" append-to-body>
+      <div v-for="item in tagGroupList" :key="item.id" >
+        <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
+          <span class="name-background">{{ item.name }}</span>
+        </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">
+        <el-button type="primary" @click="addTagSubmitForm(tagChangeAdd.roleId)">确 定</el-button>
+        <el-button @click="addTagCancel(tagChangeAdd.roleId)">取消</el-button>
+      </div>
+    </el-dialog>
+
+  </div>
+</template>
+
+<script>
+import { listFastGptRole, getFastGptRole, delFastGptRole, addFastGptRole, updateFastGptRole, exportFastGptRole,getAllRoleType,getAllCourseList } from "@/api/fastGpt/fastGptRole";
+import {allListTagGroup} from "@/api/qw/tagGroup";
+import {listTag} from "@/api/qw/tag";
+import {
+  addFastGptRoleTag,
+  delFastGptRoleTag,
+  getListByRoleId,
+  updateFastGptRoleTag
+} from "@/api/fastGpt/fastGptRoleTag";
+import source from "echarts/src/data/Source";
+import ImageUpload from "@/views/qw/sop/ImageUpload.vue";
+
+export default {
+  name: "fastGptRoleUpdate",
+  components: {ImageUpload},
+  data() {
+    return {
+      logisticsOptions: [
+        { label: '是', value: 1 },
+        { label: '否', value: 0 }
+      ],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      //AI客服类型
+      typeOptions: [],
+
+      // 课程选择相关数据
+      coursePopoverVisible: false,
+      courseFilterText: '',
+      courseFilterable: true,
+      courseListHeight: 300, // 课程列表高度
+      courseItemHeight: 36, // 每个课程项的高度
+      courseStartIndex: 0,
+      courseEndIndex: 0,
+      courseScrollTop: 0,
+      courseVisibleItemCount: 0,
+      courseFlattenedNodes: [], // 扁平化的课程数据
+      courseNodeMap: new Map(), // 课程节点映射
+      courseUpdateTimer: null,
+      courseScrollRAF: null,
+      courseFilterDebounced: null,
+
+      //AI模型
+      modeOptions: [],
+      //渠道类型
+      channelOptions: [],
+      changeTagsOpen: {
+        title : "",
+        open :false,
+        roleId : null,
+      },
+      changeTagsOpenAdd:{
+        title : "",
+        open :false,
+        roleId : null,
+      },
+      //标签弹窗选择
+      tagChange:{
+        open:false,
+        title:"",
+        roleId:null,
+        id:null,
+      },
+      //新增
+      tagChangeAdd:{
+        open:false,
+        title:"",
+        roleId:null,
+        id:null,
+      },
+      //所有的标签组-标签
+      tagGroupList:[],
+
+      //所有的标签
+      tagList:[],
+      contactInfo:[],
+      //已经选择的标签
+      tagListFormIndex:[],
+
+      //新的标签
+      tagListFormIndexAdd:[],
+      // uploadUrl:process.env.VUE_APP_BASE_API+"/chat/upload/uploadFile",
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      // 选中数组
+      ids: [],
+      externalInfoOptions : [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 应用表格数据
+      fastGptRoleList: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        roleName: null,
+        companyId: null,
+        roleType: null,
+        mode: null,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null,
+        channelType: null,
+        logistics: null,
+        forbidStatus: null,
+        sendCourseStatus: null,
+        courseId: null,
+      },
+      // ... 其他数据
+      courseListLoaded: false,
+      // 表单参数
+      form: {
+      },
+      //某一个标签组
+      tagsForm:{},
+      //新增
+      tagsFormAdd:{},
+      //当前role的所有标签组
+      tagsFormList:[],
+      // 表单校验
+      rules: {
+        roleName: [
+          { required: true, message: "角色名称不能为空", trigger: "blur" }
+        ],
+        roleType: [
+          { required: true, message: "角色类型不能为空", trigger: "change" }
+        ],
+        logistics: [
+          { required: true, message: "物流提醒不能为空", trigger: "change" }
+        ],
+        forbidStatus: [
+          { required: true, message: "禁止夜间回复不能为空", trigger: "change" }
+        ],
+      },
+      tagsFormRules:{
+        tagIds:[
+          { required: true, message: "标签不能为空", trigger: "blur" }
+        ],
+        reminderWords:[
+          { required: true, message: "提示词不能为空", trigger: "blur" }
+        ]
+      },
+
+      tagsFormAddRules:{
+        tagIds:[
+          { required: true, message: "标签不能为空", trigger: "blur" }
+        ],
+        reminderWords:[
+          { required: true, message: "提示词不能为空", trigger: "blur" }
+        ]
+      }
+
+    };
+  },
+  watch: {
+    tagListFormIndex: {
+      handler(newList) {
+        if (!Array.isArray(newList)) return;  // 仅在 newList 为数组时继续
+
+        // 确保 tagsForm.tagIds 是数组并清空它
+        this.tagsForm.tagIds = [];
+
+        // 遍历 newList,添加 tagId
+        newList.forEach(tags => {
+          if (Array.isArray(tags)) {
+            // 如果 tags 是数组,遍历并添加
+            tags.forEach(item => {
+              if (item && item.tagId !== undefined) {
+                this.tagsForm.tagIds.push(item.tagId);
+              }
+            });
+          } else if (tags && tags.tagId !== undefined) {
+            // 如果 tags 是单个对象,直接添加
+            this.tagsForm.tagIds.push(tags.tagId);
+          }
+        });
+      },
+      deep: true
+    },
+
+    tagListFormIndexAdd: {
+      handler(newList) {
+
+        if (!Array.isArray(newList)) return;  // 仅在 newList 为数组时继续
+
+        // 确保 tagsForm.tagIds 是数组并清空它
+        this.tagsFormAdd.tagIds = [];
+
+        // 遍历 newList,添加 tagId
+        newList.forEach(tags => {
+          if (Array.isArray(tags)) {
+            // 如果 tags 是数组,遍历并添加
+            tags.forEach(item => {
+              if (item && item.tagId !== undefined) {
+                this.tagsFormAdd.tagIds.push(item.tagId);
+              }
+            });
+          } else if (tags && tags.tagId !== undefined) {
+            // 如果 tags 是单个对象,直接添加
+            this.tagsFormAdd.tagIds.push(tags.tagId);
+          }
+        });
+      },
+      deep: true
+    }
+  },
+  computed: {
+    courseTotalHeight() {
+      return this.courseVisibleNodes.length * this.courseItemHeight;
+    },
+    courseOffsetY() {
+      return this.courseStartIndex * this.courseItemHeight;
+    },
+    courseVisibleNodes() {
+      let nodes = this.courseFlattenedNodes;
+      if (this.courseFilterText.trim()) {
+        nodes = this.applyCourseFilter(nodes);
+      }
+      return nodes;
+    },
+    courseVisibleItems() {
+      return this.courseVisibleNodes.slice(this.courseStartIndex, this.courseEndIndex);
+    }
+  },
+  beforeDestroy() {
+    // 清理定时器
+    if (this.courseUpdateTimer) clearTimeout(this.courseUpdateTimer);
+    if (this.courseScrollRAF) cancelAnimationFrame(this.courseScrollRAF);
+  },
+  async created() {
+    this.handleUpdate();
+    await this.loadCourseList();
+    this.courseListLoaded = true
+    // 初始化课程选择相关数据
+    this.initCourseData();
+    //客服类型
+    // this.getDicts("chat_role_type").then((response) => {
+    //   this.typeOptions = response.data;
+    // });
+    //AI模型
+    this.getDicts("chat_role_mode").then((response) => {
+      this.modeOptions = response.data;
+    });
+    this.getDicts("sys_fastgpt_role_external_info").then((response) => {
+      this.externalInfoOptions = response.data;
+    });
+    //渠道类型
+    this.getDicts("sys_fastgpt_channel_type").then((response) => {
+      this.channelOptions = response.data;
+    });
+
+    getAllRoleType().then(response => {
+      this.typeOptions = response.data;
+    });
+  },
+  mounted() {
+    this.$nextTick(() => {
+      // 确保 DOM 已渲染完成
+      this.initScrollListener();
+    });
+  },
+  methods: {
+    // 初始化课程选择相关数据
+    initCourseData() {
+      this.courseVisibleItemCount = Math.ceil(this.courseListHeight / this.courseItemHeight) + 5;
+
+      // 防抖函数
+      this.handleCourseFilterDebounced = this.debounce(this.filterCourseTextChange, 300);
+    },
+
+    // 防抖函数
+    debounce(func, wait) {
+      let timeout;
+      return function executedFunction(...args) {
+        const later = () => {
+          clearTimeout(timeout);
+          func(...args);
+        };
+        clearTimeout(timeout);
+        timeout = setTimeout(later, wait);
+      };
+    },
+
+    // 处理课程数据
+    processCourseData(data) {
+      if (!Array.isArray(data)) {
+        this.courseFlattenedNodes = [];
+        this.courseNodeMap.clear();
+        return;
+      }
+
+      const flattened = [];
+      const nodeMap = new Map();
+
+      data.forEach((item, index) => {
+        const courseItem = {
+          videoId: item.videoId && typeof item.videoId === 'string' ? item.videoId : String(item.videoId || Math.random()),
+          title: item.title && typeof item.title === 'string' ? item.title : String(item.title || item.name || item.videoId || '未命名课程'),
+          originalData: item,
+          disabled: false
+        };
+
+        flattened.push(courseItem);
+        nodeMap.set(courseItem.videoId, courseItem);
+      });
+
+      this.courseFlattenedNodes = flattened;
+      this.courseNodeMap = nodeMap;
+    },
+
+    // 应用课程过滤
+    applyCourseFilter(nodesToFilter) {
+      const searchText = this.courseFilterText.toLowerCase().trim();
+      if (!searchText) return nodesToFilter;
+
+      return nodesToFilter.filter(node =>
+        node.title.toLowerCase().includes(searchText)
+      );
+    },
+
+    // 计算可见范围
+    calculateCourseVisibleRange() {
+      const containerHeight = this.courseListHeight;
+      const totalItems = this.courseVisibleNodes.length;
+
+      const newStartIndex = Math.floor(this.courseScrollTop / this.courseItemHeight);
+      const visibleCountInViewport = Math.ceil(containerHeight / this.courseItemHeight);
+
+      const buffer = 5;
+      this.courseStartIndex = Math.max(0, newStartIndex - buffer);
+      this.courseEndIndex = Math.min(totalItems, newStartIndex + visibleCountInViewport + buffer);
+    },
+
+    // 滚动处理
+    handleCourseScroll(e) {
+      const newScrollTop = e.target.scrollTop;
+      if (newScrollTop !== this.courseScrollTop) {
+        this.courseScrollTop = newScrollTop;
+        if (!this.courseScrollRAF) {
+          this.courseScrollRAF = requestAnimationFrame(() => {
+            this.calculateCourseVisibleRange();
+            this.courseScrollRAF = null;
+          });
+        }
+      }
+    },
+
+    // 重置滚动
+    resetCourseScroll() {
+      this.courseScrollTop = 0;
+      if (this.$refs.courseVirtualList) {
+        this.$refs.courseVirtualList.scrollTop = 0;
+      }
+      this.$nextTick(() => {
+        this.calculateCourseVisibleRange();
+      });
+    },
+
+    // 课程过滤文本变化
+    filterCourseTextChange() {
+      this.resetCourseScroll();
+    },
+
+    // 课程项点击
+    handleCourseItemClick(course) {
+      if (course.disabled) return;
+      this.form.courseId = course.videoId;
+      this.coursePopoverVisible = false;
+    },
+
+    // 课程弹出框显示
+    onCoursePopoverShow() {
+      this.$nextTick(() => {
+        this.resetCourseScroll();
+        if (this.courseFilterable && this.$refs.courseVirtualList) {
+          const inputEl = this.$refs.courseVirtualList.parentElement.querySelector('.course-filter-input input');
+          if (inputEl) inputEl.focus();
+        }
+      });
+    },
+
+    // 初始化滚动监听器
+    initScrollListener() {
+      this.$nextTick(() => {
+        const container = this.$refs.courseSelectContainer;
+        if (container) {
+          // 移除之前的事件监听器,避免重复绑定
+          container.removeEventListener('scroll', this.handleScroll);
+
+          // 添加新的滚动事件监听器
+          container.addEventListener('scroll', this.handleScroll);
+
+          // 添加初始调试信息
+          console.log('滚动监听器已初始化');
+          console.log('容器总高度:', container.scrollHeight);
+          console.log('可见高度:', container.clientHeight);
+          console.log('课程总数:', this.allCourseOptions.length);
+          console.log('当前显示:', this.displayCourseOptions.length);
+        }
+      });
+    },
+    getCourseTitle(videoId) {
+      if (!videoId) return '请选择课程';
+
+      console.log(videoId);
+      // 优先从处理后的课程数据中查找
+      const course = this.courseNodeMap.get(String(videoId));
+      if (course) {
+        return course.title;
+      }
+
+      // 如果在虚拟滚动数据中找不到,尝试从原始数据中查找
+      const originalCourse = this.allCourseOptions.find(item => item.videoId === videoId);
+      return originalCourse ? originalCourse.title : '未找到对应课程';
+    },
+    // 滚动处理函数
+    // 滚动处理函数 - 改进检测逻辑
+    // 滚动处理函数 - 改进检测逻辑
+    handleScroll(event) {
+      const container = event.target;
+      const { scrollTop, scrollHeight, clientHeight } = container;
+
+      // 更精确的滚动到底部检测
+      const threshold = 5; // 阈值调整为5px
+      if (scrollTop + clientHeight >= scrollHeight - threshold &&
+        !this.loadingMore &&
+        this.hasMore) {
+        this.loadMoreCourses();
+      }
+    },
+
+    // 修复滚动加载方法,添加更详细的日志
+    // 修复后的滚动加载方法
+    async loadMoreCourses() {
+      if (this.loadingMore || !this.hasMore) {
+        console.log('加载条件不满足:', { loadingMore: this.loadingMore, hasMore: this.hasMore });
+        return;
+      }
+
+      console.log('开始加载更多课程');
+      this.loadingMore = true;
+
+      try {
+        // 短暂延迟以避免频繁请求
+        await new Promise(resolve => setTimeout(resolve, 300));
+
+        // 使用全部课程列表
+        const sourceList = this.allCourseOptions;
+
+        // 修复分页逻辑:从 (currentPage - 1) * pageSize 开始
+        const start = (this.currentPage - 1) * this.pageSize;
+        const end = start + this.pageSize;
+        const moreCourses = sourceList.slice(start, end);
+
+        console.log('加载的课程范围:', { start, end, count: moreCourses.length });
+
+        if (moreCourses.length > 0) {
+          // 添加新课程到显示列表(注意:使用 concat 或扩展运算符避免重复添加)
+          this.displayCourseOptions = [...this.displayCourseOptions, ...moreCourses];
+          this.currentPage++; // 递增页码
+          // 检查是否还有更多课程
+          this.hasMore = end < sourceList.length;
+          console.log('更新后的课程列表长度:', this.displayCourseOptions.length, '还有更多:', this.hasMore);
+        } else {
+          // 没有更多课程时停止加载
+          this.hasMore = false;
+          console.log('没有更多课程可加载');
+        }
+      } catch (error) {
+        console.error('加载更多课程失败:', error);
+        this.hasMore = false;
+      } finally {
+        this.loadingMore = false;
+      }
+    },
+
+    async loadCourseList() {
+      try {
+        const companyId = this.$route.query.companyId;
+        if (!companyId) {
+          console.warn('缺少 companyId,无法加载课程列表');
+          this.courseOptions = [];
+          this.displayCourseOptions = [];
+          this.allCourseOptions = [];
+          this.hasMore = false;
+          this.courseFlattenedNodes = [];
+          this.courseNodeMap.clear();
+          return;
+        }
+        const response = await getAllCourseList(companyId);
+        if (Array.isArray(response.data)) {
+          this.courseOptions = response.data
+            .filter(item => item !== null && item !== undefined)
+            .map(item => ({
+              ...item,
+              videoId: item.videoId && typeof item.videoId === 'string' ? item.videoId : String(item.videoId || Math.random()),
+              title: item.title && typeof item.title === 'string' ? item.title : String(item.title || item.name || item.videoId || '未命名课程')
+            }));
+
+          this.allCourseOptions = [...this.courseOptions];
+          // 重置分页参数 - 从第一页开始
+          this.currentPage = 1;
+          // 默认展示前100条
+          this.displayCourseOptions = this.courseOptions.slice(0, this.pageSize);
+          this.hasMore = this.courseOptions.length > this.pageSize;
+
+          // 处理课程数据为虚拟滚动格式
+          this.processCourseData(this.courseOptions);
+        } else {
+          console.warn('getAllCourseList 返回的数据不是数组格式:', response.data);
+          this.courseOptions = [];
+          this.displayCourseOptions = [];
+          this.allCourseOptions = [];
+          this.hasMore = false;
+          this.courseFlattenedNodes = [];
+          this.courseNodeMap.clear();
+        }
+      } catch (error) {
+        console.error('课程列表加载失败:', error);
+        this.courseOptions = [];
+        this.displayCourseOptions = [];
+        this.allCourseOptions = [];
+        this.hasMore = false;
+        this.courseFlattenedNodes = [];
+        this.courseNodeMap.clear();
+      }
+    },
+
+
+    /** 查询应用列表 */
+    getList() {
+      this.loading = true;
+      listFastGptRole(this.queryParams).then(response => {
+        this.fastGptRoleList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+
+      this.reset();
+      window.location.replace('/fastGpt/fastGptRole')
+    },
+    cancelTags(){
+      this.changeTagsOpen.open=false;
+    },
+    cancelAddTags(){
+      this.changeTagsOpenAdd.open=false;
+    },
+    handleAvatarSuccess(res, file) {
+      if(res.code==200){
+        if(res.data.errcode!=0){
+          this.msgError(res.data.errmsg);
+        }
+        else{
+          //获取图片
+          this.form.kfMediaId=res.data.media_id;
+          this.form.avatar = res.ossUrl;
+          this.$forceUpdate()
+        }
+
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUpload(file) {
+      const isLt1M = file.size / 1024 / 1024 < 1;
+      if (!isLt1M) {
+        this.$message.error('上传图片大小不能超过 1MB!');
+      }
+      return   isLt1M;
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        roleId: null,
+        roleName: null,
+        companyId: null,
+        createTime: null,
+        updateTime: null,
+        roleType: null,
+        modeConfigJson:{APPKey:null,Key:null},
+        mode: 2,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null,
+        reminderWords: null,
+        isTags:null,
+      };
+      this.resetForm("form");
+    },
+
+    tagRest(){
+      this.tagListFormIndex=[];
+      this.tagsForm={
+        tagIds:null,
+        reminderWords:null,
+      };
+    },
+
+    tagAddRest(){
+      this.tagListFormIndexAdd=[];
+      this.tagsFormAdd={
+        tagIds:null,
+        reminderWords:null,
+      };
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.roleId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+
+    /** 修改按钮操作 */
+    handleUpdate() {
+      var id=this.$route.params.command
+      this.reset();
+      getFastGptRole(id).then(response => {
+        this.form = response.role;
+        this.form.roleType = JSON.stringify(response.role.roleType);
+        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(",");
+        }
+        //含标签吗
+        getListByRoleId(id).then(res => {
+          this.tagsFormList=res.rows;
+        })
+
+
+        allListTagGroup({corpId:this.form.corpId}).then(response => {
+          this.tagGroupList = response.rows;
+        });
+
+        //所有的标签
+        listTag({corpId:this.form.corpId}).then(response => {
+          this.tagList = response.rows;
+        });
+
+
+        this.title = "修改应用";
+      });
+
+    },
+
+    getTagNames(tagIds) {
+      // 确保 tagIds 是数组,如果是字符串则分割
+      const idsArray = Array.isArray(tagIds) ? tagIds : tagIds.split(",");
+      return idsArray.map(tagId => {
+        const tag = this.tagList.find(list => list.tagId === tagId);
+        return tag ? tag.name : tagId; // 返回标签名称或原 tagId
+      }).join(', '); // 使用逗号和空格连接标签名称
+    },
+
+    //标签回复人设弹窗
+    handleAddTags(value,bindCorpId) {
+      if (bindCorpId==null){
+        this.$message({
+          message: '请先给AI客服-绑定企业 再添加标签【企微管理->员工管理->企微员工】',
+          type: 'warning'
+        });
+        return;
+      }
+
+      this.changeTagsOpenAdd.title="创建标签回复人设";
+      this.changeTagsOpenAdd.open=true;
+      this.changeTagsOpenAdd.roleId=value;
+    },
+
+    //选择标签弹窗
+    handleChangeTags(roleId){
+
+      this.tagChange.open=true;
+      this.tagChange.title='选择标签';
+      this.tagChange.roleId=roleId;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(this.tagListFormIndex.map(tagItem => tagItem.tagId));
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
+
+    },
+    handleChangeTagsAdd(roleId){
+      this.tagChangeAdd.open=true;
+      this.tagChangeAdd.title='选择标签';
+      this.tagChangeAdd.roleId=roleId;
+    },
+    //标签的选择
+    tagSelection(row){
+      row.isSelected= !row.isSelected;
+      this.$forceUpdate();
+    },
+    //删除一些已经选择了的标签
+    handleCloseTag(list){
+
+      // 假设 list 对象具有一个 id 属性
+      const ls = this.tagListFormIndex.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.tagListFormIndex.splice(ls, 1);
+        this.tagListFormIndex = [...this.tagListFormIndex];
+      }
+    },
+
+    //删除一下已经存在的
+    handleCloseRoleTag(row){
+      delFastGptRoleTag(row.id).then(res => {
+        getListByRoleId(row.roleId).then(res => {
+          this.tagsFormList=res.rows;
+        })
+      })
+    },
+
+    //点击单个
+    handleEditRoleTag(value, index) {
+
+      this.changeTagsOpen.open = true;
+      this.changeTagsOpen.title = "编辑标签回复人设";
+      this.changeTagsOpen.roleId = value.roleId;
+      this.tagsForm = value;
+
+      // 过滤 tagList,获取匹配的标签
+      const tagIdsArray = Array.isArray(value.tagIds) ? value.tagIds : value.tagIds.split(",");
+      this.tagListFormIndex = this.tagList.filter(tag => tagIdsArray.includes(tag.tagId));
+    },
+    //选择标签
+    editTagSubmitForm(){
+
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+            if (!this.tagListFormIndex) {
+              this.tagListFormIndex = [];
+            }
+
+            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+            let tagExists = this.tagListFormIndex.some(
+              tag => tag.id === this.tagGroupList[i].tag[x].id
+            );
+
+            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+            if (!tagExists) {
+              this.tagListFormIndex.push(this.tagGroupList[i].tag[x]);
+            }
+          }
+        }
+      }
+      if (!this.tagListFormIndex || this.tagListFormIndex.length === 0) {
+        return this.$message('请选择标签');
+      }
+
+      this.tagChange.open = false;
+    },
+
+    //新增的标签
+    addTagSubmitForm(){
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+            if (!this.tagListFormIndexAdd) {
+              this.tagListFormIndexAdd = [];
+            }
+
+            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+            let tagExists = this.tagListFormIndexAdd.some(
+              tag => tag.id === this.tagGroupList[i].tag[x].id
+            );
+
+            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+            if (!tagExists) {
+              this.tagListFormIndexAdd.push(this.tagGroupList[i].tag[x]);
+            }
+          }
+        }
+      }
+      if (!this.tagListFormIndexAdd || this.tagListFormIndexAdd.length === 0) {
+        return this.$message('请选择标签');
+      }
+
+      this.tagChangeAdd.open = false;
+    },
+
+    //取消选择标签
+    editTagCancel(){
+      this.tagChange.open = false;
+      // this.tagRest();
+    },
+    addTagCancel(){
+      this.tagChangeAdd.open = false;
+      this.tagAddRest();
+    },
+    /** 提交按钮 */
+    submitForm() {
+      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)
+          if (this.form.roleId != null) {
+            updateFastGptRole(this.form).then(response => {
+              this.msgSuccess("修改成功");
+              window.location.replace('/fastGpt/fastGptRole')
+            });
+          }
+        }
+      });
+    },
+    submitTagsForm(roleId){
+      this.$refs["tagsForm"].validate(valid => {
+        if (valid) {
+
+          this.tagsForm.roleId=roleId
+          this.tagsForm.tagIds=this.tagsForm.tagIds.join(",")
+          if (this.tagsForm.id!= null) {
+            updateFastGptRoleTag(this.tagsForm).then(response => {
+              this.msgSuccess("修改成功");
+              this.changeTagsOpen = false;
+              this.getList();
+            });
+          }else {
+            addFastGptRoleTag(this.tagsForm).then(response => {
+              this.msgSuccess("新增成功");
+              this.changeTagsOpen = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+
+    submitAddTagsForm(roleId){
+      this.$refs["tagsFormAdd"].validate(valid => {
+        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();
+          });
+
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    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(() => {});
+    },
+    /** 导出按钮操作 */
+    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(() => {});
+    }
+  }
+};
+</script>
+<style scoped>
+/* CSS 样式 */
+.tag-container {
+  display: flex;
+  flex-wrap: wrap; /* 超出宽度时自动换行 */
+  gap: 8px; /* 设置标签之间的间距 */
+}
+.name-background {
+  display: inline-block;
+  background-color: #abece6; /* 背景颜色 */
+  padding: 4px 8px; /* 调整内边距,让背景包裹文字 */
+  border-radius: 4px; /* 可选:设置圆角 */
+}
+.tag-box {
+  padding: 8px 12px;
+  border: 1px solid #989797;
+  border-radius: 4px;
+  cursor: pointer;
+  display: inline-block;
+}
+
+.tag-selected {
+  background-color: #00bc98;
+  color: #fff;
+  border-color: #00bc98;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+
+
+.button-new-tag {
+  margin-left: 10px;
+  height: 32px;
+  line-height: 30px;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+.input-new-tag {
+  width: 90px;
+  margin-left: 10px;
+  vertical-align: bottom;
+}
+.text-container {
+  max-height: 5em; /* 设置最大高度为6行,根据字体大小调整 */
+  overflow-y: auto; /* 内容超出时显示滚动条 */
+  line-height: 1.5em; /* 行高设置,确保每行高度一致 */
+}
+</style>

+ 809 - 0
src/views/fastGpt/fastGptRole/index.vue

@@ -0,0 +1,809 @@
+<template>
+  <div >
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px" style="margin-left: 50px;margin-top: 20px;">
+      <el-form-item label="" prop="roleName" >
+        <el-input
+          v-model="queryParams.roleName"
+          placeholder="请输入客服名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+      <!--  <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> -->
+      </el-form-item>
+    </el-form>
+
+	<div style="padding-left: 50px;padding-right: 50px;">
+		<div class="container">
+			 <div class="card add-card" @click="addCard">
+			       <h3>+ 创建模型</h3>
+			 </div>
+		    <div class="card" v-for="(item, index) in fastGptRoleList" :key="index" @click="updateGpt(item)">
+			  <div class="avatar-container" >
+			      <div class="avatar-wrapper">
+			          <img :src="item.avatar" alt="头像" class="avatar" />
+			      </div>
+			      <div class="avatar-info-wrapper">
+			          <div class="card-name">{{item.roleName}}</div>
+			      </div>
+			  </div>
+
+		      <div  class="card-content"  >{{item.reminderWords}}</div>
+			  <div>
+
+				  <span class="author">创建时间: {{item.createTime}}</span>
+				  <span style="float: right;margin-right: 10px;">
+					  <el-popover
+						  popper-class="customPopper"
+					      placement="left"
+					      trigger="hover"
+						  width="50px"
+						  >
+						  <div>
+						  <div>
+							<el-button
+							  size="mini"
+							  type="text"
+							  @click="copy(item)"
+							>复制</el-button>
+						  </div>
+						 <div>
+						<el-button
+						  size="mini"
+						  type="text"
+						  @click="handleDelete(item)"
+						>删除</el-button>
+						</div>
+						  </div>
+					      <a slot="reference">⋮</a>
+					   </el-popover>
+				  </span>
+
+			  </div>
+
+		 </div>
+		</div>
+	</div>
+
+    <pagination style="margin-right: 40px;"
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改应用对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
+        <el-form-item label="销售公司" prop="companyId">
+          <el-select
+            v-model="form.companyId"
+            filterable
+            clearable
+            placeholder="请选择销售公司"
+            style="width: 100%"
+          >
+            <el-option
+              v-for="item in companyOptions"
+              :key="item.companyId"
+              :label="item.companyName"
+              :value="item.companyId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="客服名称" prop="roleName">
+          <el-input v-model="form.roleName" placeholder="请输入角色名" />
+        </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>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listFastGptRole, getFastGptRole, delFastGptRole, addFastGptRole, updateFastGptRole, exportFastGptRole,copyFastGptRole } from "@/api/fastGpt/fastGptRole";
+import {allListTagGroup} from "@/api/qw/tagGroup";
+import {listTag} from "@/api/qw/tag";
+import {
+  addFastGptRoleTag,
+  delFastGptRoleTag,
+  getListByRoleId,
+  updateFastGptRoleTag
+} from "@/api/fastGpt/fastGptRoleTag";
+import { getCompanyList } from "@/api/company/company";
+import source from "echarts/src/data/Source";
+import ImageUpload from "@/views/qw/sop/ImageUpload.vue";
+
+export default {
+  name: "FastGptRole",
+  components: {ImageUpload},
+  data() {
+    return {
+
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      //AI客服类型
+      typeOptions: [],
+      //AI模型
+      modeOptions: [],
+      changeTagsOpen: {
+        title : "",
+        open :false,
+        roleId : null,
+      },
+      changeTagsOpenAdd:{
+        title : "",
+        open :false,
+        roleId : null,
+      },
+      //标签弹窗选择
+      tagChange:{
+        open:false,
+        title:"",
+        roleId:null,
+        id:null,
+      },
+      //新增
+      tagChangeAdd:{
+        open:false,
+        title:"",
+        roleId:null,
+        id:null,
+      },
+      //所有的标签组-标签
+      tagGroupList:[],
+
+      //所有的标签
+      tagList:[],
+	  contactInfo:[],
+      //已经选择的标签
+      tagListFormIndex:[],
+
+      //新的标签
+      tagListFormIndexAdd:[],
+      // uploadUrl:process.env.VUE_APP_BASE_API+"/chat/upload/uploadFile",
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      // 选中数组
+      ids: [],
+	  externalInfoOptions:[],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 应用表格数据
+      fastGptRoleList: [],
+      // 销售公司下拉
+      companyOptions: [],
+      // 弹出层标题
+      title: "新增Ai角色",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        roleName: null,
+        companyId: null,
+        roleType: null,
+        mode: null,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null
+      },
+      // 表单参数
+      form: {
+      },
+      //某一个标签组
+      tagsForm:{},
+      //新增
+      tagsFormAdd:{},
+      //当前role的所有标签组
+      tagsFormList:[],
+      // 表单校验
+      rules: {
+        companyId: [
+          { required: true, message: "销售公司不能为空", trigger: "change" }
+        ],
+        roleName: [
+          { required: true, message: "角色名称不能为空", trigger: "blur" }
+        ],
+        roleType: [
+          { required: true, message: "角色类型不能为空", trigger: "change" }
+        ],
+      },
+      tagsFormRules:{
+        tagIds:[
+          { required: true, message: "标签不能为空", trigger: "blur" }
+        ],
+        reminderWords:[
+          { required: true, message: "提示词不能为空", trigger: "blur" }
+        ]
+      },
+
+      tagsFormAddRules:{
+        tagIds:[
+          { required: true, message: "标签不能为空", trigger: "blur" }
+        ],
+        reminderWords:[
+          { required: true, message: "提示词不能为空", trigger: "blur" }
+        ]
+      }
+
+    };
+  },
+  watch: {
+    tagListFormIndex: {
+      handler(newList) {
+        if (!Array.isArray(newList)) return;  // 仅在 newList 为数组时继续
+
+        // 确保 tagsForm.tagIds 是数组并清空它
+        this.tagsForm.tagIds = [];
+
+        // 遍历 newList,添加 tagId
+        newList.forEach(tags => {
+          if (Array.isArray(tags)) {
+            // 如果 tags 是数组,遍历并添加
+            tags.forEach(item => {
+              if (item && item.tagId !== undefined) {
+                this.tagsForm.tagIds.push(item.tagId);
+              }
+            });
+          } else if (tags && tags.tagId !== undefined) {
+            // 如果 tags 是单个对象,直接添加
+            this.tagsForm.tagIds.push(tags.tagId);
+          }
+        });
+      },
+      deep: true
+    },
+
+    tagListFormIndexAdd: {
+      handler(newList) {
+
+        if (!Array.isArray(newList)) return;  // 仅在 newList 为数组时继续
+
+        // 确保 tagsForm.tagIds 是数组并清空它
+        this.tagsFormAdd.tagIds = [];
+
+        // 遍历 newList,添加 tagId
+        newList.forEach(tags => {
+          if (Array.isArray(tags)) {
+            // 如果 tags 是数组,遍历并添加
+            tags.forEach(item => {
+              if (item && item.tagId !== undefined) {
+                this.tagsFormAdd.tagIds.push(item.tagId);
+              }
+            });
+          } else if (tags && tags.tagId !== undefined) {
+            // 如果 tags 是单个对象,直接添加
+            this.tagsFormAdd.tagIds.push(tags.tagId);
+          }
+        });
+      },
+      deep: true
+    }
+  },
+  created() {
+    //客服类型
+    this.getDicts("chat_role_type").then((response) => {
+      this.typeOptions = response.data;
+    });
+    //AI模型
+    this.getDicts("chat_role_mode").then((response) => {
+      this.modeOptions = response.data;
+    });
+	this.getDicts("sys_fastgpt_role_external_info").then((response) => {
+	  this.externalInfoOptions = response.data;
+	});
+    getCompanyList().then(response => {
+      this.companyOptions = response.data || [];
+    });
+    this.getList();
+  },
+  methods: {
+    /** 查询应用列表 */
+    getList() {
+      this.loading = true;
+      listFastGptRole(this.queryParams).then(response => {
+        this.fastGptRoleList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    cancelTags(){
+      this.changeTagsOpen.open=false;
+    },
+    cancelAddTags(){
+      this.changeTagsOpenAdd.open=false;
+    },
+    handleAvatarSuccess(res, file) {
+      if(res.code==200){
+        if(res.data.errcode!=0){
+          this.msgError(res.data.errmsg);
+        }
+        else{
+          //获取图片
+          this.form.kfMediaId=res.data.media_id;
+          this.form.avatar = res.ossUrl;
+          this.$forceUpdate()
+        }
+
+      }
+      else{
+        this.msgError(res.msg);
+      }
+    },
+    beforeAvatarUpload(file) {
+      const isLt1M = file.size / 1024 / 1024 < 1;
+      if (!isLt1M) {
+        this.$message.error('上传图片大小不能超过 1MB!');
+      }
+      return   isLt1M;
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+        roleId: null,
+        roleName: null,
+        companyId: null,
+        createTime: null,
+        updateTime: null,
+        roleType: null,
+        modeConfigJson:{APPKey:null,Key:null},
+        mode: 2,
+        kfId: null,
+        kfUrl: null,
+        avatar: null,
+        kfMediaId: null,
+        reminderWords: null,
+        isTags:null,
+      };
+      this.resetForm("form");
+    },
+
+    tagRest(){
+      this.tagListFormIndex=[];
+      this.tagsForm={
+        tagIds:null,
+        reminderWords:null,
+      };
+    },
+
+    tagAddRest(){
+      this.tagListFormIndexAdd=[];
+      this.tagsFormAdd={
+        tagIds:null,
+        reminderWords:null,
+      };
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.roleId)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+	  this.contactInfo=[];
+      this.open = true;
+      this.title = "添加应用";
+    },
+	addCard(){
+		this.reset();
+		this.contactInfo = [];
+		this.title="新增Ai客服"
+		this.open=true
+	},
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+	  this.contactInfo=[];
+      const roleId = row.roleId || this.ids
+      getFastGptRole(roleId).then(response => {
+        this.form = response.role;
+        this.form.roleType = JSON.stringify(response.role.roleType);
+        if(this.form.modeConfigJson!=null){
+          this.form.modeConfigJson=JSON.parse(this.form.modeConfigJson)
+        }
+		if(this.form.contactInfo!=null){
+		  this.contactInfo = (this.form.contactInfo).split(",");
+		}
+        //含标签吗
+        getListByRoleId(roleId).then(res => {
+          this.tagsFormList=res.rows;
+        })
+
+        //所有的标签组
+        allListTagGroup({corpId:row.bindCorpId}).then(response => {
+          this.tagGroupList = response.rows;
+        });
+
+        //所有的标签
+        listTag({corpId:row.bindCorpId}).then(response => {
+          this.tagList = response.rows;
+        });
+
+        this.open = true;
+        this.title = "修改应用";
+      });
+
+    },
+
+    getTagNames(tagIds) {
+      // 确保 tagIds 是数组,如果是字符串则分割
+      const idsArray = Array.isArray(tagIds) ? tagIds : tagIds.split(",");
+      return idsArray.map(tagId => {
+        const tag = this.tagList.find(list => list.tagId === tagId);
+        return tag ? tag.name : tagId; // 返回标签名称或原 tagId
+      }).join(', '); // 使用逗号和空格连接标签名称
+    },
+
+    //标签回复人设弹窗
+    handleAddTags(value,bindCorpId) {
+      if (bindCorpId==null){
+        this.$message({
+          message: '请先给AI客服-绑定企业 再添加标签【企微管理->员工管理->企微员工】',
+          type: 'warning'
+        });
+        return;
+      }
+
+      this.changeTagsOpenAdd.title="创建标签回复人设";
+      this.changeTagsOpenAdd.open=true;
+      this.changeTagsOpenAdd.roleId=value;
+    },
+
+    //选择标签弹窗
+    handleChangeTags(roleId){
+
+      this.tagChange.open=true;
+      this.tagChange.title='选择标签';
+      this.tagChange.roleId=roleId;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(this.tagListFormIndex.map(tagItem => tagItem.tagId));
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
+
+    },
+    handleChangeTagsAdd(roleId){
+      this.tagChangeAdd.open=true;
+      this.tagChangeAdd.title='选择标签';
+      this.tagChangeAdd.roleId=roleId;
+    },
+    //标签的选择
+    tagSelection(row){
+      row.isSelected= !row.isSelected;
+      this.$forceUpdate();
+    },
+    //删除一些已经选择了的标签
+    handleCloseTag(list){
+
+      // 假设 list 对象具有一个 id 属性
+      const ls = this.tagListFormIndex.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.tagListFormIndex.splice(ls, 1);
+        this.tagListFormIndex = [...this.tagListFormIndex];
+      }
+    },
+
+    //删除一下已经存在的
+    handleCloseRoleTag(row){
+      delFastGptRoleTag(row.id).then(res => {
+        getListByRoleId(row.roleId).then(res => {
+          this.tagsFormList=res.rows;
+        })
+      })
+    },
+
+    //点击单个
+    handleEditRoleTag(value, index) {
+
+      this.changeTagsOpen.open = true;
+      this.changeTagsOpen.title = "编辑标签回复人设";
+      this.changeTagsOpen.roleId = value.roleId;
+      this.tagsForm = value;
+
+      // 过滤 tagList,获取匹配的标签
+      const tagIdsArray = Array.isArray(value.tagIds) ? value.tagIds : value.tagIds.split(",");
+      this.tagListFormIndex = this.tagList.filter(tag => tagIdsArray.includes(tag.tagId));
+    },
+    //选择标签
+    editTagSubmitForm(){
+
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+            if (!this.tagListFormIndex) {
+              this.tagListFormIndex = [];
+            }
+
+            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+            let tagExists = this.tagListFormIndex.some(
+              tag => tag.id === this.tagGroupList[i].tag[x].id
+            );
+
+            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+            if (!tagExists) {
+              this.tagListFormIndex.push(this.tagGroupList[i].tag[x]);
+            }
+          }
+        }
+      }
+      if (!this.tagListFormIndex || this.tagListFormIndex.length === 0) {
+        return this.$message('请选择标签');
+      }
+
+      this.tagChange.open = false;
+    },
+
+    //新增的标签
+    addTagSubmitForm(){
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+            if (!this.tagListFormIndexAdd) {
+              this.tagListFormIndexAdd = [];
+            }
+
+            // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+            let tagExists = this.tagListFormIndexAdd.some(
+              tag => tag.id === this.tagGroupList[i].tag[x].id
+            );
+
+            // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+            if (!tagExists) {
+              this.tagListFormIndexAdd.push(this.tagGroupList[i].tag[x]);
+            }
+          }
+        }
+      }
+      if (!this.tagListFormIndexAdd || this.tagListFormIndexAdd.length === 0) {
+        return this.$message('请选择标签');
+      }
+
+      this.tagChangeAdd.open = false;
+    },
+	copy(row){
+		this.form={
+			roleId:row.roleId,
+			avatar:null,
+			roleName:null,
+		}
+		this.title="复制Ai"
+		this.open=true;
+	},
+    //取消选择标签
+    editTagCancel(){
+      this.tagChange.open = false;
+      // this.tagRest();
+    },
+    addTagCancel(){
+      this.tagChangeAdd.open = false;
+      this.tagAddRest();
+    },
+    /** 提交按钮 */
+    submitForm() {
+		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)
+          if (this.form.roleId != null) {
+            copyFastGptRole(this.form).then(response => {
+              this.msgSuccess("复制成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+			  this.form.mode=2
+			  this.form.roleType=1
+            addFastGptRole(this.form).then(response => {
+              this.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    submitTagsForm(roleId){
+      this.$refs["tagsForm"].validate(valid => {
+        if (valid) {
+
+          this.tagsForm.roleId=roleId
+          this.tagsForm.tagIds=this.tagsForm.tagIds.join(",")
+          if (this.tagsForm.id!= null) {
+            updateFastGptRoleTag(this.tagsForm).then(response => {
+              this.msgSuccess("修改成功");
+              this.changeTagsOpen = false;
+              this.getList();
+            });
+          }else {
+            addFastGptRoleTag(this.tagsForm).then(response => {
+              this.msgSuccess("新增成功");
+              this.changeTagsOpen = false;
+              this.getList();
+            });
+            }
+        }
+      });
+    },
+	updateGpt(row){
+		 this.$router.push({
+			 path: '/fastGpt/fastGptRole2/updateFastGptRole2/' + row.roleId,
+			 query: { companyId: row.companyId }
+		 })
+	},
+    submitAddTagsForm(roleId){
+      this.$refs["tagsFormAdd"].validate(valid => {
+        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.getList();
+            });
+
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    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(() => {});
+    },
+    /** 导出按钮操作 */
+    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(() => {});
+    }
+  }
+};
+</script>
+<style scoped>
+.container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16px; /* 卡片间距 */
+  justify-content: flex-start; /* 靠左对齐 */
+}
+
+.card {
+  width: calc((100% - 32px) / 3); /* 三列布局,16px间距 */
+  border: 1px solid green; /* 绿色边框 */
+  border-radius: 10px; /* 圆角 */
+  padding: 16px; /* 内边距 */
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影效果 */
+  background-color: #fff; /* 白色背景 */
+  cursor: pointer; /* 鼠标悬停样式 */
+  height: 200px;
+}
+
+.card-name {
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.card-content {
+  margin: 20px 10px 10px 10px;
+  height: 80px;
+  color: #666;
+}
+
+.author {
+  color: #999;
+}
+
+/* 新增卡片样式 */
+.add-card {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  text-align: center;
+  background-color: #f5f5f5; /* 浅灰色背景 */
+  border: 1px solid green; /* 虚线边框 */
+  color: green; /* 字体颜色 */
+}
+
+.add-card:hover {
+  background-color: #e8f5e9; /* 悬停效果 */
+}
+.pagination-container{
+	background-color: rgb(240, 242, 245); /* 悬停效果 */
+}
+.avatar {
+  width: 30px; /* 固定宽度 */
+  height: 30px; /* 固定高度 */
+  border-radius: 50%; /* 裁剪为圆形 */
+  object-fit: cover; /* 保证图片不会变形 */
+  margin-right: 8px; /* 和标题的间距 */
+}
+.avatar-container {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start; /* 左对齐 */
+}
+
+.avatar-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.avatar-info-wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+
+</style>
+<style>
+	.el-popover{
+		min-width: 10px;
+	}
+</style>

+ 87 - 2
src/views/his/user/index.vue

@@ -142,7 +142,7 @@
       <el-table-column label="最后一次登录ip" align="center" prop="lastIp" width="130px"/>
       <el-table-column label="余额" align="center" prop="balance" />
       <el-table-column label="注册时间" align="center" prop="createTime" width="150px" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="150px">
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="220px">
         <template slot-scope="scope">
           <el-button
             size="mini"
@@ -169,6 +169,12 @@
             @click="handleAddPoint(scope.row)"
             v-hasPermi="['his:user:addPoint']"
           >添加积分</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            @click="openBindPhone(scope.row)"
+            v-hasPermi="['his:user:bindPhNum']"
+          >{{ scope.row.phone ? '修改手机号' : '绑定手机号' }}</el-button>
           <el-button
             size="mini"
             type="text"
@@ -290,11 +296,30 @@
       :sip-dial-type="currentDialType"
     />
 
+    <!-- 绑定/修改手机号弹窗 -->
+    <el-dialog
+      :title="bindPhoneHasPhone ? '修改手机号' : '绑定手机号'"
+      :visible.sync="bindPhoneVisible"
+      width="420px"
+      append-to-body
+      @close="resetBindPhoneForm"
+    >
+      <el-form ref="bindPhoneForm" :model="bindPhoneForm" :rules="bindPhoneRules" label-width="90px">
+        <el-form-item label="手机号码" prop="phNum">
+          <el-input v-model="bindPhoneForm.phNum" placeholder="请输入手机号码" maxlength="11" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="bindPhoneVisible = false">取 消</el-button>
+        <el-button type="primary" :loading="bindPhoneLoading" @click="submitBindPhone">确 定</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
 <script>
-import { listUser, getUser, delUser, addUser, updateUser, exportUser, addPoint } from "@/api/his/user";
+import { listUser, getUser, delUser, addUser, updateUser, exportUser, addPoint, bindPhNum } from "@/api/his/user";
 import { getCompanyUserList, changeCompanyUser, getCompanyList } from '@/api/company/companyUser';
 import userDetails from '../../components/his/userDetails.vue';
 import userDetailsByNew from './userDetails.vue';
@@ -312,6 +337,25 @@ export default {
       currentEncryptedUserId: null,
       currentDialType: 'audio',
 
+      // 绑定/修改手机号
+      bindPhoneVisible: false,
+      bindPhoneLoading: false,
+      bindPhoneHasPhone: false,
+      bindPhoneFsUserId: null,
+      bindPhoneForm: {
+        phNum: ''
+      },
+      bindPhoneRules: {
+        phNum: [
+          { required: true, message: '请输入手机号码', trigger: 'blur' },
+          {
+            pattern: /^1[3-9]\d{9}$/,
+            message: '请输入正确的手机号码',
+            trigger: 'blur'
+          }
+        ]
+      },
+
       projectCode: process.env.VUE_APP_PROJECT_CODE,
       userIsPromoterOptions:[],
       companyName: null,
@@ -575,6 +619,47 @@ export default {
       }
       this.addPointDialog.open = true
     },
+    openBindPhone(row) {
+      this.resetBindPhoneForm();
+      this.bindPhoneFsUserId = row.userId;
+      this.bindPhoneHasPhone = !!row.phone;
+      this.bindPhoneVisible = true;
+    },
+    resetBindPhoneForm() {
+      this.bindPhoneForm = { phNum: '' };
+      this.bindPhoneLoading = false;
+      this.bindPhoneFsUserId = null;
+      this.bindPhoneHasPhone = false;
+      if (this.$refs.bindPhoneForm) {
+        this.$refs.bindPhoneForm.clearValidate();
+      }
+    },
+    submitBindPhone() {
+      this.$refs.bindPhoneForm.validate(valid => {
+        if (!valid) return;
+        if (!this.bindPhoneFsUserId) {
+          this.$message.warning('无法获取会员信息');
+          return;
+        }
+        this.bindPhoneLoading = true;
+        bindPhNum({
+          phNum: Number(this.bindPhoneForm.phNum),
+          fsUserId: this.bindPhoneFsUserId
+        }).then(res => {
+          if (res.code === 200) {
+            this.msgSuccess(res.msg || '操作成功');
+            this.bindPhoneVisible = false;
+            this.getList();
+          } else {
+            this.msgError(res.msg || '操作失败');
+          }
+        }).catch(() => {
+          // 错误信息由请求拦截器处理
+        }).finally(() => {
+          this.bindPhoneLoading = false;
+        });
+      });
+    },
     submitAddPointForm() {
       this.$refs["addPointForm"].validate(valid => {
         if (valid) {