ticket_list_result($filters, $paged); NativeUi::header( __('工单管理', 'jio'), __('查看用户问题、跟进沟通并记录处理结果。', 'jio'), 'dashicons-tickets-alt', [ [ 'label' => __('代客提交工单', 'jio'), 'dialog' => self::TICKET_CREATE_DIALOG, 'primary' => true, 'icon' => 'dashicons-plus-alt2', ], [ 'label' => __('工单配置', 'jio'), 'url' => admin_url('admin.php?page=jio-settings#/users/ticket'), 'icon' => 'dashicons-admin-generic', ], ] ); $this->render_ticket_list($filters, $paged, $result); $this->render_ticket_create_dialog(); } /** 越界页码夹到末页,避免处理最后一条工单后停在空页。返回 [查询结果, 实际页码]。 */ private function ticket_list_result(array $filters, int $paged): array { $repo = TicketRepository::get_instance(); $args = [ 'status' => $filters['status'] === '' ? [] : [$filters['status']], 'ticket_type_id' => $filters['ticket_type_id'], 'keywords' => $filters['s'], 'limit' => self::TICKET_PER_PAGE, 'offset' => ($paged - 1) * self::TICKET_PER_PAGE, ]; $result = $repo->paginate($args, true); $last_page = max(1, (int) ceil($result['total'] / self::TICKET_PER_PAGE)); if ($paged > $last_page) { $paged = $last_page; $args['offset'] = ($paged - 1) * self::TICKET_PER_PAGE; $result = $repo->paginate($args, true); } return [$result, $paged]; } private function render_ticket_list(array $filters, int $paged, array $result): void { NativeUi::card_open('', '', true, 'jio-tickets-admin__list-card'); $this->render_ticket_toolbar($filters); echo '
'; echo ''; foreach ([ 'title' => __('工单 / 问题', 'jio'), 'status' => __('状态', 'jio'), 'type' => __('部门', 'jio'), 'user' => __('用户', 'jio'), 'admin' => __('跟进人', 'jio'), 'time' => __('最后活动', 'jio'), 'actions' => __('操作', 'jio'), ] as $key => $label) { echo ''; } echo ''; if ($result['items'] === []) { $filtered = $filters['status'] !== '' || $filters['ticket_type_id'] > 0 || $filters['s'] !== ''; echo NativeUi::empty_row(7, __('暂无工单', 'jio'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ? __('请调整筛选条件,或使用标题右侧的「代客提交工单」。', 'jio') : __('暂无未关闭工单。可切换状态查看历史记录,或代用户创建工单。', 'jio')); } $context = $this->ticket_context($filters, $paged); foreach ($result['items'] as $ticket) { $this->render_ticket_center_row($ticket, $context); } echo '
' . esc_html__('工单处理列表', 'jio') . '
' . esc_html($label) . '
'; NativeUi::table_wrap_close(); AdminListPagination::render(self::TICKET_PAGE_SLUG, $result['total'], $paged, self::TICKET_PER_PAGE, $filters, 'users.php'); NativeUi::card_close(); } private function render_ticket_toolbar(array $filters): void { $statuses = array_values(TicketStatusService::map()); $counts = TicketRepository::get_instance()->statistic([ 'status' => array_column($statuses, 'status_key'), 'ticket_type_id' => $filters['ticket_type_id'], 'keywords' => $filters['s'], ], true); $open_total = 0; $views = []; foreach ($statuses as $row) { $key = (string) $row['status_key']; $count = (int) ($counts[$key] ?? 0); if (empty($row['is_closed'])) { $open_total += $count; } $views[] = [$key, (string) $row['name'], $count, (string) $row['color']]; } array_unshift($views, ['', __('未关闭', 'jio'), $open_total, '']); echo '

' . esc_html__('工单列表', 'jio') . '

'; echo ''; $this->render_ticket_filter_form($filters); echo '
'; } private function render_ticket_filter_form(array $filters): void { echo '
'; echo ''; echo ''; echo ''; echo ''; if ($filters['ticket_type_id'] > 0 || $filters['s'] !== '') { echo '' . esc_html__('清除筛选', 'jio') . ''; } echo '
'; } private function render_ticket_center_row(array $ticket, array $context): void { $id = (int) $ticket['id']; $url = esc_url($this->ticket_page_url(['id' => $id] + $context)); $last = (string) $ticket['last_reply_time'] !== '' ? (string) $ticket['last_reply_time'] : (string) $ticket['post_time']; $awaiting = in_array((string) $ticket['status'], [TicketStatusService::KEY_PENDING, TicketStatusService::KEY_USER_REPLIED], true); echo ''; echo '' . esc_html((string) $ticket['title']) . '' . $this->ticket_urge_badge($ticket); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '
' . esc_html((string) $ticket['ticket_num']) . '
'; if ((string) $ticket['order_number'] !== '') { echo '
' . esc_html__('订单:', 'jio') . esc_html((string) $ticket['order_number']) . '
'; } echo '' . $this->ticket_status_badge($ticket) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '' . esc_html((string) $ticket['ticket_type_name'] !== '' ? (string) $ticket['ticket_type_name'] : '—') . ''; echo '' . $this->ticket_user_chip((int) $ticket['user_id'], (string) $ticket['user_name']) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo '' . ((int) $ticket['admin_id'] > 0 ? esc_html((string) $ticket['admin_name']) : '' . esc_html__('未指派', 'jio') . '') . ''; echo '
' . esc_html(sprintf(__('提交于 %s', 'jio'), (string) $ticket['post_time'])) . '
'; echo '' . esc_html(!empty($ticket['is_closed']) ? __('查看记录', 'jio') : __('处理工单', 'jio')) . ''; } private function ticket_status_badge(array $ticket): string { return '' . esc_html((string) $ticket['status_label']) . ''; } private function ticket_urge_badge(array $ticket): string { $count = (int) ($ticket['urge_count'] ?? 0); if ($count <= 0 || !empty($ticket['is_closed'])) { return ''; } return ' ' . esc_html(sprintf(__('催单 ×%d', 'jio'), $count)) . ''; } private function ticket_user_chip(int $user_id, string $name): string { $avatar = function_exists('jio_get_avatar_url') ? jio_get_avatar_url($user_id, 48) : ''; $label = $name !== '' ? $name : '#' . $user_id; return '' . ($avatar !== '' ? '' : '') . '' . esc_html($label) . ''; } }