Files
happy-life-star/web/mobile-fixed.html
T

634 lines
20 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="format-detection" content="telephone=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>情绪博物馆 - 移动版</title>
<style>
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
color: white;
overflow-x: hidden;
position: fixed;
width: 100%;
height: 100%;
}
.mobile-container {
display: flex;
flex-direction: column;
height: 100vh;
height: 100dvh;
position: relative;
}
.header {
background: rgba(255,255,255,0.1);
padding: 10px 15px;
display: flex;
align-items: center;
justify-content: space-between;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255,255,255,0.2);
flex-shrink: 0;
}
.header h1 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.new-chat-btn {
background: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.3);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
}
.new-chat-btn:active {
background: rgba(255,255,255,0.3);
transform: scale(0.95);
}
.status-bar {
background: rgba(0,0,0,0.1);
padding: 8px 15px;
font-size: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
.connection-status {
display: flex;
align-items: center;
gap: 6px;
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #4CAF50;
}
.status-dot.error {
background: #f44336;
}
.chat-area {
flex: 1;
overflow-y: auto;
padding: 15px;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.message {
margin: 12px 0;
padding: 12px 16px;
border-radius: 18px;
max-width: 85%;
word-wrap: break-word;
line-height: 1.4;
animation: slideIn 0.3s ease;
}
.user-message {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
margin-left: auto;
border-bottom-right-radius: 6px;
}
.ai-message {
background: rgba(255,255,255,0.95);
color: #333;
margin-right: auto;
border-bottom-left-radius: 6px;
}
.error-message {
background: rgba(244, 67, 54, 0.2) !important;
border-left: 4px solid #f44336;
color: #fff !important;
}
.input-container {
background: rgba(255,255,255,0.1);
padding: 15px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(255,255,255,0.2);
flex-shrink: 0;
position: relative;
z-index: 1000;
}
.input-wrapper {
display: flex;
align-items: flex-end;
gap: 10px;
max-width: 100%;
}
.message-input {
flex: 1;
padding: 12px 16px;
border: none;
border-radius: 25px;
background: rgba(255,255,255,0.9);
color: #333;
font-size: 16px;
outline: none;
resize: none;
min-height: 44px;
max-height: 120px;
overflow-y: auto;
-webkit-appearance: none;
}
.message-input:focus {
background: rgba(255,255,255,1);
}
.send-btn {
background: linear-gradient(135deg, #4CAF50 0%, #45a049 100%);
border: none;
color: white;
padding: 12px 20px;
border-radius: 25px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
min-width: 60px;
min-height: 44px;
transition: all 0.3s ease;
}
.send-btn:active {
transform: scale(0.95);
}
.send-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.typing-indicator {
display: inline-flex;
align-items: center;
gap: 4px;
margin-left: 8px;
}
.typing-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #999;
animation: typing 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
.typing-dot:nth-child(3) { animation-delay: 0s; }
.emotion-tag {
display: inline-block;
background: rgba(102, 126, 234, 0.2);
color: #667eea;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
margin-top: 8px;
}
.welcome-message {
text-align: center;
padding: 40px 20px;
opacity: 0.9;
}
.welcome-message h2 {
font-size: 24px;
margin-bottom: 16px;
}
.welcome-message p {
font-size: 16px;
line-height: 1.5;
margin-bottom: 24px;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes typing {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.5;
}
40% {
transform: scale(1.2);
opacity: 1;
}
}
@media screen and (max-height: 600px) {
.chat-area {
padding: 10px;
}
.message {
margin: 8px 0;
padding: 10px 14px;
}
}
@media screen and (orientation: landscape) and (max-height: 500px) {
.header {
padding: 8px 15px;
}
.input-container {
padding: 10px 15px;
}
}
</style>
</head>
<body>
<div class="mobile-container">
<div class="header">
<h1>🏛️ 情绪博物馆</h1>
<button class="new-chat-btn" onclick="startNewChat()">新对话</button>
</div>
<div class="status-bar">
<div class="connection-status">
<div class="status-dot" id="status-dot"></div>
<span id="connection-text">连接中...</span>
</div>
<span id="message-count">0 条消息</span>
</div>
<div class="chat-area" id="chat-area">
<div class="welcome-message">
<h2>👋 欢迎使用</h2>
<p>我是您的AI心理健康助手,很高兴为您服务。请告诉我您今天的心情如何?</p>
</div>
</div>
<div class="input-container">
<div class="input-wrapper">
<textarea
class="message-input"
id="message-input"
placeholder="输入您想说的话..."
rows="1"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
></textarea>
<button class="send-btn" onclick="sendMessage()" id="send-btn">发送</button>
</div>
</div>
</div>
<script>
// 全局变量
let conversationId = null;
let messageCount = 0;
let isConnected = false;
let isSending = false;
// 防止iOS Safari的双击缩放
let lastTouchEnd = 0;
document.addEventListener('touchend', function (event) {
const now = (new Date()).getTime();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, false);
// 初始化
document.addEventListener('DOMContentLoaded', function() {
console.log('页面加载完成');
initializeApp();
});
function initializeApp() {
try {
checkConnection();
setupInputHandlers();
adjustViewport();
console.log('应用初始化完成');
} catch (error) {
console.error('初始化失败:', error);
showError('应用初始化失败,请刷新页面重试');
}
}
function adjustViewport() {
function setViewportHeight() {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', vh + 'px');
}
setViewportHeight();
window.addEventListener('resize', setViewportHeight);
window.addEventListener('orientationchange', function() {
setTimeout(setViewportHeight, 100);
});
}
function setupInputHandlers() {
const input = document.getElementById('message-input');
input.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 120) + 'px';
});
input.addEventListener('keydown', function(event) {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
});
if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
input.addEventListener('focus', function() {
setTimeout(function() {
window.scrollTo(0, 0);
document.body.scrollTop = 0;
}, 300);
});
}
}
async function checkConnection() {
const statusDot = document.getElementById('status-dot');
const connectionText = document.getElementById('connection-text');
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch('/api/ai/guest/health', {
signal: controller.signal
});
clearTimeout(timeoutId);
if (response.ok) {
const data = await response.json();
if (data.code === 200 && data.success) {
isConnected = true;
statusDot.className = 'status-dot';
connectionText.textContent = '已连接';
} else {
throw new Error('服务不可用');
}
} else {
throw new Error('HTTP ' + response.status);
}
} catch (error) {
isConnected = false;
statusDot.className = 'status-dot error';
connectionText.textContent = '连接失败';
console.error('连接检查失败:', error);
}
}
function updateMessageCount() {
document.getElementById('message-count').textContent = messageCount + ' 条消息';
}
function startNewChat() {
try {
conversationId = null;
messageCount = 0;
const chatArea = document.getElementById('chat-area');
chatArea.innerHTML =
'<div class="welcome-message">' +
'<h2>🆕 新对话开始</h2>' +
'<p>我是您的AI心理健康助手,很高兴为您服务。请告诉我您今天的心情如何?</p>' +
'</div>';
updateMessageCount();
clearInputAndFocus();
} catch (error) {
console.error('创建新对话失败:', error);
showError('创建新对话失败');
}
}
function clearInputAndFocus() {
const input = document.getElementById('message-input');
if (input) {
input.value = '';
input.style.height = 'auto';
if (!isMobile()) {
setTimeout(() => input.focus(), 100);
}
}
}
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
function showError(message) {
const chatArea = document.getElementById('chat-area');
const errorDiv = document.createElement('div');
errorDiv.className = 'message ai-message error-message';
errorDiv.innerHTML = '<strong>⚠️ 错误:</strong> ' + escapeHtml(message);
chatArea.appendChild(errorDiv);
scrollToBottom();
}
function scrollToBottom() {
const chatArea = document.getElementById('chat-area');
setTimeout(function() {
chatArea.scrollTop = chatArea.scrollHeight;
}, 100);
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
async function sendMessage() {
if (isSending) return;
const input = document.getElementById('message-input');
const chatArea = document.getElementById('chat-area');
const sendBtn = document.getElementById('send-btn');
const message = input.value.trim();
if (!message) {
input.focus();
return;
}
isSending = true;
try {
// 立即清空输入框
clearInputAndFocus();
// 添加用户消息
const userMessageDiv = document.createElement('div');
userMessageDiv.className = 'message user-message';
userMessageDiv.innerHTML = '<strong>👤 您:</strong> ' + escapeHtml(message);
chatArea.appendChild(userMessageDiv);
// 显示AI正在输入
const loadingDiv = document.createElement('div');
loadingDiv.className = 'message ai-message';
loadingDiv.innerHTML =
'<strong>🤖 AI助手:</strong> 正在思考中' +
'<div class="typing-indicator">' +
'<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>' +
'</div>';
chatArea.appendChild(loadingDiv);
// 禁用发送按钮
sendBtn.disabled = true;
sendBtn.textContent = '发送中...';
scrollToBottom();
// 发送请求
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const response = await fetch('/api/ai/guest/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: message,
title: conversationId ? null : '移动端对话 ' + new Date().toLocaleString()
}),
signal: controller.signal
});
clearTimeout(timeoutId);
// 移除加载状态
if (chatArea.contains(loadingDiv)) {
chatArea.removeChild(loadingDiv);
}
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
const data = await response.json();
if (data.code === 200 && data.success) {
// 更新会话ID
if (data.data.conversationId) {
conversationId = data.data.conversationId;
}
// 添加AI回复
const aiMessageDiv = document.createElement('div');
aiMessageDiv.className = 'message ai-message';
let aiContent = '<strong>🤖 AI助手:</strong> ' + escapeHtml(data.data.aiReply);
// 如果有情绪分析,显示
if (data.data.emotionAnalysis) {
const emotion = data.data.emotionAnalysis;
aiContent += '<div class="emotion-tag">💭 ' +
escapeHtml(emotion.primaryEmotion) + ' (强度: ' + escapeHtml(emotion.intensity) + ')</div>';
}
aiMessageDiv.innerHTML = aiContent;
chatArea.appendChild(aiMessageDiv);
messageCount += 2;
updateMessageCount();
// 更新连接状态
if (!isConnected) {
checkConnection();
}
} else {
throw new Error(data.message || '未知错误');
}
} catch (error) {
console.error('发送消息失败:', error);
// 移除加载状态
const loadingDiv = chatArea.querySelector('.message.ai-message:last-child');
if (loadingDiv && loadingDiv.innerHTML.includes('正在思考中')) {
chatArea.removeChild(loadingDiv);
}
// 显示错误消息
let errorMessage = '发送失败,请重试';
if (error.name === 'AbortError') {
errorMessage = '请求超时,请检查网络连接';
} else if (error.message.includes('HTTP')) {
errorMessage = '服务器连接失败: ' + error.message;
}
showError(errorMessage);
checkConnection();
} finally {
isSending = false;
// 恢复发送按钮
sendBtn.disabled = false;
sendBtn.textContent = '发送';
scrollToBottom();
}
}
// 定期检查连接状态
setInterval(function() {
if (!isSending) {
checkConnection();
}
}, 30000);
// 页面可见性变化时重新检查连接
document.addEventListener('visibilitychange', function() {
if (!document.hidden && !isSending) {
setTimeout(checkConnection, 1000);
}
});
</script>
</body>
</html>