这个的后端代码参见此文
使用语言向量建立常见问题的模糊搜索-CSDN博客https://blog.csdn.net/chenchihwen/article/details/144207262?spm=1001.2014.3001.5501
这段代码实现了一个简单的问答页面,页面分为左右两部分,左侧用于展示对话记录,右侧用于用户输入问题并提交,获取答案后在对应区域显示答案,同时会将每一轮的问答信息添加到对话记录中进行展示,整体通过 HTML 结构定义页面布局,结合 JavaScript 实现交互功能。
HTML 部分详细分析
- 文档结构与头部设置(
<head>
部分):
<head><meta charset="UTF-8"><title>问答测试</title><style>...(内联CSS样式,后续单独分析)</style>
</head>
- 首先通过
<meta charset="UTF-8">
指定了文档的字符编码为 UTF-8,确保能正确显示各种字符。 <title>
标签定义了页面的标题为 “问答测试”,会显示在浏览器的标题栏。- 内联的
<style>
标签中定义了页面的样式规则,用于控制页面元素的外观呈现。
- 页面主体结构(
<body>
部分):
<body><div class="main-container"><div class="conversation-log-container"><h2 class="conversation-title">对话记录</h2><div id="conversationDiv"></div></div><div class="question-answer-container"><h1 class="qa-title">提交问题与答案显示</h1><form id="questionForm" action="/process_question" method="post"><label for="user_question">请输入你的问题:</label><br><input type="text" id="user_question" name="user_question" required><br><input type="submit" value="提交"></form><div id="answerDiv"></div></div></div>
</body>
<body>
标签内是页面的主体内容,整体采用了flex
布局(由外层的.main-container
类样式控制),将页面划分为左右两部分来展示不同的功能区域。- 左侧对话记录区域(
.conversation-log-container
):- 包含一个
<h2>
标题标签,显示 “对话记录” 字样,用于提示该区域的作用。 - 有一个空的
<div>
元素,其id
为conversationDiv
,后续通过 JavaScript 动态往里面添加对话记录的具体内容。
- 包含一个
- 右侧提交问题与答案显示区域(
.question-answer-container
):- 首先有一个
<h1>
标题标签,显示 “提交问题与答案显示”,表明该区域功能。 - 包含一个
<form>
表单元素,其id
为questionForm
,action
属性指向"/process_question"
(对应后端处理问题的接口,和之前 Python 代码中的服务器路由相关联),method
属性为"post"
表示以 POST 方式提交表单数据。表单内有一个<label>
标签用于提示用户输入问题,一个<input>
文本框用于用户输入具体问题(设置了required
属性要求用户必须输入内容),还有一个<input>
提交按钮用于提交表单。 - 还有一个空的
<div>
元素,其id
为answerDiv
,用于在提交问题后通过 JavaScript 获取后端返回的答案并展示在此处。
- 首先有一个
CSS 样式部分详细分析(<style>
内联样式)
- 页面整体样式(
body
选择器):
body {font-family: Arial, sans-serif;background-color: #f4f4f4;display: flex;justify-content: center;align-items: center;min-height: 100vh;margin: 0;padding: 0;
}
- 设置了页面整体的字体为
Arial
或无衬线字体(sans-serif
作为备选),背景颜色为浅灰色(#f4f4f4
)。 - 利用
flex
布局将页面内容在水平和垂直方向上进行居中对齐,设置最小高度为视口高度(100vh
),并去除了页面默认的外边距和内边距。
- 主容器样式(
.main-container
选择器):
.main-container {display: flex;width: 1000px;
}
定义了主容器采用flex
布局,宽度为1000px
,用于划分左右两部分的功能区域。
3. 左侧对话记录容器样式(.conversation-log-container
选择器):
.conversation-log-container {background-color: #fff;padding: 20px;border-radius: 5px;box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);width: 450px;/* height: 1000px; */
}
- 将对话记录容器的背景色设置为白色(
#fff
),添加了内边距为20px
,设置了圆角边框(半径为5px
)以及一个淡淡的阴影效果(box-shadow
属性),使其在页面上有一定的立体感和视觉区分度。 - 宽度设置为
450px
,可以根据实际需求调整这个宽度值,注释掉的height
属性若启用可固定容器高度,目前采用默认高度由内容撑开的方式。
- 对话记录标题样式(
h2.conversation-title
选择器):
h2.conversation-title {color: #333;
}
设置对话记录标题的字体颜色为深灰色(#333
),使其与整体页面风格协调且有一定的视觉层次。
5. 对话记录展示区域样式(#conversationDiv
选择器):
#conversationDiv {margin-top: 20px;max-height: 450px;overflow-y: auto;font-family: Arial, sans-serif;
}
- 给对话记录展示区域添加了顶部外边距为
20px
,设置了最大高度为450px
,并当内容超出这个高度时启用垂直滚动条(overflow-y: auto
)来查看更多对话记录内容,字体同样遵循页面整体设置的Arial
或无衬线字体风格。
- 对话记录单项样式(
.conversation-item
选择器):
.conversation-item {margin-bottom: 10px;padding: 10px;border-bottom: 1px solid #ccc;white-space: pre-line; /* 让对话记录中的内容也能折行显示 */
}
.conversation-item:last-child {border-bottom: none;
}
- 每个对话记录单项有底部外边距为
10px
,内边距为10px
,并且底部有一条浅灰色(#ccc
)的边框用于区分不同的对话记录项,最后一项通过:last-child
伪类去除了底部边框,使其视觉上更简洁。 white-space: pre-line
属性设置让对话记录中的文本内容可以按照正常的文本换行规则进行折行显示,方便展示较长的问答内容。
- 问题文本样式(
.question
选择器):
.question {font-weight: bold;
}
将对话记录中的问题文本设置为加粗字体,便于区分问题和答案部分,增强可读性。
8. 右侧提交问题与答案显示容器样式(.question-answer-container
选择器):
.question-answer-container {background-color: #fff;padding: 20px;border-radius: 5px;box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);width: 550px;margin-left: 50px;
}
和左侧对话记录容器类似,设置了白色背景、内边距、圆角边框、阴影效果等,宽度设置为550px
,并且通过margin-left
属性设置了与左侧容器的间距为50px
,使其在页面上合理布局。
9. 右侧标题样式(h1.qa-title
选择器):
h1.qa-title {color: #333;
}
设置右侧区域标题的字体颜色为深灰色(#333
),保持页面整体视觉风格的一致性。
10. 表单样式(form#questionForm
选择器):
form#questionForm {text-align: center;
}
将表单内的元素在水平方向上进行居中对齐,让页面布局看起来更整齐美观。
11. 标签样式(label
选择器):
label {display: block;margin-bottom: 10px;
}
使每个<label>
标签单独占一行显示,并设置了底部外边距为10px
,方便页面排版和阅读提示信息。
12. 输入框样式(input[type="text"]
选择器):
input[type="text"] {width: 100%;padding: 10px;margin-bottom: 20px;border: 1px solid #ccc;border-radius: 3px;
}
定义了文本输入框的样式,宽度占满父容器(100%
),添加了内边距为10px
,底部外边距为20px
,设置了浅灰色的边框以及较小的圆角边框效果,使其外观简洁且符合常见的输入框设计风格。
13. 提交按钮样式(input[type="submit"]
选择器):
input[type="submit"] {background-color: #007BFF;color: #fff;padding: 10px 20px;border: none;border-radius: 3px;cursor: pointer;
}
将提交按钮的背景色设置为蓝色(#007BFF
,常见的按钮强调色),字体颜色为白色,添加了内边距,去除了边框,设置了小的圆角边框效果,并添加了鼠标指针悬停变为手型(cursor: pointer
)的交互效果,使其在页面上很容易被识别和操作。
14. 答案显示区域样式(#answerDiv
选择器):
#answerDiv {white-space: pre-line;margin-top: 20px;word-break: break-all; /* 确保长文本能正常换行显示 */
}
和对话记录展示区域类似,设置了顶部外边距为20px
,white-space: pre-line
让内容可以按正常文本换行规则显示,同时添加了word-break: break-all
属性确保长文本能在合适的位置正常换行,避免出现文本超出容器宽度而显示不全的情况。
JavaScript 部分详细分析
- 全局变量定义与表单提交事件监听:
// 用于存储对话记录的数组
var conversation = [];document.getElementById('questionForm').addEventListener('submit', function (e) {e.preventDefault();var questionInput = document.getElementById('user_question');var questionText = questionInput.value;var xhr = new XMLHttpRequest();xhr.open('POST', '/process_question', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {var answerDiv = document.getElementById('answerDiv');// 使用 innerHTML 将响应文本当作 HTML 内容解析,使图片、超链接等能正确显示answerDiv.innerHTML = xhr.responseText;// 将本次的问题和答案添加到对话记录数组中conversation.push({question: questionText,answer: xhr.responseText});// 清空输入框,方便继续提问questionInput.value = "";// 重新渲染对话记录展示区域renderConversation();}};xhr.send('user_question=' + encodeURIComponent(questionText));
});
- 首先定义了一个名为
conversation
的全局数组,用于存储每一轮问答的信息(问题和对应的答案)。 - 通过
document.getElementById
获取到页面中的表单元素,并给其添加submit
事件监听器。当用户提交表单时:- 调用
e.preventDefault()
阻止表单默认的提交行为,避免页面刷新。 - 获取用户在输入框中输入的问题文本内容。
- 创建一个
XMLHttpRequest
对象用于发送 HTTP 请求到后端("/process_question"
这个接口,对应后端 Python 代码中处理问题的路由),设置请求方式为POST
,并设置请求头的Content-Type
为"application/x-www-form-urlencoded"
。 - 给
xhr
对象的onreadystatechange
事件绑定回调函数,当请求的状态变为4
(表示请求已完成)且状态码为200
(表示成功)时:- 获取页面中用于显示答案的
<div>
元素,将后端返回的响应文本通过innerHTML
属性赋值给该元素,这样可以解析响应文本中的 HTML 内容(比如如果答案中有图片、超链接等元素能正确显示)。 - 将本次的问题和答案以对象的形式添加到
conversation
数组中。 - 清空输入框内容,方便用户继续提问。
- 调用
renderConversation
函数重新渲染对话记录展示区域,使其能及时更新显示最新的问答信息。
- 获取页面中用于显示答案的
- 最后通过
xhr.send
方法发送包含用户问题的请求数据,需要对问题文本进行encodeURIComponent
编码,以符合 URL 编码规范。
- 调用
- 对话记录渲染函数(
renderConversation
):
// 渲染对话记录展示区域的函数
function renderConversation() {var conversationDiv = document.getElementById('conversationDiv');conversationDiv.innerHTML = "";conversation.forEach(function (item, index) {var conversationItem = document.createElement('div');conversationItem.className = 'conversation-item';var questionDiv = document.createElement('div');questionDiv.className = 'question';questionDiv.innerHTML = "Q" + (index + 1) + ": " + item.question;var answerDiv = document.createElement('div');answerDiv.className = 'answer';// 使用 innerHTML 将答案内容当作 HTML 内容解析,使图片等能正确显示answerDiv.innerHTML = "A" + (index + 1) + ": " + item.answer;conversationItem.appendChild(questionDiv);conversationItem.appendChild(answerDiv);conversationDiv.appendChild(conversationItem);});
}
这个函数用于更新对话记录展示区域的内容,首先获取到id
为conversationDiv
的元素,并清空其原有的innerHTML
内容。然后遍历conversation
数组中的每一项(每个问答对象):
- 创建一个新的
<div>
元素作为对话记录单项的容器,并设置其类名为conversation-item
,以应用对应的 CSS 样式。 - 分别创建用于显示问题和答案的
<div>
子元素,设置相应的类名(question
和answer
),并通过innerHTML
将问答内容填充进去,同样可以解析 HTML 内容。 - 将问题和答案的
<div>
子元素添加到对话记录单项容器中,再将该容器添加到对话记录展示区域的<div>
元素内,从而实现对话记录的动态渲染和展示。
潜在的改进点和注意事项
- 兼容性问题:代码中使用的
XMLHttpRequest
在现代浏览器中基本都支持,但对于一些较老版本的浏览器可能存在兼容性问题。可以考虑使用更现代的fetch
API 来替代,它提供了更简洁、功能更强大的异步请求方式,并且有较好的浏览器兼容性(不过需要进行适当的错误处理和功能适配)。 - 安全性考虑:在将后端返回的内容直接通过
innerHTML
解析并显示时,如果后端返回的数据不可信(比如存在恶意的 HTML、JavaScript 代码),可能会导致安全漏洞,如跨站脚本攻击(XSS)。建议对后端返回的数据进行严格的过滤和转义处理,只允许显示安全的文本内容或者对 HTML 标签进行白名单验证后再进行解析显示。 - 响应数据格式处理优化:目前假设后端返回的文本可以直接通过
innerHTML
进行解析显示,但如果后端返回的数据格式比较复杂(比如是 JSON 格式包含了多种类型的数据需要分别处理),需要进一步优化代码逻辑,对响应数据进行准确的解析和展示,例如可以通过JSON.parse
先将 JSON 数据解析成 JavaScript 对象,再根据具体的结构进行相应的页面渲染操作。
完整代码如下
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>问答测试</title><style>body {font-family: Arial, sans-serif;background-color: #f4f4f4;display: flex;justify-content: center;align-items: center;min-height: 100vh;margin: 0;padding: 0;}.main-container {display: flex;width: 1000px;}/* 左侧对话记录样式 */.conversation-log-container {background-color: #fff;padding: 20px;border-radius: 5px;box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);width: 450px;/* height: 1000px; */}h2.conversation-title {color: #333;}#conversationDiv {margin-top: 20px;max-height: 450px;overflow-y: auto;font-family: Arial, sans-serif;}.conversation-item {margin-bottom: 10px;padding: 10px;border-bottom: 1px solid #ccc;white-space: pre-line; /* 让对话记录中的内容也能折行显示 */}.conversation-item:last-child {border-bottom: none;}.question {font-weight: bold;}/* 右侧提交问题和答案显示样式 */.question-answer-container {background-color: #fff;padding: 20px;border-radius: 5px;box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);width: 550px;margin-left: 50px;}h1.qa-title {color: #333;}form#questionForm {text-align: center;}label {display: block;margin-bottom: 10px;}input[type="text"] {width: 100%;padding: 10px;margin-bottom: 20px;border: 1px solid #ccc;border-radius: 3px;}input[type="submit"] {background-color: #007BFF;color: #fff;padding: 10px 20px;border: none;border-radius: 3px;cursor: pointer;}#answerDiv {white-space: pre-line;margin-top: 20px;word-break: break-all; /* 确保长文本能正常换行显示 */}</style>
</head><body><div class="main-container"><div class="conversation-log-container"><h2 class="conversation-title">对话记录</h2><div id="conversationDiv"></div></div><div class="question-answer-container"><h1 class="qa-title">提交问题与答案显示</h1><form id="questionForm" action="/process_question" method="post"><label for="user_question">请输入你的问题:</label><br><input type="text" id="user_question" name="user_question" required><br><input type="submit" value="提交"></form><div id="answerDiv"></div></div></div><script>// 用于存储对话记录的数组var conversation = [];document.getElementById('questionForm').addEventListener('submit', function (e) {e.preventDefault();var questionInput = document.getElementById('user_question');var questionText = questionInput.value;var xhr = new XMLHttpRequest();xhr.open('POST', '/process_question', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {var answerDiv = document.getElementById('answerDiv');// 使用 innerHTML 将响应文本当作 HTML 内容解析,使图片、超链接等能正确显示answerDiv.innerHTML = xhr.responseText;// 将本次的问题和答案添加到对话记录数组中conversation.push({question: questionText,answer: xhr.responseText});// 清空输入框,方便继续提问questionInput.value = "";// 重新渲染对话记录展示区域renderConversation();}};xhr.send('user_question=' + encodeURIComponent(questionText));});// 渲染对话记录展示区域的函数function renderConversation() {var conversationDiv = document.getElementById('conversationDiv');conversationDiv.innerHTML = "";conversation.forEach(function (item, index) {var conversationItem = document.createElement('div');conversationItem.className = 'conversation-item';var questionDiv = document.createElement('div');questionDiv.className = 'question';questionDiv.innerHTML = "Q" + (index + 1) + ": " + item.question;var answerDiv = document.createElement('div');answerDiv.className = 'answer';// 使用 innerHTML 将答案内容当作 HTML 内容解析,使图片等能正确显示answerDiv.innerHTML = "A" + (index + 1) + ": " + item.answer;conversationItem.appendChild(questionDiv);conversationItem.appendChild(answerDiv);conversationDiv.appendChild(conversationItem);});}</script></body></html>