'; } public static function page_close(): void { echo ''; } /** * 页头:图标 + 标题 + 说明 + 右侧操作。 * * 带 dialog 键的动作渲染为打开对应 的按钮(由页面脚本绑定),否则渲染为链接。 * * @param array $actions */ public static function header(string $title, string $description = '', string $icon = '', array $actions = []): void { echo '
'; // h1 让 WP 把 admin_notices 挪到标题之后。 echo '

'; if ($icon !== '') { echo ''; } echo esc_html($title) . '

'; if ($description !== '') { echo '

' . esc_html($description) . '

'; } echo '
'; if ($actions !== []) { echo '
'; foreach ($actions as $action) { echo isset($action['dialog']) ? self::button_dialog($action['label'], (string) $action['dialog'], !empty($action['primary']), (string) ($action['icon'] ?? '')) : self::button_link($action['label'], (string) $action['url'], !empty($action['primary']), (string) ($action['icon'] ?? '')); } echo '
'; } echo '
'; // WP 会把 .notice 插在首个 .wrap h1 之后;这里留一个固定落点。 echo '
'; } public static function button_link(string $label, string $url, bool $primary = false, string $icon = ''): string { $class = $primary ? 'button button-primary' : 'button'; $icon_html = $icon !== '' ? '' : ''; return '' . $icon_html . esc_html($label) . ''; } /** 打开页面内 的按钮;点击行为由页面脚本按 data-aksrt-dialog-open 绑定。 */ public static function button_dialog(string $label, string $dialog_id, bool $primary = false, string $icon = ''): string { $class = $primary ? 'button button-primary' : 'button'; $icon_html = $icon !== '' ? '' : ''; return ''; } /* ------------------------------------------------------------------ * 统计卡 * ----------------------------------------------------------------*/ /** * @param array $items */ public static function stats(array $items): void { if ($items === []) { return; } echo '
'; foreach ($items as $item) { self::stat($item); } echo '
'; } /** @param array{label:string,value:string|int|float,url?:string,tone?:string,icon?:string,hint?:string,unit?:string} $item */ public static function stat(array $item): void { $tone = self::tone((string) ($item['tone'] ?? 'success')); $url = (string) ($item['url'] ?? ''); $tag = $url !== '' ? 'a' : 'div'; $attr = $url !== '' ? ' href="' . esc_url($url) . '"' : ''; echo '<' . $tag . ' class="aksrt-native-stat aksrt-native-stat--' . esc_attr($tone) . '"' . $attr . '>'; echo ''; if (!empty($item['icon'])) { echo ''; } echo esc_html($item['label']) . ''; echo ''; if (!empty($item['unit'])) { echo '' . esc_html((string) $item['unit']) . ''; } echo esc_html((string) $item['value']) . ''; if (!empty($item['hint'])) { echo '' . esc_html((string) $item['hint']) . ''; } echo ''; } /* ------------------------------------------------------------------ * 卡片 * ----------------------------------------------------------------*/ /** * 打开卡片。$actions_html 为调用方已转义的右侧操作区片段。 * $flush=true 时正文无内边距(承载整表)。 */ public static function card_open(string $title = '', string $actions_html = '', bool $flush = false, string $modifier = ''): void { $class = 'aksrt-native-card' . ($flush ? ' aksrt-native-card--flush' : '') . ($modifier !== '' ? ' ' . $modifier : ''); echo '
'; if ($title !== '' || $actions_html !== '') { echo '
'; if ($title !== '') { echo '

' . esc_html($title) . '

'; } if ($actions_html !== '') { echo '
' . $actions_html . '
'; } echo '
'; } echo '
'; } public static function card_close(): void { echo '
'; } /** 表格卡:整表铺满卡片,带横向滚动容器。 */ public static function table_card_open(string $title = '', string $actions_html = ''): void { self::card_open($title, $actions_html, true); echo '
'; } /** 关闭表格卡;分页条应在 table-wrap 关闭后、卡片关闭前输出,故拆成两步。 */ public static function table_wrap_close(): void { echo '
'; } /* ------------------------------------------------------------------ * 徽标 / 空状态 * ----------------------------------------------------------------*/ public static function badge(string $label, string $tone = 'neutral', bool $dot = true): string { $class = 'aksrt-native-badge aksrt-native-badge--' . self::tone($tone) . ($dot ? ' has-dot' : ''); return '' . esc_html($label) . ''; } /** * 按状态键取徽标。$map: 状态键 => [文案, 色调];未命中时原样输出灰色徽标。 * * @param array $map */ public static function status_badge(string $status, array $map): string { if (isset($map[$status])) { return self::badge($map[$status][0], $map[$status][1]); } return self::badge($status !== '' ? $status : '—', 'neutral'); } public static function empty_state(string $title, string $hint = '', string $icon = 'dashicons-portfolio'): string { $html = '
'; $html .= ''; $html .= '' . esc_html($title) . ''; if ($hint !== '') { $html .= '' . esc_html($hint) . ''; } return $html . '
'; } /** 表格内的空状态整行。 */ public static function empty_row(int $colspan, string $title, string $hint = ''): string { return '' . self::empty_state($title, $hint) . ''; } /** 用户单元格:头像 + 名称 + #ID。 */ public static function user_cell(int $user_id, string $fallback = ''): string { $user = $user_id > 0 ? get_userdata($user_id) : false; if (!$user) { return '' . esc_html($fallback !== '' ? $fallback : ($user_id > 0 ? '#' . $user_id : '—')) . ''; } $avatar = function_exists('aksrt_get_avatar_url') ? (string) aksrt_get_avatar_url($user_id, 28) : (string) get_avatar_url($user_id, ['size' => 28]); return '' . ($avatar !== '' ? '' : '') . '' . esc_html($user->display_name) . '' . '#' . esc_html((string) $user_id) . ''; } private static function tone(string $tone): string { return in_array($tone, self::TONES, true) ? $tone : 'neutral'; } }