feat(web): 适配行动点系统
- 居民卡片显示行动点(AP 圆点指示器) - 添加行动反馈 Toast 提示(成功/失败) - 3 秒后自动消失的反馈动画 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,20 @@
|
|||||||
.emotion-calm { background: #1e3a5f; color: #93c5fd; }
|
.emotion-calm { background: #1e3a5f; color: #93c5fd; }
|
||||||
.agent-info { font-size: 13px; color: #a1a1aa; }
|
.agent-info { font-size: 13px; color: #a1a1aa; }
|
||||||
|
|
||||||
|
/* 行动点 */
|
||||||
|
.agent-ap { display: flex; align-items: center; gap: 4px; margin-top: 6px; }
|
||||||
|
.ap-dots { display: flex; gap: 3px; }
|
||||||
|
.ap-dot { width: 8px; height: 8px; border-radius: 50%; background: #3f3f46; }
|
||||||
|
.ap-dot.filled { background: #60a5fa; }
|
||||||
|
.ap-label { font-size: 11px; color: #71717a; }
|
||||||
|
|
||||||
|
/* 行动反馈提示 */
|
||||||
|
#action-feedback { position: fixed; bottom: 20px; right: 20px; z-index: 100; }
|
||||||
|
.feedback-toast { padding: 10px 16px; border-radius: 8px; margin-top: 8px; font-size: 13px; animation: fadeIn 0.3s ease; }
|
||||||
|
.feedback-toast.success { background: #166534; color: #86efac; border: 1px solid #22c55e; }
|
||||||
|
.feedback-toast.fail { background: #7f1d1d; color: #fca5a5; border: 1px solid #ef4444; }
|
||||||
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
|
|
||||||
/* 行动日志 */
|
/* 行动日志 */
|
||||||
.actions-list { max-height: 300px; overflow-y: auto; }
|
.actions-list { max-height: 300px; overflow-y: auto; }
|
||||||
.action-item { padding: 8px; background: #27272a; border-radius: 6px; margin-bottom: 6px; font-size: 13px; }
|
.action-item { padding: 8px; background: #27272a; border-radius: 6px; margin-bottom: 6px; font-size: 13px; }
|
||||||
@@ -192,6 +206,9 @@
|
|||||||
|
|
||||||
<!-- 主内容区 -->
|
<!-- 主内容区 -->
|
||||||
<div class="main-grid">
|
<div class="main-grid">
|
||||||
|
|
||||||
|
<!-- 行动反馈提示 -->
|
||||||
|
<div id="action-feedback"></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="card" style="margin-bottom: 16px;">
|
<div class="card" style="margin-bottom: 16px;">
|
||||||
<div class="card-title">世界状态</div>
|
<div class="card-title">世界状态</div>
|
||||||
@@ -270,6 +287,21 @@
|
|||||||
const weatherMap = { sunny: '☀️', rainy: '🌧️' };
|
const weatherMap = { sunny: '☀️', rainy: '🌧️' };
|
||||||
const actionHistory = [];
|
const actionHistory = [];
|
||||||
|
|
||||||
|
// 显示行动反馈提示
|
||||||
|
function showActionFeedbacks(feedbacks) {
|
||||||
|
const container = document.getElementById('action-feedback');
|
||||||
|
feedbacks.forEach(fb => {
|
||||||
|
if (!fb.user) return;
|
||||||
|
const cls = fb.success ? 'success' : 'fail';
|
||||||
|
const icon = fb.success ? '✓' : '✗';
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = `feedback-toast ${cls}`;
|
||||||
|
toast.textContent = `${icon} ${fb.user}: ${fb.reason} (AP: ${fb.remaining_ap})`;
|
||||||
|
container.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 3000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染技能列表
|
// 渲染技能列表
|
||||||
function renderSkills(skills, names) {
|
function renderSkills(skills, names) {
|
||||||
return Object.entries(skills).map(([id, s]) => {
|
return Object.entries(skills).map(([id, s]) => {
|
||||||
@@ -284,13 +316,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateUI(data) {
|
function updateUI(data) {
|
||||||
const { world_state, actions, global_event, triggered_faction_event, story_event } = data;
|
const { world_state, actions, global_event, triggered_faction_event, story_event, action_feedbacks } = data;
|
||||||
|
|
||||||
// 世界状态
|
// 世界状态
|
||||||
document.getElementById('tick').textContent = world_state.tick;
|
document.getElementById('tick').textContent = world_state.tick;
|
||||||
document.getElementById('weather').textContent = weatherMap[world_state.weather] || world_state.weather;
|
document.getElementById('weather').textContent = weatherMap[world_state.weather] || world_state.weather;
|
||||||
document.getElementById('mood').textContent = world_state.town_mood;
|
document.getElementById('mood').textContent = world_state.town_mood;
|
||||||
|
|
||||||
|
// 行动反馈提示
|
||||||
|
if (action_feedbacks && action_feedbacks.length > 0) {
|
||||||
|
showActionFeedbacks(action_feedbacks);
|
||||||
|
}
|
||||||
|
|
||||||
// ① 能量条
|
// ① 能量条
|
||||||
if (world_state.global_meter) {
|
if (world_state.global_meter) {
|
||||||
const meter = world_state.global_meter;
|
const meter = world_state.global_meter;
|
||||||
@@ -412,12 +449,23 @@
|
|||||||
const agentsEl = document.getElementById('agents');
|
const agentsEl = document.getElementById('agents');
|
||||||
agentsEl.innerHTML = '';
|
agentsEl.innerHTML = '';
|
||||||
for (const [id, agent] of Object.entries(world_state.agents)) {
|
for (const [id, agent] of Object.entries(world_state.agents)) {
|
||||||
|
const ap = agent.action_points || 0;
|
||||||
|
const maxAp = agent.max_action_points || 3;
|
||||||
|
const apDots = Array(maxAp).fill(0).map((_, i) =>
|
||||||
|
`<span class="ap-dot ${i < ap ? 'filled' : ''}"></span>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
agentsEl.innerHTML += `
|
agentsEl.innerHTML += `
|
||||||
<div class="agent">
|
<div class="agent">
|
||||||
<div class="agent-header">
|
<div class="agent-header">
|
||||||
<span class="agent-name">${id}</span>
|
<span class="agent-name">${id}</span>
|
||||||
<span class="agent-emotion emotion-${agent.emotion}">${agent.emotion}</span>
|
<span class="agent-emotion emotion-${agent.emotion}">${agent.emotion}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="agent-ap">
|
||||||
|
<span class="ap-label">AP:</span>
|
||||||
|
<div class="ap-dots">${apDots}</div>
|
||||||
|
<span class="ap-label">${ap}/${maxAp}</span>
|
||||||
|
</div>
|
||||||
<div class="agent-info">记忆: ${agent.memory.slice(-2).join(' | ') || '-'}</div>
|
<div class="agent-info">记忆: ${agent.memory.slice(-2).join(' | ') || '-'}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user