478 lines
18 KiB
HTML
478 lines
18 KiB
HTML
<!-- 创建一个新的角色权重管理页面 weight_management.html -->
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>角色权重管理 - 战锤40K行星总督</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin.css') }}">
|
|
<style>
|
|
.weight-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.weight-card {
|
|
background-color: #fff;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
padding: 15px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.weight-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.weight-card-avatar {
|
|
font-size: 2em;
|
|
width: 50px;
|
|
height: 50px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: #f0f0f0;
|
|
border-radius: 25px;
|
|
}
|
|
|
|
.weight-card-name {
|
|
flex: 1;
|
|
}
|
|
|
|
.weight-card-name h3 {
|
|
margin: 0;
|
|
font-size: 1.2em;
|
|
}
|
|
|
|
.weight-card-name p {
|
|
margin: 4px 0 0;
|
|
font-size: 0.9em;
|
|
color: #666;
|
|
}
|
|
|
|
.weight-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.weight-control input {
|
|
flex: 1;
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.weight-control button {
|
|
padding: 8px 12px;
|
|
background-color: #343a40;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.weight-control button:hover {
|
|
background-color: #23272b;
|
|
}
|
|
|
|
.type-filter {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.type-filter button {
|
|
padding: 8px 16px;
|
|
background-color: #f0f0f0;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.type-filter button.active {
|
|
background-color: #343a40;
|
|
color: white;
|
|
}
|
|
|
|
.type-badge {
|
|
display: inline-block;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
font-size: 0.8em;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.type-resident {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.type-special {
|
|
background-color: #17a2b8;
|
|
color: white;
|
|
}
|
|
|
|
.type-status {
|
|
background-color: #6c757d;
|
|
color: white;
|
|
}
|
|
|
|
.save-all-btn {
|
|
margin-top: 20px;
|
|
padding: 10px 16px;
|
|
background-color: #28a745;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.save-all-btn:hover {
|
|
background-color: #218838;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="admin-page">
|
|
<div class="admin-sidebar">
|
|
<div class="admin-logo">
|
|
<h2>战锤40K</h2>
|
|
<p>管理员控制台</p>
|
|
</div>
|
|
<nav class="admin-nav">
|
|
<a href="{{ url_for('admin_index') }}">
|
|
<span class="icon">🏠</span>
|
|
<span class="text">主页</span>
|
|
</a>
|
|
<a href="{{ url_for('admin_characters') }}">
|
|
<span class="icon">👤</span>
|
|
<span class="text">管理人物</span>
|
|
</a>
|
|
<a href="{{ url_for('admin_dashboard') }}">
|
|
<span class="icon">📊</span>
|
|
<span class="text">数据面板</span>
|
|
</a>
|
|
<a href="{{ url_for('admin_weight_management') }}" class="active">
|
|
<span class="icon">⚖️</span>
|
|
<span class="text">权重管理</span>
|
|
</a>
|
|
<a href="{{ url_for('admin_logout') }}" class="logout">
|
|
<span class="icon">🚪</span>
|
|
<span class="text">登出</span>
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="admin-content">
|
|
<header class="admin-header">
|
|
<h1>角色权重管理</h1>
|
|
<div class="header-actions">
|
|
<button id="save-all-weights-btn" class="save-all-btn">
|
|
<span class="icon">💾</span> 保存所有权重
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="admin-toolbar">
|
|
<div class="search-box">
|
|
<input type="text" id="character-search" placeholder="搜索角色...">
|
|
</div>
|
|
<div class="type-filter">
|
|
<button data-type="all" class="active">所有类型</button>
|
|
<button data-type="resident">常驻角色</button>
|
|
<button data-type="special">特殊角色</button>
|
|
<button data-type="status">状态效果</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="weight-grid" id="weights-container">
|
|
<!-- 角色权重卡片将通过JavaScript加载 -->
|
|
<div class="loading-indicator">加载中...</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const weightsContainer = document.getElementById('weights-container');
|
|
const characterSearch = document.getElementById('character-search');
|
|
const saveAllBtn = document.getElementById('save-all-weights-btn');
|
|
const typeFilterButtons = document.querySelectorAll('.type-filter button');
|
|
|
|
// 状态变量
|
|
let characters = [];
|
|
let filteredCharacters = [];
|
|
let changedWeights = {};
|
|
let currentTypeFilter = 'all';
|
|
|
|
// 加载所有角色
|
|
function loadCharacters() {
|
|
weightsContainer.innerHTML = '<div class="loading-indicator">加载中...</div>';
|
|
|
|
fetch('/api/admin/characters')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
characters = data.characters || [];
|
|
filteredCharacters = [...characters];
|
|
renderWeightCards();
|
|
})
|
|
.catch(error => {
|
|
console.error('加载角色失败:', error);
|
|
weightsContainer.innerHTML = '<div class="error-message">加载失败,请刷新页面重试。</div>';
|
|
});
|
|
}
|
|
|
|
// 渲染权重卡片
|
|
function renderWeightCards() {
|
|
if (filteredCharacters.length === 0) {
|
|
weightsContainer.innerHTML = '<div class="empty-message">没有找到符合条件的角色</div>';
|
|
return;
|
|
}
|
|
|
|
weightsContainer.innerHTML = '';
|
|
|
|
filteredCharacters.forEach(character => {
|
|
const card = document.createElement('div');
|
|
card.className = 'weight-card';
|
|
card.dataset.id = character.id;
|
|
|
|
// 设置角色类型标签
|
|
let typeLabel = '未知';
|
|
let typeClass = '';
|
|
|
|
switch(character.type) {
|
|
case 'resident':
|
|
typeLabel = '常驻角色';
|
|
typeClass = 'type-resident';
|
|
break;
|
|
case 'special':
|
|
typeLabel = '特殊角色';
|
|
typeClass = 'type-special';
|
|
break;
|
|
case 'status':
|
|
typeLabel = '状态效果';
|
|
typeClass = 'type-status';
|
|
break;
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<div class="weight-card-header">
|
|
<div class="weight-card-avatar">${character.avatar || '👤'}</div>
|
|
<div class="weight-card-name">
|
|
<h3>${character.name || '无名角色'}</h3>
|
|
<p>${character.title || '无头衔'}</p>
|
|
<span class="type-badge ${typeClass}">${typeLabel}</span>
|
|
</div>
|
|
</div>
|
|
<div class="weight-control">
|
|
<input type="number" class="weight-input" min="1" max="10" value="${character.weight || 100}">
|
|
<button class="save-weight-btn" data-id="${character.id}">保存</button>
|
|
</div>
|
|
`;
|
|
|
|
weightsContainer.appendChild(card);
|
|
|
|
// 添加权重输入框变化事件
|
|
const weightInput = card.querySelector('.weight-input');
|
|
weightInput.addEventListener('change', () => {
|
|
const newWeight = parseInt(weightInput.value);
|
|
if (!isNaN(newWeight) && newWeight >= 1 && newWeight <= 10) {
|
|
changedWeights[character.id] = newWeight;
|
|
}
|
|
});
|
|
|
|
// 添加保存按钮点击事件
|
|
const saveBtn = card.querySelector('.save-weight-btn');
|
|
saveBtn.addEventListener('click', () => {
|
|
const input = card.querySelector('.weight-input');
|
|
const newWeight = parseInt(input.value);
|
|
if (!isNaN(newWeight) && newWeight >= 1 && newWeight <= 10) {
|
|
saveCharacterWeight(character.id, newWeight);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 保存单个角色的权重
|
|
function saveCharacterWeight(characterId, weight) {
|
|
const character = characters.find(c => c.id === characterId);
|
|
if (!character) return;
|
|
|
|
// 创建角色数据
|
|
const characterData = {
|
|
id: character.id,
|
|
name: character.name,
|
|
title: character.title,
|
|
avatar: character.avatar,
|
|
avatarPath: character.avatarPath,
|
|
type: character.type,
|
|
weight: weight,
|
|
events: character.events || []
|
|
};
|
|
|
|
// 发送请求
|
|
fetch('/api/admin/character', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(characterData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// 更新本地数据
|
|
character.weight = weight;
|
|
|
|
// 视觉反馈
|
|
const card = document.querySelector(`.weight-card[data-id="${characterId}"]`);
|
|
if (card) {
|
|
const saveBtn = card.querySelector('.save-weight-btn');
|
|
const originalText = saveBtn.textContent;
|
|
saveBtn.textContent = '已保存';
|
|
saveBtn.style.backgroundColor = '#28a745';
|
|
|
|
setTimeout(() => {
|
|
saveBtn.textContent = originalText;
|
|
saveBtn.style.backgroundColor = '';
|
|
}, 1500);
|
|
}
|
|
|
|
// 从更改列表中删除
|
|
delete changedWeights[characterId];
|
|
} else {
|
|
alert('保存失败: ' + (data.error || '未知错误'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('保存角色权重失败:', error);
|
|
alert('保存失败,请稍后重试');
|
|
});
|
|
}
|
|
|
|
// 保存所有更改的权重
|
|
function saveAllChangedWeights() {
|
|
const characterIds = Object.keys(changedWeights);
|
|
if (characterIds.length === 0) {
|
|
alert('没有需要保存的更改');
|
|
return;
|
|
}
|
|
|
|
// 显示保存中状态
|
|
saveAllBtn.textContent = '保存中...';
|
|
saveAllBtn.disabled = true;
|
|
|
|
// 创建保存请求的Promise数组
|
|
const savePromises = characterIds.map(id => {
|
|
const character = characters.find(c => c.id === id);
|
|
if (!character) return Promise.resolve();
|
|
|
|
// 创建角色数据
|
|
const characterData = {
|
|
id: character.id,
|
|
name: character.name,
|
|
title: character.title,
|
|
avatar: character.avatar,
|
|
avatarPath: character.avatarPath,
|
|
type: character.type,
|
|
weight: changedWeights[id],
|
|
events: character.events || []
|
|
};
|
|
|
|
// 发送请求
|
|
return fetch('/api/admin/character', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(characterData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// 更新本地数据
|
|
character.weight = changedWeights[id];
|
|
return true;
|
|
} else {
|
|
console.error('保存失败:', data.error);
|
|
return false;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('保存请求失败:', error);
|
|
return false;
|
|
});
|
|
});
|
|
|
|
// 等待所有保存请求完成
|
|
Promise.all(savePromises)
|
|
.then(results => {
|
|
const successCount = results.filter(Boolean).length;
|
|
|
|
// 恢复按钮状态
|
|
saveAllBtn.innerHTML = '<span class="icon">💾</span> 保存所有权重';
|
|
saveAllBtn.disabled = false;
|
|
|
|
if (successCount === characterIds.length) {
|
|
// 全部保存成功
|
|
alert(`成功保存了 ${successCount} 个角色的权重设置`);
|
|
changedWeights = {}; // 清空更改列表
|
|
|
|
// 重新加载角色列表
|
|
loadCharacters();
|
|
} else {
|
|
// 部分保存失败
|
|
alert(`成功保存了 ${successCount} 个角色的权重设置,${characterIds.length - successCount} 个保存失败`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 过滤角色
|
|
function filterCharacters() {
|
|
const searchTerm = characterSearch.value.toLowerCase();
|
|
|
|
filteredCharacters = characters.filter(character => {
|
|
const nameMatch = (character.name || '').toLowerCase().includes(searchTerm) ||
|
|
((character.title || '').toLowerCase().includes(searchTerm));
|
|
const typeMatch = currentTypeFilter === 'all' || character.type === currentTypeFilter;
|
|
|
|
return nameMatch && typeMatch;
|
|
});
|
|
|
|
renderWeightCards();
|
|
}
|
|
|
|
// 设置事件监听
|
|
characterSearch.addEventListener('input', filterCharacters);
|
|
|
|
saveAllBtn.addEventListener('click', saveAllChangedWeights);
|
|
|
|
// 类型过滤按钮
|
|
typeFilterButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
// 更新活动按钮
|
|
typeFilterButtons.forEach(btn => btn.classList.remove('active'));
|
|
button.classList.add('active');
|
|
|
|
// 设置当前过滤器类型
|
|
currentTypeFilter = button.dataset.type;
|
|
|
|
// 重新过滤
|
|
filterCharacters();
|
|
});
|
|
});
|
|
|
|
// 初始加载
|
|
loadCharacters();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |