<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>方案详情 - 超协体</title>
<script src="/js/auth.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
/* 导航栏 */
.navbar {
background: white;
border-radius: 16px;
padding: 16px 24px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.navbar-brand {
font-size: 20px;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-decoration: none;
}
/* 主内容 */
.main-content {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
@media (max-width: 968px) {
.main-content {
grid-template-columns: 1fr;
}
}
.card {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
/* 方案头部 */
.solution-header {
margin-bottom: 32px;
}
.solution-title {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 16px;
line-height: 1.3;
}
.solution-meta {
display: flex;
gap: 20px;
flex-wrap: wrap;
font-size: 14px;
color: #666;
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
}
/* 作者信息 */
.author-info {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #f8f9fa;
border-radius: 12px;
margin-bottom: 24px;
}
.author-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
}
.author-details {
flex: 1;
}
.author-name {
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.author-time {
font-size: 13px;
color: #999;
}
/* 内容区 */
.section {
margin-bottom: 32px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
}
.section-content {
font-size: 15px;
line-height: 1.8;
color: #555;
white-space: pre-wrap;
}
.code-block {
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 13px;
line-height: 1.6;
}
/* 标签 */
.tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.tag {
padding: 6px 14px;
background: #f0f4ff;
color: #667eea;
border-radius: 16px;
font-size: 13px;
}
/* 侧边栏 */
.sidebar {
display: flex;
flex-direction: column;
gap: 20px;
}
.stats-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.1);
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 28px;
font-weight: bold;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
font-size: 13px;
color: #999;
margin-top: 4px;
}
/* 评分区域 */
.rating-section {
margin-bottom: 24px;
}
.rating-overall {
text-align: center;
padding: 20px;
background: #f8f9fa;
border-radius: 12px;
margin-bottom: 20px;
}
.rating-score {
font-size: 48px;
font-weight: bold;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.rating-count {
font-size: 13px;
color: #999;
margin-top: 4px;
}
.rating-bars {
margin-bottom: 20px;
}
.rating-bar {
margin-bottom: 12px;
}
.rating-bar-label {
display: flex;
justify-content: space-between;
font-size: 13px;
margin-bottom: 4px;
}
.rating-bar-track {
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.rating-bar-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.5s;
}
/* 按钮 */
.btn {
width: 100%;
padding: 14px;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3);
}
.btn-outline {
background: white;
border: 2px solid #667eea;
color: #667eea;
}
.btn-outline:hover {
background: #f0f4ff;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* 评分模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.6);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: white;
border-radius: 16px;
padding: 32px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.modal-title {
font-size: 22px;
font-weight: bold;
margin-bottom: 24px;
color: #333;
}
.rating-input-group {
margin-bottom: 20px;
}
.rating-input-label {
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
display: block;
}
.rating-stars {
display: flex;
gap: 8px;
justify-content: center;
margin-bottom: 4px;
}
.star {
font-size: 32px;
cursor: pointer;
color: #ddd;
transition: color 0.2s;
}
.star.active {
color: #fbbf24;
}
.star:hover {
color: #fbbf24;
}
.rating-value {
text-align: center;
font-size: 14px;
color: #999;
}
.modal-buttons {
display: flex;
gap: 12px;
margin-top: 24px;
}
/* 评分列表 */
.ratings-list {
margin-top: 32px;
}
.rating-item {
padding: 16px;
background: #f8f9fa;
border-radius: 12px;
margin-bottom: 12px;
}
.rating-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.rating-user {
font-weight: 600;
color: #333;
}
.rating-stars-display {
color: #fbbf24;
font-size: 14px;
}
.rating-comment {
font-size: 14px;
color: #666;
line-height: 1.6;
}
.rating-time {
font-size: 12px;
color: #999;
margin-top: 8px;
}
/* 加载状态 */
.loading {
text-align: center;
padding: 60px;
color: #999;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<!-- 导航栏 -->
<div class="navbar">
<a href="/dashboard.html" class="navbar-brand">超协体</a>
<a href="/solution-library.html" style="text-decoration: none; color: #666; font-size: 14px;">← 返回方案库</a>
</div>
<!-- 加载状态 -->
<div id="loadingState" class="card">
<div class="loading">
<div class="spinner"></div>
<div>加载中...</div>
</div>
</div>
<!-- 主内容 -->
<div id="mainContent" class="main-content" style="display: none;">
<!-- 左侧:方案内容 -->
<div>
<div class="card">
<!-- 方案头部 -->
<div class="solution-header">
<h1 class="solution-title" id="solutionTitle"></h1>
<div class="solution-meta" id="solutionMeta"></div>
</div>
<!-- 作者信息 -->
<div class="author-info" id="authorInfo"></div>
<!-- 标签 -->
<div class="section" id="tagsSection" style="display: none;">
<div class="section-title">🏷️ 标签</div>
<div class="tags" id="tagsList"></div>
</div>
<!-- 问题描述 -->
<div class="section">
<div class="section-title">❓ 问题描述</div>
<div class="section-content" id="problemDefinition"></div>
</div>
<!-- 解决方案 -->
<div class="section">
<div class="section-title">💡 解决方案</div>
<div class="section-content" id="solutionContent"></div>
</div>
<!-- 代码片段 -->
<div class="section" id="codeSection" style="display: none;">
<div class="section-title">💻 代码实现</div>
<pre class="code-block" id="codeSnippet"></pre>
</div>
</div>
<!-- 评分列表 -->
<div class="card ratings-list" id="ratingsList" style="display: none;">
<div class="section-title">📊 评分详情</div>
<div id="ratingsContent"></div>
</div>
</div>
<!-- 右侧:统计和操作 -->
<div class="sidebar">
<!-- 统计卡片 -->
<div class="stats-card">
<div class="stats-grid" id="statsGrid"></div>
</div>
<!-- 评分卡片 -->
<div class="stats-card">
<div class="rating-section">
<div class="rating-overall" id="ratingOverall"></div>
<div class="rating-bars" id="ratingBars"></div>
<button class="btn btn-primary" onclick="openRatingModal()" id="rateBtn">
⭐ 评分方案
</button>
</div>
</div>
<!-- 操作卡片 -->
<div class="stats-card">
<button class="btn btn-outline" onclick="citeThis()" style="margin-bottom: 12px;">
🔗 引用此方案
</button>
<button class="btn btn-outline" onclick="window.location.href='/solution-library.html'">
📚 浏览更多方案
</button>
</div>
</div>
</div>
</div>
<!-- 评分模态框 -->
<div class="modal" id="ratingModal">
<div class="modal-content">
<h2 class="modal-title">为方案评分</h2>
<div class="rating-input-group">
<label class="rating-input-label">代码质量</label>
<div class="rating-stars" data-rating="quality"></div>
<div class="rating-value" id="qualityValue">未评分</div>
</div>
<div class="rating-input-group">
<label class="rating-input-label">可复用性</label>
<div class="rating-stars" data-rating="reusability"></div>
<div class="rating-value" id="reusabilityValue">未评分</div>
</div>
<div class="rating-input-group">
<label class="rating-input-label">创新性</label>
<div class="rating-stars" data-rating="innovation"></div>
<div class="rating-value" id="innovationValue">未评分</div>
</div>
<div class="rating-input-group">
<label class="rating-input-label">评价(可选)</label>
<textarea id="ratingComment" class="form-textarea" placeholder="分享你的看法..." style="width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; min-height: 80px; font-family: inherit;"></textarea>
</div>
<div class="modal-buttons">
<button class="btn btn-outline" onclick="closeRatingModal()" style="flex: 1;">
取消
</button>
<button class="btn btn-primary" onclick="submitRating()" style="flex: 2;" id="submitRatingBtn">
提交评分 (+5积分)
</button>
</div>
</div>
</div>
<script>
const API_BASE = window.location.origin;
const urlParams = new URLSearchParams(window.location.search);
const solutionId = urlParams.get('id');
let solution = null;
let currentUser = null;
const ratings = { quality: 0, reusability: 0, innovation: 0 };
// 检查登录
if (!isLoggedIn()) {
window.location.href = '/login.html';
}
// 页面加载
document.addEventListener('DOMContentLoaded', async () => {
if (!solutionId) {
alert('方案ID不存在');
window.location.href = '/solution-library.html';
return;
}
currentUser = getUser();
await loadSolution();
initRatingStars();
});
// 加载方案详情
async function loadSolution() {
try {
const response = await authFetch(`/api/solutions/${solutionId}`);
const data = await response.json();
if (data.success) {
solution = data.solution;
renderSolution();
} else {
alert('加载失败:' + data.message);
window.location.href = '/solution-library.html';
}
} catch (error) {
console.error('加载方案失败:', error);
alert('加载失败,请重试');
}
}
// 渲染方案
function renderSolution() {
// 标题
document.getElementById('solutionTitle').textContent = solution.title;
// 元数据
const meta = document.getElementById('solutionMeta');
meta.innerHTML = `
<span class="meta-item">👁️ ${solution.viewCount || 0} 浏览</span>
<span class="meta-item">🔗 ${solution.referenceCount || 0} 引用</span>
<span class="meta-item">📊 ${solution.ratingCount || 0} 评分</span>
${solution.difficultyLevel ? `<span class="meta-item">${'⭐'.repeat(solution.difficultyLevel)} 难度</span>` : ''}
`;
// 作者信息
const author = document.getElementById('authorInfo');
author.innerHTML = `
<div class="author-avatar">${solution.author.username.charAt(0).toUpperCase()}</div>
<div class="author-details">
<div class="author-name">${escapeHtml(solution.author.username)} ${solution.author.serialNumber ? '#' + String(solution.author.serialNumber).padStart(3, '0') : ''}</div>
<div class="author-time">发布于 ${formatDate(solution.createdAt)}</div>
</div>
`;
// 标签
if (solution.manualTags && solution.manualTags.length > 0) {
document.getElementById('tagsSection').style.display = 'block';
document.getElementById('tagsList').innerHTML = solution.manualTags.map(tag =>
`<span class="tag">${escapeHtml(tag)}</span>`
).join('');
}
// 问题描述
document.getElementById('problemDefinition').textContent = solution.problemDefinition;
// 解决方案
document.getElementById('solutionContent').textContent = solution.solutionContent;
// 代码片段
if (solution.codeSnippet) {
document.getElementById('codeSection').style.display = 'block';
document.getElementById('codeSnippet').textContent = solution.codeSnippet;
}
// 统计
document.getElementById('statsGrid').innerHTML = `
<div class="stat-item">
<div class="stat-value">${solution.viewCount || 0}</div>
<div class="stat-label">浏览量</div>
</div>
<div class="stat-item">
<div class="stat-value">${solution.referenceCount || 0}</div>
<div class="stat-label">引用次数</div>
</div>
<div class="stat-item">
<div class="stat-value">${solution.ratingCount || 0}</div>
<div class="stat-label">评分人数</div>
</div>
<div class="stat-item">
<div class="stat-value">${solution._count?.referencedBy || 0}</div>
<div class="stat-label">被引用</div>
</div>
`;
// 评分
renderRating();
// 评分列表
if (solution.ratings && solution.ratings.length > 0) {
renderRatings();
}
// 禁用自己给自己评分
if (solution.authorId === currentUser.id) {
document.getElementById('rateBtn').disabled = true;
document.getElementById('rateBtn').textContent = '不能给自己评分';
}
// 显示内容,隐藏加载状态
document.getElementById('loadingState').style.display = 'none';
document.getElementById('mainContent').style.display = 'grid';
}
// 渲染评分
function renderRating() {
const avg = parseFloat(solution.avgRating) || 0;
const quality = parseFloat(solution.qualityScore) || 0;
const reusability = parseFloat(solution.reusabilityScore) || 0;
const innovation = parseFloat(solution.innovationScore) || 0;
document.getElementById('ratingOverall').innerHTML = `
<div class="rating-score">${avg.toFixed(1)}</div>
<div class="rating-count">${solution.ratingCount || 0} 人评分</div>
`;
document.getElementById('ratingBars').innerHTML = `
<div class="rating-bar">
<div class="rating-bar-label">
<span>代码质量</span>
<span>${quality.toFixed(1)}</span>
</div>
<div class="rating-bar-track">
<div class="rating-bar-fill" style="width: ${quality * 10}%"></div>
</div>
</div>
<div class="rating-bar">
<div class="rating-bar-label">
<span>可复用性</span>
<span>${reusability.toFixed(1)}</span>
</div>
<div class="rating-bar-track">
<div class="rating-bar-fill" style="width: ${reusability * 10}%"></div>
</div>
</div>
<div class="rating-bar">
<div class="rating-bar-label">
<span>创新性</span>
<span>${innovation.toFixed(1)}</span>
</div>
<div class="rating-bar-track">
<div class="rating-bar-fill" style="width: ${innovation * 10}%"></div>
</div>
</div>
`;
}
// 渲染评分列表
function renderRatings() {
document.getElementById('ratingsList').style.display = 'block';
document.getElementById('ratingsContent').innerHTML = solution.ratings.map(rating => `
<div class="rating-item">
<div class="rating-header">
<span class="rating-user">${escapeHtml(rating.user.username)}</span>
<span class="rating-stars-display">${'⭐'.repeat(Math.round(parseFloat(rating.overallRating)))}</span>
</div>
<div style="font-size: 12px; color: #999; margin-bottom: 8px;">
质量: ${rating.qualityRating}/10 | 复用: ${rating.reusabilityRating}/10 | 创新: ${rating.innovationRating}/10
</div>
${rating.comment ? `<div class="rating-comment">${escapeHtml(rating.comment)}</div>` : ''}
<div class="rating-time">${formatDate(rating.createdAt)}</div>
</div>
`).join('');
}
// 初始化评分星星
function initRatingStars() {
document.querySelectorAll('.rating-stars').forEach(container => {
const type = container.dataset.rating;
for (let i = 1; i <= 10; i++) {
const star = document.createElement('span');
star.className = 'star';
star.textContent = '★';
star.dataset.value = i;
star.onclick = () => setRating(type, i);
container.appendChild(star);
}
});
}
// 设置评分
function setRating(type, value) {
ratings[type] = value;
document.getElementById(`${type}Value`).textContent = `${value}/10`;
const stars = document.querySelectorAll(`[data-rating="${type}"] .star`);
stars.forEach((star, index) => {
star.classList.toggle('active', index < value);
});
}
// 打开评分模态框
function openRatingModal() {
document.getElementById('ratingModal').classList.add('active');
}
// 关闭评分模态框
function closeRatingModal() {
document.getElementById('ratingModal').classList.remove('active');
}
// 提交评分
async function submitRating() {
if (!ratings.quality || !ratings.reusability || !ratings.innovation) {
alert('请完成所有评分项');
return;
}
const btn = document.getElementById('submitRatingBtn');
const originalText = btn.textContent;
try {
btn.disabled = true;
btn.textContent = '提交中...';
const response = await authFetch(`/api/solutions/${solutionId}/rate`, {
method: 'POST',
body: JSON.stringify({
qualityRating: ratings.quality,
reusabilityRating: ratings.reusability,
innovationRating: ratings.innovation,
comment: document.getElementById('ratingComment').value.trim()
})
});
const data = await response.json();
if (data.success) {
alert('评分成功!获得5积分');
closeRatingModal();
location.reload();
} else {
alert('评分失败:' + data.message);
btn.disabled = false;
btn.textContent = originalText;
}
} catch (error) {
console.error('提交评分失败:', error);
alert('提交失败,请重试');
btn.disabled = false;
btn.textContent = originalText;
}
}
// 引用方案
function citeThis() {
const msg = `引用此方案需要在您自己的方案中添加。\n\n请先创建或编辑您的方案,然后调用引用API。\n\n方案ID: ${solutionId}`;
alert(msg);
// 实际应用中,这里应该打开一个选择方案的界面
}
// HTML转义
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 格式化日期
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
</script>
</body>
</html>