fix: 所有 onclick 内联改为事件监听绑定

- 对话列表:新建按钮、对话项、删除按钮使用事件委托
- 对话页面:返回、清空、发送按钮使用 addEventListener
- 快捷语句按钮使用事件监听
- 消息操作按钮(复制、重新生成、删除)使用事件监听
- 修复动态生成 HTML 后 onclick 无法正确绑定的作用域问题
This commit is contained in:
2026-04-26 00:36:46 +08:00
parent 1e5b20294c
commit 15ce68cd6e

View File

@@ -13,16 +13,19 @@ let conversations = []; // 对话列表
let currentConversation = null; // 当前对话 let currentConversation = null; // 当前对话
let isLoading = false; let isLoading = false;
// DOM 元素 // DOM 元素(初始为 null在 openConversation 时重新获取)
const appContainer = document.getElementById('app'); let appContainer = null;
const messagesContainer = document.getElementById('messagesContainer'); let messagesContainer = null;
const messagesDiv = document.getElementById('messages'); let messagesDiv = null;
const userInput = document.getElementById('userInput'); let userInput = null;
const sendBtn = document.getElementById('sendBtn'); let sendBtn = null;
const welcome = document.getElementById('welcome'); let welcome = null;
// 初始化 // 初始化
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// 初始化 appContainer
appContainer = document.getElementById('app');
// 从本地存储加载对话列表 // 从本地存储加载对话列表
const saved = localStorage.getItem('conversations'); const saved = localStorage.getItem('conversations');
if (saved) { if (saved) {
@@ -68,19 +71,19 @@ function showConversationList() {
</header> </header>
<div class="list-content"> <div class="list-content">
<button class="new-chat-btn" onclick="createNewConversation()"> <button class="new-chat-btn" id="newChatBtn">
<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg> <svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
新建对话 新建对话
</button> </button>
<div class="conversation-list"> <div class="conversation-list" id="conversationList">
${conversations.length === 0 ${conversations.length === 0
? '<div class="empty-list">暂无对话记录</div>' ? '<div class="empty-list">暂无对话记录</div>'
: conversations.map(conv => ` : conversations.map(conv => `
<div class="conversation-item" onclick="openConversation('${conv.id}')"> <div class="conversation-item" data-id="${conv.id}">
<div class="conv-title">${escapeHtml(conv.title)}</div> <div class="conv-title">${escapeHtml(conv.title)}</div>
<div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div> <div class="conv-meta">${conv.messages.length} 条消息 · ${formatTime(conv.updatedAt)}</div>
<button class="conv-delete-btn" onclick="event.stopPropagation(); deleteConversation('${conv.id}')" title="删除对话"> <button class="conv-delete-btn" data-id="${conv.id}" title="删除对话">
<svg viewBox="0 0 24 24" width="16" height="16"><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> <svg viewBox="0 0 24 24" width="16" height="16"><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>
</button> </button>
</div> </div>
@@ -92,6 +95,29 @@ function showConversationList() {
`; `;
appContainer.innerHTML = listHtml; appContainer.innerHTML = listHtml;
// 绑定事件
const newChatBtn = document.getElementById('newChatBtn');
if (newChatBtn) {
newChatBtn.addEventListener('click', createNewConversation);
}
const conversationList = document.getElementById('conversationList');
if (conversationList) {
conversationList.addEventListener('click', (e) => {
const item = e.target.closest('.conversation-item');
const deleteBtn = e.target.closest('.conv-delete-btn');
if (deleteBtn) {
e.stopPropagation();
const id = deleteBtn.getAttribute('data-id');
deleteConversation(id);
} else if (item) {
const id = item.getAttribute('data-id');
openConversation(id);
}
});
}
} }
// 创建新对话 // 创建新对话
@@ -121,14 +147,14 @@ function openConversation(id) {
const chatHtml = ` const chatHtml = `
<div id="chatPage"> <div id="chatPage">
<header class="header"> <header class="header">
<button class="back-btn" onclick="showConversationList()"> <button class="back-btn" id="backBtn">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg> <svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
</button> </button>
<div class="header-title"> <div class="header-title">
<span class="logo">🤖</span> <span class="logo">🤖</span>
<h1>${escapeHtml(currentConversation.title)}</h1> <h1>${escapeHtml(currentConversation.title)}</h1>
</div> </div>
<button class="clear-btn" onclick="clearCurrentChat()" title="清空对话"> <button class="clear-btn" id="clearBtn" title="清空对话">
<svg viewBox="0 0 24 24" width="20" height="20"><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> <svg viewBox="0 0 24 24" width="20" height="20"><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> </button>
</header> </header>
@@ -152,10 +178,8 @@ function openConversation(id) {
id="userInput" id="userInput"
placeholder="输入消息..." placeholder="输入消息..."
rows="1" rows="1"
onkeydown="handleKeyDown(event)"
oninput="autoResize(this)"
></textarea> ></textarea>
<button class="send-btn" onclick="sendMessage()" id="sendBtn"> <button class="send-btn" id="sendBtn">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg> <svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button> </button>
</div> </div>
@@ -171,9 +195,17 @@ function openConversation(id) {
sendBtn = document.getElementById('sendBtn'); sendBtn = document.getElementById('sendBtn');
welcome = document.getElementById('welcome'); welcome = document.getElementById('welcome');
// 渲染消息 // 绑定按钮事件
renderMessages(); const backBtn = document.getElementById('backBtn');
userInput.focus(); if (backBtn) backBtn.addEventListener('click', showConversationList);
const clearBtn = document.getElementById('clearBtn');
if (clearBtn) clearBtn.addEventListener('click', clearCurrentChat);
// 绑定输入事件
userInput.addEventListener('keydown', handleKeyDown);
userInput.addEventListener('input', (e) => autoResize(e.target));
sendBtn.addEventListener('click', sendMessage);
// 绑定快捷按钮事件 // 绑定快捷按钮事件
document.querySelectorAll('.quick-btn').forEach(btn => { document.querySelectorAll('.quick-btn').forEach(btn => {
@@ -183,6 +215,10 @@ function openConversation(id) {
sendMessage(); sendMessage();
}); });
}); });
// 渲染消息
renderMessages();
userInput.focus();
} }
// 删除对话 // 删除对话
@@ -424,17 +460,17 @@ function renderMessages() {
const actions = isUser const actions = isUser
? `<div class="message-actions"> ? `<div class="message-actions">
<button class="action-btn copy-btn" onclick="copyMessage(${index})" title="复制">${copyIcon}</button> <button class="action-btn copy-btn" data-index="${index}" title="复制">${copyIcon}</button>
<button class="action-btn delete-btn" onclick="deleteMessage(${index})" title="删除"> <button class="action-btn delete-btn" data-index="${index}" title="删除">
<svg viewBox="0 0 24 24" width="16" height="16"><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> <svg viewBox="0 0 24 24" width="16" height="16"><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>
</button> </button>
</div>` </div>`
: `<div class="message-actions"> : `<div class="message-actions">
<button class="action-btn copy-btn" onclick="copyMessage(${index})" title="复制">${copyIcon}</button> <button class="action-btn copy-btn" data-index="${index}" title="复制">${copyIcon}</button>
<button class="action-btn regenerate-btn" onclick="regenerate(${index})" title="重新生成"> <button class="action-btn regenerate-btn" data-index="${index}" title="重新生成">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
</button> </button>
<button class="action-btn delete-btn" onclick="deleteMessage(${index})" title="删除"> <button class="action-btn delete-btn" data-index="${index}" title="删除">
<svg viewBox="0 0 24 24" width="16" height="16"><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> <svg viewBox="0 0 24 24" width="16" height="16"><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>
</button> </button>
</div>`; </div>`;
@@ -450,6 +486,17 @@ function renderMessages() {
`; `;
}).join(''); }).join('');
// 绑定消息操作按钮事件(事件委托)
messagesDiv.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', () => copyMessage(parseInt(btn.dataset.index)));
});
messagesDiv.querySelectorAll('.regenerate-btn').forEach(btn => {
btn.addEventListener('click', () => regenerate(parseInt(btn.dataset.index)));
});
messagesDiv.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', () => deleteMessage(parseInt(btn.dataset.index)));
});
scrollToBottom(); scrollToBottom();
} }