647 lines
22 KiB
JavaScript
647 lines
22 KiB
JavaScript
// 评价功能 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;
|