diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db
index 55db87a..39e9d65 100644
Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ
diff --git a/XNSimHtml/assets/icons/png/question.png b/XNSimHtml/assets/icons/png/question.png
new file mode 100644
index 0000000..21a8838
Binary files /dev/null and b/XNSimHtml/assets/icons/png/question.png differ
diff --git a/XNSimHtml/assets/icons/png/question_b.png b/XNSimHtml/assets/icons/png/question_b.png
new file mode 100644
index 0000000..c3d7e5b
Binary files /dev/null and b/XNSimHtml/assets/icons/png/question_b.png differ
diff --git a/XNSimHtml/components/content-area.js b/XNSimHtml/components/content-area.js
index 0812f52..29608a0 100644
--- a/XNSimHtml/components/content-area.js
+++ b/XNSimHtml/components/content-area.js
@@ -96,6 +96,9 @@ class ContentArea extends HTMLElement {
case 'help':
contentElement = document.createElement('help-component');
break;
+ case 'qa':
+ contentElement = document.createElement('qa-component');
+ break;
case 'run-env-config':
contentElement = document.createElement('run-env-config');
break;
diff --git a/XNSimHtml/components/qa-component.js b/XNSimHtml/components/qa-component.js
new file mode 100644
index 0000000..a481324
--- /dev/null
+++ b/XNSimHtml/components/qa-component.js
@@ -0,0 +1,846 @@
+class QAComponent extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({ mode: 'open' });
+ this.accessLevel = 0;
+ this.questions = [];
+ this.currentPage = 1;
+ this.pageSize = 5; // 每页显示5个问题
+ this.totalPages = 1;
+ }
+
+ connectedCallback() {
+ this.checkUserAccess();
+ this.loadQuestions();
+ this.render();
+ this.setupEventListeners();
+ }
+
+ async checkUserAccess() {
+ try {
+ const userInfo = localStorage.getItem('userInfo');
+ if (userInfo) {
+ const user = JSON.parse(userInfo);
+ this.accessLevel = user.access_level || 0;
+ }
+ } catch (error) {
+ console.error('获取用户权限失败:', error);
+ this.accessLevel = 0;
+ }
+ }
+
+ async loadQuestions() {
+ try {
+ console.log('Loading page:', this.currentPage);
+ const response = await fetch(`/api/qa/questions?sort=desc`);
+ const data = await response.json();
+ if (data.success) {
+ this.questions = data.questions;
+ this.renderQuestions();
+ } else {
+ console.error('加载问题失败:', data.message);
+ }
+ } catch (error) {
+ console.error('加载问题失败:', error);
+ }
+ }
+
+ render() {
+ this.shadowRoot.innerHTML = `
+
+
+
+
+
+
+
+
确认删除
+
确定要删除这个内容吗?此操作不可恢复。
+
+
+
+
+
+
+ `;
+ }
+
+ setupEventListeners() {
+ const askQuestionBtn = this.shadowRoot.querySelector('.ask-question-btn');
+ const modal = this.shadowRoot.querySelector('.modal-overlay');
+ const modalClose = this.shadowRoot.querySelector('.modal-close');
+ const cancelBtn = this.shadowRoot.querySelector('.cancel-btn');
+ const submitBtn = this.shadowRoot.querySelector('.submit-btn');
+
+ const closeModal = () => {
+ modal.classList.remove('active');
+ this.shadowRoot.getElementById('questionTitle').value = '';
+ this.shadowRoot.getElementById('questionContent').value = '';
+ };
+
+ askQuestionBtn.addEventListener('click', () => {
+ modal.classList.add('active');
+ });
+
+ modalClose.addEventListener('click', closeModal);
+ cancelBtn.addEventListener('click', closeModal);
+
+ // 点击模态框外部关闭
+ modal.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ closeModal();
+ }
+ });
+
+ submitBtn.addEventListener('click', async () => {
+ const title = this.shadowRoot.getElementById('questionTitle').value.trim();
+ const content = this.shadowRoot.getElementById('questionContent').value.trim();
+
+ if (!title || !content) {
+ alert('请填写完整的问题信息');
+ return;
+ }
+
+ try {
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
+ const response = await fetch('/api/qa/questions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ title,
+ content,
+ userInfo
+ })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ closeModal();
+ await this.loadQuestions();
+ } else {
+ alert(data.message || '创建问题失败');
+ }
+ } catch (error) {
+ console.error('创建问题失败:', error);
+ alert('创建问题失败,请稍后重试');
+ }
+ });
+ }
+
+ async addAnswer(questionId, content) {
+ try {
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
+ const response = await fetch(`/api/qa/questions/${questionId}/answers`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ content,
+ userInfo
+ })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ await this.loadQuestions();
+ } else {
+ alert(data.message || '添加回答失败');
+ }
+ } catch (error) {
+ console.error('添加回答失败:', error);
+ alert('添加回答失败,请稍后重试');
+ }
+ }
+
+ async deleteQuestion(questionId) {
+ try {
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
+ const response = await fetch(`/api/qa/questions/${questionId}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ userInfo })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ await this.loadQuestions();
+ } else {
+ alert(data.message || '删除问题失败');
+ }
+ } catch (error) {
+ console.error('删除问题失败:', error);
+ alert('删除问题失败,请稍后重试');
+ }
+ }
+
+ async deleteAnswer(answerId) {
+ try {
+ const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
+ const response = await fetch(`/api/qa/answers/${answerId}`, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ userInfo })
+ });
+
+ const data = await response.json();
+ if (data.success) {
+ await this.loadQuestions();
+ } else {
+ alert(data.message || '删除回答失败');
+ }
+ } catch (error) {
+ console.error('删除回答失败:', error);
+ alert('删除回答失败,请稍后重试');
+ }
+ }
+
+ showConfirmDialog(type, id) {
+ const modal = this.shadowRoot.querySelector('.confirm-modal');
+ const confirmBtn = modal.querySelector('.confirm-btn');
+ const cancelBtn = modal.querySelector('.cancel-btn');
+ const message = modal.querySelector('.confirm-message');
+
+ message.textContent = type === 'question' ?
+ '确定要删除这个问题吗?此操作不可恢复。' :
+ '确定要删除这个回答吗?此操作不可恢复。';
+
+ const closeModal = () => {
+ modal.classList.remove('active');
+ };
+
+ const handleConfirm = async () => {
+ if (type === 'question') {
+ await this.deleteQuestion(id);
+ } else {
+ await this.deleteAnswer(id);
+ }
+ closeModal();
+ };
+
+ // 移除旧的事件监听器
+ const newConfirmBtn = confirmBtn.cloneNode(true);
+ const newCancelBtn = cancelBtn.cloneNode(true);
+ confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
+ cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);
+
+ // 添加新的事件监听器
+ newConfirmBtn.addEventListener('click', handleConfirm);
+ newCancelBtn.addEventListener('click', closeModal);
+
+ modal.classList.add('active');
+ }
+
+ renderQuestions() {
+ const qaList = this.shadowRoot.getElementById('qaList');
+
+ if (this.questions.length === 0) {
+ qaList.innerHTML = '暂无问题
';
+ return;
+ }
+
+ this.totalPages = Math.ceil(this.questions.length / this.pageSize);
+ const startIndex = (this.currentPage - 1) * this.pageSize;
+ const endIndex = Math.min(startIndex + this.pageSize, this.questions.length);
+ const currentPageQuestions = this.questions.slice(startIndex, endIndex);
+
+ qaList.innerHTML = currentPageQuestions.map(question => `
+
+
+ ${question.title}
+
+
+
+ 提问者:${question.author} | 时间:${question.created_at}
+
+
${question.content}
+ ${question.answers.map(answer => `
+
+
${answer.content}
+
+ 回答者:${answer.author} | 时间:${answer.created_at}
+
+
+
+ `).join('')}
+
+
+
+ `).join('');
+
+ // 添加分页
+ const pagination = this.shadowRoot.getElementById('pagination');
+ pagination.innerHTML = `
+
+ 第 ${this.currentPage} 页 / 共 ${this.totalPages} 页
+
+ `;
+
+ // 添加分页按钮事件监听
+ this.addPaginationListeners();
+
+ // 重新绑定回答按钮的事件监听器
+ const answerBtns = qaList.querySelectorAll('.answer-btn');
+ answerBtns.forEach(btn => {
+ btn.addEventListener('click', () => {
+ const questionId = parseInt(btn.dataset.questionId);
+ const answerForm = qaList.querySelector(`.answer-form[data-question-id="${questionId}"]`);
+ answerForm.classList.add('active');
+ });
+ });
+
+ // 绑定回答表单的事件监听器
+ const answerForms = qaList.querySelectorAll('.answer-form');
+ answerForms.forEach(form => {
+ const questionId = parseInt(form.dataset.questionId);
+ const cancelBtn = form.querySelector('.cancel-btn');
+ const submitBtn = form.querySelector('.submit-btn');
+ const textarea = form.querySelector('textarea');
+
+ cancelBtn.addEventListener('click', () => {
+ form.classList.remove('active');
+ textarea.value = '';
+ });
+
+ submitBtn.addEventListener('click', () => {
+ const content = textarea.value.trim();
+ if (!content) {
+ alert('请输入回答内容');
+ return;
+ }
+ this.addAnswer(questionId, content);
+ form.classList.remove('active');
+ textarea.value = '';
+ });
+ });
+ }
+
+ // 添加分页按钮事件监听
+ addPaginationListeners() {
+ const prevButton = this.shadowRoot.getElementById('prevPage');
+ const nextButton = this.shadowRoot.getElementById('nextPage');
+
+ if (prevButton) {
+ prevButton.addEventListener('click', () => {
+ if (this.currentPage > 1) {
+ this.currentPage--;
+ this.renderQuestions();
+ }
+ });
+ }
+
+ if (nextButton) {
+ nextButton.addEventListener('click', () => {
+ if (this.currentPage < this.totalPages) {
+ this.currentPage++;
+ this.renderQuestions();
+ }
+ });
+ }
+ }
+}
+
+customElements.define('qa-component', QAComponent);
\ No newline at end of file
diff --git a/XNSimHtml/components/sub-toolbar.js b/XNSimHtml/components/sub-toolbar.js
index ddfd19b..0bc1a4b 100644
--- a/XNSimHtml/components/sub-toolbar.js
+++ b/XNSimHtml/components/sub-toolbar.js
@@ -166,6 +166,10 @@ class SubToolbar extends HTMLElement {
帮助
+
+

+ Q&A
+
+