mirror of
https://codeup.aliyun.com/67762337eccfc218f6110e0e/vue/learning-system-portal.git
synced 2025-12-20 16:26:43 +08:00
feat(cases): 规范化案例模块接口调用及新增AI会话初始化功能
- 新增 initChat 方法用于获取AI会话ID - 新增 handleStop方法 停止当前会话功能
This commit is contained in:
committed by
liu.zixi
parent
99760c9940
commit
a1d5d0f079
@@ -1,14 +1,14 @@
|
||||
/* 案例模块的相关处理*/
|
||||
import ajax from '@/utils/xajax.js'
|
||||
import ajaxs from '@/api/ajax.js'
|
||||
import ajax from "@/utils/xajax.js";
|
||||
import ajaxs from "@/api/ajax.js";
|
||||
/**首页查询
|
||||
* pageSize
|
||||
* orderField
|
||||
* orderAsc
|
||||
*/
|
||||
*/
|
||||
const indexList = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/case-random', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/case-random", query);
|
||||
};
|
||||
/*
|
||||
*案例分页搜索查询 是否推荐
|
||||
*@param(String) keyWord 关键词
|
||||
@@ -17,14 +17,14 @@ const indexList = function (query) {
|
||||
*@param(Boolean) breCommend 是否推荐 true或者false
|
||||
*@param(String) orgDomain 组织机构
|
||||
*@param(String) majorType 专业分类
|
||||
* @param(String) majorDomain 专业领域
|
||||
* @param(String) majorDomain 专业领域
|
||||
*/
|
||||
const queryList = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/queryList', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/queryList", query);
|
||||
};
|
||||
const queryListV2 = function (query) {
|
||||
return ajax.postJson('/xboe/m/boe/cases/queryListV2', query);
|
||||
}
|
||||
return ajax.postJson("/xboe/m/boe/cases/queryListV2", query);
|
||||
};
|
||||
/* 案例分页搜索查询 是否置顶
|
||||
*@param(String) keyWord 关键词
|
||||
@param(Boolean) isTop 是否置顶 true或false
|
||||
@@ -33,32 +33,32 @@ const queryListV2 = function (query) {
|
||||
* @param(String) majorDomain 专业领域
|
||||
*/
|
||||
const isTopList = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/isTopList', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/isTopList", query);
|
||||
};
|
||||
/* 设置置顶 取消置顶
|
||||
@param(String) id
|
||||
@param(Integer) isTop 是否置顶 0:未置顶,1:已置顶
|
||||
*/
|
||||
const updateTop = function (id, isTop) {
|
||||
return ajax.get(`/xboe/m/boe/cases/updateTop?id=${id}&isTop=${isTop}`);
|
||||
}
|
||||
};
|
||||
/* 删除案例
|
||||
*param(String) id 案例的id
|
||||
*/
|
||||
const del = function (id) {
|
||||
return ajax.get('/xboe/m/boe/cases/delete?id=' + id);
|
||||
}
|
||||
return ajax.get("/xboe/m/boe/cases/delete?id=" + id);
|
||||
};
|
||||
/*案例详情
|
||||
@param(String) id 案例的id
|
||||
@param(Boolean) addView 是否增加浏览量
|
||||
*/
|
||||
const detail = function (id, addView) {
|
||||
let pars = 'id=' + id;
|
||||
let pars = "id=" + id;
|
||||
if (addView) {
|
||||
pars += '&addView=' + addView
|
||||
pars += "&addView=" + addView;
|
||||
}
|
||||
return ajax.get('/xboe/m/boe/cases/detail?' + pars);
|
||||
}
|
||||
return ajax.get("/xboe/m/boe/cases/detail?" + pars);
|
||||
};
|
||||
/*推荐
|
||||
*@param (String) id 案例的id
|
||||
*@param (String) title 案例的标题
|
||||
@@ -66,61 +66,65 @@ const detail = function (id, addView) {
|
||||
*@param (String) recommentThat 推荐说明
|
||||
*/
|
||||
const savaRecommend = function (data) {
|
||||
return ajax.post('/xboe/m/boe/recommend/save', data);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/recommend/save", data);
|
||||
};
|
||||
/* 推荐列表
|
||||
*没有参数
|
||||
*/
|
||||
const query = function () {
|
||||
return ajax.get('/xboe/m/boe/recommend/query');
|
||||
}
|
||||
return ajax.get("/xboe/m/boe/recommend/query");
|
||||
};
|
||||
/*
|
||||
*作者排行榜
|
||||
@param pageSize 长度
|
||||
*/
|
||||
const usernameList = function (pageSize) {
|
||||
return ajax.get(`/xboe/m/boe/cases/usernamelist?pageSize=${pageSize}`);
|
||||
}
|
||||
};
|
||||
/*
|
||||
人气榜
|
||||
@param pageSize 长度
|
||||
*/
|
||||
const queryPraises = function (pageSize) {
|
||||
return ajax.get(`/xboe/m/boe/cases/query-praises?pageSize=${pageSize}`);
|
||||
}
|
||||
const queryPraisesNew = function (pageSize,rankType) {
|
||||
return ajax.get(`/xboe/m/boe/cases/queryPopularity?pageSize=${pageSize}&rankType=${rankType}`);
|
||||
}
|
||||
};
|
||||
const queryPraisesNew = function (pageSize, rankType) {
|
||||
return ajax.get(
|
||||
`/xboe/m/boe/cases/queryPopularity?pageSize=${pageSize}&rankType=${rankType}`
|
||||
);
|
||||
};
|
||||
const queryRecommendRank = function (pageSize) {
|
||||
return ajax.get(`/xboe/m/boe/cases/queryRecommendRank?pageSize=${pageSize}`);
|
||||
}
|
||||
};
|
||||
/*
|
||||
好评榜
|
||||
@param pageSize 长度
|
||||
*/
|
||||
const queryComments = function (pageSize) {
|
||||
return ajax.get(`/xboe/m/boe/cases/query-comments?pageSize=${pageSize}`);
|
||||
}
|
||||
const queryCommentsNew = function (pageSize,rankType) {
|
||||
return ajax.get(`/xboe/m/boe/cases/queryHighOpinion?pageSize=${pageSize}&rankType=${rankType}`);
|
||||
}
|
||||
};
|
||||
const queryCommentsNew = function (pageSize, rankType) {
|
||||
return ajax.get(
|
||||
`/xboe/m/boe/cases/queryHighOpinion?pageSize=${pageSize}&rankType=${rankType}`
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 专业分类
|
||||
* */
|
||||
const majorTypes = function () {
|
||||
return ajax.get('/xboe/m/boe/cases/majorTypes');
|
||||
}
|
||||
return ajax.get("/xboe/m/boe/cases/majorTypes");
|
||||
};
|
||||
|
||||
/**
|
||||
* 详情新*/
|
||||
* 详情新*/
|
||||
const details = function (id, addView) {
|
||||
let pars = 'id=' + id;
|
||||
let pars = "id=" + id;
|
||||
if (addView) {
|
||||
pars += '&addView=' + addView
|
||||
pars += "&addView=" + addView;
|
||||
}
|
||||
return ajax.get('/xboe/m/boe/cases/details?' + pars);
|
||||
}
|
||||
return ajax.get("/xboe/m/boe/cases/details?" + pars);
|
||||
};
|
||||
|
||||
/**
|
||||
* 二次查询
|
||||
@@ -129,8 +133,8 @@ const details = function (id, addView) {
|
||||
* }
|
||||
* */
|
||||
const ids = function (data) {
|
||||
return ajax.postJson('/xboe/m/boe/cases/ids', data);
|
||||
}
|
||||
return ajax.postJson("/xboe/m/boe/cases/ids", data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置和取消优秀案例
|
||||
@@ -140,9 +144,8 @@ const ids = function (data) {
|
||||
* }
|
||||
* */
|
||||
const excellent = function (data) {
|
||||
return ajax.post('/xboe/m/boe/cases/excellent', data)
|
||||
}
|
||||
|
||||
return ajax.post("/xboe/m/boe/cases/excellent", data);
|
||||
};
|
||||
|
||||
/**
|
||||
*我的案例
|
||||
@@ -152,8 +155,8 @@ const excellent = function (data) {
|
||||
* }
|
||||
* */
|
||||
const mylist = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/mylist', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/mylist", query);
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出
|
||||
@@ -163,21 +166,26 @@ const mylist = function (query) {
|
||||
* }
|
||||
* */
|
||||
const exportCases = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/export', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/export", query);
|
||||
};
|
||||
|
||||
/**
|
||||
* 导出案例后加的
|
||||
* */
|
||||
const exports = function (query) {
|
||||
return ajax.post('/xboe/m/boe/cases/exportCase', query, { responseType: 'blob' });
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/exportCase", query, {
|
||||
responseType: "blob",
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @param
|
||||
* 记录查看开始时间
|
||||
* caseRecommendId读取的案例的id
|
||||
*/
|
||||
const startReadTimer = (caseRecommendId) => ajax.get(`/xboe/m/boe/cases/recommend/startRead?caseRecommendId=${caseRecommendId}`);
|
||||
const startReadTimer = (caseRecommendId) =>
|
||||
ajax.get(
|
||||
`/xboe/m/boe/cases/recommend/startRead?caseRecommendId=${caseRecommendId}`
|
||||
);
|
||||
|
||||
/**
|
||||
* @param
|
||||
@@ -187,34 +195,56 @@ const startReadTimer = (caseRecommendId) => ajax.get(`/xboe/m/boe/cases/recomm
|
||||
* browseDuration [浏览时间秒]
|
||||
* }
|
||||
*/
|
||||
const endReadTimer = (data)=> ajax.postJson('/xboe/m/boe/cases/recommend/recordBrowseDuration',data)
|
||||
const endReadTimer = (data) =>
|
||||
ajax.postJson("/xboe/m/boe/cases/recommend/recordBrowseDuration", data);
|
||||
|
||||
/**年份查询
|
||||
*
|
||||
*/
|
||||
*/
|
||||
const caseYears = function (query = {}) {
|
||||
return ajax.post('/xboe/m/boe/cases/caseYears', query);
|
||||
}
|
||||
return ajax.post("/xboe/m/boe/cases/caseYears", query);
|
||||
};
|
||||
//案例推荐榜单
|
||||
const getQueryRecommendRank = function (params) {
|
||||
return ajax.get(`/xboe/m/boe/cases/queryRecommendRank?pageSize=${params.pageSize}`)
|
||||
}
|
||||
return ajax.get(
|
||||
`/xboe/m/boe/cases/queryRecommendRank?pageSize=${params.pageSize}`
|
||||
);
|
||||
};
|
||||
//案例上榜
|
||||
const riseIntoRank = (data) => ajax.post(`/xboe/m/boe/cases/riseIntoRank?caseId=${data.caseId}`)
|
||||
const riseIntoRank = (data) =>
|
||||
ajax.post(`/xboe/m/boe/cases/riseIntoRank?caseId=${data.caseId}`);
|
||||
//取消上榜
|
||||
const cancelRiseIntoRank = (data) => ajax.post(`/xboe/m/boe/cases/cancelRiseIntoRank?caseId=${data.caseId}`)
|
||||
const cancelRiseIntoRank = (data) =>
|
||||
ajax.post(`/xboe/m/boe/cases/cancelRiseIntoRank?caseId=${data.caseId}`);
|
||||
//调整榜单
|
||||
const adjustRank = (data) => ajax.post(`/xboe/m/boe/cases/adjustRank?caseIds=${data.caseIds}`)
|
||||
const adjustRank = (data) =>
|
||||
ajax.post(`/xboe/m/boe/cases/adjustRank?caseIds=${data.caseIds}`);
|
||||
//收藏案例
|
||||
const queryFavoriteCaseOfIndex = (params) => ajax.get(`/xboe/m/boe/cases/queryFavoriteCaseOfIndex?pageIndex=${params.pageIndex}&pageSize=${params.pageSize}&orderField=${params.orderField}&orderAsc=${params.orderAsc}`)
|
||||
const queryFavoriteCaseOfIndex = (params) =>
|
||||
ajax.get(
|
||||
`/xboe/m/boe/cases/queryFavoriteCaseOfIndex?pageIndex=${params.pageIndex}&pageSize=${params.pageSize}&orderField=${params.orderField}&orderAsc=${params.orderAsc}`
|
||||
);
|
||||
//浏览记录
|
||||
const queryViewRecord = (data) => ajaxs.post(`/statApi/xboe/m/stat/userdynamic/list?pageIndex=${data.pageIndex}&pageSize=${data.pageSize}&contentType=${data.contentType}&aid=${data.aid}&hidden=${data.hidden}&eventKey=${data.eventKey}`)
|
||||
const queryViewRecord = (data) =>
|
||||
ajaxs.post(
|
||||
`/statApi/xboe/m/stat/userdynamic/list?pageIndex=${data.pageIndex}&pageSize=${data.pageSize}&contentType=${data.contentType}&aid=${data.aid}&hidden=${data.hidden}&eventKey=${data.eventKey}`
|
||||
);
|
||||
//浏览量TOP榜单
|
||||
const queryPopularityOfMajor = (params) => ajax.get(`/xboe/m/boe/cases/queryPopularityOfMajor?pageSize=${params.pageSize}&rankMonth=${params.rankMonth}&majorId=${params.majorId}`)
|
||||
const queryPopularityOfMajor = (params) =>
|
||||
ajax.get(
|
||||
`/xboe/m/boe/cases/queryPopularityOfMajor?pageSize=${params.pageSize}&rankMonth=${params.rankMonth}&majorId=${params.majorId}`
|
||||
);
|
||||
//浏览记录新
|
||||
const browsingHistory = (params) => ajax.get(`/xboe/m/boe/cases/browsingHistory?pageIndex=${params.pageIndex}&pageSize=5`)
|
||||
const browsingHistory = (params) =>
|
||||
ajax.get(
|
||||
`/xboe/m/boe/cases/browsingHistory?pageIndex=${params.pageIndex}&pageSize=5`
|
||||
);
|
||||
//获取所有的专业月份
|
||||
const queryAllTimePopularityOfMajor = () => ajax.get('/xboe/m/boe/cases/queryAllTimePopularityOfMajor')
|
||||
const queryAllTimePopularityOfMajor = () =>
|
||||
ajax.get("/xboe/m/boe/cases/queryAllTimePopularityOfMajor");
|
||||
// 获取会话ID
|
||||
const initChat = () => ajax.get("/xboe/m/boe/case/ai/initChat");
|
||||
|
||||
export default {
|
||||
queryAllTimePopularityOfMajor,
|
||||
getQueryRecommendRank,
|
||||
@@ -249,5 +279,6 @@ export default {
|
||||
exports,
|
||||
startReadTimer,
|
||||
endReadTimer,
|
||||
caseYears
|
||||
}
|
||||
caseYears,
|
||||
initChat,
|
||||
};
|
||||
|
||||
BIN
src/assets/images/case/stop.png
Normal file
BIN
src/assets/images/case/stop.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,363 +1,621 @@
|
||||
<template>
|
||||
<div class="input-area">
|
||||
<el-input v-model="inputContent" type="textarea" class="input-placeholder" placeholder="有问题,尽管问"
|
||||
@keyup.enter.native.prevent="handleSend" :disabled="disabled" :autosize="{ minRows: 2, maxRows: 4 }"
|
||||
resize="none"></el-input>
|
||||
<el-input
|
||||
v-model="inputContent"
|
||||
type="textarea"
|
||||
class="input-placeholder"
|
||||
placeholder="有问题,尽管问"
|
||||
@keyup.enter.native.prevent="handleSend"
|
||||
:disabled="disabled"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
resize="none"
|
||||
></el-input>
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" size="small" class="start-btn" @click="handleNewConversation">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="start-btn"
|
||||
@click="handleNewConversation"
|
||||
>
|
||||
+ 开启新对话
|
||||
</el-button>
|
||||
<el-button type="text" class="send-btn" @click="handleSend" :disabled="disabled">
|
||||
<el-button
|
||||
type="text"
|
||||
class="send-btn"
|
||||
@click="handleSend"
|
||||
v-if="sendShow"
|
||||
>
|
||||
<i class="el-icon-s-promotion"></i>
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
class="send-btn"
|
||||
@click="handleStop"
|
||||
v-if="stopShow"
|
||||
>
|
||||
<img class="stop" src="@/assets/images/case/stop.png" alt="" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { aiChat } from '@/api/boe/aiChat.js'
|
||||
import { aiChat } from "@/api/boe/aiChat.js";
|
||||
import apiCase from "@/api/modules/cases.js";
|
||||
|
||||
export default {
|
||||
name: 'SendMessage',
|
||||
name: "SendMessage",
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: "",
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
sendShow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
stopShow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
messageList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
suggestions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
default: () => [],
|
||||
},
|
||||
conversationId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputContent: this.value,
|
||||
conversationId: '' // 会话ID
|
||||
}
|
||||
currentAbortController: null, // 添加abort controller引用
|
||||
isStopped: false, // 添加停止状态
|
||||
typingTimer: null, // 添加这一行
|
||||
thinkTypingTimer: null, // 添加这一行
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.inputContent = newVal
|
||||
}
|
||||
this.inputContent = newVal;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getConversationId();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getConversationId() {
|
||||
apiCase.initChat().then((res) => {
|
||||
if (res.status == 200) {
|
||||
const { result } = res;
|
||||
// console.log(result);
|
||||
this.conversationId = result;
|
||||
sessionStorage.setItem("conversationId", this.conversationId);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleSend(event) {
|
||||
// 阻止事件的默认行为和冒泡
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log('preventDefault');
|
||||
console.log("preventDefault");
|
||||
}
|
||||
console.log('handleSend');
|
||||
if (!this.inputContent.trim() || this.disabled) return
|
||||
console.log("handleSend");
|
||||
if (!this.inputContent.trim() || this.disabled) return;
|
||||
// 重置停止状态,确保可以正常发送新消息
|
||||
this.isStopped = false;
|
||||
|
||||
// 添加用户消息到列表
|
||||
const userMessage = {
|
||||
isBot: false,
|
||||
text: this.inputContent
|
||||
text: this.inputContent,
|
||||
};
|
||||
this.messageList.push(userMessage);
|
||||
|
||||
// 显示加载状态
|
||||
this.$emit('loading', true);
|
||||
this.$emit("loading", true);
|
||||
|
||||
// 调用AI聊天接口 (暂时注释掉SSE,使用模拟数据)
|
||||
this.callAIChat(this.inputContent);
|
||||
|
||||
// 清空输入框
|
||||
this.inputContent = ''
|
||||
this.inputContent = "";
|
||||
},
|
||||
handleStop() {
|
||||
console.log(this.accumulatedContent, "accumulatedContent");
|
||||
// 设置停止状态
|
||||
this.isStopped = true;
|
||||
// 清除打字机定时器
|
||||
if (this.typingTimer) {
|
||||
clearInterval(this.typingTimer);
|
||||
this.typingTimer = null;
|
||||
}
|
||||
|
||||
if (this.thinkTypingTimer) {
|
||||
clearInterval(this.thinkTypingTimer);
|
||||
this.thinkTypingTimer = null;
|
||||
}
|
||||
if (this.conversationId) {
|
||||
// const stopAbortController = new AbortController();
|
||||
fetch(
|
||||
`/systemapi/xboe/m/boe/case/ai/stop?conversationId=${this.conversationId}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
// signal: stopAbortController.signal,
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
console.log("成功发送停止请求"); // 通知父组件需要重新获取会话ID
|
||||
this.$emit("need-new-conversation-id");
|
||||
} else {
|
||||
console.error("停止请求失败:", response.status);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("停止请求出错:", error);
|
||||
});
|
||||
}
|
||||
// 终止当前正在进行的聊天请求
|
||||
if (this.currentAbortController) {
|
||||
this.currentAbortController.abort();
|
||||
}
|
||||
|
||||
// 立即发送加载完成事件,停止按钮状态切换
|
||||
this.$emit("loading", false);
|
||||
// 发送按钮状态更新事件
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: true,
|
||||
stopShow: false,
|
||||
});
|
||||
},
|
||||
|
||||
// 真实的SSE实现(暂时注释掉)
|
||||
callAIChat(question) {
|
||||
// 重置停止状态
|
||||
this.isStopped = false;
|
||||
// 开始请求时显示加载状态
|
||||
this.$emit("loading", true);
|
||||
// 创建AbortController实例
|
||||
this.currentAbortController = new AbortController();
|
||||
|
||||
// 创建新的AI消息对象
|
||||
const aiMessage = {
|
||||
docId: '',
|
||||
docId: "",
|
||||
isBot: true,
|
||||
text: '',
|
||||
text: "",
|
||||
status: null,
|
||||
thinkText: '',
|
||||
thinkText: "",
|
||||
caseRefers: [], // 添加caseRefers字段
|
||||
textCompleted: false // 添加文字处理完成状态,默认为false
|
||||
textCompleted: false, // 添加文字处理完成状态,默认为false
|
||||
};
|
||||
this.messageList.push(aiMessage);
|
||||
let hasFinished = false; // 新增
|
||||
|
||||
// 构造请求参数
|
||||
const requestData = {
|
||||
conversationId: this.conversationId,
|
||||
query: question
|
||||
query: question,
|
||||
};
|
||||
// 创建POST请求
|
||||
fetch('/systemapi/xboe/m/boe/case/ai/chat', {
|
||||
method: 'POST',
|
||||
fetch("/systemapi/xboe/m/boe/case/ai/chat", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"accept": "text/event-stream",
|
||||
"Content-Type": "application/json",
|
||||
accept: "text/event-stream",
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
}).then(r => {
|
||||
return r
|
||||
}).then(response => {
|
||||
// 处理流式响应
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
let buffer = '';
|
||||
let accumulatedContent = ''; // 累积的内容用于打字机效果
|
||||
let accumulatedThinkContent = ''; // 累积的思考内容
|
||||
let inThinkSection = false; // 是否在思考部分
|
||||
let typingTimer = null; // 打字机定时器
|
||||
let thinkTypingTimer = null; // 思考内容打字机定时器
|
||||
|
||||
// 逐字显示文本的函数
|
||||
const typeText = (message, fullContent) => {
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (typingTimer) {
|
||||
clearInterval(typingTimer);
|
||||
}
|
||||
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.text.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typingSpeed = 30; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
typingTimer = setInterval(() => {
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.text.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.text = fullContent.substring(0, nextIndex);
|
||||
this.$emit('update-message', message);
|
||||
} else {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
// 当打字机效果完成时,检查是否应该设置textCompleted为true
|
||||
// 这应该在status 4(交互完成)时才设置
|
||||
if (message.status === 4) {
|
||||
if (nextIndex >= targetLength) {
|
||||
message.textCompleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 逐字显示思考内容的函数
|
||||
const typeThinkText = (message, fullThinkContent) => {
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (thinkTypingTimer) {
|
||||
clearInterval(thinkTypingTimer);
|
||||
}
|
||||
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.thinkText.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullThinkContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从当前显示位置开始继续显示(避免清空重显)
|
||||
const startIndex = currentLength;
|
||||
|
||||
const typingSpeed = 20; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
thinkTypingTimer = setInterval(() => {
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.thinkText.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.thinkText = fullThinkContent.substring(0, nextIndex);
|
||||
this.$emit('update-message', message);
|
||||
} else {
|
||||
clearInterval(thinkTypingTimer);
|
||||
thinkTypingTimer = null;
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 添加一个检查是否所有文本都已完成显示的函数
|
||||
const isTextDisplayCompleted = (message, fullContent) => {
|
||||
return message.text.length >= fullContent.length;
|
||||
};
|
||||
|
||||
// 读取流数据
|
||||
const read = () => {
|
||||
reader.read().then(({ done, value }) => {
|
||||
if (done) {
|
||||
// 当流结束时,等待打字机效果完成
|
||||
const waitForTyping = () => {
|
||||
if (!typingTimer) {
|
||||
this.$emit('loading', false);
|
||||
} else {
|
||||
setTimeout(waitForTyping, 100);
|
||||
}
|
||||
};
|
||||
waitForTyping();
|
||||
body: JSON.stringify(requestData),
|
||||
signal: this.currentAbortController.signal, // 添加signal
|
||||
})
|
||||
.then((r) => {
|
||||
return r;
|
||||
})
|
||||
.then((response) => {
|
||||
// 处理流式响应
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
let buffer = "";
|
||||
let accumulatedContent = ""; // 累积的内容用于打字机效果
|
||||
let accumulatedThinkContent = ""; // 累积的思考内容
|
||||
let inThinkSection = false; // 是否在思考部分
|
||||
let typingTimer = null; // 打字机定时器
|
||||
let thinkTypingTimer = null; // 思考内容打字机定时器
|
||||
// 逐字显示文本的函数
|
||||
const typeText = (message, fullContent) => {
|
||||
// 如果已经停止,则直接显示全部内容
|
||||
if (this.isStopped) {
|
||||
message.text = fullContent;
|
||||
this.$emit("update-message", message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码数据
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// 按行分割数据
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop(); // 保留不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data:')) {
|
||||
try {
|
||||
const jsonData = JSON.parse(line.substring(5));
|
||||
// 根据status处理不同类型的数据
|
||||
switch (jsonData.status) {
|
||||
case 0: // 引用文件
|
||||
// 处理引用文件信息
|
||||
if (jsonData.fileRefer && jsonData.fileRefer.caseRefers) {
|
||||
aiMessage.caseRefers = jsonData.fileRefer.caseRefers;
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
// 从响应中获取并保存conversationId
|
||||
if (jsonData.conversationId) {
|
||||
this.conversationId = jsonData.conversationId;
|
||||
sessionStorage.setItem('conversationId', jsonData.conversationId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // 流式对话内容
|
||||
// 处理
|
||||
const content = jsonData.content;
|
||||
aiMessage.hasThink = false;
|
||||
if (content.startsWith('<think>')) {
|
||||
aiMessage.hasThink = true
|
||||
inThinkSection = true;
|
||||
accumulatedThinkContent = content.replace('<think>', '');
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (content.startsWith('</think>')) {
|
||||
inThinkSection = false;
|
||||
accumulatedThinkContent += content.replace('</think>', '');
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (inThinkSection) {
|
||||
accumulatedThinkContent += content;
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else {
|
||||
// 累积内容并使用打字机效果更新显示
|
||||
accumulatedContent += content;
|
||||
// 如果thinkText已经显示完整,则继续使用打字机效果显示内容
|
||||
if (aiMessage.hasThink) {
|
||||
if (aiMessage.thinkText.length >= accumulatedThinkContent.length) {
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
} else {
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
|
||||
}
|
||||
// 不在这里直接更新,让打字机效果处理更新
|
||||
break;
|
||||
|
||||
case 2: // 回答完成
|
||||
// 不再在这里设置textCompleted状态
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
// 从响应中获取并保存conversationId
|
||||
|
||||
break;
|
||||
|
||||
case 3: // 返回建议
|
||||
// 这里可以处理建议问题
|
||||
this.$emit('update-suggestions', jsonData.suggestions);
|
||||
break;
|
||||
|
||||
case 4: // 交互完成
|
||||
aiMessage.status = 4
|
||||
|
||||
// 从响应中获取并保存conversationId
|
||||
this.$emit('loading', false);
|
||||
// 检查文本是否已经完全显示,如果是则设置textCompleted为true
|
||||
if (isTextDisplayCompleted(aiMessage, accumulatedContent)) {
|
||||
// aiMessage.textCompleted = true;
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case 9:
|
||||
if (jsonData.docId) {
|
||||
aiMessage.docId = jsonData.docId
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
console.log(jsonData)
|
||||
break
|
||||
default:
|
||||
if (jsonData.docId) {
|
||||
aiMessage.docId = jsonData.docId
|
||||
this.$emit('update-message', aiMessage);
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析SSE数据错误:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 继续读取
|
||||
read();
|
||||
}).catch(error => {
|
||||
console.error('SSE连接错误:', error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (typingTimer) {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
}
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit('loading', false);
|
||||
aiMessage.text = '当前无法获取回答,请稍后重试';
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
});
|
||||
};
|
||||
|
||||
// 开始读取数据
|
||||
read();
|
||||
}).catch(error => {
|
||||
console.error('请求失败:', error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit('loading', false);
|
||||
aiMessage.text = '当前无法获取回答,请稍后重试';
|
||||
// 更新父组件的messageList
|
||||
this.$emit('update-message', aiMessage);
|
||||
});
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.text.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
const typingSpeed = 30; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
typingTimer = setInterval(() => {
|
||||
// 如果已停止,立即停止渲染并保持当前内容
|
||||
if (this.isStopped) {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
// 保持当前已渲染的内容,不显示剩余内容
|
||||
this.$emit("update-message", message);
|
||||
return;
|
||||
}
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.text.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.text = fullContent.substring(0, nextIndex);
|
||||
this.$emit("update-message", message);
|
||||
} else {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
// 当打字机效果完成时,检查是否应该设置textCompleted为true
|
||||
// 这应该在status 4(交互完成)时才设置
|
||||
if (message.status === 4) {
|
||||
if (nextIndex >= targetLength) {
|
||||
message.textCompleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 逐字显示思考内容的函数
|
||||
const typeThinkText = (message, fullThinkContent) => {
|
||||
// 如果已经停止,则直接显示当前已渲染的内容并清理定时器
|
||||
if (this.isStopped) {
|
||||
// 清理可能存在的定时器
|
||||
if (thinkTypingTimer) {
|
||||
clearInterval(thinkTypingTimer);
|
||||
thinkTypingTimer = null;
|
||||
}
|
||||
// 保持当前已渲染的内容,不显示完整内容
|
||||
this.$emit("update-message", message);
|
||||
return;
|
||||
}
|
||||
// 如果已有定时器在运行,先清除它
|
||||
if (thinkTypingTimer) {
|
||||
clearInterval(thinkTypingTimer);
|
||||
}
|
||||
|
||||
// 获取当前已显示的文本长度
|
||||
const currentLength = message.thinkText.length;
|
||||
// 获取完整文本
|
||||
const targetLength = fullThinkContent.length;
|
||||
|
||||
// 如果已经显示完整文本,不需要继续
|
||||
if (currentLength >= targetLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从当前显示位置开始继续显示(避免清空重显)
|
||||
const startIndex = currentLength;
|
||||
|
||||
const typingSpeed = 20; // 每个字符的间隔时间(毫秒)
|
||||
|
||||
thinkTypingTimer = setInterval(() => {
|
||||
// 如果已停止,立即停止渲染并保持当前内容
|
||||
if (this.isStopped) {
|
||||
clearInterval(thinkTypingTimer);
|
||||
thinkTypingTimer = null;
|
||||
// 保持当前已渲染的内容,不显示剩余内容
|
||||
this.$emit("update-message", message);
|
||||
return;
|
||||
}
|
||||
// 计算下一个要显示的字符索引
|
||||
const nextIndex = message.thinkText.length + 1;
|
||||
if (nextIndex <= targetLength) {
|
||||
message.thinkText = fullThinkContent.substring(0, nextIndex);
|
||||
this.$emit("update-message", message);
|
||||
} else {
|
||||
clearInterval(thinkTypingTimer);
|
||||
thinkTypingTimer = null;
|
||||
}
|
||||
}, typingSpeed);
|
||||
};
|
||||
|
||||
// 添加一个检查是否所有文本都已完成显示的函数
|
||||
const isTextDisplayCompleted = (message, fullContent) => {
|
||||
return message.text.length >= fullContent.length;
|
||||
};
|
||||
|
||||
// 读取流数据
|
||||
const read = () => {
|
||||
// 如果已停止,直接返回
|
||||
if (this.isStopped || hasFinished) {
|
||||
this.$emit("loading", false);
|
||||
return;
|
||||
}
|
||||
|
||||
reader
|
||||
.read()
|
||||
.then(({ done, value }) => {
|
||||
// 如果已停止,直接返回
|
||||
if (this.isStopped || hasFinished) {
|
||||
this.$emit("loading", false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
// 当流结束时,等待打字机效果完成
|
||||
const waitForTyping = () => {
|
||||
if (!typingTimer) {
|
||||
this.$emit("loading", false);
|
||||
} else {
|
||||
setTimeout(waitForTyping, 100);
|
||||
}
|
||||
};
|
||||
waitForTyping();
|
||||
return;
|
||||
}
|
||||
|
||||
// 解码数据
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// 按行分割数据
|
||||
const lines = buffer.split("\n");
|
||||
buffer = lines.pop(); // 保留不完整的行
|
||||
|
||||
for (const line of lines) {
|
||||
// 如果已停止,跳出循环
|
||||
if (this.isStopped) {
|
||||
this.$emit("loading", false);
|
||||
return;
|
||||
}
|
||||
if (line.startsWith("data:")) {
|
||||
try {
|
||||
const jsonData = JSON.parse(line.substring(5));
|
||||
// 根据status处理不同类型的数据
|
||||
switch (jsonData.status) {
|
||||
case 0: // 引用文件
|
||||
// 处理引用文件信息
|
||||
if (
|
||||
jsonData.fileRefer &&
|
||||
jsonData.fileRefer.caseRefers
|
||||
) {
|
||||
aiMessage.caseRefers =
|
||||
jsonData.fileRefer.caseRefers;
|
||||
// 更新父组件的messageList
|
||||
this.$emit("update-message", aiMessage);
|
||||
}
|
||||
// 从响应中获取并保存conversationId
|
||||
// if (jsonData.conversationId) {
|
||||
// this.conversationId = jsonData.conversationId;
|
||||
// sessionStorage.setItem(
|
||||
// "conversationId",
|
||||
// jsonData.conversationId
|
||||
// );
|
||||
// }
|
||||
break;
|
||||
|
||||
case 1: // 流式对话内容
|
||||
if (!this.isStopped) {
|
||||
// 处理
|
||||
const content = jsonData.content;
|
||||
aiMessage.hasThink = false;
|
||||
if (content.startsWith("<think>")) {
|
||||
aiMessage.hasThink = true;
|
||||
inThinkSection = true;
|
||||
accumulatedThinkContent = content.replace(
|
||||
"<think>",
|
||||
""
|
||||
);
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (content.startsWith("</think>")) {
|
||||
inThinkSection = false;
|
||||
accumulatedThinkContent += content.replace(
|
||||
"</think>",
|
||||
""
|
||||
);
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else if (inThinkSection) {
|
||||
accumulatedThinkContent += content;
|
||||
// 使用打字机效果显示think内容
|
||||
typeThinkText(aiMessage, accumulatedThinkContent);
|
||||
} else {
|
||||
// 累积内容并使用打字机效果更新显示
|
||||
accumulatedContent += content;
|
||||
// 如果thinkText已经显示完整,则继续使用打字机效果显示内容
|
||||
if (aiMessage.hasThink) {
|
||||
if (
|
||||
aiMessage.thinkText.length >=
|
||||
accumulatedThinkContent.length
|
||||
) {
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
} else {
|
||||
typeText(aiMessage, accumulatedContent);
|
||||
}
|
||||
}
|
||||
// 发送事件更新按钮状态:显示停止按钮
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: false,
|
||||
stopShow: true,
|
||||
});
|
||||
}
|
||||
// 不在这里直接更新,让打字机效果处理更新
|
||||
break;
|
||||
|
||||
case 2: // 回答完成
|
||||
// 不再在这里设置textCompleted状态
|
||||
// 更新父组件的messageList
|
||||
// 只有在未停止状态下才更新
|
||||
if (!this.isStopped) {
|
||||
// 更新父组件的messageList
|
||||
this.$emit("update-message", aiMessage);
|
||||
// 发送事件更新按钮状态:显示停止按钮
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: false,
|
||||
stopShow: true,
|
||||
});
|
||||
}
|
||||
// 从响应中获取并保存conversationId
|
||||
|
||||
break;
|
||||
|
||||
case 3: // 返回建议
|
||||
// 只有在未停止状态下才处理建议
|
||||
if (!this.isStopped) {
|
||||
// 这里可以处理建议问题
|
||||
this.$emit(
|
||||
"update-suggestions",
|
||||
jsonData.suggestions
|
||||
);
|
||||
// 发送事件更新按钮状态:显示停止按钮
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: false,
|
||||
stopShow: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 4: // 交互完成
|
||||
// 只有在未停止状态下才处理完成状态
|
||||
if (!this.isStopped) {
|
||||
aiMessage.status = 4;
|
||||
|
||||
// 从响应中获取并保存conversationId
|
||||
// this.$emit("loading", false);
|
||||
// 检查文本是否已经完全显示,如果是则设置textCompleted为true
|
||||
if (
|
||||
isTextDisplayCompleted(
|
||||
aiMessage,
|
||||
accumulatedContent
|
||||
)
|
||||
) {
|
||||
// aiMessage.textCompleted = true;
|
||||
this.$emit("update-message", aiMessage);
|
||||
// 发送事件更新按钮状态:显示发送按钮
|
||||
hasFinished = true; // 标记完成
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: true,
|
||||
stopShow: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 9:
|
||||
// 只有在未停止状态下才处理docId
|
||||
if (!this.isStopped && jsonData.docId) {
|
||||
aiMessage.docId = jsonData.docId;
|
||||
this.$emit("update-message", aiMessage);
|
||||
}
|
||||
// 显示发送按钮,隐藏停止按钮
|
||||
// this.$emit("loading", false);
|
||||
console.log(jsonData);
|
||||
// 发送事件更新按钮状态:显示发送按钮
|
||||
hasFinished = true; // 标记完成
|
||||
this.$emit("update-button-state", {
|
||||
sendShow: true,
|
||||
stopShow: false,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
if (!this.isStopped && jsonData.docId) {
|
||||
aiMessage.docId = jsonData.docId;
|
||||
this.$emit("update-message", aiMessage);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("解析SSE数据错误:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 继续读取(除非已停止)
|
||||
if (!this.isStopped) {
|
||||
read();
|
||||
}
|
||||
// 继续读取
|
||||
// read();
|
||||
})
|
||||
.catch((error) => {
|
||||
// 处理AbortError
|
||||
if (error.name === "AbortError") {
|
||||
console.log("请求已被用户取消");
|
||||
// aiMessage.text = "请求已被取消";
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit("update-message", aiMessage);
|
||||
} else {
|
||||
console.error("SSE连接错误:", error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
if (typingTimer) {
|
||||
clearInterval(typingTimer);
|
||||
typingTimer = null;
|
||||
}
|
||||
aiMessage.textCompleted = true;
|
||||
aiMessage.text = "当前无法获取回答,请稍后重试";
|
||||
// 更新父组件的messageList
|
||||
this.$emit("update-message", aiMessage);
|
||||
}
|
||||
this.$emit("loading", false);
|
||||
});
|
||||
};
|
||||
// 继续读取(除非已停止)
|
||||
if (!this.isStopped) {
|
||||
read();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// 处理AbortError
|
||||
if (error.name === "AbortError") {
|
||||
console.log("请求已被用户取消");
|
||||
aiMessage.text = "请求已被取消";
|
||||
aiMessage.textCompleted = true;
|
||||
this.$emit("update-message", aiMessage);
|
||||
} else {
|
||||
console.error("请求失败:", error);
|
||||
// 出错时也设置文字处理完成状态
|
||||
aiMessage.textCompleted = true;
|
||||
aiMessage.text = "当前无法获取回答,请稍后重试";
|
||||
// 更新父组件的messageList
|
||||
this.$emit("update-message", aiMessage);
|
||||
}
|
||||
// 只有在非停止状态下才发送 loading 事件
|
||||
if (!this.isStopped) {
|
||||
this.$emit("loading", false);
|
||||
}
|
||||
});
|
||||
},
|
||||
handleNewConversation() {
|
||||
this.conversationId = ''
|
||||
this.$emit('new-conversation')
|
||||
}
|
||||
}
|
||||
}
|
||||
// this.conversationId = "";
|
||||
this.$emit("new-conversation");
|
||||
this.getConversationId();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.input-area {
|
||||
background-color: white;
|
||||
@@ -405,5 +663,10 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.stop {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user