feat: SSR 投票帖发帖与详情投票卡

compose 支持 poll 类型与选项,详情可投票/查看结果/结束。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 01:18:58 +08:00
parent 7f21ffb14e
commit 4f93397ef3
10 changed files with 403 additions and 43 deletions

View File

@@ -15,6 +15,30 @@
{{end}}
</select>
</label>
{{if .IsEdit}}
<p class="j13-muted">类型:{{if eq .PostType "poll"}}投票{{else}}普通讨论{{end}}(不可更改)</p>
<input type="hidden" name="post_type" value="{{.PostType}}"/>
{{else}}
<label>类型
<select name="post_type" id="compose-post-type">
<option value="normal"{{if ne .PostType "poll"}} selected{{end}}>普通讨论</option>
<option value="poll"{{if eq .PostType "poll"}} selected{{end}}>投票</option>
</select>
</label>
<fieldset class="j13-poll-fields" id="compose-poll-fields"{{if ne .PostType "poll"}} hidden{{end}}>
<legend>投票设置</legend>
<label class="j13-check"><input type="checkbox" name="poll_multi" value="1"{{if .PollMulti}} checked{{end}}/> 允许多选</label>
<label>最多可选
<input name="poll_max_choices" type="number" min="1" max="10" value="{{.PollMaxChoices}}"/>
</label>
<label>截止时间(可选)
<input name="poll_ends_at" type="datetime-local" value="{{.PollEndsAt}}"/>
</label>
<label>选项每行一项210 个)
<textarea name="poll_options" rows="6" placeholder="选项 A&#10;选项 B&#10;选项 C">{{.PollOptions}}</textarea>
</label>
</fieldset>
{{end}}
<label>标题
<input name="title" required maxlength="{{.TitleMax}}" value="{{.Title}}"/>
</label>
@@ -39,4 +63,16 @@
</form>
</main>
{{template "base/footer" .}}
{{if not .IsEdit}}
<script>
(function () {
var sel = document.getElementById('compose-post-type');
var box = document.getElementById('compose-poll-fields');
if (!sel || !box) return;
function sync() { box.hidden = sel.value !== 'poll'; }
sel.addEventListener('change', sync);
sync();
})();
</script>
{{end}}
{{end}}

View File

@@ -89,6 +89,58 @@
</div>
{{end}}
</header>
{{if .Poll}}
<section class="j13-poll" aria-label="投票">
<h2 class="j13-poll__title">投票</h2>
<p class="j13-muted">
{{if .Poll.Multi}}多选(最多 {{.Poll.MaxChoices}} 项){{else}}单选{{end}}
· 共 {{.Poll.TotalVotes}} 票
{{if .Poll.EndsLabel}} · 截止 {{.Poll.EndsLabel}}{{end}}
{{if .Poll.Closed}} · 已结束{{else if .Poll.HasVoted}} · 你已投票{{end}}
</p>
{{if .Poll.CanVote}}
<form method="post" action="/post/{{.PostID}}/poll/vote" class="j13-form j13-poll__form">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<ul class="j13-poll__options">
{{range .Poll.Options}}
<li>
<label class="j13-check">
{{if $.Poll.Multi}}
<input type="checkbox" name="option_ids" value="{{.ID}}"/>
{{else}}
<input type="radio" name="option_id" value="{{.ID}}" required/>
{{end}}
{{.Text}}
</label>
</li>
{{end}}
</ul>
<button type="submit">提交投票</button>
</form>
{{else}}
<ul class="j13-poll__options">
{{range .Poll.Options}}
<li class="j13-poll__option{{if .Selected}} is-selected{{end}}">
<span class="j13-poll__opt-text">{{.Text}}</span>
{{if $.Poll.ShowResults}}
<span class="j13-poll__bar" style="--pct: {{.Percent}}%"></span>
<span class="j13-muted">{{.Percent}}%{{.VoteCount}}</span>
{{end}}
</li>
{{end}}
</ul>
{{if and (not $.LoggedIn) (not .Poll.Closed)}}
<p class="j13-muted"><a href="/login?redirect={{.PostPath}}">登录</a> 后可投票。</p>
{{end}}
{{end}}
{{if .Poll.CanClose}}
<form method="post" action="/post/{{.PostID}}/poll/close" class="j13-inline-form" onsubmit="return confirm('确认结束投票?');">
<input type="hidden" name="_csrf" value="{{.CSRF}}"/>
<button type="submit" class="j13-linkbtn">结束投票</button>
</form>
{{end}}
</section>
{{end}}
<div class="j13-post__content post-detail-content">{{safeHTML .BodyHTML}}</div>
</article>