- 修复 Admin 改名路径未扣域能(非审核路径补充 HasCompletedTask + DeductOnRename) - 赋能按钮改为 Modal 弹窗(轻赋/重赋选择 + 每日上限提示) - EnergyInfo API 增加 daily_energize_exp/daily_exp_cap_reached 返回值 - "我的域能" TAB 增加经验值和等级展示 - Energize 事务修复:导出 EnergyStore + WithTx 方法确保 DB 操作原子性 - Studio 写文章页新增 LV0 锁止横幅 + 禁用提交按钮
224 lines
8.5 KiB
HTML
224 lines
8.5 KiB
HTML
{{template "layout/header.html" .}}
|
||
<body>
|
||
|
||
{{template "layout/nav.html" .}}
|
||
|
||
<div class="container post-detail">
|
||
<article class="post-article">
|
||
<header class="post-detail-header">
|
||
<h1 class="post-detail-title">{{.Post.Title}}</h1>
|
||
<div class="post-detail-meta">
|
||
<span class="post-author">{{if .Post.AuthorName}}{{.Post.AuthorName}}{{else}}该用户已注销{{end}}</span>
|
||
<span class="post-time">{{.Post.CreatedAt.Format "2006-01-02 15:04"}}</span>
|
||
{{if ne .Post.Status "approved"}}
|
||
<span class="post-status post-status-{{.Post.Status}}">{{index $.StatusNames .Post.Status}}</span>
|
||
{{end}}
|
||
{{if .Post.IsLocked}}
|
||
<span class="post-status post-status-locked">已锁定</span>
|
||
{{end}}
|
||
{{if and .Post.PendingBody (eq $.Post.UserID $.UID)}}
|
||
<span class="post-status post-status-revision">修订审核中</span>
|
||
{{end}}
|
||
</div>
|
||
</header>
|
||
|
||
{{if .Post.RejectReason}}
|
||
<div class="post-reject-reason">
|
||
<strong>退回理由:</strong>{{.Post.RejectReason}}
|
||
</div>
|
||
{{end}}
|
||
|
||
<div class="post-detail-body" id="postContent"></div>
|
||
</article>
|
||
|
||
{{if .IsLoggedIn}}
|
||
<div class="post-actions">
|
||
{{if and $.Post (or (eq $.Post.UserID $.UID) $.CanAccessAdmin) (ne $.Post.Status "pending") (not $.Post.IsLocked)}}
|
||
<a href="/studio/write?id={{$.Post.ID}}" class="btn btn-secondary">编辑</a>
|
||
{{end}}
|
||
{{if and $.Post (eq $.Post.UserID $.UID) (or (eq $.Post.Status "draft") (eq $.Post.Status "rejected")) (not $.Post.IsLocked)}}
|
||
<button class="btn btn-primary" id="submitAuditBtn" data-id="{{$.Post.ID}}">提交审核</button>
|
||
{{end}}
|
||
{{if and $.Post (ne $.Post.UserID $.UID)}}
|
||
<button class="btn btn-primary" id="energizeBtn" data-id="{{$.Post.ID}}">赋能</button>
|
||
{{end}}
|
||
</div>
|
||
|
||
<!-- 赋能弹窗 -->
|
||
<div class="energize-overlay" id="energizeOverlay" style="display:none">
|
||
<div class="energize-modal">
|
||
<div class="energize-modal-header">
|
||
<h3>为这篇文章注入能量</h3>
|
||
<button class="energize-close" id="energizeClose">×</button>
|
||
</div>
|
||
<div class="energize-modal-body">
|
||
<p class="energize-hint" id="energizeCapHint" style="display:none">
|
||
今日赋能经验奖励已达上限,继续赋能将消耗域能,对方仍会获得激励。
|
||
</p>
|
||
<div class="energize-options">
|
||
<button class="energize-option-btn" id="energizeLight" data-amount="10">
|
||
<span class="energize-option-title">轻赋</span>
|
||
<span class="energize-option-cost">消耗 1 域能</span>
|
||
<span class="energize-option-gain">获得 10 经验</span>
|
||
</button>
|
||
<button class="energize-option-btn" id="energizeHeavy" data-amount="20">
|
||
<span class="energize-option-title">重赋</span>
|
||
<span class="energize-option-cost">消耗 2 域能</span>
|
||
<span class="energize-option-gain">获得 20 经验</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{{end}}
|
||
</div>
|
||
|
||
{{template "layout/footer.html" .}}
|
||
|
||
{{/* MD 内容安全传递给 JS */}}
|
||
<textarea id="postMdContent" style="display:none">{{.Post.Body}}</textarea>
|
||
|
||
<link rel="stylesheet" href="/static/vditor/dist/index.css?v={{assetV "/static/vditor/dist/index.css"}}">
|
||
<script src="/static/vditor/dist/method.min.js?v={{assetV "/static/vditor/dist/method.min.js"}}"></script>
|
||
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}"></script>
|
||
<script src="/static/js/shortcode.js?v={{assetV "/static/js/shortcode.js"}}"></script>
|
||
<script>
|
||
(function() {
|
||
var el = document.getElementById('postContent');
|
||
var mdEl = document.getElementById('postMdContent');
|
||
if (!el || !mdEl) return;
|
||
|
||
var md = mdEl.value;
|
||
Vditor.preview(el, md, {
|
||
cdn: '/static/vditor',
|
||
theme: { current: 'light', path: '/static/vditor/dist/css/content-theme' },
|
||
hljs: { style: 'github-dark', enable: true },
|
||
after: function() {
|
||
// 外部链接自动加 nofollow + target="_blank",避免权重流失
|
||
var host = window.location.host;
|
||
var links = el.querySelectorAll('a[href^="http"]');
|
||
for (var i = 0; i < links.length; i++) {
|
||
var a = links[i];
|
||
if (a.host && a.host !== host) {
|
||
a.setAttribute('rel', 'nofollow noopener noreferrer');
|
||
a.setAttribute('target', '_blank');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
// Vditor.preview() 渲染 MD,shortcode 占位 div 原样保留
|
||
// 渲染完成后,将占位 div 替换为对应卡片 UI
|
||
renderShortcodes(el);
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
// 提交审核按钮
|
||
(function() {
|
||
var btn = document.getElementById('submitAuditBtn');
|
||
if (!btn) return;
|
||
btn.addEventListener('click', function() {
|
||
if (!confirm('确认提交审核?审核通过后将公开发布。')) return;
|
||
var csrf = document.querySelector('meta[name="csrf-token"]');
|
||
fetch('/api/posts/' + btn.dataset.id + '/submit', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-Token': csrf ? csrf.getAttribute('content') : ''
|
||
}
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) { window.location.reload(); }
|
||
else { alert(d.message || '提交失败'); }
|
||
})
|
||
.catch(function() { alert('请求失败'); });
|
||
});
|
||
})();
|
||
</script>
|
||
|
||
<script>
|
||
// 赋能按钮 + 弹窗
|
||
(function() {
|
||
var btn = document.getElementById('energizeBtn');
|
||
var overlay = document.getElementById('energizeOverlay');
|
||
var closeBtn = document.getElementById('energizeClose');
|
||
var capHint = document.getElementById('energizeCapHint');
|
||
var lightBtn = document.getElementById('energizeLight');
|
||
var heavyBtn = document.getElementById('energizeHeavy');
|
||
if (!btn || !overlay) return;
|
||
|
||
var postId = btn.dataset.id;
|
||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
||
|
||
function showOverlay() {
|
||
overlay.style.display = 'flex';
|
||
}
|
||
function hideOverlay() {
|
||
overlay.style.display = 'none';
|
||
capHint.style.display = 'none';
|
||
lightBtn.disabled = false;
|
||
heavyBtn.disabled = false;
|
||
}
|
||
|
||
btn.addEventListener('click', function() {
|
||
// 先获取域能状态,判断是否需要显示上限提示
|
||
fetch('/api/energy/info')
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success && d.data && d.data.daily_exp_cap_reached) {
|
||
capHint.style.display = 'block';
|
||
}
|
||
showOverlay();
|
||
})
|
||
.catch(function() { showOverlay(); });
|
||
});
|
||
|
||
closeBtn.addEventListener('click', hideOverlay);
|
||
overlay.addEventListener('click', function(e) {
|
||
if (e.target === overlay) hideOverlay();
|
||
});
|
||
|
||
function doEnergize(amount) {
|
||
lightBtn.disabled = true;
|
||
heavyBtn.disabled = true;
|
||
|
||
fetch('/api/energy/energize', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRF-Token': csrfToken
|
||
},
|
||
body: JSON.stringify({ post_id: parseInt(postId), amount: amount })
|
||
})
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(d) {
|
||
if (d.success) {
|
||
alert(d.data.message || '赋能成功!');
|
||
hideOverlay();
|
||
} else {
|
||
alert(d.message || '赋能失败');
|
||
lightBtn.disabled = false;
|
||
heavyBtn.disabled = false;
|
||
}
|
||
})
|
||
.catch(function() {
|
||
alert('请求失败');
|
||
lightBtn.disabled = false;
|
||
heavyBtn.disabled = false;
|
||
});
|
||
}
|
||
|
||
lightBtn.addEventListener('click', function() {
|
||
doEnergize(parseInt(lightBtn.dataset.amount));
|
||
});
|
||
heavyBtn.addEventListener('click', function() {
|
||
doEnergize(parseInt(heavyBtn.dataset.amount));
|
||
});
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|