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 '' . esc_html__('工单处理列表', 'jio') . '';
foreach ([
'title' => __('工单 / 问题', 'jio'),
'status' => __('状态', 'jio'),
'type' => __('部门', 'jio'),
'user' => __('用户', 'jio'),
'admin' => __('跟进人', 'jio'),
'time' => __('最后活动', 'jio'),
'actions' => __('操作', 'jio'),
] as $key => $label) {
echo '| ' . esc_html($label) . ' | ';
}
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 '
';
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 '
';
}
private function render_ticket_filter_form(array $filters): void {
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) . '';
}
}