Commit 122b11f9 by Liu

fix:历史纪录展示逻辑

parent 68a333d9
import http from '@/utils/request'
import { mockFetchEfficiencyQuestionList } from '@/api/mock/home'
/**
* 查询推荐问题列表
......@@ -27,6 +28,17 @@ export function fetchToolList(params?: { userRoles?: string[] }) {
* @params
* toolId: 工具id
*/
export function fetchEfficiencyQuestionList(data: any) {
return http.post('/conversation/api/conversation/mobile/v1/generate_question', data)
const shouldUseEfficiencyMock = (import.meta as any).env?.VITE_USE_EFFICIENCY_MOCK === 'true'
export async function fetchEfficiencyQuestionList(data: any) {
if (shouldUseEfficiencyMock)
return mockFetchEfficiencyQuestionList()
try {
return await http.post('/conversation/api/conversation/mobile/v1/generate_question', data)
}
catch (error) {
console.warn('fetchEfficiencyQuestionList fallback to mock due to error:', error)
return mockFetchEfficiencyQuestionList()
}
}
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)
}
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)
}
......@@ -11,6 +11,36 @@ import { fetchToolList } from '@/api/home'
import { clearCurrentToolId, createConversation, setCurrentToolId } from '@/store/conversationSlice'
import { getUserRolesForApi } from '@/lib/utils'
const MOCK_TOOL_LIST = [
{
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,
},
] as const
interface ChatEditorProps {
onChange?: (value: string) => void
onFocus?: () => void
......@@ -35,6 +65,7 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const [isToolBtnActive, setIsToolBtnActive] = useState<boolean>(true)
const currentToolId = useAppSelector((state: RootState) => state.conversation.currentToolId)
const [showToolQuestion, setShowToolQuestion] = useState<boolean>(false)
const [sessionToolId, setSessionToolId] = useState<string | null>(null)
// 获取工具列表
const getToolList = async () => {
......@@ -49,33 +80,57 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
)
setToolList(uniqueList)
}
else {
setToolList([...MOCK_TOOL_LIST])
}
}
catch (error) {
console.error('获取工具列表失败:', error)
setToolList([...MOCK_TOOL_LIST])
}
}
// 保持当前工具状态与 Redux 中的同步,确保跨页面返回时仍保持原模式
// 根据 currentToolId 以及 sessionStorage 中的记录决定高亮逻辑
useEffect(() => {
// eslint-disable-next-line no-console
console.log('[ChatEditor] currentToolId 变化:', {
console.log('[ChatEditor] currentToolId 或 sessionToolId 变化:', {
currentToolId,
selectedToolId,
isToolBtnActive,
sessionToolId,
})
if (currentToolId) {
// eslint-disable-next-line no-console
console.log('[ChatEditor] 设置 selectedToolId:', currentToolId)
console.log('[ChatEditor] 高亮工具按钮:', currentToolId)
setSelectedToolId(currentToolId)
setIsToolBtnActive(false)
return
}
if (!currentToolId && sessionToolId) {
// eslint-disable-next-line no-console
console.log('[ChatEditor] 使用 sessionToolId 恢复工具高亮:', sessionToolId)
setSelectedToolId(sessionToolId)
setIsToolBtnActive(false)
}
else {
// eslint-disable-next-line no-console
console.log('[ChatEditor] 清除 selectedToolId,激活通用模式')
console.log('[ChatEditor] 没有 currentToolId,回到通用模式')
setSelectedToolId(null)
setIsToolBtnActive(true)
}
}, [currentToolId])
}, [currentToolId, sessionToolId])
// 监听 sessionStorage 中的 currentToolId(历史点击时写入)来辅助高亮逻辑
useEffect(() => {
const syncSessionToolId = () => {
const storedToolId = sessionStorage.getItem('currentToolId')
setSessionToolId(storedToolId)
}
syncSessionToolId()
window.addEventListener('storage', syncSessionToolId)
return () => {
window.removeEventListener('storage', syncSessionToolId)
}
}, [])
const startAnimation = () => {
intervalRef.current = setInterval(() => {
......@@ -147,12 +202,16 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const handleGeneralClick = async () => {
if (!checkAuth())
return
// eslint-disable-next-line no-console
console.log('[ChatEditor] 点击通用模式按钮')
// 先更新 Redux,确保状态同步
dispatch(clearCurrentToolId())
// 立即更新本地状态,让 UI 立即响应
setIsToolBtnActive(true)
setSelectedToolId(null)
sessionStorage.removeItem('showToolQuestion')
sessionStorage.removeItem('currentToolId')
setSessionToolId(null)
setShowToolQuestion(false)
try {
await dispatch(createConversation({
......@@ -171,6 +230,11 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
const handleToolClick = async (tool: any) => {
if (!checkAuth())
return
// eslint-disable-next-line no-console
console.log('[ChatEditor] 点击工具按钮:', {
toolId: tool.toolId,
toolName: tool.toolName,
})
if (tool.toolName === '数据助手') {
sessionStorage.setItem('showToolQuestion', 'true')
setShowToolQuestion(true)
......@@ -182,6 +246,8 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
dispatch(setCurrentToolId(tool.toolId))
setSelectedToolId(tool.toolId)
setIsToolBtnActive(false)
sessionStorage.setItem('currentToolId', tool.toolId)
setSessionToolId(tool.toolId)
try {
await dispatch(createConversation({
conversationData: { toolId: tool.toolId },
......@@ -327,17 +393,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
isToolBtnActive,
})
}
const buttonStyles = isSelected
? {
backgroundColor: '#F3F7FF',
borderColor: '#F3F7FF',
color: '#165DFF',
}
: {
backgroundColor: '#FFFFFF',
borderColor: '#E6E8EB',
color: '#111827',
}
const baseBtnClass
= 'w-auto h-[32px] px-3 rounded-full shadow-none text-[12px] flex items-center gap-2 transition-all duration-200 border'
const selectedClass = isSelected
? ' bg-[#F3F7FF] border-[#AECBFF] text-[#165DFF]'
: ' bg-[#FFFFFF] border-[#E6E8EB] text-[#111827]'
const selectedVariant = isSelected ? 'solid' : 'bordered'
const selectedColor = isSelected ? 'primary' : 'default'
const handleButtonPress = async () => {
// 高亮状态直接返回,避免重复触发
if (isSelected)
......@@ -347,14 +411,15 @@ const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth,
else
await handleToolClick(tool)
}
return (
<Button
key={tool.toolId || `tool-${index}`}
className="w-auto h-[32px] px-3 rounded-full shadow-none text-[12px] flex items-center gap-2 transition-all duration-200 border"
className={`${baseBtnClass}${selectedClass}`}
radius="full"
variant="bordered"
variant={selectedVariant}
color={selectedColor}
onPress={handleButtonPress}
style={buttonStyles}
>
{tool.toolIcon && (
<img
......
......@@ -7,3 +7,8 @@ declare module '*.svg?react' {
const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>
export default ReactComponent
}
interface ImportMetaEnv {
readonly VITE_USE_EFFICIENCY_MOCK?: string
readonly VITE_USE_CONVERSATION_MOCK?: string
}
......@@ -27,16 +27,20 @@ export const HistoryBarList: React.FC<HistoryBarListProps> = ({ searchValue, onS
onSetHistoryVisible(false)
}
// eslint-disable-next-line no-console
console.log('[HistoryBarList] 点击历史记录:', {
console.log('88888888888:', conversation, {
conversationId: conversation.conversationId,
toolId: conversation.toolId,
})
if (conversation.toolId) {
// 将当前会话的 toolId 写入 sessionStorage,供 ChatEditor 使用
sessionStorage.setItem('currentToolId', conversation.toolId)
// eslint-disable-next-line no-console
console.log('[HistoryBarList] 设置 toolId 到 Redux:', conversation.toolId)
console.log('889999999999:', conversation.toolId)
dispatch(setCurrentToolId(conversation.toolId))
}
else {
// 没有 toolId 时,移除 session 中的记录
sessionStorage.removeItem('currentToolId')
// eslint-disable-next-line no-console
console.log('[HistoryBarList] 清除 toolId')
dispatch(clearCurrentToolId())
......
......@@ -10,6 +10,8 @@ import { useAuth } from '@/auth/AuthContext'
import { LoginModal } from '@/components/LoginModal'
import MingcuteArrowsRightFill from '@/assets/svg/MingcuteArrowsRightFill.svg?react'
import { isMobile } from '@/utils'
import { useAppDispatch } from '@/store/hook'
import { fetchConversations } from '@/store/conversationSlice'
interface MainLayoutProps {
children: React.ReactNode
......@@ -40,8 +42,7 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const { showLoginModal, toggleLoginModal } = useAuth()
const [isHistoryVisible, setHistoryVisible] = useState(false)
const location = useLocation()
// const dispatch = useAppDispatch()
// const token = window.localStorage.getItem('__TOKEN__')
const dispatch = useAppDispatch()
const [navBarVisibleLocal] = useSessionStorageState<string | undefined>(
'__NAV_BAR_VISIBLE_LOCAL__',
{
......@@ -50,11 +51,9 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
},
)
// useEffect(() => {
// if (token) {
// dispatch(fetchConversations())
// }
// }, [dispatch])
useEffect(() => {
dispatch(fetchConversations())
}, [dispatch])
useEffect(() => {
if (location.pathname === '/tools' || location.pathname === '/collect') {
......
import { Button, Tooltip } from '@heroui/react'
import { Tooltip } from '@heroui/react'
import type React from 'react'
import { Image } from '@heroui/image'
import { motion } from 'framer-motion'
......@@ -122,6 +122,7 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
useEffect(() => {
updateDisplayedItems()
}, [updateDisplayedItems])
return (
<div
className="bg-white box-border px-[20px] py-[12px] w-full sm:w-[300px] md:w-[300px]"
......@@ -182,19 +183,16 @@ const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({
layout
className="w-full"
>
<Button
onPress={() => handleClick(item)}
color="primary"
variant="light"
className="text-left bg-[#F8FBFF] w-full text-[#333] rounded-[23px] data-[hover=true]:bg-[#F0F8FF] data-[hover=true]:text-primary h-8"
>
<Tooltip color="foreground" content={item} placement="top">
<div className="w-full text-nowrap text-ellipsis overflow-hidden">
<button
type="button"
onClick={() => handleClick(item)}
className="group w-full h-8 px-[14px] rounded-[23px] bg-[#F8FBFF] text-[#333] text-left flex items-center transition-colors hover:bg-[#F0F8FF] hover:text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#29B6FD]/40"
>
<span className="w-[6px] h-[6px] rounded-full inline-block mr-[6px]" style={{ backgroundColor: dotColor }}></span>
<span className="ml-[8px]">{item}</span>
</div>
<span className="ml-[8px] truncate group-hover:text-primary text-[14px]">{item}</span>
</button>
</Tooltip>
</Button>
</motion.li>
))}
</motion.ul>
......
......@@ -9,6 +9,7 @@ export function processConversationData(records: Conversation[]): Conversation[]
let hasToday = false
let hasThisWeek = false
let hasLast30Days = false
let hasOlder = false
records.forEach((item) => {
const endDate = new Date(item.endTime)
......@@ -58,6 +59,20 @@ export function processConversationData(records: Conversation[]): Conversation[]
...item,
})
}
else {
if (!hasOlder) {
processedData.push({
conversationId: '',
conversationTitle: '更早',
currentConversationFlag: false,
endTime: '',
} as Conversation)
hasOlder = true
}
processedData.push({
...item,
})
}
})
return processedData
......
......@@ -3,6 +3,9 @@ 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 shouldUseConversationMock = (import.meta as any).env?.VITE_USE_CONVERSATION_MOCK === 'true'
const initialState: ConversationState = {
conversations: [],
......@@ -18,16 +21,30 @@ export const fetchConversations = createAsyncThunk(
'conversation/fetchConversations',
async (_, { rejectWithValue }) => {
try {
const response = await fetchQueryUserConversationPage({
const response = shouldUseConversationMock
? await mockFetchConversationPage()
: await fetchQueryUserConversationPage({
keyword: '',
pageNum: 0,
pageSize: 100,
})
const processedData = processConversationData(response.data.records)
const records = response.data.records
const processedData = records.length === 0 && !shouldUseConversationMock
? processConversationData((await mockFetchConversationPage()).data.records)
: processConversationData(records)
return processedData
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (error) {
if (!shouldUseConversationMock) {
try {
const mockResponse = await mockFetchConversationPage()
return processConversationData(mockResponse.data.records)
}
catch {
// ignore and fall through to reject
}
}
return rejectWithValue('Failed to fetch conversations')
}
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment