feat(admin): rebuild the payment center overview with ECharts analytics
- Overview page: realtime today vs. same time yesterday, 6 KPI cards with period-over-period deltas, trend (paid / net / refund curves + order count line), category curves, payment method donut, top 10 paid content, conversion / close / refund rates, todos and shortcuts - Date presets, custom range and category filter via GET aksrt/v1/admin/pay-overview (manage_options + wp_rest nonce) - Metrics: paid amount by paid date including later-refunded orders, refunds by refund date, net = paid - refund; CNY only, points and recharge excluded, foreign-currency orders listed separately; integer cents internally - Daily records cached per month in transients (today/yesterday live), invalidated on order status change and on purge - ECharts 6.1.0 subset bundled to assets/js/runtime/libs/echarts-pay.min.js (npm run build:pay-charts), loaded only on the overview page - Split payment-attempt methods out of OrderRepositoryCore into OrderRepositoryAttempts; remove dead get_stats(), cash_decimal(), Order::get_stats() and the old .aksrt-native-links styles Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d98f8d7975
commit
9805a371d8
19 files changed
+2203
-405
No files matched your search
@@ -27,23 +27,28 @@ class PayMenus {
|
||||
}
|
||||
|
||||
NativeUi::boot();
|
||||
PayOverview::get_instance()->init();
|
||||
add_action('admin_menu', [$this, 'register_menus']);
|
||||
add_action('admin_notices', [$this, 'pending_notices']);
|
||||
// Domain admin_post handlers are registered by dedicated modules.
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{slug:string,title:string,callback:callable}>
|
||||
* @return array<string, array{slug:string,title:string,icon:string,desc:string,callback:callable}>
|
||||
*/
|
||||
private function pages(): array {
|
||||
return [
|
||||
'overview' => [
|
||||
'slug' => self::MENU_SLUG,
|
||||
'icon' => 'dashicons-cart',
|
||||
'desc' => '',
|
||||
'title' => __('概览', 'aksrt'),
|
||||
'callback' => [$this, 'render_overview_page'],
|
||||
],
|
||||
'orders' => [
|
||||
'slug' => 'aksrt_orders',
|
||||
'icon' => 'dashicons-list-view',
|
||||
'desc' => __('收款确认、关闭与退款标记', 'aksrt'),
|
||||
'title' => __('订单明细', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\Admin\Native\PayOrders')) {
|
||||
@@ -55,6 +60,8 @@ class PayMenus {
|
||||
],
|
||||
'products' => [
|
||||
'slug' => 'aksrt_pay_products',
|
||||
'icon' => 'dashicons-media-document',
|
||||
'desc' => __('付费文章定价与销量', 'aksrt'),
|
||||
'title' => __('付费文章', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayProducts')) {
|
||||
@@ -66,6 +73,8 @@ class PayMenus {
|
||||
],
|
||||
'coupons' => [
|
||||
'slug' => 'aksrt_coupons',
|
||||
'icon' => 'dashicons-tickets-alt',
|
||||
'desc' => __('创建与批量生成优惠码', 'aksrt'),
|
||||
'title' => __('优惠码', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayCoupons')) {
|
||||
@@ -77,6 +86,8 @@ class PayMenus {
|
||||
],
|
||||
'charge_cards' => [
|
||||
'slug' => 'aksrt_charge_cards',
|
||||
'icon' => 'dashicons-id-alt',
|
||||
'desc' => __('充值卡密生成与导出', 'aksrt'),
|
||||
'title' => __('卡密管理', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayChargeCards')) {
|
||||
@@ -88,6 +99,8 @@ class PayMenus {
|
||||
],
|
||||
'income' => [
|
||||
'slug' => 'aksrt_income',
|
||||
'icon' => 'dashicons-chart-pie',
|
||||
'desc' => __('创作者分成结算', 'aksrt'),
|
||||
'title' => __('创作分成', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayIncome')) {
|
||||
@@ -99,6 +112,8 @@ class PayMenus {
|
||||
],
|
||||
'withdraw' => [
|
||||
'slug' => 'aksrt_withdraw',
|
||||
'icon' => 'dashicons-money-alt',
|
||||
'desc' => __('提现申请审核与打款', 'aksrt'),
|
||||
'title' => __('提现审核', 'aksrt'),
|
||||
'callback' => static function (): void {
|
||||
if (class_exists('AKSRT\\Admin\\Native\\PayWithdrawals')) {
|
||||
@@ -164,72 +179,23 @@ class PayMenus {
|
||||
}
|
||||
}
|
||||
|
||||
private function ensure_cap(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
}
|
||||
}
|
||||
|
||||
private function page_url(string $slug, array $args = []): string {
|
||||
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
|
||||
}
|
||||
|
||||
/** 快捷入口图标,键同 pages()。 */
|
||||
private const PAGE_ICONS = [
|
||||
'orders' => 'dashicons-list-view',
|
||||
'products' => 'dashicons-media-document',
|
||||
'coupons' => 'dashicons-tickets-alt',
|
||||
'charge_cards' => 'dashicons-id-alt',
|
||||
'income' => 'dashicons-chart-pie',
|
||||
'withdraw' => 'dashicons-money-alt',
|
||||
];
|
||||
|
||||
public function render_overview_page(): void {
|
||||
$this->ensure_cap();
|
||||
|
||||
$order_stats = class_exists('\AKSRT\Payment\OrderRepository')
|
||||
? \AKSRT\Payment\OrderRepository::get_instance()->get_stats()
|
||||
: [];
|
||||
$withdraw_stats = class_exists('\AKSRT\Payment\Withdrawal')
|
||||
? \AKSRT\Payment\Withdrawal::get_instance()->get_stats()
|
||||
: [];
|
||||
$card_stats = class_exists('\AKSRT\Payment\ChargeCard')
|
||||
? \AKSRT\Payment\ChargeCard::get_instance()->get_stats()
|
||||
: [];
|
||||
|
||||
NativeUi::page_open();
|
||||
NativeUi::header(
|
||||
__('支付中心', 'aksrt'),
|
||||
__('运营数据入口。配置项仍在 AKSRT 主题设置 SPA。', 'aksrt'),
|
||||
'dashicons-cart',
|
||||
[[
|
||||
'label' => __('打开支付配置(SPA)', 'aksrt'),
|
||||
'url' => admin_url('admin.php?page=aksrt-settings#/payment/gateway'),
|
||||
'icon' => 'dashicons-admin-generic',
|
||||
]]
|
||||
);
|
||||
|
||||
NativeUi::stats([
|
||||
['label' => __('订单总数', 'aksrt'), 'value' => (int) ($order_stats['total'] ?? 0), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-list-view', 'tone' => 'info'],
|
||||
['label' => __('待支付', 'aksrt'), 'value' => (int) ($order_stats['pending'] ?? 0), 'url' => $this->page_url('aksrt_orders', ['status' => 'pending']), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
|
||||
['label' => __('已支付', 'aksrt'), 'value' => (int) ($order_stats['paid'] ?? 0), 'url' => $this->page_url('aksrt_orders', ['status' => 'paid']), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
|
||||
['label' => __('营收', 'aksrt'), 'value' => (string) ($order_stats['revenue'] ?? '0.00'), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-chart-line', 'tone' => 'success'],
|
||||
['label' => __('待审提现', 'aksrt'), 'value' => (int) ($withdraw_stats['pending'] ?? 0), 'url' => $this->page_url('aksrt_withdraw', ['status' => 'pending']), 'icon' => 'dashicons-money-alt', 'tone' => 'danger'],
|
||||
['label' => __('可用卡密', 'aksrt'), 'value' => (int) ($card_stats['active'] ?? 0), 'url' => $this->page_url('aksrt_charge_cards', ['status' => 'active']), 'icon' => 'dashicons-id-alt', 'tone' => 'info'],
|
||||
]);
|
||||
|
||||
NativeUi::card_open(__('快捷入口', 'aksrt'));
|
||||
echo '<ul class="aksrt-native-links">';
|
||||
foreach ($this->pages() as $key => $page) {
|
||||
/**
|
||||
* 概览页快捷入口:除概览外的子页面。
|
||||
*
|
||||
* @return array<int, array{slug:string,title:string,icon:string,desc:string}>
|
||||
*/
|
||||
public function shortcuts(): array {
|
||||
$items = [];
|
||||
foreach ($this->pages() as $page) {
|
||||
if ($page['slug'] === self::MENU_SLUG) {
|
||||
continue;
|
||||
}
|
||||
$icon = self::PAGE_ICONS[$key] ?? 'dashicons-admin-links';
|
||||
echo '<li><a href="' . esc_url($this->page_url($page['slug'])) . '"><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>' . esc_html($page['title']) . '</a></li>';
|
||||
$items[] = ['slug' => $page['slug'], 'title' => $page['title'], 'icon' => $page['icon'], 'desc' => $page['desc']];
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
return $items;
|
||||
}
|
||||
|
||||
NativeUi::page_close();
|
||||
public function render_overview_page(): void {
|
||||
PayOverview::get_instance()->render_page();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
/**
|
||||
* 支付中心「概览」:今日实时、区间指标卡、成交趋势、分类成交、支付方式、转化与退款、热销排行、待办与快捷入口。
|
||||
*
|
||||
* 统计口径见 \AKSRT\Payment\OrderAnalytics。首屏报表(近 30 天)随页面输出为 JSON,
|
||||
* 之后切换区间 / 分类走 REST(aksrt/v1/admin/pay-overview,见 \AKSRT\Admin\PayOverviewController)。
|
||||
* 图表用按需打包的 ECharts(assets/js/runtime/libs/echarts-pay.min.js,tools/build-pay-charts.mjs 生成),
|
||||
* 绘制逻辑在 assets/js/admin-pay-overview.js,样式在 assets/css/admin/pay-overview.css。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin\Native;
|
||||
|
||||
use AKSRT\Payment\OrderAnalytics;
|
||||
use AKSRT\Payment\OrderAnalyticsDaily;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class PayOverview {
|
||||
|
||||
private const DEFAULT_DAYS = 30;
|
||||
private const DATA_ID = 'aksrt-pay-overview-data';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
|
||||
}
|
||||
|
||||
public function enqueue_assets(): void {
|
||||
$page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
|
||||
if ($page !== PayMenus::MENU_SLUG) {
|
||||
return;
|
||||
}
|
||||
$lib = '/assets/js/runtime/libs/echarts-pay.min.js';
|
||||
$css = '/assets/css/admin/pay-overview.css';
|
||||
$js = '/assets/js/admin-pay-overview.js';
|
||||
wp_enqueue_style('aksrt-admin-pay-overview', AKSRT_URI . $css, [NativeUi::STYLE_HANDLE], (string) filemtime(AKSRT_DIR . $css));
|
||||
wp_enqueue_script('aksrt-echarts-pay', AKSRT_URI . $lib, [], (string) filemtime(AKSRT_DIR . $lib), true);
|
||||
wp_enqueue_script('aksrt-admin-pay-overview', AKSRT_URI . $js, ['aksrt-echarts-pay'], (string) filemtime(AKSRT_DIR . $js), true);
|
||||
}
|
||||
|
||||
public function render_page(): void {
|
||||
if (!current_user_can(PayMenus::CAPABILITY)) {
|
||||
wp_die(esc_html__('您没有权限访问此页面。', 'aksrt'));
|
||||
}
|
||||
|
||||
$analytics = OrderAnalytics::get_instance();
|
||||
$today = current_time('Y-m-d');
|
||||
$from = OrderAnalyticsDaily::shift($today, -(self::DEFAULT_DAYS - 1));
|
||||
$report = $analytics->report($from, $today, '');
|
||||
$realtime = $analytics->today_vs_yesterday();
|
||||
$symbol = $this->currency_symbol();
|
||||
|
||||
NativeUi::page_open('aksrt-po-page');
|
||||
NativeUi::header(
|
||||
__('支付中心', 'aksrt'),
|
||||
__('成交走势、收入结构与待办一览。收款渠道、价格策略等配置在主题设置中修改。', 'aksrt'),
|
||||
'dashicons-cart',
|
||||
[
|
||||
['label' => __('订单明细', 'aksrt'), 'url' => $this->page_url('aksrt_orders'), 'icon' => 'dashicons-list-view'],
|
||||
['label' => __('支付配置', 'aksrt'), 'url' => admin_url('admin.php?page=aksrt-settings#/payment/gateway'), 'icon' => 'dashicons-admin-generic', 'primary' => true],
|
||||
]
|
||||
);
|
||||
|
||||
$this->render_realtime($realtime, $symbol);
|
||||
$this->render_toolbar($from, $today);
|
||||
echo '<div class="aksrt-po-notice" data-aksrt-po-excluded hidden></div>';
|
||||
echo '<div class="aksrt-po-error notice notice-error" data-aksrt-po-error hidden><p></p></div>';
|
||||
$this->render_kpis();
|
||||
|
||||
echo '<div class="aksrt-po-body" data-aksrt-po-body>';
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->chart_card('trend', __('成交趋势', 'aksrt'), __('支付金额、净收入、退款金额曲线与支付订单数折线', 'aksrt'), 'lg');
|
||||
$this->render_funnel_card();
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->chart_card('groups', __('分类成交', 'aksrt'), __('按商品分类的支付金额曲线', 'aksrt'), 'md');
|
||||
$this->chart_card('methods', __('支付方式', 'aksrt'), __('按支付金额的支付方式占比', 'aksrt'), 'md');
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-po-grid aksrt-po-grid--main">';
|
||||
$this->render_top_card();
|
||||
$this->render_todo_card();
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
$this->render_shortcuts_card();
|
||||
|
||||
$payload = [
|
||||
'currency' => $symbol,
|
||||
'today' => $today,
|
||||
'maxDays' => OrderAnalytics::MAX_RANGE_DAYS,
|
||||
'rest' => ['url' => rest_url('aksrt/v1/admin/pay-overview'), 'nonce' => wp_create_nonce('wp_rest')],
|
||||
'initial' => ['from' => $from, 'to' => $today, 'group' => '', 'report' => $report],
|
||||
'i18n' => [
|
||||
'paid' => __('支付金额', 'aksrt'),
|
||||
'net' => __('净收入', 'aksrt'),
|
||||
'refund' => __('退款金额', 'aksrt'),
|
||||
'count' => __('支付订单数', 'aksrt'),
|
||||
'unit' => __('笔', 'aksrt'),
|
||||
'people' => __('人', 'aksrt'),
|
||||
'empty' => __('所选区间暂无成交', 'aksrt'),
|
||||
'loadError' => __('统计数据加载失败,请稍后重试。', 'aksrt'),
|
||||
'basis' => __('较上一周期', 'aksrt'),
|
||||
'new' => __('新增', 'aksrt'),
|
||||
'total' => __('合计', 'aksrt'),
|
||||
'excluded' => __('以下外币订单未计入统计:', 'aksrt'),
|
||||
'noTop' => __('所选区间暂无付费内容成交', 'aksrt'),
|
||||
'badRange' => __('请选择有效的日期区间。', 'aksrt'),
|
||||
],
|
||||
];
|
||||
echo '<script type="application/json" id="' . esc_attr(self::DATA_ID) . '">'
|
||||
. wp_json_encode($payload, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE)
|
||||
. '</script>';
|
||||
|
||||
NativeUi::page_close();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 今日实时(同时段对比,不随区间变化)
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
/** @param array{today:array{amount:float,count:int},yesterday:array{amount:float,count:int},until:string} $data */
|
||||
private function render_realtime(array $data, string $symbol): void {
|
||||
$amount = (float) $data['today']['amount'];
|
||||
$prev = (float) $data['yesterday']['amount'];
|
||||
echo '<section class="aksrt-po-live" aria-label="' . esc_attr__('今日实时', 'aksrt') . '">';
|
||||
echo '<div class="aksrt-po-live__main"><span class="aksrt-po-live__dot" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-live__label">' . esc_html__('今日支付金额', 'aksrt') . '</span>'
|
||||
. '<strong class="aksrt-po-live__value">' . esc_html($symbol . number_format_i18n($amount, 2)) . '</strong>'
|
||||
. $this->delta_html($amount, $prev) . '</div>';
|
||||
echo '<div class="aksrt-po-live__meta">';
|
||||
/* translators: %d order count */
|
||||
echo '<span>' . esc_html(sprintf(__('今日 %d 笔', 'aksrt'), (int) $data['today']['count'])) . '</span>';
|
||||
echo '<span>' . esc_html(sprintf(
|
||||
/* translators: 1 time 2 amount 3 order count */
|
||||
__('昨日 00:00–%1$s:%2$s,%3$d 笔', 'aksrt'),
|
||||
$data['until'],
|
||||
$symbol . number_format_i18n($prev, 2),
|
||||
(int) $data['yesterday']['count']
|
||||
)) . '</span>';
|
||||
echo '</div></section>';
|
||||
}
|
||||
|
||||
/** 同时段环比徽标(服务端渲染,仅今日实时使用;区间指标的环比由前端计算)。 */
|
||||
private function delta_html(float $current, float $previous): string {
|
||||
$basis = __('较昨日同时段', 'aksrt');
|
||||
if ($previous <= 0) {
|
||||
$text = $current > 0 ? __('新增', 'aksrt') : '—';
|
||||
$class = $current > 0 ? 'is-up' : 'is-flat';
|
||||
} else {
|
||||
$delta = round(($current - $previous) / $previous * 100, 1);
|
||||
$class = $delta > 0 ? 'is-up' : ($delta < 0 ? 'is-down' : 'is-flat');
|
||||
$text = ($delta > 0 ? '↑' : ($delta < 0 ? '↓' : '')) . number_format_i18n(abs($delta), 1) . '%';
|
||||
}
|
||||
return '<span class="aksrt-po-delta ' . esc_attr($class) . '">' . esc_html($basis . ' ' . $text) . '</span>';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 筛选条 / 指标卡
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function render_toolbar(string $from, string $to): void {
|
||||
$presets = [
|
||||
'd7' => __('近 7 天', 'aksrt'),
|
||||
'd30' => __('近 30 天', 'aksrt'),
|
||||
'd90' => __('近 90 天', 'aksrt'),
|
||||
'this_month' => __('本月', 'aksrt'),
|
||||
'last_month' => __('上月', 'aksrt'),
|
||||
'd365' => __('近一年', 'aksrt'),
|
||||
];
|
||||
echo '<div class="aksrt-po-toolbar">';
|
||||
echo '<div class="aksrt-po-seg" role="group" data-aksrt-po-presets aria-label="' . esc_attr__('统计区间', 'aksrt') . '">';
|
||||
foreach ($presets as $key => $label) {
|
||||
echo '<button type="button" data-preset="' . esc_attr($key) . '" aria-pressed="' . ($key === 'd30' ? 'true' : 'false') . '">' . esc_html($label) . '</button>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<form class="aksrt-po-range" data-aksrt-po-range>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span>'
|
||||
. '<input type="date" name="from" value="' . esc_attr($from) . '" max="' . esc_attr($to) . '" required></label>';
|
||||
echo '<span aria-hidden="true">–</span>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span>'
|
||||
. '<input type="date" name="to" value="' . esc_attr($to) . '" max="' . esc_attr($to) . '" required></label>';
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('商品分类', 'aksrt') . '</span><select name="group">'
|
||||
. '<option value="">' . esc_html__('全部分类', 'aksrt') . '</option>';
|
||||
foreach (OrderAnalytics::group_labels() as $key => $label) {
|
||||
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
|
||||
}
|
||||
echo '</select></label>';
|
||||
echo '<button type="submit" class="button">' . esc_html__('查询', 'aksrt') . '</button>';
|
||||
echo '</form>';
|
||||
echo '<span class="aksrt-po-range-label" data-aksrt-po-range-label aria-live="polite"></span>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private function render_kpis(): void {
|
||||
$cards = [
|
||||
['paid_amount', __('支付金额', 'aksrt'), 'success'],
|
||||
['net_amount', __('净收入', 'aksrt'), 'info'],
|
||||
['paid_count', __('支付订单数', 'aksrt'), 'primary'],
|
||||
['buyers', __('支付人数', 'aksrt'), 'primary'],
|
||||
['avg_amount', __('客单价', 'aksrt'), 'warning'],
|
||||
['refund_amount', __('退款金额', 'aksrt'), 'danger'],
|
||||
];
|
||||
echo '<div class="aksrt-po-kpis" data-aksrt-po-kpis>';
|
||||
foreach ($cards as [$key, $label, $tone]) {
|
||||
echo '<div class="aksrt-po-kpi aksrt-po-kpi--' . esc_attr($tone) . '" data-kpi="' . esc_attr($key) . '">'
|
||||
. '<span class="aksrt-po-kpi__label">' . esc_html($label) . '</span>'
|
||||
. '<span class="aksrt-po-kpi__value" data-kpi-value>—</span>'
|
||||
. '<span class="aksrt-po-kpi__foot"><span class="aksrt-po-delta is-flat" data-kpi-delta>—</span>'
|
||||
. '<span class="aksrt-po-kpi__meta" data-kpi-meta></span></span>'
|
||||
. '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 卡片
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function chart_card(string $key, string $title, string $label, string $size): void {
|
||||
NativeUi::card_open($title, '', false, 'aksrt-po-card');
|
||||
echo '<div class="aksrt-po-chart aksrt-po-chart--' . esc_attr($size) . '" data-aksrt-po-chart="' . esc_attr($key) . '" role="img" aria-label="' . esc_attr($label) . '"></div>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_funnel_card(): void {
|
||||
$rows = [
|
||||
['created_count', __('下单数', 'aksrt'), __('区间内创建的订单', 'aksrt')],
|
||||
['conversion', __('支付转化率', 'aksrt'), __('下单后完成支付的比例', 'aksrt')],
|
||||
['close_rate', __('超时关闭率', 'aksrt'), __('未支付而关闭的比例', 'aksrt')],
|
||||
['refund_rate', __('退款率', 'aksrt'), __('退款金额 ÷ 支付金额', 'aksrt')],
|
||||
];
|
||||
NativeUi::card_open(__('转化与退款', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-funnel" data-aksrt-po-funnel>';
|
||||
foreach ($rows as [$key, $label, $hint]) {
|
||||
echo '<li data-metric="' . esc_attr($key) . '"><div class="aksrt-po-funnel__head">'
|
||||
. '<span class="aksrt-po-funnel__label">' . esc_html($label) . '</span>'
|
||||
. '<strong class="aksrt-po-funnel__value" data-value>—</strong></div>'
|
||||
. '<span class="aksrt-po-funnel__bar" aria-hidden="true"><i data-bar></i></span>'
|
||||
. '<span class="aksrt-po-funnel__hint">' . esc_html($hint) . ' <span data-delta></span></span></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_top_card(): void {
|
||||
NativeUi::card_open(__('付费内容热销 TOP 10', 'aksrt'), '', true, 'aksrt-po-card');
|
||||
echo '<div class="aksrt-native-table-wrap"><table class="widefat aksrt-po-top">'
|
||||
. '<caption class="screen-reader-text">' . esc_html__('付费内容热销排行', 'aksrt') . '</caption>'
|
||||
. '<thead><tr><th scope="col" class="column-rank">#</th><th scope="col">' . esc_html__('文章', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-num">' . esc_html__('销量', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-num">' . esc_html__('支付金额', 'aksrt') . '</th>'
|
||||
. '<th scope="col" class="column-share">' . esc_html__('占比', 'aksrt') . '</th></tr></thead>'
|
||||
. '<tbody data-aksrt-po-top></tbody></table></div>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_todo_card(): void {
|
||||
$orders = class_exists('\AKSRT\Payment\OrderRepository') ? \AKSRT\Payment\OrderRepository::get_instance()->get_orders_status_counts() : [];
|
||||
$withdraw = class_exists('\AKSRT\Payment\Withdrawal') ? \AKSRT\Payment\Withdrawal::get_instance()->get_stats() : [];
|
||||
$income = class_exists('\AKSRT\Payment\Revenue') ? \AKSRT\Payment\Revenue::get_instance()->get_stats() : [];
|
||||
$cards = class_exists('\AKSRT\Payment\ChargeCard') ? \AKSRT\Payment\ChargeCard::get_instance()->get_stats() : [];
|
||||
|
||||
$items = [
|
||||
[__('待审提现', 'aksrt'), (int) ($withdraw['pending'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'pending']), 'dashicons-money-alt', 'danger'],
|
||||
[__('打款中提现', 'aksrt'), (int) ($withdraw['processing'] ?? 0), $this->page_url('aksrt_withdraw', ['status' => 'processing']), 'dashicons-update', 'warning'],
|
||||
[__('待结算分成', 'aksrt'), (string) ($income['cash']['pending'] ?? '0.00'), $this->page_url('aksrt_income', ['status' => 'pending']), 'dashicons-chart-pie', 'warning'],
|
||||
[__('待支付订单', 'aksrt'), (int) ($orders['pending'] ?? 0), $this->page_url('aksrt_orders', ['status' => 'pending']), 'dashicons-clock', 'info'],
|
||||
[__('可用卡密', 'aksrt'), (int) ($cards['active'] ?? 0), $this->page_url('aksrt_charge_cards', ['status' => 'active']), 'dashicons-id-alt', 'neutral'],
|
||||
];
|
||||
|
||||
NativeUi::card_open(__('待办事项', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-todo">';
|
||||
foreach ($items as [$label, $value, $url, $icon, $tone]) {
|
||||
$idle = (string) $value === '0' || (string) $value === '0.00';
|
||||
echo '<li><a class="aksrt-po-todo__item aksrt-po-todo__item--' . esc_attr($idle ? 'neutral' : $tone) . '" href="' . esc_url($url) . '">'
|
||||
. '<span class="aksrt-po-todo__icon dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-todo__label">' . esc_html($label) . '</span>'
|
||||
. '<span class="aksrt-po-todo__value">' . esc_html(is_int($value) ? number_format_i18n($value) : $this->currency_symbol() . $value) . '</span>'
|
||||
. '</a></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
private function render_shortcuts_card(): void {
|
||||
NativeUi::card_open(__('快捷入口', 'aksrt'), '', false, 'aksrt-po-card');
|
||||
echo '<ul class="aksrt-po-links">';
|
||||
foreach (PayMenus::get_instance()->shortcuts() as $item) {
|
||||
echo '<li><a href="' . esc_url($this->page_url($item['slug'])) . '">'
|
||||
. '<span class="aksrt-po-links__icon dashicons ' . esc_attr($item['icon']) . '" aria-hidden="true"></span>'
|
||||
. '<span class="aksrt-po-links__text"><strong>' . esc_html($item['title']) . '</strong>'
|
||||
. ($item['desc'] !== '' ? '<span>' . esc_html($item['desc']) . '</span>' : '')
|
||||
. '</span></a></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
NativeUi::card_close();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* 助手
|
||||
* ----------------------------------------------------------------*/
|
||||
|
||||
private function currency_symbol(): string {
|
||||
$paid = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||||
return (string) ($paid['display']['currency_symbol'] ?? '¥');
|
||||
}
|
||||
|
||||
/** @param array<string, string> $args */
|
||||
private function page_url(string $slug, array $args = []): string {
|
||||
return add_query_arg(array_merge(['page' => $slug], $args), admin_url('admin.php'));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user