优化:文章列表支持分页加载
This commit is contained in:
121
src/pages/ai/debug-fix.md
Normal file
121
src/pages/ai/debug-fix.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# AI问答页面输入问题修复
|
||||
|
||||
## 问题分析
|
||||
|
||||
第一次无法输入提问内容的可能原因:
|
||||
|
||||
1. ✅ **WebSocket连接问题**: 使用了错误的连接参数(AI_TOKEN而不是UserId)
|
||||
2. ✅ **用户标识错误**: 消息中的user字段使用了AI_TOKEN而不是UserId
|
||||
3. ✅ **初始化时序问题**: 输入框可能在初始化完成前被禁用
|
||||
4. ✅ **状态管理问题**: 缺少初始化状态跟踪
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1. WebSocket连接修复
|
||||
```typescript
|
||||
// 修复前
|
||||
wsRef.current = createWebSocket(WSS_API_URL + "/chat/" + Taro.getStorageSync('AI_TOKEN'));
|
||||
|
||||
// 修复后
|
||||
const userId = Taro.getStorageSync('UserId') || 'anonymous';
|
||||
wsRef.current = createWebSocket(WSS_API_URL + "/chat/" + userId);
|
||||
```
|
||||
|
||||
### 2. 用户标识修复
|
||||
```typescript
|
||||
// 修复前
|
||||
user: `${Taro.getStorageSync('AI_TOKEN')}`
|
||||
|
||||
// 修复后
|
||||
user: `${Taro.getStorageSync('UserId') || 'anonymous'}`
|
||||
```
|
||||
|
||||
### 3. 初始化状态管理
|
||||
```typescript
|
||||
// 添加初始化状态
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// 初始化完成后设置状态
|
||||
setIsInitialized(true);
|
||||
```
|
||||
|
||||
### 4. 输入框状态优化
|
||||
```typescript
|
||||
// 输入框禁用逻辑
|
||||
disabled={!isInitialized || isLoading}
|
||||
|
||||
// 占位符文本
|
||||
placeholder={
|
||||
!isInitialized ? "正在初始化..." :
|
||||
isLoading ? "AI正在回复中..." :
|
||||
"请输入您的问题..."
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 发送按钮状态优化
|
||||
```typescript
|
||||
// 发送按钮禁用逻辑
|
||||
disabled={!isInitialized || !inputValue.trim()}
|
||||
```
|
||||
|
||||
### 6. 调试功能
|
||||
- 添加了详细的状态调试信息
|
||||
- 输入框焦点和点击事件监听
|
||||
- 初始化完成状态跟踪
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 基本输入测试
|
||||
1. 打开AI问答页面
|
||||
2. 等待初始化完成(看到"请输入您的问题...")
|
||||
3. 点击输入框,检查是否能正常输入
|
||||
4. 输入文字,检查是否正常显示
|
||||
|
||||
### 2. 快捷问题测试
|
||||
1. 点击快捷问题按钮
|
||||
2. 检查是否能正常发送消息
|
||||
3. 验证初始化状态检查
|
||||
|
||||
### 3. 状态调试测试
|
||||
1. 打开浏览器控制台
|
||||
2. 查看初始化日志:
|
||||
- "AI Token初始化完成: xxx"
|
||||
- "输入框状态调试: {...}"
|
||||
3. 点击输入框查看状态变化
|
||||
|
||||
### 4. WebSocket连接测试
|
||||
1. 检查控制台WebSocket连接日志
|
||||
2. 验证连接URL是否正确
|
||||
3. 确认连接状态指示器工作正常
|
||||
|
||||
## 预期结果
|
||||
|
||||
- ✅ 页面加载后输入框立即可用
|
||||
- ✅ 输入框状态正确显示
|
||||
- ✅ 快捷问题正常工作
|
||||
- ✅ WebSocket连接正常
|
||||
- ✅ 消息发送功能正常
|
||||
|
||||
## 调试信息示例
|
||||
|
||||
控制台应该显示类似信息:
|
||||
```
|
||||
AI Token初始化完成: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||
wsUrl: wss://cms-api.websoft.top/api/chat/12345
|
||||
WebSocket连接成功
|
||||
输入框状态调试: {
|
||||
isInitialized: true,
|
||||
isLoading: false,
|
||||
wsConnected: true,
|
||||
inputValue: "",
|
||||
inputDisabled: false,
|
||||
sendButtonDisabled: true
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保UserId存在,否则使用'anonymous'作为默认值
|
||||
2. AI_TOKEN仍然用于API请求认证
|
||||
3. WebSocket连接使用UserId进行房间区分
|
||||
4. 初始化状态确保用户体验流畅
|
||||
@@ -13,10 +13,11 @@ html {
|
||||
}
|
||||
|
||||
.ai-chat {
|
||||
height: 98vh;
|
||||
height: calc(100vh - 80px); // 减去TabBar高度
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
margin-bottom: 80px; // 为TabBar预留空间
|
||||
|
||||
.chat-header {
|
||||
background: linear-gradient(135deg, #a6ea66 0%, #ead1ff 100%);
|
||||
|
||||
@@ -11,6 +11,7 @@ import MarkdownRenderer from '@/components/MarkdownRenderer';
|
||||
import {View, RichText} from '@tarojs/components'
|
||||
import './index.scss';
|
||||
import {WSS_API_URL} from "@/utils/server";
|
||||
import SimpleH5TabBar from "@/components/SimpleH5TabBar";
|
||||
|
||||
// 消息类型
|
||||
interface Message {
|
||||
@@ -162,6 +163,7 @@ const AiChat = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Taro.hideTabBar()
|
||||
// 初始化时检查并生成AI Token
|
||||
const token = checkAiToken();
|
||||
console.log('AI Token初始化完成:', token);
|
||||
|
||||
135
src/pages/ai/input-fix-checklist.md
Normal file
135
src/pages/ai/input-fix-checklist.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# AI问答页面输入修复检查清单
|
||||
|
||||
## 🔧 修复内容总结
|
||||
|
||||
### 1. WebSocket连接修复 ✅
|
||||
- **问题**: 使用AI_TOKEN作为连接标识
|
||||
- **修复**: 改为使用UserId,无UserId时使用'anonymous'
|
||||
- **代码**: `WSS_API_URL + "/chat/" + (userId || 'anonymous')`
|
||||
|
||||
### 2. 用户标识修复 ✅
|
||||
- **问题**: 消息中user字段使用AI_TOKEN
|
||||
- **修复**: 改为使用UserId
|
||||
- **代码**: `user: Taro.getStorageSync('UserId') || 'anonymous'`
|
||||
|
||||
### 3. 初始化状态管理 ✅
|
||||
- **问题**: 缺少初始化完成状态跟踪
|
||||
- **修复**: 添加isInitialized状态
|
||||
- **功能**: 防止初始化期间的误操作
|
||||
|
||||
### 4. 输入框状态优化 ✅
|
||||
- **问题**: 输入框可能被意外禁用
|
||||
- **修复**: 基于初始化状态控制禁用
|
||||
- **逻辑**: `disabled={!isInitialized || isLoading}`
|
||||
|
||||
### 5. 调试功能增强 ✅
|
||||
- **添加**: 详细的状态调试信息
|
||||
- **监听**: 输入框焦点和点击事件
|
||||
- **日志**: 初始化过程跟踪
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 基础功能测试
|
||||
1. **页面加载测试**
|
||||
- [ ] 打开AI问答页面
|
||||
- [ ] 检查控制台是否显示"AI Token初始化完成"
|
||||
- [ ] 检查控制台是否显示"当前UserId"
|
||||
- [ ] 等待看到"请输入您的问题..."占位符
|
||||
|
||||
2. **输入框测试**
|
||||
- [ ] 点击输入框
|
||||
- [ ] 检查是否能正常输入文字
|
||||
- [ ] 检查控制台调试信息
|
||||
- [ ] 验证输入框不会被意外禁用
|
||||
|
||||
3. **发送消息测试**
|
||||
- [ ] 输入测试消息
|
||||
- [ ] 点击发送按钮
|
||||
- [ ] 检查消息是否正常发送
|
||||
- [ ] 验证WebSocket连接正常
|
||||
|
||||
4. **快捷问题测试**
|
||||
- [ ] 点击快捷问题
|
||||
- [ ] 检查是否正常发送
|
||||
- [ ] 验证初始化状态检查
|
||||
|
||||
### 边界情况测试
|
||||
1. **无UserId情况**
|
||||
- [ ] 清除本地存储中的UserId
|
||||
- [ ] 刷新页面
|
||||
- [ ] 检查是否使用'anonymous'
|
||||
- [ ] 验证功能正常
|
||||
|
||||
2. **网络问题测试**
|
||||
- [ ] 断开网络连接
|
||||
- [ ] 检查连接状态提示
|
||||
- [ ] 恢复网络
|
||||
- [ ] 验证重连功能
|
||||
|
||||
3. **初始化期间操作**
|
||||
- [ ] 页面加载时立即点击输入框
|
||||
- [ ] 检查是否显示"正在初始化..."
|
||||
- [ ] 验证不会出现错误
|
||||
|
||||
## 📊 预期控制台输出
|
||||
|
||||
```
|
||||
AI Token初始化完成: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||
当前UserId: 12345 (或 anonymous)
|
||||
wsUrl: wss://cms-api.websoft.top/api/chat/12345
|
||||
WebSocket连接成功
|
||||
输入框状态调试: {
|
||||
isInitialized: true,
|
||||
isLoading: false,
|
||||
wsConnected: true,
|
||||
inputValue: "",
|
||||
inputDisabled: false,
|
||||
sendButtonDisabled: true
|
||||
}
|
||||
```
|
||||
|
||||
## 🚨 常见问题排查
|
||||
|
||||
### 问题1: 输入框仍然无法输入
|
||||
**检查项**:
|
||||
- [ ] isInitialized是否为true
|
||||
- [ ] isLoading是否为false
|
||||
- [ ] 控制台是否有错误信息
|
||||
- [ ] 输入框disabled属性值
|
||||
|
||||
### 问题2: WebSocket连接失败
|
||||
**检查项**:
|
||||
- [ ] 网络连接是否正常
|
||||
- [ ] WSS_API_URL是否正确
|
||||
- [ ] UserId是否获取成功
|
||||
- [ ] 服务器是否正常运行
|
||||
|
||||
### 问题3: 消息发送失败
|
||||
**检查项**:
|
||||
- [ ] AI_TOKEN是否生成成功
|
||||
- [ ] API请求参数是否正确
|
||||
- [ ] 网络请求是否成功
|
||||
- [ ] 服务器响应是否正常
|
||||
|
||||
## 🔍 调试命令
|
||||
|
||||
在浏览器控制台中运行:
|
||||
|
||||
```javascript
|
||||
// 检查当前状态
|
||||
console.log('AI_TOKEN:', Taro.getStorageSync('AI_TOKEN'));
|
||||
console.log('UserId:', Taro.getStorageSync('UserId'));
|
||||
|
||||
// 手动触发调试
|
||||
// (需要在页面上下文中执行)
|
||||
```
|
||||
|
||||
## ✅ 修复验证
|
||||
|
||||
所有测试通过后,应该能够:
|
||||
- ✅ 页面加载后立即可以输入
|
||||
- ✅ 快捷问题正常工作
|
||||
- ✅ 消息发送和接收正常
|
||||
- ✅ WebSocket连接稳定
|
||||
- ✅ 错误处理完善
|
||||
- ✅ 用户体验流畅
|
||||
60
src/pages/ai/test-realtime.md
Normal file
60
src/pages/ai/test-realtime.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# AI聊天实时显示优化完成
|
||||
|
||||
## 🚀 主要优化内容
|
||||
|
||||
### 1. 实时消息显示
|
||||
- ✅ **优化WebSocket消息处理**:简化了消息更新逻辑,直接追加内容而不是复杂的状态管理
|
||||
- ✅ **移除延迟效果**:去掉了打字机效果,改为实时显示内容
|
||||
- ✅ **实时滚动**:消息更新时立即滚动到底部,延迟仅50ms
|
||||
|
||||
### 2. 用户体验改进
|
||||
- ✅ **即时反馈**:发送消息后立即显示AI占位符"正在思考中..."
|
||||
- ✅ **智能按钮状态**:加载时显示"停止"按钮,支持中断AI回复
|
||||
- ✅ **输入框优化**:加载时禁用输入并显示状态提示
|
||||
- ✅ **错误处理**:网络错误时显示友好的错误消息
|
||||
|
||||
### 3. 连接状态管理
|
||||
- ✅ **连接状态指示器**:实时显示WebSocket连接状态
|
||||
- ✅ **智能重连机制**:递增重连间隔,避免频繁重连
|
||||
- ✅ **手动重连**:提供"立即重连"按钮,用户可主动重连
|
||||
- ✅ **连接检查**:发送消息前检查连接状态
|
||||
|
||||
### 4. 视觉效果优化
|
||||
- ✅ **打字光标动画**:AI回复时显示闪烁光标效果
|
||||
- ✅ **流畅布局**:消息内容支持实时追加,无布局跳动
|
||||
- ✅ **按钮样式**:优化发送和停止按钮的视觉效果
|
||||
- ✅ **状态提示**:连接断开时显示醒目的状态栏
|
||||
|
||||
### 5. 性能优化
|
||||
- ✅ **减少重渲染**:优化状态更新逻辑,减少不必要的组件重渲染
|
||||
- ✅ **内存管理**:正确清理WebSocket连接和定时器
|
||||
- ✅ **错误边界**:添加完善的错误处理和用户提示
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. **基本功能测试**
|
||||
- 打开AI聊天页面
|
||||
- 发送一条消息
|
||||
- 观察AI回复是否实时显示
|
||||
|
||||
2. **实时性测试**
|
||||
- 发送较长的问题
|
||||
- 观察回复内容是否逐字显示
|
||||
- 检查是否有明显延迟
|
||||
|
||||
3. **连接状态测试**
|
||||
- 断开网络连接
|
||||
- 观察连接状态指示器
|
||||
- 恢复网络,检查重连功能
|
||||
|
||||
4. **交互测试**
|
||||
- 测试停止按钮功能
|
||||
- 测试快捷问题点击
|
||||
- 测试输入框状态变化
|
||||
|
||||
## 预期效果
|
||||
|
||||
- AI回复内容应该实时逐字显示,无明显延迟
|
||||
- 用户发送消息后立即看到"正在思考中..."提示
|
||||
- 连接断开时有明确的状态提示
|
||||
- 整体交互更加流畅和响应迅速
|
||||
@@ -1,5 +1,6 @@
|
||||
import {useEffect} from "react";
|
||||
import {Image, Space} from '@nutui/nutui-react-taro'
|
||||
import {Image} from '@nutui/nutui-react-taro'
|
||||
import {Space} from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
const BestSellers = (props: any) => {
|
||||
|
||||
@@ -2,7 +2,8 @@ import {useEffect, useState} from "react";
|
||||
import Taro from '@tarojs/taro';
|
||||
import {Button, Space} from '@nutui/nutui-react-taro'
|
||||
import {TriangleDown,ArrowLeft} from '@nutui/icons-react-taro'
|
||||
import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
|
||||
import {Popup, NavBar} from '@nutui/nutui-react-taro'
|
||||
import {Image} from '@nutui/nutui-react-taro'
|
||||
import {TenantId} from "@/utils/config";
|
||||
|
||||
const Header = (props: any) => {
|
||||
@@ -82,9 +83,15 @@ const Header = (props: any) => {
|
||||
<div style={{display: 'flex', alignItems: 'center'}}>
|
||||
<Button style={{color: '#000'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Space>
|
||||
<Avatar
|
||||
size="22"
|
||||
src={props.user?.avatar}
|
||||
<Image
|
||||
src={props.user?.avatar || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iMzAiIGN5PSIzMCIgcj0iMzAiIGZpbGw9IiNGNUY1RjUiLz4KPGNpcmNsZSBjeD0iMzAiIGN5PSIyNCIgcj0iMTAiIGZpbGw9IiNEOUQ5RDkiLz4KPHBhdGggZD0iTTEwIDUwQzEwIDQwIDIwIDM1IDMwIDM1QzQwIDM1IDUwIDQwIDUwIDUwIiBmaWxsPSIjRDlEOUQ5Ii8+Cjwvc3ZnPgo='}
|
||||
style={{
|
||||
width: '22px',
|
||||
height: '22px',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<span style={{color: '#000'}}>{props.user?.nickname}</span>
|
||||
</Space>
|
||||
|
||||
@@ -23,11 +23,26 @@ const Page = () => {
|
||||
<div className={'py-2 my-3 mx-2'}>
|
||||
<div className={'bg-white grid grid-cols-1 md:grid-cols-3 lg:grid-cols-3 gap-2'}>
|
||||
{
|
||||
navItems?.map((item) => (
|
||||
<div className={'flex flex-col justify-center items-center'} onClick={() => Taro.navigateTo({url: `/${item.model}/index?id=${item.navigationId}`})}>
|
||||
<Image className={'shadow-xl rounded-lg'} style={{borderRadius: '8px'}} src={item.icon}
|
||||
height={90} width={90}/>
|
||||
<div className={'mt-2 text-gray-700'} style={{fontSize: '15px'}}>{item?.title}</div>
|
||||
navItems?.map((item, index) => (
|
||||
<div
|
||||
key={item.navigationId || index}
|
||||
className={'flex flex-col justify-center items-center'}
|
||||
onClick={() => Taro.navigateTo({url: `/${item.model}/index?id=${item.navigationId}`})}
|
||||
>
|
||||
<Image
|
||||
className={'shadow-xl rounded-lg'}
|
||||
style={{
|
||||
borderRadius: '8px',
|
||||
width: '90px',
|
||||
height: '90px',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
src={item.icon}
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<div className={'mt-2 text-gray-700'} style={{fontSize: '15px'}}>
|
||||
{item?.title}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import {getSiteInfo} from "@/api/layout";
|
||||
import Login from "./Login";
|
||||
import Banner from "./Banner";
|
||||
import Menu from "./Menu";
|
||||
import TabBar from "@/components/TabBar";
|
||||
import Image from "./Image";
|
||||
import SimpleH5TabBar from "@/components/SimpleH5TabBar";
|
||||
|
||||
function Home() {
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
@@ -29,7 +29,7 @@ function Home() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Taro.showTabBar()
|
||||
Taro.hideTabBar()
|
||||
// 获取站点信息
|
||||
getSiteInfo().then((data) => {
|
||||
console.log(data, 'siteInfo')
|
||||
@@ -45,8 +45,9 @@ function Home() {
|
||||
<Image/>
|
||||
<Menu/>
|
||||
<Banner/>
|
||||
<TabBar/>
|
||||
</>)}
|
||||
{/* H5模式下显示自定义TabBar */}
|
||||
{process.env.TARO_ENV === 'h5' && <SimpleH5TabBar current={0} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {navigateTo} from '@tarojs/taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Button} from '@tarojs/components';
|
||||
import {Image} from '@nutui/nutui-react-taro'
|
||||
import {Button, Image} from '@tarojs/components';
|
||||
import {getUserInfo, getWxOpenId} from "@/api/layout";
|
||||
import {TenantId} from "@/utils/config";
|
||||
import {User} from "@/api/system/user/model";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Avatar} from '@nutui/nutui-react-taro'
|
||||
import {useEffect, useState} from "react";
|
||||
import {Avatar} from '@nutui/nutui-react-taro'
|
||||
import {User} from "@/api/system/user/model";
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
@@ -2,20 +2,25 @@ import {useEffect} from 'react'
|
||||
import UserCard from "./components/UserCard";
|
||||
import UserCell from "./components/UserCell";
|
||||
import TabBar from "@/components/TabBar";
|
||||
import SimpleH5TabBar from "@/components/SimpleH5TabBar";
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
function User() {
|
||||
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
return (
|
||||
<div style={{ backgroundColor: '#ffefef', height: '100vh'}}>
|
||||
<div className={'fixed w-full'}>
|
||||
<UserCard />
|
||||
<UserCell />
|
||||
<TabBar/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
useEffect(() => {
|
||||
Taro.hideTabBar()
|
||||
}, []);
|
||||
return (
|
||||
<div style={{backgroundColor: '#ffefef', height: '100vh'}}>
|
||||
<div className={'fixed w-full'}>
|
||||
<UserCard/>
|
||||
<UserCell/>
|
||||
{/* 小程序模式显示原TabBar,H5模式显示H5TabBar */}
|
||||
{process.env.TARO_ENV !== 'h5' && <TabBar/>}
|
||||
{process.env.TARO_ENV === 'h5' && <SimpleH5TabBar current={2}/>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default User
|
||||
|
||||
Reference in New Issue
Block a user