fix(ticket): rework detail modal layout and restore urge

- Detail modal: fixed-height dialog, thread is the only (thin) scroll area
- Collapsible pinned summary bar (collapsed by default, state kept across re-render)
- Composer: toolbar (image / urge / close) above input, pending image tray with remove
- Paste screenshots to upload; create form uses the same button + tray
- Urge/close now POST (routes are CREATABLE); urge cooldown countdown via urge_wait
- Notify admins on urge when ticket is unassigned
- Split composer/form styles into tickets-compose.css; drop dead uploader code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 21:37:03 +08:00
1 parent b74381cfa2
commit 13bde11dda
7 files changed
+713 -392

No files matched your search

+10
View File
@@ -424,6 +424,16 @@ trait ScriptsPart1b {
(string) filemtime($tickets_css)
);
}
// 回复框 / 新建表单 / 待发送图片条:依赖 tickets.css 的 --tickets-* token。
$tickets_compose_css = get_template_directory() . '/assets/css/routes/tickets-compose.css';
if (file_exists($tickets_compose_css)) {
wp_enqueue_style(
'aksrt-tickets-compose',
$base_uri . '/assets/css/routes/tickets-compose.css',
['aksrt-tickets'],
(string) filemtime($tickets_compose_css)
);
}
// 共享工具层(请求 / 弹窗 / 上传 / 行内校验)先于面板脚本加载。
$ticket_core_js = get_template_directory() . '/assets/js/ticket-center-core.js';
if (file_exists($ticket_core_js)) {
+6 -2
View File
@@ -42,10 +42,10 @@ final class TicketNotifier {
self::send($user_id, 'ticket_reply', $content, __('工单有新回复', 'aksrt'), self::user_url());
}
/** 用户回复 / 催单 → 通知跟进人。 */
/** 用户回复 / 催单 → 通知跟进人;催单时若尚无跟进人则广播给管理员。 */
public static function admin_activity(array $ticket, string $kind): void {
$admin_id = (int) ($ticket['admin_id'] ?? 0);
if ($admin_id <= 0) {
if ($admin_id <= 0 && $kind !== 'urge') {
return;
}
if ($kind === 'urge') {
@@ -65,6 +65,10 @@ final class TicketNotifier {
);
$title = __('工单有新回复', 'aksrt');
}
if ($admin_id <= 0) {
self::to_admins('ticket_' . $kind, $content, $title);
return;
}
self::send($admin_id, 'ticket_' . $kind, $content, $title, self::admin_url());
}
+15 -2
View File
@@ -226,6 +226,7 @@ final class TicketRest {
}
$ticket['replies'] = $repo->replies($id);
$ticket['notes'] = [];
$ticket['urge_wait'] = empty($ticket['is_closed']) ? $this->urge_wait($id) : 0;
return rest_ensure_response(['success' => true, 'data' => $ticket]);
}
@@ -336,11 +337,14 @@ final class TicketRest {
}
$last = get_transient('aksrt_ticket_urge_' . $id);
if ($last !== false && (int) $last + TicketSettings::urge_interval() * MINUTE_IN_SECONDS > time()) {
return new \WP_Error('aksrt_ticket_urge_limit', __('刚刚已催单,请稍后再试', 'aksrt'), ['status' => 429]);
return new \WP_Error('aksrt_ticket_urge_limit', __('刚刚已催单,请稍后再试', 'aksrt'), [
'status' => 429,
'urge_wait' => $this->urge_wait($id),
]);
}
set_transient('aksrt_ticket_urge_' . $id, time(), TicketSettings::urge_interval() * MINUTE_IN_SECONDS + 60);
TicketNotifier::admin_activity($ticket, 'urge');
return rest_ensure_response(['success' => true]);
return rest_ensure_response(['success' => true, 'data' => ['urge_wait' => $this->urge_wait($id)]]);
}
public function close(\WP_REST_Request $request) {
@@ -363,6 +367,15 @@ final class TicketRest {
* 内部工具
* ------------------------------------------------------------------ */
/** 距离下次可催单的剩余秒数(0 = 可立即催单)。 */
private function urge_wait(int $id): int {
$last = get_transient('aksrt_ticket_urge_' . $id);
if ($last === false) {
return 0;
}
return max(0, (int) $last + TicketSettings::urge_interval() * MINUTE_IN_SECONDS - time());
}
private function ensure_enabled(): bool {
return TicketSettings::enabled();
}