2025-07-09 05:22:28 +08:00

647 lines
22 KiB
JavaScript
Raw 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.

// 评价功能 JavaScript
document.addEventListener('DOMContentLoaded', function() {
initializeReviewForm();
initializeImageUpload();
});
// 初始化评价表单
function initializeReviewForm() {
const starRating = document.getElementById('starRating');
const ratingInput = document.getElementById('rating');
const ratingText = document.getElementById('ratingText');
const reviewForm = document.getElementById('reviewForm');
if (starRating) {
const stars = starRating.querySelectorAll('.star');
const ratingTexts = {
1: '很差',
2: '较差',
3: '一般',
4: '满意',
5: '非常满意'
};
let currentRating = 0; // 当前选中的评分
// 初始化:设置所有星星为空心
stars.forEach(star => {
star.textContent = '☆'; // 空心星星
});
// 星级点击事件
stars.forEach((star, index) => {
star.addEventListener('click', function() {
const rating = index + 1;
setRating(rating);
});
// 鼠标悬停事件
star.addEventListener('mouseenter', function() {
const rating = index + 1;
showHoverStars(rating);
// 显示临时评分文字
const tempText = ratingTexts[rating] || '请选择评分';
ratingText.textContent = tempText;
ratingText.style.backgroundColor = '#ff6b35';
ratingText.style.color = 'white';
ratingText.style.borderColor = '#ff6b35';
});
});
// 鼠标离开星级评分区域
starRating.addEventListener('mouseleave', function() {
showSelectedStars(currentRating);
// 恢复原来的评分文字
if (currentRating > 0) {
ratingText.textContent = ratingTexts[currentRating];
ratingText.classList.add('selected');
ratingText.style.backgroundColor = '#ff6b35';
ratingText.style.color = 'white';
ratingText.style.borderColor = '#ff6b35';
} else {
ratingText.textContent = '请选择评分';
ratingText.classList.remove('selected');
ratingText.style.backgroundColor = '#f8f9fa';
ratingText.style.color = '#666';
ratingText.style.borderColor = '#e9ecef';
}
});
// 设置评分
function setRating(rating) {
currentRating = rating;
ratingInput.value = rating;
ratingText.textContent = ratingTexts[rating] || '请选择评分';
ratingText.classList.add('selected');
ratingText.style.backgroundColor = '#ff6b35';
ratingText.style.color = 'white';
ratingText.style.borderColor = '#ff6b35';
showSelectedStars(rating);
}
// 显示悬停状态的星星
function showHoverStars(rating) {
stars.forEach((star, index) => {
star.classList.remove('filled');
if (index < rating) {
star.textContent = '★'; // 实心星星
star.classList.add('filled');
} else {
star.textContent = '☆'; // 空心星星
}
});
}
// 显示选中状态的星星
function showSelectedStars(rating) {
stars.forEach((star, index) => {
star.classList.remove('filled');
if (index < rating) {
star.textContent = '★'; // 实心星星
star.classList.add('filled');
} else {
star.textContent = '☆'; // 空心星星
}
});
}
}
// 表单提交
if (reviewForm) {
reviewForm.addEventListener('submit', function(e) {
e.preventDefault();
submitReview();
});
}
}
// 初始化图片上传
function initializeImageUpload() {
const uploadArea = document.getElementById('uploadArea');
const imageInput = document.getElementById('imageInput');
const uploadedImages = document.getElementById('uploadedImages');
if (!uploadArea || !imageInput) return;
let uploadedImageUrls = [];
// 点击上传区域
uploadArea.addEventListener('click', function() {
imageInput.click();
});
// 拖拽上传
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
this.style.borderColor = '#007bff';
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
this.style.borderColor = '#ddd';
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
this.style.borderColor = '#ddd';
const files = Array.from(e.dataTransfer.files);
handleFiles(files);
});
// 文件选择
imageInput.addEventListener('change', function() {
const files = Array.from(this.files);
handleFiles(files);
});
// 处理文件上传
function handleFiles(files) {
if (uploadedImageUrls.length + files.length > 5) {
showAlert('最多只能上传5张图片', 'warning');
return;
}
files.forEach(file => {
if (!file.type.startsWith('image/')) {
showAlert('只能上传图片文件', 'warning');
return;
}
if (file.size > 5 * 1024 * 1024) {
showAlert('图片大小不能超过5MB', 'warning');
return;
}
uploadImage(file);
});
}
// 上传图片到服务器
function uploadImage(file) {
const formData = new FormData();
formData.append('file', file);
// 显示上传进度
const previewElement = createImagePreview(URL.createObjectURL(file), true);
uploadedImages.appendChild(previewElement);
fetch('/review/upload_image', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 更新预览元素
const img = previewElement.querySelector('img');
img.src = data.url;
// 强制设置图片样式
forceImageStyles(img);
previewElement.classList.remove('uploading');
previewElement.dataset.url = data.url;
uploadedImageUrls.push(data.url);
} else {
showAlert(data.message || '图片上传失败', 'error');
previewElement.remove();
}
})
.catch(error => {
showAlert('图片上传失败', 'error');
previewElement.remove();
});
}
// 创建图片预览元素
function createImagePreview(src, isUploading = false) {
const div = document.createElement('div');
div.className = `image-preview ${isUploading ? 'uploading' : ''}`;
// 强制设置容器样式
div.style.cssText = `
position: relative !important;
width: 80px !important;
height: 80px !important;
max-width: 80px !important;
max-height: 80px !important;
min-width: 80px !important;
min-height: 80px !important;
border-radius: 8px !important;
overflow: hidden !important;
border: 2px solid #e9ecef !important;
flex-shrink: 0 !important;
flex-grow: 0 !important;
display: inline-block !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
vertical-align: top !important;
`;
const img = document.createElement('img');
img.src = src;
img.alt = '评价图片';
// 强制设置图片样式
forceImageStyles(img);
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
removeBtn.innerHTML = '×';
removeBtn.type = 'button';
removeBtn.style.cssText = `
position: absolute !important;
top: 2px !important;
right: 2px !important;
background: rgba(255, 255, 255, 0.9) !important;
border: none !important;
border-radius: 50% !important;
width: 20px !important;
height: 20px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
font-size: 12px !important;
color: #dc3545 !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.2) !important;
z-index: 10 !important;
`;
removeBtn.onclick = function() {
const url = div.dataset.url;
if (url) {
uploadedImageUrls = uploadedImageUrls.filter(u => u !== url);
}
div.remove();
};
div.appendChild(img);
div.appendChild(removeBtn);
return div;
}
// 强制设置图片样式的函数
function forceImageStyles(img) {
img.style.cssText = `
display: block !important;
width: 80px !important;
height: 80px !important;
max-width: 80px !important;
max-height: 80px !important;
min-width: 80px !important;
min-height: 80px !important;
object-fit: cover !important;
border-radius: 6px !important;
box-sizing: border-box !important;
position: relative !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
margin: 0 !important;
padding: 0 !important;
border: none !important;
outline: none !important;
background: none !important;
vertical-align: top !important;
flex-shrink: 0 !important;
flex-grow: 0 !important;
`;
// 图片加载完成后再次强制设置样式
img.onload = function() {
forceImageStyles(this);
};
}
// 获取上传的图片URL列表
window.getUploadedImages = function() {
return uploadedImageUrls;
};
}
// 提交评价
function submitReview() {
const submitBtn = document.getElementById('submitBtn');
const orderId = document.getElementById('orderId').value;
const productId = document.getElementById('productId').value;
const rating = document.getElementById('rating').value;
const content = document.getElementById('content').value;
const isAnonymous = document.getElementById('isAnonymous').checked;
// 验证
if (!rating) {
showAlert('请选择评分', 'warning');
return;
}
// 禁用提交按钮
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="bi bi-hourglass-split"></i> 提交中...';
const data = {
order_id: parseInt(orderId),
product_id: parseInt(productId),
rating: parseInt(rating),
content: content.trim(),
is_anonymous: isAnonymous,
images: window.getUploadedImages ? window.getUploadedImages() : []
};
fetch('/review/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert(data.message, 'success');
setTimeout(() => {
window.location.href = `/order/detail/${orderId}`;
}, 1500);
} else {
showAlert(data.message, 'error');
}
})
.catch(error => {
showAlert('提交失败,请重试', 'error');
})
.finally(() => {
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="bi bi-check-circle"></i> 提交评价';
});
}
// 加载商品评价列表(用于商品详情页)
function loadProductReviews(productId, page = 1, rating = null) {
const reviewsContainer = document.getElementById('reviewsContainer');
if (!reviewsContainer) return;
const params = new URLSearchParams({
page: page
});
if (rating) {
params.append('rating', rating);
}
reviewsContainer.innerHTML = '<div class="text-center p-4"><i class="bi bi-hourglass-split"></i> 加载中...</div>';
fetch(`/review/product/${productId}?${params}`)
.then(response => response.json())
.then(data => {
if (data.success) {
renderReviews(data);
} else {
reviewsContainer.innerHTML = '<div class="text-center p-4 text-muted">加载失败</div>';
}
})
.catch(error => {
reviewsContainer.innerHTML = '<div class="text-center p-4 text-muted">加载失败</div>';
});
}
// 渲染评价列表
function renderReviews(data) {
const reviewsContainer = document.getElementById('reviewsContainer');
if (!reviewsContainer) return;
let html = '';
// 评价统计
if (data.stats) {
html += renderReviewStats(data.stats);
}
// 评价筛选
html += renderReviewFilter();
// 评价列表
if (data.reviews && data.reviews.length > 0) {
data.reviews.forEach(review => {
html += renderReviewItem(review);
});
// 分页
if (data.pagination && data.pagination.pages > 1) {
html += renderPagination(data.pagination);
}
} else {
html += '<div class="text-center p-4 text-muted">暂无评价</div>';
}
reviewsContainer.innerHTML = html;
}
// 渲染评价统计
function renderReviewStats(stats) {
const goodRate = stats.good_rate || 0;
const totalReviews = stats.total_reviews || 0;
let html = `
<div class="reviews-stats">
<div class="rating-summary">
<div class="overall-rating">
<div class="score">${goodRate}%</div>
<div class="total">好评率 (${totalReviews}条评价)</div>
</div>
<div class="rating-breakdown">
`;
for (let i = 5; i >= 1; i--) {
const count = stats.rating_stats[i] || 0;
const percentage = totalReviews > 0 ? (count / totalReviews * 100) : 0;
html += `
<div class="rating-bar">
<span class="label">${i}星</span>
<div class="progress">
<div class="progress-bar" style="width: ${percentage}%"></div>
</div>
<span class="count">${count}</span>
</div>
`;
}
html += `
</div>
</div>
</div>
`;
return html;
}
// 渲染评价筛选
function renderReviewFilter() {
return `
<div class="reviews-filter">
<button class="btn btn-outline-primary btn-sm active" onclick="filterReviews(null)">
全部评价
</button>
<button class="btn btn-outline-primary btn-sm" onclick="filterReviews(5)">
好评 (5星)
</button>
<button class="btn btn-outline-primary btn-sm" onclick="filterReviews(3)">
中评 (3星)
</button>
<button class="btn btn-outline-primary btn-sm" onclick="filterReviews(1)">
差评 (1星)
</button>
</div>
`;
}
// 渲染单个评价 - 修复图片和头像问题
function renderReviewItem(review) {
let html = `
<div class="review-list-item">
<div class="reviewer-info">
`;
if (review.user_avatar) {
// 用户头像 - 添加内联样式强制约束尺寸
html += `<img src="${review.user_avatar}"
class="reviewer-avatar"
alt="用户头像"
style="width: 40px !important; height: 40px !important; max-width: 40px !important; max-height: 40px !important; min-width: 40px !important; min-height: 40px !important; border-radius: 50% !important; object-fit: cover !important; border: 2px solid #e9ecef !important; display: block !important; flex-shrink: 0 !important;">`;
} else {
html += `<div class="reviewer-avatar bg-secondary d-flex align-items-center justify-content-center text-white"
style="width: 40px !important; height: 40px !important; max-width: 40px !important; max-height: 40px !important; min-width: 40px !important; min-height: 40px !important; border-radius: 50% !important; flex-shrink: 0 !important;">
<i class="bi bi-person"></i>
</div>`;
}
html += `
<div>
<div class="reviewer-name">${review.username}</div>
<div class="review-time">${new Date(review.created_at).toLocaleDateString()}</div>
</div>
</div>
<div class="rating-display mb-2">
<span class="stars">${review.rating_stars}</span>
<span class="text-muted">${review.rating}分</span>
</div>
`;
if (review.content) {
html += `<p class="review-content">${review.content}</p>`;
}
if (review.images && review.images.length > 0) {
html += '<div class="product-review-images mb-2">';
review.images.forEach(imageUrl => {
// 评价图片 - 使用特殊的类名和内联样式确保图片尺寸正确
html += `<img src="${imageUrl}"
class="product-review-image"
alt="评价图片"
style="width: 80px !important; height: 80px !important; max-width: 80px !important; max-height: 80px !important; min-width: 80px !important; min-height: 80px !important; object-fit: cover !important; border-radius: 6px !important; border: 1px solid #dee2e6 !important; cursor: pointer !important; margin-right: 8px !important; margin-bottom: 8px !important; display: inline-block !important; vertical-align: top !important;"
onclick="showImageModal('${imageUrl}')">`;
});
html += '</div>';
}
html += '</div>';
return html;
}
// 渲染分页
function renderPagination(pagination) {
if (pagination.pages <= 1) return '';
let html = '<nav aria-label="评价分页"><ul class="pagination justify-content-center">';
// 上一页
if (pagination.has_prev) {
html += `<li class="page-item">
<a class="page-link" href="#" onclick="loadProductReviews(window.currentProductId, ${pagination.page - 1}); return false;">
上一页
</a>
</li>`;
}
// 页码
const startPage = Math.max(1, pagination.page - 2);
const endPage = Math.min(pagination.pages, pagination.page + 2);
for (let i = startPage; i <= endPage; i++) {
const activeClass = i === pagination.page ? 'active' : '';
html += `<li class="page-item ${activeClass}">
<a class="page-link" href="#" onclick="loadProductReviews(window.currentProductId, ${i}); return false;">
${i}
</a>
</li>`;
}
// 下一页
if (pagination.has_next) {
html += `<li class="page-item">
<a class="page-link" href="#" onclick="loadProductReviews(window.currentProductId, ${pagination.page + 1}); return false;">
下一页
</a>
</li>`;
}
html += '</ul></nav>';
return html;
}
// 筛选评价
function filterReviews(rating) {
// 更新筛选按钮状态
const filterButtons = document.querySelectorAll('.reviews-filter .btn');
filterButtons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
// 重新加载评价
loadProductReviews(window.currentProductId, 1, rating);
}
// 显示图片模态框
function showImageModal(imageUrl) {
const modal = document.getElementById('imageModal');
const modalImage = document.getElementById('modalImage');
if (modal && modalImage) {
modalImage.src = imageUrl;
new bootstrap.Modal(modal).show();
}
}
// 显示提示信息
function showAlert(message, type = 'info') {
// 创建警告框
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 添加到页面
document.body.appendChild(alertDiv);
// 自动消失
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
// 全局变量用于存储当前商品ID
window.currentProductId = null;