391 lines
13 KiB
JavaScript
391 lines
13 KiB
JavaScript
// 全局变量
|
|
let customCards = [];
|
|
let currentSortType = 'newest';
|
|
let currentFilterType = 'all';
|
|
let currentUserId = null;
|
|
let currentCardId = null;
|
|
let userVotes = {};
|
|
|
|
// DOM元素
|
|
const cardsContainer = document.getElementById('cards-container');
|
|
const sortBySelect = document.getElementById('sort-by');
|
|
const filterBySelect = document.getElementById('filter-by');
|
|
const cardModal = document.getElementById('card-modal');
|
|
const closeModal = document.querySelector('.close-modal');
|
|
const upvoteBtn = document.getElementById('upvote-btn');
|
|
const downvoteBtn = document.getElementById('downvote-btn');
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// 获取当前用户ID
|
|
getCurrentUser()
|
|
.then(userId => {
|
|
currentUserId = userId;
|
|
// 获取用户投票历史
|
|
return getUserVotes(userId);
|
|
})
|
|
.then(votes => {
|
|
userVotes = votes;
|
|
// 加载自定义卡牌
|
|
loadCustomCards();
|
|
})
|
|
.catch(error => {
|
|
console.error('初始化失败:', error);
|
|
showErrorMessage('加载数据失败,请刷新页面重试');
|
|
});
|
|
|
|
// 添加事件监听器
|
|
sortBySelect.addEventListener('change', () => {
|
|
currentSortType = sortBySelect.value;
|
|
renderCards();
|
|
});
|
|
|
|
filterBySelect.addEventListener('change', () => {
|
|
currentFilterType = filterBySelect.value;
|
|
renderCards();
|
|
});
|
|
|
|
// 关闭模态框
|
|
closeModal.addEventListener('click', () => {
|
|
cardModal.style.display = 'none';
|
|
});
|
|
|
|
// 点击模态框外部关闭
|
|
window.addEventListener('click', (event) => {
|
|
if (event.target === cardModal) {
|
|
cardModal.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// 投票按钮点击事件
|
|
upvoteBtn.addEventListener('click', () => {
|
|
if (currentCardId) {
|
|
voteCard(currentCardId, 'upvote');
|
|
}
|
|
});
|
|
|
|
downvoteBtn.addEventListener('click', () => {
|
|
if (currentCardId) {
|
|
voteCard(currentCardId, 'downvote');
|
|
}
|
|
});
|
|
});
|
|
|
|
// 获取当前用户ID
|
|
async function getCurrentUser() {
|
|
try {
|
|
const response = await fetch('/api/current_user');
|
|
if (!response.ok) {
|
|
throw new Error('获取用户信息失败');
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.user_id;
|
|
} catch (error) {
|
|
console.error('获取用户ID失败:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// 获取用户投票历史
|
|
async function getUserVotes(userId) {
|
|
if (!userId) return {};
|
|
|
|
try {
|
|
const response = await fetch(`/api/user_votes`);
|
|
if (!response.ok) {
|
|
throw new Error('获取投票历史失败');
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.votes || {};
|
|
} catch (error) {
|
|
console.error('获取投票历史失败:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// 加载自定义卡牌
|
|
async function loadCustomCards() {
|
|
cardsContainer.innerHTML = '<div class="loading-indicator">加载中...</div>';
|
|
|
|
try {
|
|
const response = await fetch('/api/custom_cards');
|
|
if (!response.ok) {
|
|
throw new Error('加载卡牌失败');
|
|
}
|
|
|
|
const data = await response.json();
|
|
customCards = data.cards || [];
|
|
|
|
// 渲染卡牌
|
|
renderCards();
|
|
} catch (error) {
|
|
console.error('加载卡牌失败:', error);
|
|
cardsContainer.innerHTML = `<div class="error-message">加载卡牌失败: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
// 渲染卡牌列表
|
|
function renderCards() {
|
|
if (!customCards || customCards.length === 0) {
|
|
cardsContainer.innerHTML = `
|
|
<div class="empty-state">
|
|
<h2>暂无卡牌</h2>
|
|
<p>成为第一个创建自制卡牌的人吧!</p>
|
|
<a href="/create_card" class="btn btn-primary">创建卡牌</a>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// 过滤卡牌
|
|
let filteredCards = [...customCards];
|
|
|
|
if (currentFilterType === 'my' && currentUserId) {
|
|
filteredCards = filteredCards.filter(card => card.creator_id === currentUserId);
|
|
|
|
// 如果过滤后没有卡牌
|
|
if (filteredCards.length === 0) {
|
|
cardsContainer.innerHTML = `
|
|
<div class="empty-state">
|
|
<h2>您还没有创建卡牌</h2>
|
|
<p>点击下方按钮开始创建您的第一张自制卡牌!</p>
|
|
<a href="/create_card" class="btn btn-primary">创建卡牌</a>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 排序卡牌
|
|
sortCards(filteredCards);
|
|
|
|
// 渲染卡牌
|
|
cardsContainer.innerHTML = filteredCards.map(card => {
|
|
// 计算净投票数
|
|
const voteScore = (card.upvotes || 0) - (card.downvotes || 0);
|
|
const voteClass = voteScore > 0 ? 'vote-positive' : (voteScore < 0 ? 'vote-negative' : 'vote-neutral');
|
|
|
|
// 格式化创建时间
|
|
const createdDate = new Date(card.created_at);
|
|
const formattedDate = `${createdDate.getFullYear()}-${(createdDate.getMonth() + 1).toString().padStart(2, '0')}-${createdDate.getDate().toString().padStart(2, '0')}`;
|
|
|
|
return `
|
|
<div class="card-item" data-id="${card.id}">
|
|
<div class="card-header">
|
|
<div class="card-avatar">${card.character.avatar || '👤'}</div>
|
|
<div class="card-info">
|
|
<h3>${card.character.name}</h3>
|
|
<p>${card.character.title}</p>
|
|
</div>
|
|
</div>
|
|
<div class="card-content">
|
|
<h2 class="card-title">${card.title}</h2>
|
|
<p class="card-text">${card.description}</p>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="card-creator">
|
|
由 ${card.creator_name} 创建于 ${formattedDate}
|
|
</div>
|
|
<div class="card-votes ${voteClass}">
|
|
<span class="vote-count">${voteScore}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// 添加卡牌点击事件
|
|
document.querySelectorAll('.card-item').forEach(card => {
|
|
card.addEventListener('click', () => {
|
|
const cardId = card.getAttribute('data-id');
|
|
openCardDetails(cardId);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 排序卡牌
|
|
function sortCards(cards) {
|
|
switch (currentSortType) {
|
|
case 'newest':
|
|
// 按创建时间降序排序(最新的在前面)
|
|
cards.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
break;
|
|
case 'popular':
|
|
// 按点赞数降序排序
|
|
cards.sort((a, b) => ((b.upvotes || 0) - (b.downvotes || 0)) - ((a.upvotes || 0) - (a.downvotes || 0)));
|
|
break;
|
|
case 'controversial':
|
|
// 按总投票数排序 (争议性)
|
|
cards.sort((a, b) => ((b.upvotes || 0) + (b.downvotes || 0)) - ((a.upvotes || 0) + (a.downvotes || 0)));
|
|
break;
|
|
default:
|
|
// 默认按最新排序
|
|
cards.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
|
}
|
|
}
|
|
|
|
// 打开卡牌详情
|
|
function openCardDetails(cardId) {
|
|
const card = customCards.find(c => c.id === cardId);
|
|
if (!card) return;
|
|
|
|
// 更新当前卡牌ID
|
|
currentCardId = cardId;
|
|
|
|
// 填充模态框内容
|
|
document.getElementById('modal-card-title').textContent = card.title;
|
|
document.getElementById('modal-card-creator').textContent = card.creator_name;
|
|
|
|
// 格式化创建时间
|
|
const createdDate = new Date(card.created_at);
|
|
const formattedDate = `${createdDate.getFullYear()}-${(createdDate.getMonth() + 1).toString().padStart(2, '0')}-${createdDate.getDate().toString().padStart(2, '0')}`;
|
|
document.getElementById('modal-card-date').textContent = formattedDate;
|
|
|
|
document.getElementById('modal-card-description').textContent = card.description;
|
|
document.getElementById('modal-option-a-text').textContent = card.option_a.text;
|
|
document.getElementById('modal-option-b-text').textContent = card.option_b.text;
|
|
|
|
// 生成效果标签
|
|
const optionAEffects = document.getElementById('modal-option-a-effects');
|
|
const optionBEffects = document.getElementById('modal-option-b-effects');
|
|
|
|
optionAEffects.innerHTML = generateEffectTags(card.option_a.effects);
|
|
optionBEffects.innerHTML = generateEffectTags(card.option_b.effects);
|
|
|
|
// 更新投票状态
|
|
const voteScore = (card.upvotes || 0) - (card.downvotes || 0);
|
|
document.getElementById('modal-card-votes').textContent = voteScore;
|
|
|
|
// 检查用户之前的投票
|
|
const userVote = userVotes[cardId];
|
|
upvoteBtn.classList.toggle('voted', userVote === 'upvote');
|
|
downvoteBtn.classList.toggle('voted', userVote === 'downvote');
|
|
|
|
// 显示模态框
|
|
cardModal.style.display = 'block';
|
|
}
|
|
|
|
// 生成效果标签HTML
|
|
function generateEffectTags(effects) {
|
|
if (!effects) return '';
|
|
|
|
const tags = [];
|
|
for (const [stat, value] of Object.entries(effects)) {
|
|
if (value === 0) continue;
|
|
|
|
let statName = '';
|
|
let statIcon = '';
|
|
|
|
switch (stat) {
|
|
case 'loyalty':
|
|
statName = '帝国忠诚度';
|
|
statIcon = '🦅';
|
|
break;
|
|
case 'chaos':
|
|
statName = '混沌侵染';
|
|
statIcon = '🌀';
|
|
break;
|
|
case 'population':
|
|
statName = '民众控制';
|
|
statIcon = '👥';
|
|
break;
|
|
case 'military':
|
|
statName = '军事力量';
|
|
statIcon = '🔫';
|
|
break;
|
|
case 'resources':
|
|
statName = '资源储备';
|
|
statIcon = '⚙️';
|
|
break;
|
|
}
|
|
|
|
const isPositive = value > 0;
|
|
tags.push(`
|
|
<div class="effect-tag ${isPositive ? 'positive' : 'negative'}">
|
|
${statIcon} ${statName} ${isPositive ? '+' : ''}${value}
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
return tags.join('');
|
|
}
|
|
|
|
// 投票
|
|
async function voteCard(cardId, voteType) {
|
|
if (!currentUserId) {
|
|
showMessage('请先登录后再投票');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/vote_card`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
card_id: cardId,
|
|
vote_type: voteType
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('投票失败');
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
// 更新本地投票记录
|
|
userVotes[cardId] = data.newVote || null;
|
|
|
|
// 更新卡牌数据
|
|
const cardIndex = customCards.findIndex(c => c.id === cardId);
|
|
if (cardIndex !== -1) {
|
|
customCards[cardIndex].upvotes = data.upvotes;
|
|
customCards[cardIndex].downvotes = data.downvotes;
|
|
|
|
// 更新UI
|
|
const voteScore = data.upvotes - data.downvotes;
|
|
document.getElementById('modal-card-votes').textContent = voteScore;
|
|
|
|
// 更新投票按钮状态
|
|
upvoteBtn.classList.toggle('voted', data.newVote === 'upvote');
|
|
downvoteBtn.classList.toggle('voted', data.newVote === 'downvote');
|
|
|
|
// 渲染卡牌列表以更新投票数量
|
|
renderCards();
|
|
}
|
|
} catch (error) {
|
|
console.error('投票失败:', error);
|
|
showMessage('投票失败,请稍后再试');
|
|
}
|
|
}
|
|
|
|
// 显示错误消息
|
|
function showErrorMessage(message) {
|
|
cardsContainer.innerHTML = `<div class="error-message">${message}</div>`;
|
|
}
|
|
|
|
// 显示消息(临时弹出提示)
|
|
function showMessage(message) {
|
|
const messageElement = document.createElement('div');
|
|
messageElement.className = 'message-popup';
|
|
messageElement.textContent = message;
|
|
|
|
document.body.appendChild(messageElement);
|
|
|
|
// 消息显示动画
|
|
setTimeout(() => {
|
|
messageElement.classList.add('show');
|
|
}, 10);
|
|
|
|
// 3秒后移除消息
|
|
setTimeout(() => {
|
|
messageElement.classList.remove('show');
|
|
setTimeout(() => {
|
|
messageElement.remove();
|
|
}, 300);
|
|
}, 3000);
|
|
} |