XNSim/XNSimHtml/components/update-history.js

771 lines
26 KiB
JavaScript
Raw Normal View History

2025-04-28 12:25:20 +08:00
class UpdateHistory extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.versions = [];
this.accessLevel = 0;
2025-04-28 12:25:20 +08:00
this.currentPage = 1;
this.pageSize = 10;
this.totalPages = 1;
2025-04-28 12:25:20 +08:00
}
connectedCallback() {
this.render();
this.loadData();
this.checkUserAccess();
}
// 检查用户权限
async checkUserAccess() {
try {
// 从localStorage获取用户信息
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
this.accessLevel = user.access_level || 0;
} catch (parseError) {
console.error('解析用户信息失败:', parseError);
this.accessLevel = 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);
2025-04-28 12:25:20 +08:00
let html = `
<div class="version-list">
${currentPageVersions.map(version => `
<div class="version-item" data-version="${version.verNum}">
<div class="version-header">
2025-04-28 12:25:20 +08:00
<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>
` : ''}
2025-04-28 12:25:20 +08:00
</div>
${version.note ? `
<div class="version-description">${version.note}</div>
2025-04-28 12:25:20 +08:00
` : ''}
</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>
`;
2025-04-28 12:25:20 +08:00
contentContainer.innerHTML = html;
2025-04-28 12:25:20 +08:00
// 添加展开/收起事件
this.addExpandListeners();
// 更新添加按钮显示状态
this.updateAddButton();
// 添加分页按钮事件监听
this.addPaginationListeners();
2025-04-28 12:25:20 +08:00
}
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;
2025-04-28 12:25:20 +08:00
position: relative;
overflow: visible;
min-height: 200px;
2025-04-28 12:25:20 +08:00
}
.title-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
2025-04-28 12:25:20 +08:00
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 {
2025-04-28 12:25:20 +08:00
position: relative;
padding-left: 30px;
margin-bottom: 25px;
2025-04-28 12:25:20 +08:00
}
.version-item {
position: relative;
margin-bottom: 30px;
}
.version-item:last-child {
margin-bottom: 0;
2025-04-28 12:25:20 +08:00
}
.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;
2025-04-28 12:25:20 +08:00
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;
}
2025-04-28 12:25:20 +08:00
</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 = '未知用户';
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
author = user.username || '未知用户';
} catch (parseError) {
console.error('解析用户信息失败:', parseError);
}
}
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();
}
});
}
}
2025-04-28 12:25:20 +08:00
}
customElements.define('update-history', UpdateHistory);