feat(ticket): record urges in thread, show urge count in admin, mail admins

- aksrt_ticket gains urge_count / last_urge_time (schema 3.1.0)
- cooldown moves from transient to an atomic conditional UPDATE
- urge inserts a system reply rendered as a centered notice (not quotable)
- admin list badge + info card row for urge count / last time
- new ticket_urge_admin mail event via aksrt_ticket_urged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 22:17:40 +08:00
1 parent ac3ed9c956
commit 5febdb35c8
14 files changed
+198 -25

No files matched your search

@@ -49,6 +49,12 @@ trait UsersMenusTicketsDetail {
} else {
$html .= '<ol class="aksrt-ticket-detail__replies">';
foreach ($replies as $reply) {
if ((string) $reply['author_type'] === 'system') {
// 系统记录(如催单):居中提示行,无头像 / 引用 / 附件。
$html .= '<li class="aksrt-ticket-detail__system"><span>' . esc_html((string) $reply['content'])
. '</span><time>' . esc_html((string) $reply['created_at']) . '</time></li>';
continue;
}
$is_admin = (string) $reply['author_type'] === 'admin';
$html .= '<li class="aksrt-ticket-detail__reply is-' . ($is_admin ? 'admin' : 'user') . '">';
$html .= $this->ticket_message_head(
@@ -159,6 +165,17 @@ trait UsersMenusTicketsDetail {
}
$rows[__('提交时间', 'aksrt')] = esc_html((string) $ticket['post_time']);
$rows[__('最后回复', 'aksrt')] = esc_html((string) $ticket['last_reply_time'] !== '' ? (string) $ticket['last_reply_time'] : '—');
if ((int) $ticket['urge_count'] > 0) {
$rows[__('催单', 'aksrt')] = '<span class="aksrt-ticket-urge">' . esc_html(sprintf(
/* translators: %d urge count */
__('%d 次', 'aksrt'),
(int) $ticket['urge_count']
)) . '</span> <span class="aksrt-tickets-admin__muted">' . esc_html(sprintf(
/* translators: %s last urge time */
__('最近 %s', 'aksrt'),
(string) $ticket['last_urge_time']
)) . '</span>';
}
$html = '<section class="aksrt-ticket-detail__card"><dl class="aksrt-ticket-detail__info">';
foreach ($rows as $label => $value) {