$ticket */ private function render_ticket_detail_panel(array $ticket): string { $id = (int) $ticket['id']; $repo = \AKSRT\Ticket\TicketRepository::get_instance(); $replies = $repo->replies($id); $notes = \AKSRT\Ticket\TicketNoteService::for_ticket($id); $closed = !empty($ticket['is_closed']); $html = '
'; // ---- 左栏:对话 ---- $html .= '
'; $html .= '
'; $html .= $this->ticket_message_head( (int) $ticket['user_id'], (string) $ticket['user_name'], __('提交了工单', 'aksrt'), (string) $ticket['post_time'] ); $html .= '
' . wpautop(esc_html((string) $ticket['content'])) . '
'; $html .= $this->ticket_attachments_html((array) $ticket['attachment']); $html .= '
'; $html .= '

' . esc_html(sprintf( /* translators: %d reply count */ __('沟通记录(%d)', 'aksrt'), count($replies) )) . '

'; if ($replies === []) { $html .= '

' . esc_html__('暂无沟通记录', 'aksrt') . '

'; } else { $html .= '
    '; foreach ($replies as $reply) { if ((string) $reply['author_type'] === 'system') { // 系统记录(如催单):居中提示行,无头像 / 引用 / 附件。 $html .= '
  1. ' . esc_html((string) $reply['content']) . '
  2. '; continue; } $is_admin = (string) $reply['author_type'] === 'admin'; $html .= '
  3. '; $html .= $this->ticket_message_head( (int) $reply['author_id'], (string) $reply['author_name'], $is_admin ? __('客服', 'aksrt') : __('用户', 'aksrt'), (string) $reply['created_at'] ); if (is_array($reply['quote'] ?? null)) { $quote = $reply['quote']; $html .= '
    ' . esc_html(!empty($quote['is_deleted']) ? __('原回复已删除', 'aksrt') : (string) $quote['content']) . '
    '; } $html .= '
    ' . wpautop(esc_html((string) $reply['content'])) . '
    '; $html .= $this->ticket_attachments_html((array) $reply['attachment']); $html .= '
  4. '; } $html .= '
'; } if ($closed) { $html .= '

' . esc_html__('工单已关闭,无法继续回复。', 'aksrt') . '

'; } else { $html .= $this->ticket_reply_form($id); } $html .= '
'; // ---- 右栏:信息 / 处理 / 备注 ---- $html .= ''; $html .= '
'; return $html; } private function ticket_reply_form(int $id): string { $field = 'aksrt-ticket-reply-' . $id; $html = $this->ticket_form_open('reply', $id, 'aksrt-ticket-detail__reply-form'); $html .= wp_nonce_field('aksrt_ticket_reply_' . $id, '_wpnonce', true, false); $html .= ''; $html .= ''; $html .= '
'; $prereplies = \AKSRT\Ticket\TicketPrereplyService::all(); if ($prereplies !== []) { $html .= ''; $html .= ''; } $html .= '0 / ' . (int) \AKSRT\Ticket\TicketSettings::reply_max() . ''; $html .= ''; $html .= '
'; return $html; } /** @param array $ticket */ private function ticket_info_card(array $ticket): string { $rows = [ __('工单号', 'aksrt') => '' . esc_html((string) $ticket['ticket_num']) . '', __('状态', 'aksrt') => '' . esc_html((string) $ticket['status_label']) . '', __('用户', 'aksrt') => $this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name']), __('部门', 'aksrt') => esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—'), __('跟进人', 'aksrt') => (int) $ticket['admin_id'] > 0 ? esc_html((string) $ticket['admin_name']) : '' . esc_html__('未指派', 'aksrt') . '', ]; if ((string) $ticket['order_number'] !== '') { $rows[__('关联订单', 'aksrt')] = '' . esc_html((string) $ticket['order_number']) . ''; } $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')] = '' . esc_html(sprintf( /* translators: %d urge count */ __('%d 次', 'aksrt'), (int) $ticket['urge_count'] )) . ' ' . esc_html(sprintf( /* translators: %s last urge time */ __('最近 %s', 'aksrt'), (string) $ticket['last_urge_time'] )) . ''; } $html = '
'; foreach ($rows as $label => $value) { $html .= '
' . esc_html($label) . '
' . $value . '
'; } return $html . '
'; } /** 处理表单:状态仅列出 can_transition 允许的目标。 */ private function ticket_status_form(array $ticket): string { $id = (int) $ticket['id']; $from = (string) $ticket['status']; $html = '
'; $html .= '

' . esc_html__('处理', 'aksrt') . '

'; $html .= $this->ticket_form_open('status', $id, 'aksrt-ticket-detail__status-form'); $html .= wp_nonce_field('aksrt_ticket_status_' . $id, '_wpnonce', true, false); $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= '
'; return $html; } /** @return array 可指派的管理员 user_id => 显示名(单请求内缓存)。 */ private function ticket_admin_options(): array { static $options = null; if ($options === null) { $options = []; foreach (get_users([ 'capability' => self::CAPABILITY, 'fields' => ['ID', 'display_name'], 'orderby' => 'display_name', 'number' => 200, ]) as $user) { $options[(int) $user->ID] = (string) $user->display_name; } } return $options; } private function ticket_message_head(int $user_id, string $name, string $role, string $time): string { $avatar = function_exists('aksrt_get_avatar_url') ? aksrt_get_avatar_url($user_id, 64) : ''; return '
' . ($avatar !== '' ? '' : '') . '' . esc_html($name !== '' ? $name : '#' . $user_id) . '' . '' . esc_html($role) . '' . '
'; } /** @param list $urls 图片附件(TicketAttachment 已清洗) */ private function ticket_attachments_html(array $urls): string { if ($urls === []) { return ''; } $html = '
    '; foreach ($urls as $url) { $html .= '
  • ' . '' . esc_attr__('附件', 'aksrt') . '
  • '; } return $html . '
'; } }