mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
@@ -14,7 +14,7 @@ from apps.chat.curd.chat import delete_chat_with_user, get_chart_data_with_user,
|
||||
list_chats, get_chat_with_records, create_chat, rename_chat, \
|
||||
delete_chat, get_chat_chart_data, get_chat_predict_data, get_chat_with_records_with_data, get_chat_record_by_id, \
|
||||
format_json_data, format_json_list_data, get_chart_config, list_recent_questions, get_chat as get_chat_exec, \
|
||||
rename_chat_with_user
|
||||
rename_chat_with_user, get_chat_log_history
|
||||
from apps.chat.models.chat_model import CreateChat, ChatRecord, RenameChat, ChatQuestion, AxisObj, QuickCommand, \
|
||||
ChatInfo, Chat, ChatFinishStep
|
||||
from apps.chat.task.llm import LLMService
|
||||
@@ -91,6 +91,14 @@ async def chat_predict_data(session: SessionDep, current_user: CurrentUser, chat
|
||||
return await asyncio.to_thread(inner)
|
||||
|
||||
|
||||
@router.get("/record/{chat_record_id}/log", summary=f"{PLACEHOLDER_PREFIX}get_record_log")
|
||||
async def chat_record_log(session: SessionDep, current_user: CurrentUser, chat_record_id: int):
|
||||
def inner():
|
||||
return get_chat_log_history(session, chat_record_id, current_user)
|
||||
|
||||
return await asyncio.to_thread(inner)
|
||||
|
||||
|
||||
""" @router.post("/rename", response_model=str, summary=f"{PLACEHOLDER_PREFIX}rename_chat")
|
||||
@system_log(LogConfig(
|
||||
operation_type=OperationType.UPDATE,
|
||||
|
||||
@@ -8,7 +8,7 @@ from sqlalchemy import desc, func
|
||||
from sqlalchemy.orm import aliased
|
||||
|
||||
from apps.chat.models.chat_model import Chat, ChatRecord, CreateChat, ChatInfo, RenameChat, ChatQuestion, ChatLog, \
|
||||
TypeEnum, OperationEnum, ChatRecordResult
|
||||
TypeEnum, OperationEnum, ChatRecordResult, ChatLogHistory, ChatLogHistoryItem
|
||||
from apps.datasource.crud.recommended_problem import get_datasource_recommended_chart
|
||||
from apps.datasource.models.datasource import CoreDatasource
|
||||
from apps.db.constant import DB
|
||||
@@ -345,11 +345,60 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
|
||||
result = session.execute(stmt).all()
|
||||
record_list: list[ChatRecordResult] = []
|
||||
|
||||
# 批量获取所有ChatRecord的token消耗
|
||||
record_ids = [row.id for row in result]
|
||||
token_usage_map = {}
|
||||
|
||||
if record_ids:
|
||||
# 查询所有相关ChatLog的token_usage
|
||||
log_stmt = select(ChatLog.pid, ChatLog.token_usage).where(
|
||||
and_(
|
||||
ChatLog.pid.in_(record_ids),
|
||||
ChatLog.local_operation == False,
|
||||
ChatLog.token_usage.is_not(None) # 排除token_usage为空的记录
|
||||
)
|
||||
)
|
||||
log_results = session.execute(log_stmt).all()
|
||||
|
||||
# 按pid分组计算total_tokens总和
|
||||
for pid, token_usage in log_results:
|
||||
if pid and token_usage is not None:
|
||||
tokens_to_add = 0
|
||||
|
||||
if isinstance(token_usage, dict):
|
||||
# 处理字典类型: {"input_tokens": 961, "total_tokens": 1006, "output_tokens": 45}
|
||||
if token_usage: # 非空字典
|
||||
if "total_tokens" in token_usage:
|
||||
token_value = token_usage["total_tokens"]
|
||||
if isinstance(token_value, (int, float)):
|
||||
tokens_to_add = int(token_value)
|
||||
elif isinstance(token_usage, (int, float)):
|
||||
tokens_to_add = int(token_usage)
|
||||
if tokens_to_add > 0:
|
||||
if pid not in token_usage_map:
|
||||
token_usage_map[pid] = 0
|
||||
token_usage_map[pid] += tokens_to_add
|
||||
|
||||
for row in result:
|
||||
# 计算耗时
|
||||
duration = None
|
||||
if row.create_time and row.finish_time:
|
||||
try:
|
||||
time_diff = row.finish_time - row.create_time
|
||||
duration = time_diff.total_seconds() # 转换为秒
|
||||
except Exception:
|
||||
duration = None
|
||||
|
||||
# 获取token总消耗
|
||||
total_tokens = token_usage_map.get(row.id, 0)
|
||||
|
||||
if not with_data:
|
||||
record_list.append(
|
||||
ChatRecordResult(id=row.id, chat_id=row.chat_id, create_time=row.create_time,
|
||||
finish_time=row.finish_time,
|
||||
duration=duration,
|
||||
total_tokens=total_tokens,
|
||||
question=row.question, sql_answer=row.sql_answer, sql=row.sql,
|
||||
chart_answer=row.chart_answer, chart=row.chart,
|
||||
analysis=row.analysis, predict=row.predict,
|
||||
@@ -367,6 +416,8 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
|
||||
record_list.append(
|
||||
ChatRecordResult(id=row.id, chat_id=row.chat_id, create_time=row.create_time,
|
||||
finish_time=row.finish_time,
|
||||
duration=duration,
|
||||
total_tokens=total_tokens,
|
||||
question=row.question, sql_answer=row.sql_answer, sql=row.sql,
|
||||
chart_answer=row.chart_answer, chart=row.chart,
|
||||
analysis=row.analysis, predict=row.predict,
|
||||
@@ -437,6 +488,23 @@ def format_record(record: ChatRecordResult):
|
||||
_dict['sql'] = sqlparse.format(record.sql, reindent=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 格式化duration字段,保留2位小数
|
||||
if 'duration' in _dict and _dict['duration'] is not None:
|
||||
try:
|
||||
# 可以格式化为更易读的形式
|
||||
_dict['duration'] = round(_dict['duration'], 2) # 保留2位小数
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 格式化total_tokens字段
|
||||
if 'total_tokens' in _dict and _dict['total_tokens'] is not None:
|
||||
try:
|
||||
# 确保是整数类型
|
||||
_dict['total_tokens'] = int(_dict['total_tokens']) if _dict['total_tokens'] else 0
|
||||
except Exception:
|
||||
_dict['total_tokens'] = 0
|
||||
|
||||
# 去除返回前端多余的字段
|
||||
_dict.pop('sql_reasoning_content', None)
|
||||
_dict.pop('chart_reasoning_content', None)
|
||||
@@ -446,6 +514,90 @@ def format_record(record: ChatRecordResult):
|
||||
return _dict
|
||||
|
||||
|
||||
def get_chat_log_history(session: SessionDep, chat_record_id: int, current_user: CurrentUser) -> ChatLogHistory:
|
||||
"""
|
||||
获取ChatRecord的详细历史记录
|
||||
|
||||
Args:
|
||||
session: 数据库会话
|
||||
chat_record_id: ChatRecord的ID
|
||||
current_user: 当前用户
|
||||
|
||||
Returns:
|
||||
ChatLogHistory: 包含历史步骤和时间信息的对象
|
||||
"""
|
||||
# 1. 首先验证ChatRecord存在且属于当前用户
|
||||
chat_record = session.get(ChatRecord, chat_record_id)
|
||||
if not chat_record:
|
||||
raise Exception(f"ChatRecord with id {chat_record_id} not found")
|
||||
|
||||
if chat_record.create_by != current_user.id:
|
||||
raise Exception(f"ChatRecord with id {chat_record_id} not owned by the current user")
|
||||
|
||||
# 2. 查询与该ChatRecord相关的所有ChatLog记录
|
||||
chat_logs = session.query(ChatLog).filter(
|
||||
ChatLog.pid == chat_record_id
|
||||
).order_by(ChatLog.start_time).all()
|
||||
|
||||
# 3. 计算总的时间和token信息
|
||||
total_tokens = 0
|
||||
steps = []
|
||||
|
||||
for log in chat_logs:
|
||||
# 计算单条记录的耗时
|
||||
duration = None
|
||||
if log.start_time and log.finish_time:
|
||||
try:
|
||||
time_diff = log.finish_time - log.start_time
|
||||
duration = time_diff.total_seconds()
|
||||
except Exception:
|
||||
duration = None
|
||||
|
||||
# 计算单条记录的token消耗
|
||||
log_tokens = 0
|
||||
if log.token_usage is not None:
|
||||
if isinstance(log.token_usage, dict):
|
||||
if log.token_usage and "total_tokens" in log.token_usage:
|
||||
token_value = log.token_usage["total_tokens"]
|
||||
if isinstance(token_value, (int, float)):
|
||||
log_tokens = int(token_value)
|
||||
elif isinstance(log.token_usage, (int, float)):
|
||||
log_tokens = log.token_usage
|
||||
|
||||
# 累加到总token消耗
|
||||
total_tokens += log_tokens
|
||||
|
||||
# 创建ChatLogHistoryItem
|
||||
history_item = ChatLogHistoryItem(
|
||||
start_time=log.start_time,
|
||||
finish_time=log.finish_time,
|
||||
duration=duration,
|
||||
total_tokens=log_tokens,
|
||||
operate=log.operate,
|
||||
local_operation=log.local_operation
|
||||
)
|
||||
steps.append(history_item)
|
||||
|
||||
# 4. 计算总耗时(使用ChatRecord的时间)
|
||||
total_duration = None
|
||||
if chat_record.create_time and chat_record.finish_time:
|
||||
try:
|
||||
time_diff = chat_record.finish_time - chat_record.create_time
|
||||
total_duration = time_diff.total_seconds()
|
||||
except Exception:
|
||||
total_duration = None
|
||||
|
||||
# 5. 创建并返回ChatLogHistory对象
|
||||
chat_log_history = ChatLogHistory(
|
||||
start_time=chat_record.create_time, # 使用ChatRecord的create_time
|
||||
finish_time=chat_record.finish_time, # 使用ChatRecord的finish_time
|
||||
duration=total_duration,
|
||||
total_tokens=total_tokens,
|
||||
steps=steps
|
||||
)
|
||||
|
||||
return chat_log_history
|
||||
|
||||
def get_chat_brief_generate(session: SessionDep, chat_id: int):
|
||||
chat = get_chat(session=session, chat_id=chat_id)
|
||||
if chat is not None and chat.brief_generate is not None:
|
||||
@@ -877,7 +1029,12 @@ def save_error_message(session: SessionDep, record_id: int, message: str) -> Cha
|
||||
|
||||
session.commit()
|
||||
|
||||
# todo log error finish
|
||||
# log error finish
|
||||
stmt = update(ChatLog).where(and_(ChatLog.pid == record.id, ChatLog.finish_time.is_(None))).values(
|
||||
finish_time=record.finish_time
|
||||
)
|
||||
session.execute(stmt)
|
||||
session.commit()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -155,6 +155,8 @@ class ChatRecordResult(BaseModel):
|
||||
chart_reasoning_content: Optional[str] = None
|
||||
analysis_reasoning_content: Optional[str] = None
|
||||
predict_reasoning_content: Optional[str] = None
|
||||
duration: Optional[float] = None # 耗时字段(单位:秒)
|
||||
total_tokens: Optional[int] = None # token总消耗
|
||||
|
||||
|
||||
class CreateChat(BaseModel):
|
||||
@@ -186,6 +188,22 @@ class ChatInfo(BaseModel):
|
||||
records: List[ChatRecord | dict] = []
|
||||
|
||||
|
||||
class ChatLogHistoryItem(BaseModel):
|
||||
start_time: Optional[datetime] = None
|
||||
finish_time: Optional[datetime] = None
|
||||
duration: Optional[float] = None # 耗时字段(单位:秒)
|
||||
total_tokens: Optional[int] = None # token总消耗
|
||||
operate: Optional[OperationEnum] = None
|
||||
local_operation: Optional[bool] = False
|
||||
|
||||
class ChatLogHistory(BaseModel):
|
||||
start_time: Optional[datetime] = None
|
||||
finish_time: Optional[datetime] = None
|
||||
duration: Optional[float] = None # 耗时字段(单位:秒)
|
||||
total_tokens: Optional[int] = None # token总消耗
|
||||
steps: List[ChatLogHistoryItem | dict] = []
|
||||
|
||||
|
||||
class AiModelQuestion(BaseModel):
|
||||
question: str = None
|
||||
ai_modal_id: int = None
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
"get_chat_with_data": "Get Chat Details (With Data)",
|
||||
"get_chart_data": "Get Chart Data",
|
||||
"get_chart_predict_data": "Get Chart Prediction Data",
|
||||
"get_record_log": "Get Chart Record Log",
|
||||
"rename_chat": "Rename Chat",
|
||||
"delete_chat": "Delete Chat",
|
||||
"start_chat": "Create Chat",
|
||||
|
||||
@@ -141,6 +141,7 @@
|
||||
"get_chat_with_data": "获取对话详情(带数据)",
|
||||
"get_chart_data": "获取图表数据",
|
||||
"get_chart_predict_data": "获取图表预测数据",
|
||||
"get_record_log": "获取对话日志",
|
||||
"rename_chat": "重命名对话",
|
||||
"delete_chat": "删除对话",
|
||||
"start_chat": "创建对话",
|
||||
|
||||
+117
-3
@@ -52,6 +52,8 @@ export class ChatRecord {
|
||||
analysis_record_id?: number
|
||||
predict_record_id?: number
|
||||
regenerate_record_id?: number
|
||||
duration?: number
|
||||
total_tokens?: number
|
||||
|
||||
constructor()
|
||||
constructor(
|
||||
@@ -77,7 +79,9 @@ export class ChatRecord {
|
||||
recommended_question: string | undefined,
|
||||
analysis_record_id: number | undefined,
|
||||
predict_record_id: number | undefined,
|
||||
regenerate_record_id: number | undefined
|
||||
regenerate_record_id: number | undefined,
|
||||
duration: number | undefined,
|
||||
total_tokens: number | undefined
|
||||
)
|
||||
constructor(
|
||||
id?: number,
|
||||
@@ -102,7 +106,9 @@ export class ChatRecord {
|
||||
recommended_question?: string,
|
||||
analysis_record_id?: number,
|
||||
predict_record_id?: number,
|
||||
regenerate_record_id?: number
|
||||
regenerate_record_id?: number,
|
||||
duration?: number,
|
||||
total_tokens?: number
|
||||
) {
|
||||
this.id = id
|
||||
this.chat_id = chat_id
|
||||
@@ -127,6 +133,8 @@ export class ChatRecord {
|
||||
this.analysis_record_id = analysis_record_id
|
||||
this.predict_record_id = predict_record_id
|
||||
this.regenerate_record_id = regenerate_record_id
|
||||
this.duration = duration
|
||||
this.total_tokens = total_tokens
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +275,9 @@ const toChatRecord = (data?: any): ChatRecord | undefined => {
|
||||
data.recommended_question,
|
||||
data.analysis_record_id,
|
||||
data.predict_record_id,
|
||||
data.regenerate_record_id
|
||||
data.regenerate_record_id,
|
||||
data.duration,
|
||||
data.total_tokens
|
||||
)
|
||||
}
|
||||
const toChatRecordList = (list: any = []): ChatRecord[] => {
|
||||
@@ -281,6 +291,95 @@ const toChatRecordList = (list: any = []): ChatRecord[] => {
|
||||
return records
|
||||
}
|
||||
|
||||
export class ChatLogHistoryItem {
|
||||
start_time?: Date | string
|
||||
finish_time?: Date | string
|
||||
duration?: number | undefined
|
||||
total_tokens?: number | undefined
|
||||
operate?: string | undefined
|
||||
local_operation?: boolean | undefined
|
||||
|
||||
constructor()
|
||||
constructor(
|
||||
start_time: Date | string,
|
||||
finish_time: Date | string,
|
||||
duration: number | undefined,
|
||||
total_tokens: number | undefined,
|
||||
operate: string | undefined,
|
||||
local_operation: boolean | undefined
|
||||
)
|
||||
constructor(
|
||||
start_time?: Date | string,
|
||||
finish_time?: Date | string,
|
||||
duration?: number | undefined,
|
||||
total_tokens?: number | undefined,
|
||||
operate?: string | undefined,
|
||||
local_operation?: boolean | undefined
|
||||
) {
|
||||
this.start_time = getDate(start_time)
|
||||
this.finish_time = getDate(finish_time)
|
||||
this.duration = duration
|
||||
this.total_tokens = total_tokens
|
||||
this.operate = operate
|
||||
this.local_operation = !!local_operation
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatLogHistory {
|
||||
start_time?: Date | string
|
||||
finish_time?: Date | string
|
||||
duration?: number | undefined
|
||||
total_tokens?: number | undefined
|
||||
steps?: Array<ChatLogHistoryItem> | undefined
|
||||
|
||||
constructor()
|
||||
constructor(
|
||||
start_time: Date | string,
|
||||
finish_time: Date | string,
|
||||
duration: number | undefined,
|
||||
total_tokens: number | undefined,
|
||||
steps: Array<ChatLogHistoryItem> | undefined
|
||||
)
|
||||
constructor(
|
||||
start_time?: Date | string,
|
||||
finish_time?: Date | string,
|
||||
duration?: number | undefined,
|
||||
total_tokens?: number | undefined,
|
||||
steps?: Array<ChatLogHistoryItem> | undefined
|
||||
) {
|
||||
this.start_time = getDate(start_time)
|
||||
this.finish_time = getDate(finish_time)
|
||||
this.duration = duration
|
||||
this.total_tokens = total_tokens
|
||||
this.steps = steps ? steps : []
|
||||
}
|
||||
}
|
||||
|
||||
const toChatLogHistoryItem = (data?: any): any | undefined => {
|
||||
if (!data) {
|
||||
return undefined
|
||||
}
|
||||
return new ChatLogHistoryItem(
|
||||
data.start_time,
|
||||
data.finish_time,
|
||||
data.duration,
|
||||
data.total_tokens,
|
||||
data.operate,
|
||||
data.local_operation
|
||||
)
|
||||
}
|
||||
|
||||
const toChatLogHistoryItemList = (list: any = []): ChatLogHistoryItem[] => {
|
||||
const records: Array<ChatLogHistoryItem> = []
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const record = toChatLogHistoryItem(list[i])
|
||||
if (record) {
|
||||
records.push(record)
|
||||
}
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
export const chatApi = {
|
||||
toChatInfo: (data?: any): ChatInfo | undefined => {
|
||||
if (!data) {
|
||||
@@ -312,6 +411,18 @@ export const chatApi = {
|
||||
}
|
||||
return infos
|
||||
},
|
||||
toChatLogHistory: (data?: any): ChatLogHistory | undefined => {
|
||||
if (!data) {
|
||||
return undefined
|
||||
}
|
||||
return new ChatLogHistory(
|
||||
data.start_time,
|
||||
data.finish_time,
|
||||
data.duration,
|
||||
data.total_tokens,
|
||||
toChatLogHistoryItemList(data.steps)
|
||||
)
|
||||
},
|
||||
list: (): Promise<Array<ChatInfo>> => {
|
||||
return request.get('/chat/list')
|
||||
},
|
||||
@@ -327,6 +438,9 @@ export const chatApi = {
|
||||
get_chart_predict_data: (record_id?: number): Promise<any> => {
|
||||
return request.get(`/chat/record/${record_id}/predict_data`)
|
||||
},
|
||||
get_chart_log_history: (record_id?: number): Promise<any> => {
|
||||
return request.get(`/chat/record/${record_id}/log`)
|
||||
},
|
||||
startChat: (data: any): Promise<ChatInfo> => {
|
||||
return request.post('/chat/start', data)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { chatApi, type ChatLogHistory } from '@/api/chat.ts'
|
||||
|
||||
const props = defineProps<{
|
||||
recordId?: number
|
||||
duration?: number | undefined
|
||||
totalTokens?: number | undefined
|
||||
}>()
|
||||
|
||||
const logHistory = ref<ChatLogHistory>()
|
||||
|
||||
function getLogList() {
|
||||
chatApi.get_chart_log_history(props.recordId).then((res) => {
|
||||
logHistory.value = chatApi.toChatLogHistory(res)
|
||||
console.log(logHistory.value)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="recordId && (duration || totalTokens)" class="tool-container">
|
||||
<span>token: {{ totalTokens }}</span>
|
||||
<span>duration: {{ duration }}</span>
|
||||
<el-button @click="getLogList">log</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.tool-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
row-gap: 8px;
|
||||
|
||||
min-height: 22px;
|
||||
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.tool-times {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: rgba(100, 106, 115, 1);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.time {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -239,6 +239,11 @@
|
||||
>
|
||||
<ErrorInfo :error="message.record?.error" class="error-container" />
|
||||
<template #tool>
|
||||
<ChatTokenTime
|
||||
:record-id="message.record?.id"
|
||||
:duration="message.record?.duration"
|
||||
:total-tokens="message.record?.total_tokens"
|
||||
/>
|
||||
<ChatToolBar v-if="!message.isTyping" :message="message">
|
||||
<div class="tool-btns">
|
||||
<el-tooltip
|
||||
@@ -329,6 +334,11 @@
|
||||
>
|
||||
<ErrorInfo :error="message.record?.error" class="error-container" />
|
||||
<template #tool>
|
||||
<ChatTokenTime
|
||||
:record-id="message.record?.id"
|
||||
:duration="message.record?.duration"
|
||||
:total-tokens="message.record?.total_tokens"
|
||||
/>
|
||||
<ChatToolBar v-if="!message.isTyping" :message="message" />
|
||||
</template>
|
||||
</AnalysisAnswer>
|
||||
@@ -351,6 +361,11 @@
|
||||
>
|
||||
<ErrorInfo :error="message.record?.error" class="error-container" />
|
||||
<template #tool>
|
||||
<ChatTokenTime
|
||||
:record-id="message.record?.id"
|
||||
:duration="message.record?.duration"
|
||||
:total-tokens="message.record?.total_tokens"
|
||||
/>
|
||||
<ChatToolBar v-if="!message.isTyping" :message="message" />
|
||||
</template>
|
||||
</PredictAnswer>
|
||||
@@ -443,6 +458,7 @@ import UserChat from './chat-block/UserChat.vue'
|
||||
import RecommendQuestion from './RecommendQuestion.vue'
|
||||
import ChatListContainer from './ChatListContainer.vue'
|
||||
import ChatCreator from '@/views/chat/ChatCreator.vue'
|
||||
import ChatTokenTime from '@/views/chat/ChatTokenTime.vue'
|
||||
import ErrorInfo from './ErrorInfo.vue'
|
||||
import ChatToolBar from './ChatToolBar.vue'
|
||||
import { dsTypeWithImg } from '@/views/ds/js/ds-type'
|
||||
@@ -592,8 +608,7 @@ const handleScroll = (val: any) => {
|
||||
}, 400)
|
||||
|
||||
const threshold =
|
||||
innerRef.value!.clientHeight -
|
||||
(document.querySelector('.chat-record-list')!.clientHeight - 20)
|
||||
innerRef.value!.clientHeight - (document.querySelector('.chat-record-list')!.clientHeight - 20)
|
||||
const isNearBottom = scrollTopVal + 50 >= threshold
|
||||
|
||||
// 用户滚动离开底部时,标记并停止自动滚动
|
||||
|
||||
Reference in New Issue
Block a user