Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
sdream-ai-fe
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
侯明涛
sdream-ai-fe
Commits
1f2aae8c
Commit
1f2aae8c
authored
Dec 02, 2025
by
Liu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix:调用常见问题逻辑
parent
0cfabedf
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
265 additions
and
236 deletions
+265
-236
src/api/mock/conversation.ts
+104
-103
src/api/mock/home.ts
+79
-78
src/components/ChatEditor/index.tsx
+12
-17
src/layouts/HistoryBar/components/HistoryBarList/index.tsx
+11
-0
src/lib/utils.ts
+23
-1
src/pages/Home/HomeNew.tsx
+33
-30
src/store/conversationSlice.ts
+3
-7
No files found.
src/api/mock/conversation.ts
View file @
1f2aae8c
import
{
subDays
,
subMinutes
}
from
'date-fns'
import
type
{
Conversation
}
from
'@/types/conversation'
interface
ConversationPageResponse
{
code
:
string
message
:
string
data
:
{
records
:
Conversation
[]
total
:
number
size
:
number
current
:
number
pages
:
number
}
ok
:
boolean
}
const
now
=
new
Date
()
function
formatDate
(
date
:
Date
)
{
return
date
.
toISOString
().
slice
(
0
,
19
).
replace
(
'T'
,
' '
)
}
function
createMockConversation
({
id
,
title
,
daysAgo
=
0
,
minutesAgo
=
0
,
qaNum
,
toolId
,
isCurrent
=
false
,
}:
{
id
:
string
title
:
string
daysAgo
?:
number
minutesAgo
?:
number
qaNum
:
number
toolId
?:
string
isCurrent
?:
boolean
}):
Conversation
{
const
endTime
=
subMinutes
(
subDays
(
now
,
daysAgo
),
minutesAgo
)
const
startTime
=
subMinutes
(
endTime
,
5
)
return
{
conversationId
:
id
,
conversationTitle
:
title
,
currentConversationFlag
:
isCurrent
,
startTime
:
formatDate
(
startTime
),
endTime
:
formatDate
(
endTime
),
qaNum
,
...(
toolId
?
{
toolId
}
:
{}),
}
}
const
conversationRecords
:
Conversation
[]
=
[
createMockConversation
({
id
:
'1995419273074701146'
,
title
:
'未知会话内容'
,
qaNum
:
3
,
toolId
:
'6712395743240'
,
isCurrent
:
true
,
}),
createMockConversation
({
id
:
'1995417608679268353'
,
title
:
'资产审评的目标及关键要素1'
,
qaNum
:
1
,
toolId
:
'6712395743241'
,
minutesAgo
:
20
,
}),
createMockConversation
({
id
:
'19954143358382255489'
,
title
:
'对公授信材料清单2'
,
qaNum
:
4
,
daysAgo
:
2
,
minutesAgo
:
10
,
toolId
:
'6712395743241'
,
}),
createMockConversation
({
id
:
'19954114588762233881'
,
title
:
'项目贷款调研要点3'
,
toolId
:
'6712395743240'
,
qaNum
:
2
,
daysAgo
:
9
,
}),
createMockConversation
({
id
:
'19954099873351298711'
,
title
:
'投融资合规的最新政策4'
,
qaNum
:
6
,
daysAgo
:
21
,
}),
]
const
_conversationPageMockResponse
:
ConversationPageResponse
=
{
code
:
'00000000'
,
message
:
'成功'
,
data
:
{
records
:
conversationRecords
,
total
:
conversationRecords
.
length
,
size
:
100
,
current
:
1
,
pages
:
1
,
},
ok
:
true
,
}
// NOTE: 该文件中的 mock 数据已废弃,保留代码仅作参考,全部使用注释禁用。
// import { subDays, subMinutes } from 'date-fns'
// import type { Conversation } from '@/types/conversation'
//
// interface ConversationPageResponse {
// code: string
// message: string
// data: {
// records: Conversation[]
// total: number
// size: number
// current: number
// pages: number
// }
// ok: boolean
// }
//
// const now = new Date()
//
// function formatDate(date: Date) {
// return date.toISOString().slice(0, 19).replace('T', ' ')
// }
//
// function createMockConversation({
// id,
// title,
// daysAgo = 0,
// minutesAgo = 0,
// qaNum,
// toolId,
// isCurrent = false,
// }: {
// id: string
// title: string
// daysAgo?: number
// minutesAgo?: number
// qaNum: number
// toolId?: string
// isCurrent?: boolean
// }): Conversation {
// const endTime = subMinutes(subDays(now, daysAgo), minutesAgo)
// const startTime = subMinutes(endTime, 5)
// return {
// conversationId: id,
// conversationTitle: title,
// currentConversationFlag: isCurrent,
// startTime: formatDate(startTime),
// endTime: formatDate(endTime),
// qaNum,
// ...(toolId ? { toolId } : {}),
// }
// }
//
// const conversationRecords: Conversation[] = [
// createMockConversation({
// id: '1995419273074701146',
// title: '未知会话内容',
// qaNum: 3,
// toolId: '6712395743240',
// isCurrent: true,
// }),
// createMockConversation({
// id: '1995417608679268353',
// title: '资产审评的目标及关键要素1',
// qaNum: 1,
// toolId: '6712395743241',
// minutesAgo: 20,
// }),
// createMockConversation({
// id: '19954143358382255489',
// title: '对公授信材料清单2',
// qaNum: 4,
// daysAgo: 2,
// minutesAgo: 10,
// toolId: '6712395743241',
// }),
// createMockConversation({
// id: '19954114588762233881',
// title: '项目贷款调研要点3',
// toolId: '6712395743240',
// qaNum: 2,
// daysAgo: 9,
// }),
// createMockConversation({
// id: '19954099873351298711',
// title: '投融资合规的最新政策4',
// qaNum: 6,
// daysAgo: 21,
// }),
// ]
//
// const conversationPageMockResponse: ConversationPageResponse = {
// code: '00000000',
// message: '成功',
// data: {
// records: conversationRecords,
// total: conversationRecords.length,
// size: 100,
// current: 1,
// pages: 1,
// },
// ok: true,
// }
//
// export function mockFetchConversationPage() {
// return Promise.resolve(conversationPageMockResponse)
// }
src/api/mock/home.ts
View file @
1f2aae8c
interface
EfficiencyQuestionResponse
{
code
:
string
message
:
string
data
:
{
questions
:
string
[]
}
ok
:
boolean
}
const
_efficiencyQuestionMockResponse
:
EfficiencyQuestionResponse
=
{
code
:
'00000000'
,
message
:
'成功'
,
data
:
{
questions
:
[
'灾备部署有哪些要求?'
,
'触达中心如何设计渠道?'
,
'用户旅程设计如何整合信息?'
,
'文本管理应该具备哪些能力?'
,
'差旅规则有哪些?'
,
'反洗钱小组的职责是什么?'
,
'有效投诉的处理流程是什么?'
,
'公司项目审核会主要从哪几个方面审核项目?'
,
],
},
ok
:
true
,
}
// NOTE: 该文件中的 mock 数据已废弃,保留代码仅作参考,全部使用注释禁用。
// interface EfficiencyQuestionResponse {
// code: string
// message: string
// data: {
// questions: string[]
// }
// ok: boolean
// }
//
// const efficiencyQuestionMockResponse: EfficiencyQuestionResponse = {
// code: '00000000',
// message: '成功',
// data: {
// questions: [
// '灾备部署有哪些要求?',
// '触达中心如何设计渠道?',
// '用户旅程设计如何整合信息?',
// '文本管理应该具备哪些能力?',
// '差旅规则有哪些?',
// '反洗钱小组的职责是什么?',
// '有效投诉的处理流程是什么?',
// '公司项目审核会主要从哪几个方面审核项目?',
// ],
// },
// ok: true,
// }
//
// export function mockFetchEfficiencyQuestionList(): Promise<EfficiencyQuestionResponse> {
// return Promise.resolve(efficiencyQuestionMockResponse)
// }
interface
ToolListResponse
{
code
:
string
message
:
string
data
:
Array
<
{
toolId
:
string
toolName
:
string
toolContent
:
string
toolIcon
:
string
toolType
:
string
userRole
:
string
showOrder
:
number
}
>
ok
:
boolean
}
const
_
toolListMockResponse
:
ToolListResponse
=
{
code
:
'00000000'
,
message
:
'成功'
,
data
:
[
{
toolId
:
'6712395743241'
,
toolName
:
'提质增效'
,
toolContent
:
'https://sit-wechat.guominpension.com/underwrite'
,
toolIcon
:
'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-increase.svg'
,
toolType
:
'03'
,
userRole
:
'02'
,
showOrder
:
8
,
},
{
toolId
:
'6712395743240'
,
toolName
:
'数据助手'
,
toolContent
:
'https://sit-wechat.guominpension.com/underwrite'
,
toolIcon
:
'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-data.svg'
,
toolType
:
'03'
,
userRole
:
'01'
,
showOrder
:
8
,
},
{
toolId
:
'general-mode'
,
toolName
:
'通用模式'
,
toolContent
:
'https://sit-wechat.guominpension.com/underwrite'
,
toolIcon
:
'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-normal.svg'
,
toolType
:
'01'
,
userRole
:
'00'
,
showOrder
:
8
,
},
],
ok
:
true
,
}
//
//
interface ToolListResponse {
//
code: string
//
message: string
//
data: Array<{
//
toolId: string
//
toolName: string
//
toolContent: string
//
toolIcon: string
//
toolType: string
//
userRole: string
//
showOrder: number
//
}>
//
ok: boolean
//
}
//
// const
toolListMockResponse: ToolListResponse = {
//
code: '00000000',
//
message: '成功',
//
data: [
//
{
//
toolId: '6712395743241',
//
toolName: '提质增效',
//
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
//
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-increase.svg',
//
toolType: '03',
//
userRole: '02',
//
showOrder: 8,
//
},
//
{
//
toolId: '6712395743240',
//
toolName: '数据助手',
//
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
//
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-data.svg',
//
toolType: '03',
//
userRole: '01',
//
showOrder: 8,
//
},
//
{
//
toolId: 'general-mode',
//
toolName: '通用模式',
//
toolContent: 'https://sit-wechat.guominpension.com/underwrite',
//
toolIcon: 'http://p-cf-co-1255000025.cos.bj.csyun001.ccbcos.co/tool-normal.svg',
//
toolType: '01',
//
userRole: '00',
//
showOrder: 8,
//
},
//
],
//
ok: true,
//
}
//
// export function mockFetchToolList(): Promise<ToolListResponse> {
// return Promise.resolve(toolListMockResponse)
// }
src/components/ChatEditor/index.tsx
View file @
1f2aae8c
...
...
@@ -9,9 +9,9 @@ import SendIcon from '@/assets/svg/send.svg?react'
import
{
type
WithAuthProps
,
withAuth
}
from
'@/auth/withAuth'
import
{
useAppDispatch
,
useAppSelector
}
from
'@/store/hook'
import
{
fetchToolList
}
from
'@/api/home'
import
{
clearCurrentToolId
,
createConversation
,
setCurrentToolId
}
from
'@/store/conversationSlice'
import
{
getUserRolesForApi
}
from
'@/lib/utils'
import
{
clearCurrentToolId
,
createConversation
,
setCurrentToolId
}
from
'@/store/conversationSlice'
// 使用本地 mock 工具列表常量,便于离线/联调阶段使用(已废弃,仅保留注释)
// const MOCK_TOOL_LIST = [
// {
// toolId: '6712395743241',
...
...
@@ -73,9 +73,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
// 获取工具列表
const
getToolList
=
async
()
=>
{
try
{
// 使用 mock 数据(已注释)
// setToolList([...MOCK_TOOL_LIST])
// 真实API调用
// 调用真实接口获取工具列表
const
userRoles
=
getUserRolesForApi
()
const
res
=
await
fetchToolList
({
userRoles
})
if
(
res
?.
data
)
{
...
...
@@ -86,24 +84,23 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
setToolList
(
uniqueList
)
}
else
{
// 当接口无数据时,不使用本地 mock,直接置空
setToolList
([])
}
}
catch
(
error
)
{
console
.
error
(
'获取工具列表失败:'
,
error
)
// 接口异常时不再回退到本地 mock 常量
setToolList
([])
}
}
// 根据 currentToolId 以及 sessionStorage 中的记录决定高亮逻辑
useEffect
(()
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'[ChatEditor] currentToolId 或 sessionToolId 变化:'
,
{
currentToolId
,
sessionToolId
,
toolIdFromUrl
,
})
if
(
currentToolId
)
{
// 只有当 sessionStorage 或 URL 中还能找到 toolId 时,才认为 Redux 中的 currentToolId 仍然有效
const
hasPersistentToolSource
=
Boolean
(
sessionToolId
||
toolIdFromUrl
)
if
(
currentToolId
&&
hasPersistentToolSource
)
{
setSelectedToolId
(
currentToolId
)
setIsToolBtnActive
(
false
)
return
...
...
@@ -442,12 +439,10 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const
getToolIconSrc
=
()
=>
{
if
(
!
tool
.
toolIcon
)
return
''
// 已经是完整的 dataURL,直接返回
if
(
typeof
tool
.
toolIcon
===
'string'
&&
tool
.
toolIcon
.
startsWith
(
'data:image'
))
// 如果已经是完整的 data URL,直接返回
if
(
tool
.
toolIcon
.
startsWith
(
'data:'
))
return
tool
.
toolIcon
// 否则默认按 png 的纯 base64 拼接,如有需要可根据后端类型字段动态调整
// 否则拼接为 base64 图片格式
return
`data:image/png;base64,${tool.toolIcon}`
}
...
...
src/layouts/HistoryBar/components/HistoryBarList/index.tsx
View file @
1f2aae8c
...
...
@@ -51,6 +51,17 @@ export const HistoryBarList: React.FC<HistoryBarListProps> = ({ searchValue, onS
toolId
:
conversation
.
toolId
||
null
,
},
})
// 通知首页根据对应 toolId 重新拉取常见问题(与底部工具按钮保持一致逻辑)
window
.
dispatchEvent
(
new
CustomEvent
(
'toolButtonClick'
,
{
detail
:
{
// 有 toolId 表示工具模式,没 toolId 表示通用模式
isToolBtn
:
!
conversation
.
toolId
,
toolId
:
conversation
.
toolId
||
''
,
toolName
:
''
,
shouldChangeStyle
:
true
,
},
}))
}
const
handleFilter
=
useDebounceFn
(()
=>
{
...
...
src/lib/utils.ts
View file @
1f2aae8c
...
...
@@ -13,9 +13,31 @@ const USER_ROLES_STORAGE_KEY = '__USER_ROLES__'
* 从路由获取 userRoles 并存储到 localStorage
* @returns 返回获取到的 userRoles 数组
*/
function
getQueryBeforeSecondQuestion
():
string
{
try
{
const
href
=
window
.
location
.
href
||
''
const
firstQuestionIndex
=
href
.
indexOf
(
'?'
)
if
(
firstQuestionIndex
===
-
1
)
return
''
const
queryCandidate
=
href
.
slice
(
firstQuestionIndex
+
1
)
const
secondQuestionIndex
=
queryCandidate
.
indexOf
(
'?'
)
const
sliced
=
secondQuestionIndex
===
-
1
?
queryCandidate
:
queryCandidate
.
slice
(
0
,
secondQuestionIndex
)
// 兼容后端返回 `&&` 作为分隔符的情况,统一替换成单个 `&`
return
sliced
.
replace
(
/&
{2,}
/g
,
'&'
)
}
catch
{
return
''
}
}
export
function
getUserRolesFromRouteAndStore
():
string
[]
{
try
{
const
searchParams
=
new
URLSearchParams
(
window
.
location
.
search
)
const
sanitizedSearch
=
getQueryBeforeSecondQuestion
()
const
searchParams
=
new
URLSearchParams
(
sanitizedSearch
||
window
.
location
.
search
)
const
rolesFromRepeatedKeys
=
searchParams
.
getAll
(
'userRoles'
).
filter
(
Boolean
)
let
userRoles
:
string
[]
=
[]
...
...
src/pages/Home/HomeNew.tsx
View file @
1f2aae8c
...
...
@@ -78,42 +78,45 @@ export const Home: React.FC = () => {
// 处理工具按钮点击
const
_handleToolClick
=
useCallback
(
async
(
isToolBtn
:
boolean
,
toolId
?:
string
)
=>
{
// if (!isToolBtn && toolId) {
// 提质增效模式:只修改左侧页面内容,加载工具相关问题
// 先清空数据,确保显示空数据样式
setOtherQuestions
((
prev
:
any
)
=>
({
...
prev
,
content
:
[],
}))
setIsDataLoaded
(
false
)
// 重置加载状态
try
{
// 获取 toolId:优先使用传入参数,其次从 sessionStorage,再次从路由参数,都没有则使用空字符串
let
finalToolId
=
toolId
||
''
if
(
!
finalToolId
)
{
finalToolId
=
sessionStorage
.
getItem
(
'currentToolId'
)
||
''
}
// 提质增效模式 / 数据助手 / 通用模式:都先清空数据,重新拉常见问题
setOtherQuestions
((
prev
:
any
)
=>
({
...
prev
,
content
:
[],
}))
setIsDataLoaded
(
false
)
// 重置加载状态
try
{
/**
* 获取最终用于请求的 toolId:
* - 如果传入了 toolId:
* - 通用模式(isToolBtn === true):保持传入值(可能是空字符串),不再从 sessionStorage / URL 回退
* - 工具模式(isToolBtn === false):如果传入为空,再从 sessionStorage / URL 回退
* - 如果没有传入 toolId:保留原有回退逻辑
*/
let
finalToolId
=
toolId
||
''
if
(
!
finalToolId
&&
!
isToolBtn
)
{
// 仅在工具模式下才使用回退逻辑,避免通用模式误用上一次的 toolId
finalToolId
=
sessionStorage
.
getItem
(
'currentToolId'
)
||
''
if
(
!
finalToolId
)
{
const
searchParams
=
new
URLSearchParams
(
location
.
search
)
finalToolId
=
searchParams
.
get
(
'toolId'
)
||
''
}
const
res
=
await
fetchEfficiencyQuestionList
({
toolId
:
finalToolId
})
if
(
res
&&
res
.
data
&&
res
.
data
.
questions
)
{
setOtherQuestions
((
prev
:
any
)
=>
({
...
prev
,
content
:
res
.
data
.
questions
||
[],
}))
}
}
catch
(
error
)
{
console
.
error
(
'获取工具相关问题失败:'
,
error
)
}
finally
{
setIsDataLoaded
(
true
)
// 无论成功失败都标记为已加载
const
res
=
await
fetchEfficiencyQuestionList
({
toolId
:
finalToolId
})
if
(
res
&&
res
.
data
&&
res
.
data
.
questions
)
{
setOtherQuestions
((
prev
:
any
)
=>
({
...
prev
,
content
:
res
.
data
.
questions
||
[],
}))
}
// }
// else if (isToolBtn) {
// setIsDataLoaded(true) // 恢复原始数据时标记为已加载
// }
}
catch
(
error
)
{
console
.
error
(
'获取工具相关问题失败:'
,
error
)
}
finally
{
setIsDataLoaded
(
true
)
// 无论成功失败都标记为已加载
}
},
[
originalOtherQuestions
,
location
.
search
])
// 监听工具按钮点击事件
...
...
src/store/conversationSlice.ts
View file @
1f2aae8c
...
...
@@ -3,7 +3,6 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import
{
processConversationData
}
from
'./conversationSlice.helper'
import
type
{
Conversation
,
ConversationState
}
from
'@/types/conversation'
import
{
fetchCreateConversation
,
fetchDeleteUserConversation
,
fetchQueryUserConversationPage
}
from
'@/api/conversation'
// import { mockFetchConversationPage } from '@/api/mock/conversation'
const
initialState
:
ConversationState
=
{
conversations
:
[],
...
...
@@ -19,13 +18,10 @@ export const fetchConversations = createAsyncThunk(
'conversation/fetchConversations'
,
async
(
_
,
{
rejectWithValue
})
=>
{
try
{
// 使用mock数据(已注释)
// const response = await mockFetchConversationPage()
// 真实API调用
// 调用真实接口获取会话列表
const
response
=
await
fetchQueryUserConversationPage
({
keyword
:
''
,
pageNum
:
0
,
pageSize
:
100
,
current
:
1
,
size
:
100
,
})
const
records
=
response
.
data
?.
records
||
[]
return
processConversationData
(
records
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment