Compare commits

...

2 Commits

Author SHA1 Message Date
295218b4e4 feat: 注册增加手机号验证和邮箱
- 手机号输入(必填,11位中国手机号格式验证)
- 验证码机制(模拟,6位随机码,5分钟有效期)
- 发送验证码按钮(60秒倒计时)
- 验证码输入框
- 邮箱输入(可选,格式验证)
- 手机号唯一性检查(防止重复注册)
2026-04-27 11:26:51 +08:00
0812a6192f fix: 普通对话返回后不再显示智能体对话
- showConversationList 简化为直接返回主页
- 主页对话列表正确过滤(只显示没有 agentId 的普通对话)
- 添加操作菜单支持(重命名、分享、置顶、删除)
- 统一使用全局变量 currentActionConvId
2026-04-27 11:18:15 +08:00
3 changed files with 237 additions and 236 deletions

View File

@@ -494,6 +494,28 @@ function renderChatsPage() {
</div>
</div>
<!-- 操作菜单 -->
<div class="action-menu" id="actionMenu">
<div class="action-menu-content">
<div class="action-menu-item" data-action="rename">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
<span>重命名</span>
</div>
<div class="action-menu-item" data-action="share">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.35 2.92 3 2.92s3-1.31 3-2.92c0-1.61-1.35-2.92-3-2.92z"/></svg>
<span>分享</span>
</div>
<div class="action-menu-item" data-action="pin">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z"/></svg>
<span id="pinText">置顶</span>
</div>
<div class="action-menu-item delete-action" data-action="delete">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
<span>删除</span>
</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-bar" id="searchBar">
<div class="search-input-wrapper">
@@ -592,6 +614,26 @@ function bindChatsPageEvents() {
// 长按事件
setupLongPressEvents(conversationList);
}
// 操作菜单事件
const actionMenu = document.getElementById('actionMenu');
if (actionMenu) {
actionMenu.addEventListener('click', (e) => {
const item = e.target.closest('.action-menu-item');
if (item && currentActionConvId) {
const action = item.getAttribute('data-action');
handleActionMenuAction(action, currentActionConvId);
hideActionMenu();
}
});
// 点击其他地方关闭菜单
document.addEventListener('click', (e) => {
if (actionMenu.classList.contains('show') && !actionMenu.contains(e.target)) {
hideActionMenu();
}
});
}
}
// ==================== 智能体页面 ====================
@@ -1349,7 +1391,7 @@ function renderProfilePage() {
</div>
<div class="profile-footer">
<p>AI助手 v3.3.0</p>
<p>AI助手 v3.4.0</p>
<p>基于智谱 GLM-4.5-Air</p>
</div>
</div>
@@ -1518,6 +1560,21 @@ function showRegisterPage() {
<label>用户名</label>
<input type="text" id="registerUsername" placeholder="请输入用户名3-20字符" autocomplete="username">
</div>
<div class="auth-input-group">
<label>手机号 <span class="required">*</span></label>
<div class="phone-input-wrapper">
<input type="tel" id="registerPhone" placeholder="请输入手机号" maxlength="11" autocomplete="tel">
<button class="send-code-btn" id="sendCodeBtn">获取验证码</button>
</div>
</div>
<div class="auth-input-group">
<label>验证码 <span class="required">*</span></label>
<input type="text" id="registerCode" placeholder="请输入验证码" maxlength="6" autocomplete="one-time-code">
</div>
<div class="auth-input-group">
<label>邮箱 <span class="optional">(可选)</span></label>
<input type="email" id="registerEmail" placeholder="请输入邮箱(可选)" autocomplete="email">
</div>
<div class="auth-input-group">
<label>密码</label>
<input type="password" id="registerPassword" placeholder="请输入密码6-20字符" autocomplete="new-password">
@@ -1558,22 +1615,139 @@ function showRegisterPage() {
});
}
// 发送验证码按钮
const sendCodeBtn = document.getElementById('sendCodeBtn');
if (sendCodeBtn) {
sendCodeBtn.addEventListener('click', handleSendCode);
}
// 回车注册
document.getElementById('registerPasswordConfirm')?.addEventListener('keydown', (e) => {
if (e.key === 'Enter') handleRegister();
});
// 手机号输入限制(只允许数字)
const phoneInput = document.getElementById('registerPhone');
if (phoneInput) {
phoneInput.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/\D/g, '');
});
}
}
// 验证码状态
let verifyCode = null;
let verifyCodePhone = null;
let verifyCodeExpire = null;
// 发送验证码(模拟)
function handleSendCode() {
const phoneInput = document.getElementById('registerPhone');
const phone = phoneInput?.value.trim();
const sendCodeBtn = document.getElementById('sendCodeBtn');
// 验证手机号格式
if (!validatePhone(phone)) {
showToast('请输入正确的手机号');
return;
}
// 检查手机号是否已注册
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
if (users.find(u => u.phone === phone)) {
showToast('该手机号已注册');
return;
}
// 生成6位验证码
const code = Math.floor(100000 + Math.random() * 900000).toString();
verifyCode = code;
verifyCodePhone = phone;
verifyCodeExpire = Date.now() + 5 * 60 * 1000; // 5分钟有效期
// 显示验证码(模拟,实际应发送短信)
console.log('验证码:', code); // 调试用
showToast(`验证码已发送:${code}`); // 实际项目中应隐藏
// 禁用按钮60秒
if (sendCodeBtn) {
sendCodeBtn.disabled = true;
let countdown = 60;
const timer = setInterval(() => {
countdown--;
if (countdown > 0) {
sendCodeBtn.textContent = `${countdown}s`;
} else {
clearInterval(timer);
sendCodeBtn.disabled = false;
sendCodeBtn.textContent = '获取验证码';
}
}, 1000);
}
}
// 验证手机号格式
function validatePhone(phone) {
// 中国手机号11位以1开头
const phoneRegex = /^1[3-9]\d{9}$/;
return phoneRegex.test(phone);
}
// 验证邮箱格式
function validateEmail(email) {
if (!email) return true; // 可选,空值有效
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function handleRegister() {
const username = document.getElementById('registerUsername')?.value.trim();
const phone = document.getElementById('registerPhone')?.value.trim();
const code = document.getElementById('registerCode')?.value.trim();
const email = document.getElementById('registerEmail')?.value.trim();
const password = document.getElementById('registerPassword')?.value;
const passwordConfirm = document.getElementById('registerPasswordConfirm')?.value;
// 验证用户名
if (!username || username.length < 3 || username.length > 20) {
showToast('用户名需要3-20个字符');
return;
}
// 验证手机号
if (!validatePhone(phone)) {
showToast('请输入正确的手机号');
return;
}
// 验证验证码
if (!code) {
showToast('请输入验证码');
return;
}
if (!verifyCode || verifyCodePhone !== phone) {
showToast('请先获取验证码');
return;
}
if (code !== verifyCode) {
showToast('验证码错误');
return;
}
if (Date.now() > verifyCodeExpire) {
showToast('验证码已过期,请重新获取');
return;
}
// 验证邮箱(可选)
if (!validateEmail(email)) {
showToast('请输入正确的邮箱格式');
return;
}
// 验证密码
if (!password || password.length < 6 || password.length > 20) {
showToast('密码需要6-20个字符');
return;
@@ -1591,9 +1765,17 @@ function handleRegister() {
return;
}
// 检查手机号是否已注册
if (users.find(u => u.phone === phone)) {
showToast('该手机号已注册');
return;
}
// 注册新用户
const newUser = {
username,
phone,
email: email || '',
password,
registeredAt: Date.now()
};
@@ -1601,8 +1783,13 @@ function handleRegister() {
users.push(newUser);
localStorage.setItem('registeredUsers', JSON.stringify(users));
// 清除验证码
verifyCode = null;
verifyCodePhone = null;
verifyCodeExpire = null;
// 自动登录
currentUser = { username: newUser.username, registeredAt: newUser.registeredAt };
currentUser = { username: newUser.username, phone: newUser.phone, registeredAt: newUser.registeredAt };
saveCurrentUser();
showToast('注册成功');
@@ -1983,9 +2170,11 @@ function showAgentChatPage() {
}
// 设置长按事件
// 全局变量当前操作的对话ID
let currentActionConvId = null;
function setupLongPressEvents(container) {
let longPressTimer = null;
let currentActionConvId = null;
container.addEventListener('touchstart', (e) => {
const item = e.target.closest('.conversation-item');
@@ -2038,236 +2227,7 @@ function setupLongPressEvents(container) {
function showConversationList() {
currentConversation = null;
// 渲染对话列表
const listHtml = `
<div class="conversation-list-page">
<header class="list-header">
<div class="header-title">
<span class="logo">🤖</span>
<h1>AI助手</h1>
</div>
<div class="header-actions">
<button class="header-btn search-toggle-btn" id="searchToggleBtn" title="搜索">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
</button>
<button class="header-btn new-chat-btn-header" id="newChatBtn" title="新建对话">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
</button>
</div>
</header>
<div class="list-content">
<div class="conversation-list" id="conversationList">
${conversations.length === 0
? '<div class="empty-list">暂无对话记录</div>'
: sortConversations().map(conv => `
<div class="conversation-item ${conv.is_pinned ? 'pinned' : ''}" data-id="${conv.id}">
${conv.is_pinned ? '<span class="pin-icon">📌</span>' : ''}
<div class="conv-title">${escapeHtml(conv.title)}</div>
<div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div>
</div>
`).join('')
}
</div>
</div>
<!-- 操作菜单 -->
<div class="action-menu" id="actionMenu">
<div class="action-menu-content">
<div class="action-menu-item" data-action="rename">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
<span>重命名</span>
</div>
<div class="action-menu-item" data-action="share">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.35 2.92 3 2.92s3-1.31 3-2.92c0-1.61-1.35-2.92-3-2.92z"/></svg>
<span>分享</span>
</div>
<div class="action-menu-item" data-action="pin">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z"/></svg>
<span id="pinText">置顶</span>
</div>
<div class="action-menu-item delete-action" data-action="delete">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></svg>
<span>删除</span>
</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-bar" id="searchBar">
<div class="search-input-wrapper">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
<input type="text" id="searchInput" placeholder="搜索对话标题或内容...">
<button class="search-close-btn" id="searchCloseBtn">
<svg viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
</button>
</div>
<div class="search-results" id="searchResults"></div>
</div>
</div>
</div>
`;
appContainer.innerHTML = listHtml;
// 绑定事件
const newChatBtn = document.getElementById('newChatBtn');
if (newChatBtn) {
newChatBtn.addEventListener('click', createNewConversation);
}
// 搜索功能
const searchToggleBtn = document.getElementById('searchToggleBtn');
const searchBar = document.getElementById('searchBar');
const searchInput = document.getElementById('searchInput');
const searchCloseBtn = document.getElementById('searchCloseBtn');
const searchResults = document.getElementById('searchResults');
if (searchToggleBtn) {
searchToggleBtn.addEventListener('click', () => {
if (searchBar) {
searchBar.classList.add('show');
if (searchInput) {
searchInput.focus();
}
}
});
}
if (searchCloseBtn) {
searchCloseBtn.addEventListener('click', () => {
hideSearchBar();
});
}
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value.trim();
if (keyword) {
searchConversations(keyword);
} else {
if (searchResults) searchResults.innerHTML = '';
}
});
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
hideSearchBar();
}
});
}
// 点击搜索结果
if (searchResults) {
searchResults.addEventListener('click', (e) => {
const item = e.target.closest('.search-result-item');
if (item) {
const id = item.getAttribute('data-id');
hideSearchBar();
openConversation(id);
}
});
}
function hideSearchBar() {
if (searchBar) {
searchBar.classList.remove('show');
}
if (searchInput) {
searchInput.value = '';
}
if (searchResults) {
searchResults.innerHTML = '';
}
}
const conversationList = document.getElementById('conversationList');
const actionMenu = document.getElementById('actionMenu');
let longPressTimer = null;
let currentActionConvId = null;
if (conversationList) {
// 点击事件
conversationList.addEventListener('click', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
const id = item.getAttribute('data-id');
openConversation(id);
}
});
// 长按事件
conversationList.addEventListener('touchstart', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
longPressTimer = setTimeout(() => {
currentActionConvId = item.getAttribute('data-id');
showActionMenu(currentActionConvId);
}, 500); // 500ms长按
}
});
conversationList.addEventListener('touchend', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
conversationList.addEventListener('touchmove', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
// 鼠标长按PC端
conversationList.addEventListener('mousedown', (e) => {
const item = e.target.closest('.conversation-item');
if (item) {
longPressTimer = setTimeout(() => {
currentActionConvId = item.getAttribute('data-id');
showActionMenu(currentActionConvId);
}, 500);
}
});
conversationList.addEventListener('mouseup', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
conversationList.addEventListener('mouseleave', () => {
if (longPressTimer) {
clearTimeout(longPressTimer);
longPressTimer = null;
}
});
}
// 操作菜单事件
if (actionMenu) {
actionMenu.addEventListener('click', (e) => {
const item = e.target.closest('.action-menu-item');
if (item && currentActionConvId) {
const action = item.getAttribute('data-action');
handleActionMenuAction(action, currentActionConvId);
hideActionMenu();
}
});
// 点击其他地方关闭菜单
document.addEventListener('click', (e) => {
if (actionMenu.classList.contains('show') && !actionMenu.contains(e.target)) {
hideActionMenu();
}
});
}
showMainPage(); // 返回主页,会正确显示过滤后的对话列表
}
// 排序对话(置顶在前)

View File

@@ -8,12 +8,12 @@
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>AI助手</title>
<link rel="stylesheet" href="style.css?v=3.3.0">
<link rel="stylesheet" href="style.css?v=3.4.0">
<link rel="manifest" href="manifest.json">
</head>
<body>
<div id="app"></div>
<script src="marked.min.js?v=3.3.0"></script>
<script src="app.js?v=3.3.0"></script>
<script src="marked.min.js?v=3.4.0"></script>
<script src="app.js?v=3.4.0"></script>
</body>
</html>

View File

@@ -753,6 +753,47 @@ body {
border-color: var(--primary);
}
.auth-input-group .required {
color: #e53e3e;
font-size: 12px;
}
.auth-input-group .optional {
color: var(--text-light);
font-size: 12px;
}
/* 手机号输入框布局 */
.phone-input-wrapper {
display: flex;
gap: 8px;
}
.phone-input-wrapper input {
flex: 1;
}
.send-code-btn {
padding: 12px 16px;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 14px;
cursor: pointer;
white-space: nowrap;
transition: opacity 0.2s;
}
.send-code-btn:hover {
opacity: 0.9;
}
.send-code-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.auth-submit-btn {
width: 100%;
padding: 14px;