Files
tamabox/templates/user/question-list.html
T
tamakyi c680671b7e feat(user/questions): 手机端时间跨年自动补年份,避免省略年份后产生歧义
DateCompact:与当前同一年的渲染「月-日 时:分」,跨年补回完整「年-月-日 时:分」。
只砍年份会让「去年今天」和「今年今天」显示成同一串,逐条补年份比按年分组便宜得多
(列表带游标分页 + 可切排序/筛选,组标题会跨页重复或错位)。
桌面端不受影响,仍显示 Y-m-d H:i:s。
测试覆盖年份边界(同年 1/1 与 12/31、跨年);预览数据里放了一条 2025 年的提问用于肉眼核对。
2026-10-23 21:04:50 +08:00

526 lines
29 KiB
HTML
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.
{{template "base/header" .}}
<style>
.qlist-tabs {
display: flex;
justify-content: center;
gap: 4px;
padding: 4px;
margin: 6px auto 20px;
max-width: 320px;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius-sm);
background: var(--tb-surface-2);
}
.qlist-tab {
flex: 1 1 0;
border: 0;
border-radius: var(--tb-radius-xs);
padding: 8px 15px;
background: transparent;
color: var(--tb-text-secondary);
cursor: pointer;
font-size: var(--tb-fs-md);
transition: background 0.2s ease, color 0.2s ease;
}
.qlist-tab:hover { color: var(--tb-primary); }
.qlist-tab.is-active {
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
color: var(--tb-primary);
font-weight: 600;
}
.qlist-panel[hidden] { display: none !important; }
/* 三个面板兜一层卡片底。此前面板内容是裸行(只靠 <hr> 分隔)直接铺在页面背景上,
默认主题底色是纯白所以看不出来;换成带整页插画的主题后,场景会整片透上来,
看着就像"这一页的卡片是透明的"。和其它后台页保持一致,也和主题无关。 */
.qlist-panel {
background: var(--tb-surface);
border-radius: var(--tb-radius);
box-shadow: var(--tb-shadow);
padding: 18px 20px 8px;
}
/* 面板内部本来还有一张卡片(投票 tab 的「我的投票」标题条):降成次级底色,
否则就是卡片套卡片,阴影会叠一层脏边。 */
.qlist-panel .uk-card-default { background: var(--tb-surface-2); box-shadow: none; }
@media (max-width: 640px) {
.qlist-panel { padding: 14px 12px 4px; }
}
.all-votes-list { display: grid; gap: 10px; }
.all-vote-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 16px;
border-bottom: 1px solid var(--tb-border);
text-decoration: none;
}
.all-vote-item:first-child { border-top: 1px solid var(--tb-border); }
.all-vote-item:hover { background: #f8fbff; text-decoration: none; }
.all-vote-item .hot-vote-title { max-width: 100%; }
.hot-vote-title {
display: block;
overflow: hidden;
color: var(--tb-text);
font-size: var(--tb-fs-md);
font-weight: 600;
line-height: 1.45;
text-overflow: ellipsis;
white-space: nowrap;
}
.hot-vote-title:hover { color: var(--tb-primary); }
.hot-vote-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 7px;
margin-top: 8px;
}
.hot-vote-meta .uk-label { font-size: var(--tb-fs-xs); line-height: 1.6; }
.hot-vote-action { flex: 0 0 auto; color: var(--tb-primary); font-size: var(--tb-fs-md); white-space: nowrap; }
/* 提问列表右上角:状态标签与操作按钮统一尺寸(等宽等高、垂直居中、完全对齐)。
每个元素显式带 .qlist-pill 类,选择器挂在 .qlist-pills 容器下提升优先级,
避免浏览器对 <button> 的默认样式或 UIkit .uk-label 造成差异。
移动端默认显示短文字标签(每语言短词,如 未读→新/New,宽度可控);
≥640px 显示完整文案(配合 title 悬浮提示,多语言安全)。 */
.qlist-pills .qlist-pill {
display: inline-flex;
align-items: center;
justify-content: center;
height: 24px;
/* 手机端收窄横向内边距:一行要塞下「时间 + 状态标签 + 操作按钮」,
8px 的内边距在 360/390 宽下刚好差十几像素把按钮挤到第二行。
≥640px 的桌面样式在下面单独覆盖回 62px/10px,桌面端不受影响。 */
min-width: 34px;
padding: 0 6px;
box-sizing: border-box;
font-size: var(--tb-fs-xs);
line-height: 1;
white-space: nowrap;
border-radius: var(--tb-radius-xs);
border: 0;
vertical-align: middle;
}
.qlist-pills .tb-short { display: inline; }
.qlist-pills .tb-text { display: none; }
/* 按钮额外重置浏览器默认样式,保证与 span 标签渲染完全一致 */
.qlist-pills .qlist-pill.tb-label-btn {
cursor: pointer;
font-family: inherit;
-webkit-appearance: none;
appearance: none;
background-image: none;
outline: none;
margin: 0;
}
.qlist-pills .qlist-pill.tb-label-btn:hover { opacity: 0.85; }
@media (min-width: 640px) {
/* 桌面端纯文字:隐藏移动端短词,只显示完整文案 */
.qlist-pills .qlist-pill { min-width: 62px; padding: 0 10px; }
.qlist-pills .tb-short { display: none; }
.qlist-pills .tb-text { display: inline; }
}
html[data-theme="dark"] .qlist-tabs { border-color: var(--tb-border); background: var(--tb-surface-2); }
html[data-theme="dark"] .qlist-tab { color: var(--tb-text-muted); }
html[data-theme="dark"] .qlist-tab.is-active { background: var(--tb-surface); color: var(--tb-primary); }
html[data-theme="dark"] .all-vote-item { border-color: var(--tb-border); }
html[data-theme="dark"] .all-vote-item:hover { background: rgba(78, 168, 245, 0.10); }
html[data-theme="dark"] .hot-vote-title { color: var(--tb-text); }
/* 紧凑统计看板:替代原 UIkit 卡片网格。手机端 4 列(7 项=2 行)、桌面端 7 列 1 行,
轻量描边小格子、缩字号、去掉大卡片内边距,显著降低纵向高度,减少手机端滚动。
--4 修饰类用于项数较少的看板(如私聊 4 项),在所有断点固定 4 列,避免末尾留白过多。 */
.qlist-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 16px;
}
@media (min-width: 768px) {
.qlist-stats { grid-template-columns: repeat(7, 1fr); }
}
.qlist-stats--4 { grid-template-columns: repeat(4, 1fr); }
@media (min-width: 768px) {
.qlist-stats--4 { grid-template-columns: repeat(4, 1fr); }
}
/* --2 修饰类用于项数很少的看板(如投票 2 项),固定 2 列,避免末尾留白 */
.qlist-stats--2 { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 768px) {
.qlist-stats--2 { grid-template-columns: repeat(2, 1fr); }
}
.qlist-stat {
background: #fff;
border: 1px solid var(--tb-border);
border-radius: var(--tb-radius-sm);
padding: 8px 6px;
text-align: center;
}
.qlist-stat .qlist-stat-num {
display: block;
font-size: var(--tb-fs-lg);
font-weight: 700;
line-height: 1.15;
color: #222;
}
.qlist-stat .qlist-stat-lbl {
display: block;
margin-top: 2px;
font-size: var(--tb-fs-xs);
color: #888;
line-height: 1.15;
}
html[data-theme="dark"] .qlist-stat { background: var(--tb-surface); border-color: var(--tb-border); }
html[data-theme="dark"] .qlist-stat .qlist-stat-num { color: var(--tb-text); }
html[data-theme="dark"] .qlist-stat .qlist-stat-lbl { color: var(--tb-text-muted); }
</style>
<div class="uk-container">
<div class="uk-flex uk-flex-middle uk-flex-between uk-margin-small-bottom">
<h2 class="uk-margin-remove">{{t .LangCode "qlist.title"}}</h2>
<a class="uk-button uk-button-default uk-button-small" href="/user/profile">{{t .LangCode "admin.featuresEntry"}}</a>
</div>
<!-- 居中切换:提问 / 投票 / 私聊 -->
<div class="qlist-tabs" role="tablist" aria-label="{{t .LangCode "qlist.title"}}">
<button type="button" class="qlist-tab is-active" data-qlist-tab="questions" role="tab" aria-selected="true">{{t .LangCode "qlist.questionsTab"}}</button>
<button type="button" class="qlist-tab" data-qlist-tab="votes" role="tab" aria-selected="false">{{t .LangCode "qlist.votesTab"}}</button>
<button type="button" class="qlist-tab" data-qlist-tab="chats" role="tab" aria-selected="false">{{t .LangCode "qlist.chatsTab"}}</button>
</div>
<!-- 提问面板(默认展示) -->
<div class="qlist-panel" data-qlist-panel="questions">
<!-- 统一提示区:标记已读/未读、置顶等操作的成功/错误提示沿用 base/alert 样式 -->
{{template "base/alert" .}}
{{ if .RestrictionLevel }}
<!-- 处置期常驻提示:L1 停分发 / L2 新回答不公开 / L3 禁止发布 -->
<div class="uk-alert-warning" uk-alert>
<div>{{t .LangCode "qlist.restrictionBanner" (or .RestrictionLevelName .RestrictionLevel) .RestrictionExpiresAt}} <span class="uk-text-meta">·</span> <a href="/rules">{{t .LangCode "qlist.restrictionRulesLink"}}</a></div>
</div>
{{ end }}
<!-- 提问箱统计看板(紧凑多列网格:手机4列/桌面7列,降低纵向高度) -->
<div class="qlist-stats">
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.total }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsTotal"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.answered }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsAnswered"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.unanswered }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsUnanswered"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.read }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsRead"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.unread }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsUnread"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.private }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsPrivate"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .Stats.pinned }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.statsPinned"}}</span></div>
</div>
<div class="uk-margin-medium-bottom uk-text-center">
<!-- 全文搜索自己提问箱 -->
<form method="get" action="" class="uk-form-stacked">
<div class="uk-flex uk-flex-center uk-flex-nowrap uk-overflow-auto">
<div class="uk-margin-small-right uk-width-expand" style="max-width: 420px;">
<input type="text" name="q" class="uk-input uk-form-small" value="{{ .Keyword }}" placeholder="{{t .LangCode "qlist.searchPlaceholder"}}" aria-label="{{t .LangCode "qlist.searchPlaceholder"}}">
</div>
<div class="uk-margin-small-right">
<button type="submit" class="uk-button uk-button-primary uk-button-small">{{t .LangCode "qlist.searchBtn"}}</button>
</div>
{{ if ne .Keyword "" }}
<div>
<a class="uk-button uk-button-default uk-button-small" href="/user/questions">{{t .LangCode "qlist.searchClear"}}</a>
</div>
{{ end }}
</div>
</form>
<form method="get" action="" class="uk-form-stacked uk-margin-small-top">
<div class="uk-flex uk-flex-center uk-flex-nowrap uk-overflow-auto">
<div class="uk-margin-small-right" style="width: 120px;">
<label class="uk-form-label uk-text-small">{{t .LangCode "qlist.sort"}}</label>
<select name="sort" class="uk-select uk-form-small" onchange="this.form.submit()" style="width: 100%;">
<option value="latest" {{if eq .SelectedSort "latest"}}selected{{end}}>{{t .LangCode "qlist.latest"}}</option>
<option value="oldest" {{if eq .SelectedSort "oldest"}}selected{{end}}>{{t .LangCode "qlist.oldest"}}</option>
</select>
</div>
<div class="uk-margin-small-right" style="width: 120px;">
<label class="uk-form-label uk-text-small">{{t .LangCode "qlist.status"}}</label>
<select name="status" class="uk-select uk-form-small" onchange="this.form.submit()" style="width: 100%;">
<option value="all" {{if eq .SelectedStatus "all"}}selected{{end}}>{{t .LangCode "qlist.all"}}</option>
<option value="answered" {{if eq .SelectedStatus "answered"}}selected{{end}}>{{t .LangCode "qlist.answered"}}</option>
<option value="unanswered" {{if eq .SelectedStatus "unanswered"}}selected{{end}}>{{t $.LangCode "qlist.unanswered"}}</option>
</select>
</div>
<div class="uk-margin-small-right" style="width: 120px;">
<label class="uk-form-label uk-text-small">{{t .LangCode "qlist.type"}}</label>
<select name="type" class="uk-select uk-form-small" onchange="this.form.submit()" style="width: 100%;">
<option value="all" {{if eq .SelectedType "all"}}selected{{end}}>{{t .LangCode "qlist.all"}}</option>
<option value="public" {{if eq .SelectedType "public"}}selected{{end}}>{{t .LangCode "qlist.public"}}</option>
<option value="private_question" {{if eq .SelectedType "private_question"}}selected{{end}}>{{t .LangCode "qlist.privateQuestion"}}</option>
</select>
</div>
<div class="uk-margin-small-right" style="width: 120px;">
<label class="uk-form-label uk-text-small">{{t .LangCode "qlist.reportFilter"}}</label>
<select name="reported" class="uk-select uk-form-small" onchange="this.form.submit()" style="width: 100%;">
<option value="all" {{if eq .SelectedReported "all"}}selected{{end}}>{{t .LangCode "qlist.all"}}</option>
<option value="reported" {{if eq .SelectedReported "reported"}}selected{{end}}>{{t .LangCode "qlist.reportedOnly"}}</option>
</select>
</div>
<div style="width: 120px;">
<label class="uk-form-label uk-text-small">{{t .LangCode "qlist.readFilter"}}</label>
<select name="read" class="uk-select uk-form-small" onchange="this.form.submit()" style="width: 100%;">
<option value="all" {{if eq .SelectedRead "all"}}selected{{end}}>{{t .LangCode "qlist.all"}}</option>
<option value="read" {{if eq .SelectedRead "read"}}selected{{end}}>{{t .LangCode "qlist.filterYes"}}</option>
<option value="unread" {{if eq .SelectedRead "unread"}}selected{{end}}>{{t .LangCode "qlist.filterNo"}}</option>
</select>
</div>
</div>
</form>
</div>
<!-- 我收到的提问(标签右浮同行时间,内容下一行) -->
<h4>{{t .LangCode "qlist.received"}}</h4>
{{ if .ReceivedItems }}
{{ range .ReceivedItems }}
<div class="uk-margin-medium-bottom" id="question-{{.QuestionID}}">
<hr>
<!-- 标签在左、按钮在右:两组分开布局;移动端状态/按钮只显示 emoji 图标,保持单行紧凑 -->
<div class="uk-flex uk-flex-middle uk-flex-wrap qlist-pills" style="gap: 6px;">
<!-- 左:时间 + 状态标签 -->
<div class="uk-flex uk-flex-middle uk-flex-wrap" style="gap: 6px;">
<!-- 手机端只显示「月-日 时:分」:完整时间占 127px,配合状态标签 + 操作按钮后
一行放不下(360/390 宽实测会折行);桌面端仍显示完整年月日时分秒。
跨年的条目由 DateCompact 自动补回年份(否则「去年今天」和「今年今天」
会渲染成同一串)。tb-short / tb-text 与右侧状态标签共用 640px 断点。 -->
<span class="uk-text-small uk-text-muted"><span class="tb-short">{{DateCompact .CreatedAt}}</span><span class="tb-text">{{Date .CreatedAt "Y-m-d H:i:s"}}</span></span>
{{ if .IsPinned }}
<span class="uk-label uk-label-warning qlist-pill" title="{{t $.LangCode "qlist.pinned"}}"><span class="tb-short">{{t $.LangCode "qlist.pinnedShort"}}</span><span class="tb-text">{{t $.LangCode "qlist.pinned"}}</span></span>
{{ end }}
{{ if eq .Type "question_private" }}
<span class="uk-label uk-label-warning qlist-pill" title="{{t $.LangCode "qlist.private"}}"><span class="tb-short">{{t $.LangCode "qlist.privateShort"}}</span><span class="tb-text">{{t $.LangCode "qlist.private"}}</span></span>
{{ end }}
{{ if not .IsAnswered }}
<span class="uk-label qlist-pill" title="{{t $.LangCode "qlist.unanswered"}}"><span class="tb-short">{{t $.LangCode "qlist.unansweredShort"}}</span><span class="tb-text">{{t $.LangCode "qlist.unanswered"}}</span></span>
{{ end }}
{{ if .ReportCount }}
<span class="uk-label uk-label-danger qlist-pill" title="{{t $.LangCode "qlist.reported" .ReportCount}}"><span class="tb-short">{{t $.LangCode "qlist.reportedShort" .ReportCount}}</span><span class="tb-text">{{t $.LangCode "qlist.reported" .ReportCount}}</span></span>
{{ end }}
{{ if .IsRead }}
<span class="uk-label uk-label-success qlist-pill" title="{{t $.LangCode "qlist.read"}}"><span class="tb-short">{{t $.LangCode "qlist.readShort"}}</span><span class="tb-text">{{t $.LangCode "qlist.read"}}</span></span>
{{ else }}
<span class="uk-label qlist-pill" title="{{t $.LangCode "qlist.unread"}}"><span class="tb-short">{{t $.LangCode "qlist.unreadShort"}}</span><span class="tb-text">{{t $.LangCode "qlist.unread"}}</span></span>
{{ end }}
</div>
<!-- 右:操作按钮(margin-left:auto 推到最右,换行后仍保持右对齐) -->
<div class="uk-flex uk-flex-middle uk-flex-wrap" style="gap: 6px; margin-left: auto;">
{{ if not .IsRead }}
<!-- 快捷标记已读:沿用原 tb-label-btn 样式,display:contents 不破坏 flex 布局 -->
<form method="post" action="/_/{{$.LoggedUser.Domain}}/{{.QuestionID}}/read" style="margin: 0; display: contents;">
{{ $.CSRFTokenHTML }}
<button type="submit" class="uk-label tb-label-btn uk-label-success qlist-pill" title="{{t $.LangCode "qitem.markRead"}}"><span class="tb-short">{{t $.LangCode "qitem.markReadShort"}}</span><span class="tb-text">{{t $.LangCode "qitem.markRead"}}</span></button>
</form>
{{ end }}
<!-- form 用 display:contents 不产生盒子,置顶按钮直接参与 flex 对齐 -->
<form method="post" action="{{.PinLink}}" style="margin: 0; display: contents;">
{{ $.CSRFTokenHTML }}
<button type="submit" class="uk-label tb-label-btn {{ if .IsPinned }}uk-label-danger{{ else }}uk-label-primary{{ end }} qlist-pill" title="{{ if .IsPinned }}{{t $.LangCode "qlist.unpin"}}{{ else }}{{t $.LangCode "qlist.pin"}}{{ end }}"><span class="tb-short">{{ if .IsPinned }}{{t $.LangCode "qlist.unpinShort"}}{{ else }}{{t $.LangCode "qlist.pinShort"}}{{ end }}</span><span class="tb-text">{{ if .IsPinned }}{{t $.LangCode "qlist.unpin"}}{{ else }}{{t $.LangCode "qlist.pin"}}{{ end }}</span></button>
</form>
</div>
</div>
<a href="{{.Link}}">
<div class="uk-text-small uk-text-break tb-preline tb-clamp-5 uk-margin-small-top">{{MarkdownPlainText .Content}}</div>
</a>
</div>
{{ end }}
{{ if .HasMoreQuestions }}
<div class="uk-text-center uk-margin-medium-top">
<a class="uk-button uk-button-default uk-button-small" href="{{ .QuestionLoadMoreURL }}">{{t .LangCode "qlist.loadMore"}}</a>
</div>
{{ end }}
{{ else }}
<p class="uk-text-muted">{{t .LangCode "qlist.empty"}}</p>
{{ end }}
</div>
<!-- 投票面板 -->
<div class="qlist-panel" data-qlist-panel="votes" hidden>
<!-- 统一提示区:发布/结束/删除等操作的成功/错误提示沿用 base/alert 样式 -->
{{template "base/alert" .}}
<!-- 投票统计看板(与提问/私聊看板一致) -->
<div class="qlist-stats qlist-stats--2">
<div class="qlist-stat"><span class="qlist-stat-num">{{ .VoteStats.total }}</span><span class="qlist-stat-lbl">{{t .LangCode "vote.statsTotal"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .VoteStats.published }}</span><span class="qlist-stat-lbl">{{t .LangCode "vote.statsPublished"}}</span></div>
</div>
<div class="uk-card uk-card-default uk-card-small uk-card-body uk-margin-bottom uk-flex uk-flex-between uk-flex-middle">
<div>
<div class="uk-text-bold">{{t .LangCode "qlist.myVotes"}}</div>
<div class="uk-text-meta uk-text-small">{{t .LangCode "vote.manageDesc"}}</div>
</div>
<a class="uk-button uk-button-primary uk-button-small" href="/user/votes/new">{{t .LangCode "qlist.newVote"}}</a>
</div>
{{ if .MyVotes }}
{{ range .MyVotes }}
<div class="uk-margin-medium-bottom">
<hr>
<!-- 第一行:标题 + 类型 + 状态 + 元信息 -->
<div class="uk-flex uk-flex-middle uk-flex-wrap" style="gap: 8px;">
<span class="uk-text-bold">{{ if .Title }}{{ .Title }}{{ else }}{{t $.LangCode "qlist.untitledVote"}}{{ end }}</span>
{{ if eq .Type "single" }}<span class="tb-chip">{{t $.LangCode "vote.typeSingle"}}</span>{{ end }}
{{ if eq .Type "multiple" }}<span class="tb-chip">{{t $.LangCode "vote.typeMultiple"}}</span>{{ end }}
{{ if eq .Type "rating" }}<span class="tb-chip">{{t $.LangCode "vote.typeRating"}}</span>{{ end }}
{{ if eq .Type "rank" }}<span class="tb-chip">{{t $.LangCode "vote.typeRank"}}</span>{{ end }}
{{ if eq .Type "form" }}<span class="tb-chip">{{t $.LangCode "vote.typeForm"}}</span>{{ end }}
{{ if eq .EffectiveStatus "draft" }}<span class="uk-label">{{t $.LangCode "vote.statusDraft"}}</span>{{ end }}
{{ if eq .EffectiveStatus "published" }}<span class="uk-label uk-label-success">{{t $.LangCode "vote.statusPublished"}}</span>{{ end }}
{{ if eq .EffectiveStatus "not_started" }}<span class="tb-chip">{{t $.LangCode "vote.statusNotStarted"}}</span>{{ end }}
{{ if eq .EffectiveStatus "ended" }}<span class="uk-label uk-label-warning">{{t $.LangCode "vote.statusEnded"}}</span>{{ end }}
{{ if eq .EffectiveStatus "archived" }}<span class="uk-label uk-label-secondary">{{t $.LangCode "vote.statusArchived"}}</span>{{ end }}
<span class="uk-text-meta">{{t $.LangCode "vote.participants"}}: {{ .SubmissionCount }}</span>
{{ if .RemainingText }}<span class="uk-text-meta">{{ .RemainingText }}</span>{{ end }}
<span class="uk-text-meta">{{t $.LangCode "vote.createdAt"}}: {{ .CreatedAt.Format "2006-01-02 15:04" }}</span>
</div>
<!-- 第二行:操作按钮(统一右对齐,所有状态保持一致的两行布局) -->
<div class="uk-flex uk-flex-wrap uk-margin-small-top" style="gap: 6px; justify-content: flex-end;">
{{ if eq .EffectiveStatus "draft" }}
<form method="post" action="/user/votes/{{ .ID }}/publish" class="uk-display-inline">
{{ $.CSRFTokenHTML }}
<button class="uk-button uk-button-primary uk-button-small" onclick="return confirm('{{t $.LangCode "vote.confirmPublish"}}')">{{t $.LangCode "vote.publish"}}</button>
</form>
<a class="uk-button uk-button-default uk-button-small" href="/user/votes/{{ .ID }}/preview">{{t $.LangCode "vote.preview"}}</a>
{{ end }}
{{ if or (eq .EffectiveStatus "published") (eq .EffectiveStatus "not_started") }}
<form method="post" action="/user/votes/{{ .ID }}/close" class="uk-display-inline">
{{ $.CSRFTokenHTML }}
<button class="uk-button uk-button-warning uk-button-small" onclick="return confirm('{{t $.LangCode "vote.confirmClose"}}')">{{t $.LangCode "vote.close"}}</button>
</form>
{{ end }}
{{ if ne .EffectiveStatus "archived" }}
<a class="uk-button uk-button-default uk-button-small" href="/user/votes/{{ .ID }}/edit">{{t $.LangCode "vote.edit"}}</a>
{{ end }}
{{ if ne .EffectiveStatus "draft" }}
<a class="uk-button uk-button-default uk-button-small" href="/user/votes/{{ .ID }}/results">{{t $.LangCode "vote.results"}}</a>
<a class="uk-button uk-button-default uk-button-small" href="/user/votes/{{ .ID }}/export">{{t $.LangCode "vote.export"}}</a>
{{ end }}
{{ if or (eq .EffectiveStatus "published") (eq .EffectiveStatus "not_started") }}
<button class="uk-button uk-button-default uk-button-small" onclick="copyShareURL('{{ .LinkToken }}', this)">{{t $.LangCode "vote.share"}}</button>
{{ end }}
<form method="post" action="/user/votes/{{ .ID }}/delete" class="uk-display-inline">
{{ $.CSRFTokenHTML }}
<button class="uk-button uk-button-danger uk-button-small" onclick="return confirm('{{t $.LangCode "vote.confirmDelete"}}')">{{t $.LangCode "common.delete"}}</button>
</form>
</div>
</div>
{{ end }}
{{ if .HasMoreVotes }}
<div class="uk-text-center uk-margin-medium-top">
<a class="uk-button uk-button-default uk-button-small" href="{{ .VoteLoadMoreURL }}">{{t .LangCode "qlist.loadMore"}}</a>
</div>
{{ end }}
{{ else }}
<div class="uk-alert uk-alert-primary uk-margin-remove">{{t .LangCode "vote.empty"}}</div>
{{ end }}
</div>
<!-- 私聊面板 -->
<div class="qlist-panel" data-qlist-panel="chats" hidden>
<!-- 统一提示区:私聊相关操作的成功/错误提示沿用 base/alert 样式 -->
{{template "base/alert" .}}
<!-- 私聊统计看板(与提问/投票看板一致) -->
<div class="qlist-stats qlist-stats--4">
<div class="qlist-stat"><span class="qlist-stat-num">{{ .ChatStats.total }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.chatStatsTotal"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .ChatStats.received }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.chatStatsReceived"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .ChatStats.initiated }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.chatStatsInitiated"}}</span></div>
<div class="qlist-stat"><span class="qlist-stat-num">{{ .ChatStats.closed }}</span><span class="qlist-stat-lbl">{{t .LangCode "qlist.chatStatsClosed"}}</span></div>
</div>
<!-- 我收到的私聊 -->
<h4>{{t .LangCode "qlist.receivedChats"}}({{ .ChatStats.received }})</h4>
{{ if .ReceivedChats }}
{{ range .ReceivedChats }}
<div class="uk-margin-medium-bottom">
<hr>
<div class="uk-float-right uk-flex uk-flex-middle uk-flex-wrap" style="gap: 6px;">
<span class="uk-label uk-label-success">{{t $.LangCode "chat.title"}}</span>
{{ if .IsBlocked }}
<span class="uk-label uk-label-danger">{{t $.LangCode "qlist.blocked"}}</span>
{{ end }}
</div>
<a href="{{.Link}}">
<div class="uk-text-left uk-text-small uk-text-muted">{{Date .CreatedAt "Y-m-d H:i:s"}}</div>
<div class="uk-text-small uk-text-break tb-preline tb-clamp-5 uk-margin-small-top">{{.Content}}</div>
</a>
</div>
{{ end }}
{{ else }}
<p class="uk-text-muted">{{t .LangCode "qlist.chatEmpty"}}</p>
{{ end }}
{{ if .HasMoreChats }}
<div class="uk-text-center uk-margin-medium-top">
<a class="uk-button uk-button-default uk-button-small" href="{{ .ChatLoadMoreURL }}">{{t .LangCode "qlist.loadMore"}}</a>
</div>
{{ end }}
<!-- 我发起的私聊 -->
{{ if .ConversationDetails }}
<h4 class="uk-margin-top">{{t .LangCode "qlist.myChats"}}({{ .ChatStats.initiated }})</h4>
{{ range .ConversationDetails }}
<a href="/_/{{.OwnerDomain}}/chat/{{.Conversation.Token}}" class="uk-display-block uk-overflow-hidden uk-margin-medium-bottom">
<div>
<hr>
<span class="uk-label uk-label-warning uk-float-right uk-margin-small-right">{{t $.LangCode "chat.title"}}</span>
{{ if .IsBlocked }}
<span class="uk-label uk-label-danger uk-float-right uk-margin-small-right">{{t $.LangCode "qlist.blocked"}}</span>
{{ end }}
<div class="uk-text-left uk-text-small uk-text-muted">{{Date .Conversation.CreatedAt "Y-m-d H:i:s"}}</div>
<div class="uk-text-small uk-text-break tb-preline tb-clamp-5 uk-margin-small-top">{{.FirstMessage}}</div>
</div>
</a>
{{ end }}
{{ end }}
{{ if .HasMoreAskedChats }}
<div class="uk-text-center uk-margin-medium-top">
<a class="uk-button uk-button-default uk-button-small" href="{{ .AskedChatsLoadMoreURL }}">{{t .LangCode "qlist.loadMore"}}</a>
</div>
{{ end }}
</div>
</div>
<script>
function copyShareURL(token, btn) {
var url = window.location.origin + "/vote/" + token;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(url).then(function () {
var old = btn.textContent;
btn.textContent = '{{t .LangCode "vote.copied"}}';
setTimeout(function () { btn.textContent = old; }, 1500);
});
} else {
prompt('{{t .LangCode "vote.shareURL"}}', url);
}
}
document.addEventListener('DOMContentLoaded', function() {
const tabs = document.querySelectorAll('[data-qlist-tab]');
const panels = document.querySelectorAll('[data-qlist-panel]');
function showQlistPanel(name) {
tabs.forEach(function(tab) {
const active = tab.dataset.qlistTab === name;
tab.classList.toggle('is-active', active);
tab.setAttribute('aria-selected', active ? 'true' : 'false');
});
panels.forEach(function(panel) {
panel.hidden = panel.dataset.qlistPanel !== name;
});
if (window.history && window.history.replaceState) {
const hash = name === 'votes' ? '#votes' : (name === 'chats' ? '#chats' : '');
window.history.replaceState({}, document.title, window.location.pathname + hash);
}
}
tabs.forEach(function(tab) {
tab.addEventListener('click', function() { showQlistPanel(tab.dataset.qlistTab); });
});
if (window.location.hash === '#votes') showQlistPanel('votes');
if (window.location.hash === '#chats') showQlistPanel('chats');
});
</script>
{{template "base/footer" .}}