| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279 |
- <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 response = await getAllCourseList();
- 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>
|