优化:底部导航菜单
This commit is contained in:
@@ -1,53 +1,53 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type { ApiResult } from '@/api/index';
|
import type {ApiResult} from '@/api/index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI聊天消息接口
|
* AI聊天消息接口
|
||||||
*/
|
*/
|
||||||
export interface AiChatMessage {
|
export interface AiChatMessage {
|
||||||
query: string;
|
query: string;
|
||||||
user?: string;
|
user?: string;
|
||||||
responseMode?: string;
|
responseMode?: string;
|
||||||
conversationId?: string;
|
conversationId?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
requestType?: number;
|
requestType?: number;
|
||||||
aiToken?: string;
|
aiToken?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI聊天响应接口
|
* AI聊天响应接口
|
||||||
*/
|
*/
|
||||||
export interface AiChatResponse {
|
export interface AiChatResponse {
|
||||||
answer: string;
|
answer: string;
|
||||||
taskId: string;
|
taskId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送AI聊天消息
|
* 发送AI聊天消息
|
||||||
*/
|
*/
|
||||||
export async function sendAiMessage(data: AiChatMessage) {
|
export async function sendAiMessage(data: AiChatMessage) {
|
||||||
const res = await request.post<ApiResult<string>>(
|
const res = await request.post<ApiResult<string>>(
|
||||||
'/chat/message',
|
'/chat/message',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止AI聊天
|
* 停止AI聊天
|
||||||
*/
|
*/
|
||||||
export async function stopAiMessage(data: { taskId: string; type?: string }) {
|
export async function stopAiMessage(data: { taskId: string; authCode?: string; user?: string; type?: string }) {
|
||||||
const res = await request.post<ApiResult<string>>(
|
const res = await request.post<ApiResult<string>>(
|
||||||
'/chat/messageStop',
|
'https://ai-console.gxshucheng.com/ai-console-api/stop/v1',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@
|
|||||||
|
|
||||||
.h5-tabbar-container {
|
.h5-tabbar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 60px;
|
height: 88px;
|
||||||
align-items: center;
|
align-items: flex-end;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 0 16px;
|
padding: 4px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.h5-tabbar-item {
|
.h5-tabbar-item {
|
||||||
@@ -32,8 +32,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
@@ -41,6 +39,11 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保图片图标保持正方形
|
||||||
|
image {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 特殊AI按钮样式
|
// 特殊AI按钮样式
|
||||||
@@ -48,35 +51,39 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: -8px;
|
margin-bottom: -10px;
|
||||||
|
|
||||||
.h5-ai-circle {
|
.h5-ai-circle {
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ffffff;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -20px;
|
top: -8px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
.h5-ai-text {
|
.h5-ai-text {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-size: 14px;
|
font-size: 18px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保AI图标保持正方形并居中
|
||||||
|
image {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
// 光晕效果
|
// 光晕效果
|
||||||
&::before {
|
&::before {
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 4px 12px rgba(255, 255, 255, 0.8);
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -4px;
|
top: -10px;
|
||||||
left: -4px;
|
left: -7px;
|
||||||
right: -4px;
|
right: -7px;
|
||||||
bottom: -4px;
|
bottom: -7px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
@@ -90,19 +97,18 @@
|
|||||||
|
|
||||||
// 文字样式
|
// 文字样式
|
||||||
.h5-tabbar-text {
|
.h5-tabbar-text {
|
||||||
font-size: 12px;
|
font-size: 18px;
|
||||||
color: #8a8a8a;
|
color: #8a8a8a;
|
||||||
transition: color 0.3s ease;
|
transition: color 0.3s ease;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
color: #d81e06;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.special-text {
|
&.special-text {
|
||||||
color: #ff6b35;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-top: -12px;
|
margin-top: -12px;
|
||||||
}
|
}
|
||||||
@@ -126,17 +132,6 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
|
||||||
.h5-special-icon .h5-ai-circle {
|
|
||||||
background: linear-gradient(135deg, #ff6b35 0%, #ff4500 100%);
|
|
||||||
box-shadow: 0 6px 16px rgba(255, 69, 0, 0.5);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.5) 0%, rgba(255, 69, 0, 0.5) 100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击效果
|
// 点击效果
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import {useState, useEffect} from 'react';
|
import {useState, useEffect} from 'react';
|
||||||
import {View, Text} from '@tarojs/components';
|
import {View, Text, Image} from '@tarojs/components';
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import './SimpleH5TabBar.scss';
|
import './SimpleH5TabBar.scss';
|
||||||
import {Image} from '@nutui/nutui-react-taro'
|
|
||||||
|
|
||||||
interface TabBarProps {
|
interface TabBarProps {
|
||||||
current?: number;
|
current?: number;
|
||||||
@@ -73,12 +72,28 @@ function SimpleH5TabBar({current}: TabBarProps) {
|
|||||||
{item.isSpecial ? (
|
{item.isSpecial ? (
|
||||||
<View className="h5-special-icon">
|
<View className="h5-special-icon">
|
||||||
<View className="h5-ai-circle">
|
<View className="h5-ai-circle">
|
||||||
<Image src={item.icon} width={'24px'} height={'24px'}/>
|
<Image
|
||||||
|
src={item.icon}
|
||||||
|
style={{
|
||||||
|
width: '68px',
|
||||||
|
height: '68px',
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View className="h5-normal-icon">
|
<View className="h5-normal-icon">
|
||||||
<Image src={item.icon} width={'24px'} height={'24px'}/>
|
<Image
|
||||||
|
src={item.icon}
|
||||||
|
style={{
|
||||||
|
width: '28px',
|
||||||
|
height: '28px',
|
||||||
|
objectFit: 'contain'
|
||||||
|
}}
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Textarea} from '@tarojs/components';
|
|||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import {Button} from '@nutui/nutui-react-taro';
|
import {Button} from '@nutui/nutui-react-taro';
|
||||||
import {User, Home} from '@nutui/icons-react-taro';
|
import {User, Home} from '@nutui/icons-react-taro';
|
||||||
import {sendAiMessage} from '@/api/ai';
|
import {sendAiMessage, stopAiMessage} from '@/api/ai';
|
||||||
import {createWebSocket} from '@/utils/websocket';
|
import {createWebSocket} from '@/utils/websocket';
|
||||||
import {getAiToken} from '@/utils/aiToken';
|
import {getAiToken} from '@/utils/aiToken';
|
||||||
import MarkdownRenderer from '@/components/MarkdownRenderer';
|
import MarkdownRenderer from '@/components/MarkdownRenderer';
|
||||||
@@ -11,7 +11,6 @@ import MarkdownRenderer from '@/components/MarkdownRenderer';
|
|||||||
import {View, RichText} from '@tarojs/components'
|
import {View, RichText} from '@tarojs/components'
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import {WSS_API_URL} from "@/utils/server";
|
import {WSS_API_URL} from "@/utils/server";
|
||||||
import SimpleH5TabBar from "@/components/SimpleH5TabBar";
|
|
||||||
|
|
||||||
// 消息类型
|
// 消息类型
|
||||||
interface Message {
|
interface Message {
|
||||||
@@ -31,7 +30,7 @@ const AiChat = () => {
|
|||||||
const [messages, setMessages] = useState<Message[]>([]);
|
const [messages, setMessages] = useState<Message[]>([]);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
// const [currentTaskId, setCurrentTaskId] = useState<string>('');
|
const [currentTaskId, setCurrentTaskId] = useState<string>('');
|
||||||
const [wsConnected, setWsConnected] = useState(false);
|
const [wsConnected, setWsConnected] = useState(false);
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -79,8 +78,8 @@ const AiChat = () => {
|
|||||||
wsRef.current = createWebSocket(WSS_API_URL + "/chat/" + userToken);
|
wsRef.current = createWebSocket(WSS_API_URL + "/chat/" + userToken);
|
||||||
|
|
||||||
wsRef.current.onMessage((data: any) => {
|
wsRef.current.onMessage((data: any) => {
|
||||||
console.log('收到WebSocket消息:', data);
|
console.log('收到WebSocket消息:', data.taskId);
|
||||||
|
setCurrentTaskId(data.taskId)
|
||||||
if (data.answer) {
|
if (data.answer) {
|
||||||
if (data.answer === '__END__') {
|
if (data.answer === '__END__') {
|
||||||
// 消息结束,移除typing状态
|
// 消息结束,移除typing状态
|
||||||
@@ -275,20 +274,24 @@ const AiChat = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 停止AI回复
|
// 停止AI回复
|
||||||
// const handleStopMessage = async () => {
|
const handleStop = async () => {
|
||||||
// if (currentTaskId) {
|
if (currentTaskId) {
|
||||||
// try {
|
try {
|
||||||
// await stopAiMessage({taskId: currentTaskId});
|
await stopAiMessage({
|
||||||
// setIsLoading(false);
|
taskId: currentTaskId,
|
||||||
// setCurrentTaskId('');
|
authCode: '1fbfa21a-a3df-445e-9ca5-2c1a9eead7f4',
|
||||||
// setMessages(prev => prev.map(msg =>
|
user: `${Taro.getStorageSync('AI_TOKEN') || 'anonymous'}`
|
||||||
// msg.isTyping ? {...msg, isTyping: false} : msg
|
});
|
||||||
// ));
|
setIsLoading(false);
|
||||||
// } catch (error) {
|
setCurrentTaskId('');
|
||||||
// console.error('停止消息失败:', error);
|
setMessages(prev => prev.map(msg =>
|
||||||
// }
|
msg.isTyping ? {...msg, isTyping: false} : msg
|
||||||
// }
|
));
|
||||||
// };
|
} catch (error) {
|
||||||
|
console.error('停止消息失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 处理快捷问题点击
|
// 处理快捷问题点击
|
||||||
const handleQuickQuestion = (question: string) => {
|
const handleQuickQuestion = (question: string) => {
|
||||||
@@ -385,7 +388,8 @@ const AiChat = () => {
|
|||||||
|
|
||||||
<View className="input-container">
|
<View className="input-container">
|
||||||
<div className="input-wrapper flex justify-between items-center">
|
<div className="input-wrapper flex justify-between items-center">
|
||||||
<Home size={26} className={'bg-white'} onClick={() => Taro.reLaunch({url: '/pages/index/index'})}/>
|
<Home size={26} className={'bg-white'}
|
||||||
|
onClick={() => Taro.reLaunch({url: '/pages/index/index'})}/>
|
||||||
<div className={'w-full mx-2'}>
|
<div className={'w-full mx-2'}>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="message-input"
|
className="message-input"
|
||||||
@@ -405,10 +409,11 @@ const AiChat = () => {
|
|||||||
className={'flex justify-center items-center pr-1'}
|
className={'flex justify-center items-center pr-1'}
|
||||||
onClick={() => handleSendMessage(inputValue)}
|
onClick={() => handleSendMessage(inputValue)}
|
||||||
>
|
>
|
||||||
<img alt={'发送'} src={'https://oss.wsdns.cn/20250709/13424d78bb004352864051d61afe9f0e.png'} width={'30px'} />
|
<img alt={'发送'} src={'https://oss.wsdns.cn/20250709/13424d78bb004352864051d61afe9f0e.png'}
|
||||||
|
width={'30px'}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Button onClick={handleStop}>停止</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user