XNSim/XNSimHtml/components/update-history.js

769 lines
26 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class UpdateHistory extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.versions = [];
this.accessLevel = 0;
this.currentPage = 1;
this.pageSize = 10;
this.totalPages = 1;
}
connectedCallback() {
this.render();
this.loadData();
this.checkUserAccess();
}
// 检查用户权限
async checkUserAccess() {
try {
const response = await fetch('/api/check-auth', {
credentials: 'include'
});
const result = await response.json();
if (result.success) {
this.accessLevel = result.user.access_level || 0;
} else {
this.accessLevel = 0;
}
this.updateAddButton();
} catch (error) {
console.error('获取用户权限失败:', error);
this.accessLevel = 0;
this.updateAddButton();
}
}
// 更新添加按钮显示状态
updateAddButton() {
const addButton = this.shadowRoot.querySelector('#addVersionBtn');
if (addButton) {
addButton.style.display = this.accessLevel >= 2 ? 'flex' : 'none';
}
}
async loadData() {
try {
const response = await fetch('/api/versions');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// 按版本号降序排序,然后按日期降序
this.versions = data.sort((a, b) => {
const versionA = parseFloat(a.verNum);
const versionB = parseFloat(b.verNum);
if (versionA !== versionB) {
return versionB - versionA;
}
const dateA = new Date(a.date + ' ' + a.time);
const dateB = new Date(b.date + ' ' + b.time);
return dateB - dateA;
});
this.renderVersions();
} catch (error) {
console.error('加载版本信息失败:', error);
this.showError();
}
}
renderVersions() {
const contentContainer = this.shadowRoot.querySelector('#content');
if (this.versions.length === 0) {
contentContainer.innerHTML = '<div class="error">暂无更新记录</div>';
return;
}
this.totalPages = Math.ceil(this.versions.length / this.pageSize);
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = Math.min(startIndex + this.pageSize, this.versions.length);
const currentPageVersions = this.versions.slice(startIndex, endIndex);
let html = `
<div class="version-list">
${currentPageVersions.map(version => `
<div class="version-item" data-version="${version.verNum}">
<div class="version-header">
<div class="version-info">
<span class="version-number">v${version.verNum}</span>
<span class="version-title">${version.title}</span>
<span class="version-date">${version.date} ${version.time}</span>
<span class="version-author">作者: ${version.author || '未知用户'}</span>
</div>
${version.note ? `
<button class="expand-btn" aria-label="展开详情">
<img src="assets/icons/png/chevron-down_b.png" alt="展开">
</button>
` : ''}
</div>
${version.note ? `
<div class="version-description">${version.note}</div>
` : ''}
</div>
`).join('')}
</div>
<div class="pagination">
<button class="pagination-btn" id="prevPage" ${this.currentPage === 1 ? 'disabled' : ''}>
上一页
</button>
<span class="page-info">第 ${this.currentPage} 页 / 共 ${this.totalPages} 页</span>
<button class="pagination-btn" id="nextPage" ${this.currentPage === this.totalPages ? 'disabled' : ''}>
下一页
</button>
</div>
`;
contentContainer.innerHTML = html;
// 添加展开/收起事件
this.addExpandListeners();
// 更新添加按钮显示状态
this.updateAddButton();
// 添加分页按钮事件监听
this.addPaginationListeners();
}
addExpandListeners() {
const expandButtons = this.shadowRoot.querySelectorAll('.expand-btn');
expandButtons.forEach((button, index) => {
// 移除旧事件(如果有)
button.removeEventListener('click', this.onButtonClick);
// 添加新事件
button.addEventListener('click', (e) => {
const versionItem = button.closest('.version-item');
const description = versionItem.querySelector('.version-description');
// 切换显示/隐藏状态
description.classList.toggle('show');
// 更新按钮状态
button.classList.toggle('expanded');
// 更新按钮文本和图标
const img = button.querySelector('img');
if (description.classList.contains('show')) {
button.setAttribute('aria-label', '收起详情');
img.alt = '收起';
} else {
button.setAttribute('aria-label', '展开详情');
img.alt = '展开';
}
});
});
}
showError() {
const contentContainer = this.shadowRoot.querySelector('#content');
contentContainer.innerHTML = '<div class="error">加载失败,请稍后重试</div>';
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.container {
background: white;
border-radius: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 25px;
position: relative;
overflow: visible;
min-height: 200px;
}
.title-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid #eee;
}
.title {
font-size: 24px;
color: #2c3e50;
margin: 0;
}
#addVersionBtn {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: absolute;
right: 30px;
top: 30px;
}
#addVersionBtn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
#addVersionBtn img {
width: 16px;
height: 16px;
}
.version-list {
position: relative;
padding-left: 30px;
margin-bottom: 25px;
}
.version-item {
position: relative;
margin-bottom: 30px;
}
.version-item:last-child {
margin-bottom: 0;
}
.version-item::before {
content: '';
position: absolute;
left: -30px;
top: 0;
width: 16px;
height: 16px;
border-radius: 50%;
background: #667eea;
border: 3px solid white;
box-shadow: 0 0 0 2px #667eea;
}
.version-item::after {
content: '';
position: absolute;
left: -23px;
top: 20px;
bottom: -30px;
width: 2px;
background: #e2e8f0;
}
.version-item:last-child::after {
display: none;
}
.version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.version-info {
display: flex;
align-items: center;
line-height: 1;
}
.version-number {
font-size: 18px;
font-weight: bold;
color: #667eea;
margin-right: 15px;
display: inline-flex;
align-items: center;
}
.version-title {
font-size: 16px;
font-weight: 500;
color: #2d3748;
margin-right: 15px;
display: inline-flex;
align-items: center;
}
.version-date {
color: #718096;
font-size: 14px;
display: inline-flex;
align-items: center;
}
.version-author {
color: #718096;
font-size: 14px;
display: inline-flex;
align-items: center;
margin-left: 15px;
}
.expand-btn {
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 50%;
transition: background-color 0.3s;
padding: 0;
margin: 0;
}
.expand-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.expand-btn img {
width: 20px;
height: 20px;
transition: transform 0.3s;
}
.expand-btn.expanded img {
transform: rotate(180deg);
}
.version-description {
display: none;
padding: 10px 15px;
background-color: #f8f9fa;
border-radius: 8px;
margin-top: 10px;
color: #4a5568;
line-height: 1.6;
}
.version-description.show {
display: block;
}
.loading {
text-align: center;
padding: 40px;
color: #718096;
}
.error {
text-align: center;
padding: 40px;
color: #e53e3e;
}
/* 对话框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
display: none;
}
.modal-overlay.active {
display: flex;
}
.modal {
background-color: white;
border-radius: 12px;
padding: 24px;
width: 500px;
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.modal-title {
font-size: 20px;
font-weight: bold;
color: #2c3e50;
margin: 0;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #718096;
line-height: 1;
padding: 0;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #2d3748;
}
.form-control {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 15px;
transition: border-color 0.3s;
box-sizing: border-box;
}
.form-control:focus {
border-color: #667eea;
outline: none;
}
.form-control:disabled {
background-color: #f8f9fa;
}
textarea.form-control {
min-height: 120px;
resize: vertical;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.btn {
padding: 10px 16px;
border-radius: 6px;
font-size: 15px;
cursor: pointer;
transition: all 0.3s;
}
.btn-secondary {
background-color: #f8f9fa;
border: 1px solid #ddd;
color: #718096;
}
.btn-secondary:hover {
background-color: #edf2f7;
}
.btn-primary {
background: linear-gradient(135deg, #667eea, #764ba2);
border: none;
color: white;
}
.btn-primary:hover {
opacity: 0.9;
transform: translateY(-1px);
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 30px;
gap: 15px;
padding: 15px 0;
}
.pagination-btn {
padding: 8px 16px;
border: 1px solid #ddd;
border-radius: 6px;
background-color: white;
color: #2d3748;
cursor: pointer;
transition: all 0.3s ease;
min-width: 80px;
}
.pagination-btn:hover:not(:disabled) {
background-color: #f8f9fa;
border-color: #667eea;
color: #667eea;
}
.pagination-btn:disabled {
background-color: #f8f9fa;
color: #a0aec0;
cursor: not-allowed;
}
.page-info {
color: #4a5568;
font-size: 14px;
}
</style>
<div class="container">
<div class="title-container">
<h1 class="title">更新记录</h1>
</div>
<button id="addVersionBtn" style="display: none;">
<img src="assets/icons/png/plus.png" alt="添加">
提交更新
</button>
<div id="content">
<div class="loading">加载中...</div>
</div>
</div>
<!-- 提交更新对话框 -->
<div class="modal-overlay" id="versionModal">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">提交更新记录</h2>
<button class="close-btn" id="closeModalBtn">&times;</button>
</div>
<form id="versionForm">
<div class="form-group">
<label for="verNum">版本号</label>
<input type="text" class="form-control" id="verNum" required>
</div>
<div class="form-group">
<label for="date">日期</label>
<input type="text" class="form-control" id="date" required>
</div>
<div class="form-group">
<label for="time">时间</label>
<input type="text" class="form-control" id="time" required>
</div>
<div class="form-group">
<label for="title">标题</label>
<input type="text" class="form-control" id="title" required>
</div>
<div class="form-group">
<label for="note">内容</label>
<textarea class="form-control" id="note" required></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelBtn">取消</button>
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
</div>
</div>
`;
// 添加事件监听器
this.addModalEventListeners();
}
// 添加模态框事件监听器
addModalEventListeners() {
const addButton = this.shadowRoot.getElementById('addVersionBtn');
const closeButton = this.shadowRoot.getElementById('closeModalBtn');
const cancelButton = this.shadowRoot.getElementById('cancelBtn');
const modal = this.shadowRoot.getElementById('versionModal');
const form = this.shadowRoot.getElementById('versionForm');
// 点击添加按钮显示模态框
if (addButton) {
addButton.addEventListener('click', () => {
this.prepareModalData();
modal.classList.add('active');
});
}
// 关闭模态框
if (closeButton) {
closeButton.addEventListener('click', () => {
modal.classList.remove('active');
});
}
// 取消按钮
if (cancelButton) {
cancelButton.addEventListener('click', () => {
modal.classList.remove('active');
});
}
// 表单提交
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
this.submitVersion();
});
}
}
// 准备模态框数据
prepareModalData() {
// 获取表单元素
const verNumInput = this.shadowRoot.getElementById('verNum');
const dateInput = this.shadowRoot.getElementById('date');
const timeInput = this.shadowRoot.getElementById('time');
// 设置当前日期和时间
const now = new Date();
const formattedDate = now.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
}).replace(/\//g, '-');
const formattedTime = now.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
dateInput.value = formattedDate;
timeInput.value = formattedTime;
// 计算新版本号
if (this.versions && this.versions.length > 0) {
const latestVersion = this.versions[0];
const currentVersionParts = latestVersion.verNum.split('.');
// 增加最后一位
let newVersion;
if (currentVersionParts.length >= 3) {
currentVersionParts[2] = (parseInt(currentVersionParts[2]) + 1).toString();
newVersion = currentVersionParts.join('.');
} else {
// 如果版本号格式不是 x.y.z则简单地加0.0.1
const versionNum = parseFloat(latestVersion.verNum);
newVersion = (versionNum + 0.001).toFixed(3);
}
verNumInput.value = newVersion;
} else {
// 如果没有现有版本使用默认的1.0.0
verNumInput.value = '1.0.0';
}
}
// 提交版本信息
async submitVersion() {
const modal = this.shadowRoot.getElementById('versionModal');
const verNum = this.shadowRoot.getElementById('verNum').value;
const date = this.shadowRoot.getElementById('date').value;
const time = this.shadowRoot.getElementById('time').value;
const title = this.shadowRoot.getElementById('title').value;
const note = this.shadowRoot.getElementById('note').value;
// 获取当前登录用户名
let author = '未知用户';
try {
const response = await fetch('/api/check-auth', {
credentials: 'include'
});
const result = await response.json();
if (result.success) {
author = result.user.username || '未知用户';
}
} catch (error) {
console.error('获取用户信息失败:', error);
}
try {
const response = await fetch('/api/versions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
verNum,
date,
time,
title,
note,
author
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 重新加载版本数据
this.loadData();
// 关闭模态框
modal.classList.remove('active');
// 清空表单
this.shadowRoot.getElementById('title').value = '';
this.shadowRoot.getElementById('note').value = '';
// 显示成功消息
alert('更新记录提交成功!');
} catch (error) {
console.error('提交更新记录失败:', error);
alert('提交失败,请重试!');
}
}
// 添加分页按钮事件监听
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.renderVersions();
}
});
}
if (nextButton) {
nextButton.addEventListener('click', () => {
if (this.currentPage < this.totalPages) {
this.currentPage++;
this.renderVersions();
}
});
}
}
}
customElements.define('update-history', UpdateHistory);