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 + Q&A +
+