- Split PayWithdrawals into shell + PayWithdrawalsList flat sibling trait - Add process/policy callout, open/paid/fee stats, status views with counts - Filter by source and payout method; reset keeps the status view - Show net payout with gross/fee breakdown, copyable payee, note column - Replace inline select form with per-row actions and a confirm dialog; reject requires a reason; double-submit guard; retry reuses the idempotency key; redirect back preserving filters and paging - Withdrawal: add get_status_counts() and method filter; get_stats() now returns open_gross / paid_net / paid_fee - Remove unused REST routes (/payment/withdrawals, /transition), admin_withdrawals(), update_status(), has_open_withdrawal(), count_open(), resolve_gross_amount() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
390 lines
23 KiB
PHP
390 lines
23 KiB
PHP
<?php
|
||
/**
|
||
* 提现审核:列表视图(流程提示 + 统计 + 状态视图 + 筛选 + 申请表 + 分页 + 处理弹窗)。
|
||
*
|
||
* 作为 flat sibling trait 被 PayWithdrawals 使用(PSR-4:trait 必须与宿主类同目录)。
|
||
*
|
||
* @package AKSRT
|
||
*/
|
||
|
||
namespace AKSRT\Admin\Native;
|
||
|
||
use AKSRT\Payment\Withdrawal;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
trait PayWithdrawalsList {
|
||
|
||
private function render_withdrawals_list_page(): void {
|
||
NativeUi::header(
|
||
__('提现审核', 'aksrt'),
|
||
__('用户发起的余额与推广佣金提现申请。按收款信息线下打款后,在这里确认完成;拒绝会把冻结金额退回原资金池。', 'aksrt'),
|
||
'dashicons-money-alt',
|
||
[[
|
||
'label' => __('提现策略', 'aksrt'),
|
||
'url' => admin_url('admin.php?page=aksrt-settings#/payment/withdrawal'),
|
||
'icon' => 'dashicons-admin-generic',
|
||
]]
|
||
);
|
||
$this->render_flash();
|
||
|
||
$svc = Withdrawal::get_instance();
|
||
$filters = $this->list_filters();
|
||
$per_page = AdminListPagination::DEFAULT_PER_PAGE;
|
||
$page_num = max(1, (int) ($_GET['paged'] ?? 1));
|
||
$retry_id = isset($_GET['retry_id']) ? max(0, (int) $_GET['retry_id']) : 0;
|
||
$retry_key = isset($_GET['retry_key']) ? sanitize_text_field((string) wp_unslash($_GET['retry_key'])) : '';
|
||
|
||
// 翻页、状态视图与重置链接共用的筛选参数(不含 paged)。
|
||
// retry_id / retry_key 是一次性动作参数,绝不能进翻页链接。
|
||
$query_args = array_filter([
|
||
'status' => $filters['status'],
|
||
's' => $filters['keyword'],
|
||
'source' => $filters['source'],
|
||
'method' => $filters['method'],
|
||
'user_id' => $filters['user_id'],
|
||
'date_from' => $filters['date_from'],
|
||
'date_to' => $filters['date_to'],
|
||
], static function ($value): bool {
|
||
return $value !== '' && $value !== 0;
|
||
});
|
||
|
||
$stats = $svc->get_stats();
|
||
$this->render_process_callout($stats);
|
||
$this->render_withdrawal_stats($stats);
|
||
|
||
// 状态计数同时给出状态视图的数字与当前视图的总数,查询前就能夹取页码。
|
||
$repo_args = array_diff_key($filters, ['status' => true]);
|
||
$counts = $svc->get_status_counts($repo_args);
|
||
$total = $filters['status'] !== '' ? (int) ($counts[$filters['status']] ?? 0) : array_sum($counts);
|
||
$pages = max(1, (int) ceil($total / $per_page));
|
||
|
||
// 页码越界(手工改 URL、或筛选后总数变小)会让 offset 落到数据之外:表格空掉
|
||
// 且分页条不输出,只能手改 URL 退出。先夹到最后一页再查。
|
||
if ($page_num > $pages) {
|
||
$page_num = $pages;
|
||
}
|
||
|
||
$items = $svc->get_all($repo_args + [
|
||
'status' => $filters['status'],
|
||
'limit' => $per_page,
|
||
'offset' => ($page_num - 1) * $per_page,
|
||
]);
|
||
$user_ids = array_values(array_unique(array_filter(array_map('intval', array_column($items, 'user_id')))));
|
||
if ($user_ids !== []) {
|
||
cache_users($user_ids);
|
||
}
|
||
|
||
$this->render_status_views($filters['status'], $query_args, $counts);
|
||
$this->render_filter_card($filters, $query_args);
|
||
|
||
NativeUi::table_card_open(__('提现申请', 'aksrt'));
|
||
echo '<table class="wp-list-table widefat striped aksrt-wd-table">';
|
||
echo '<caption class="screen-reader-text">' . esc_html__('提现申请', 'aksrt') . '</caption><thead><tr>';
|
||
foreach ([
|
||
'number' => esc_html__('订单号', 'aksrt'),
|
||
'user' => esc_html__('用户', 'aksrt'),
|
||
'amount' => esc_html__('金额', 'aksrt'),
|
||
'payee' => esc_html__('收款信息', 'aksrt'),
|
||
'status' => esc_html__('状态', 'aksrt'),
|
||
'note' => esc_html__('备注', 'aksrt'),
|
||
'actions' => esc_html__('操作', 'aksrt'),
|
||
] as $key => $label) {
|
||
echo '<th scope="col" class="column-' . esc_attr($key) . '">' . $label . '</th>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
|
||
}
|
||
echo '</tr></thead><tbody>';
|
||
|
||
if (!$items) {
|
||
$filtered = array_diff_key($query_args, ['status' => true]) !== [];
|
||
echo NativeUi::empty_row(7, __('暂无提现记录', 'aksrt'), $filtered // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
? __('没有符合当前筛选条件的申请,请调整或重置筛选。', 'aksrt')
|
||
: __('用户在用户中心发起余额或佣金提现后,申请会出现在这里。', 'aksrt'));
|
||
}
|
||
foreach ($items as $row) {
|
||
$this->render_withdrawal_row($row, $retry_id, $retry_key);
|
||
}
|
||
|
||
echo '</tbody></table>';
|
||
NativeUi::table_wrap_close();
|
||
AdminListPagination::render(self::PAGE_SLUG, $total, $page_num, $per_page, $query_args);
|
||
NativeUi::card_close();
|
||
|
||
$this->render_process_dialog();
|
||
echo '<div class="aksrt-wd-toast" role="status" aria-live="polite" data-aksrt-wd-toast'
|
||
. ' data-copied="' . esc_attr__('收款信息已复制', 'aksrt') . '"'
|
||
. ' data-copy-failed="' . esc_attr__('自动复制失败,请手动选中后复制。', 'aksrt') . '"></div>';
|
||
}
|
||
|
||
private function render_withdrawal_row(array $row, int $retry_id, string $retry_key): void {
|
||
$id = (int) ($row['id'] ?? 0);
|
||
$user_id = (int) ($row['user_id'] ?? 0);
|
||
$status = (string) ($row['status'] ?? '');
|
||
$number = (string) ($row['withdrawal_number'] ?? '');
|
||
$gross = (string) ($row['gross_amount'] ?? '0.00');
|
||
$net = (string) ($row['amount'] ?? '0.00');
|
||
$source = (string) ($row['source'] ?? Withdrawal::SOURCE_BALANCE);
|
||
$method = (string) ($row['method'] ?? '');
|
||
$account = (string) ($row['display_account'] ?? '');
|
||
$note = trim((string) ($row['note'] ?? ''));
|
||
$source_label = $this->source_labels()[$source] ?? $source;
|
||
$method_label = $this->method_labels()[$method] ?? ($method !== '' ? $method : '—');
|
||
$user = $user_id > 0 ? get_userdata($user_id) : false;
|
||
|
||
echo '<tr' . ($retry_id === $id ? ' class="is-retry"' : '') . '>';
|
||
echo '<td class="column-number"><code class="aksrt-native-mono">' . esc_html($number !== '' ? $number : '#' . $id) . '</code>'
|
||
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('%s 申请', 'aksrt'), (string) ($row['created_at'] ?? ''))) . '</div></td>';
|
||
echo '<td class="column-user">' . NativeUi::user_cell($user_id) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('来源:%s', 'aksrt'), $source_label)) . '</div></td>';
|
||
|
||
echo '<td class="column-amount"><strong class="aksrt-native-amount">' . esc_html($this->format_money($net)) . '</strong>'
|
||
. '<div class="aksrt-wd-sub">' . esc_html(sprintf(
|
||
/* translators: 1 gross 2 fee 3 fee percent */
|
||
__('申请 %1$s · 手续费 %2$s(%3$d%%)', 'aksrt'),
|
||
$this->format_money($gross),
|
||
$this->format_money($this->fee_decimal($gross, $net)),
|
||
(int) ($row['fee_rate'] ?? 0)
|
||
)) . '</div></td>';
|
||
|
||
echo '<td class="column-payee"><span class="aksrt-wd-method aksrt-wd-method--' . esc_attr(sanitize_html_class($method)) . '">' . esc_html($method_label) . '</span>';
|
||
if ($account !== '') {
|
||
echo '<button type="button" class="aksrt-wd-account" data-clipboard-text="' . esc_attr($account) . '" aria-label="' . esc_attr(sprintf(__('复制收款信息:%s', 'aksrt'), $account)) . '">'
|
||
. '<span>' . esc_html($account) . '</span><span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button>';
|
||
}
|
||
echo '</td>';
|
||
|
||
$processed = (string) ($row['processed_at'] ?? '');
|
||
echo '<td class="column-status">' . NativeUi::status_badge($status, $this->status_badge_map()) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
. ($processed !== '' ? '<div class="aksrt-wd-sub">' . esc_html(sprintf(__('%s 处理', 'aksrt'), $processed)) . '</div>' : '') . '</td>';
|
||
|
||
echo '<td class="column-note">' . ($note !== ''
|
||
? '<div class="aksrt-wd-note">' . nl2br(esc_html($note)) . '</div>'
|
||
: '<span class="aksrt-native-muted">—</span>') . '</td>';
|
||
|
||
echo '<td class="column-actions">';
|
||
$next = $this->next_statuses($status);
|
||
if ($next === []) {
|
||
echo '<span class="aksrt-native-muted">—</span></td></tr>';
|
||
return;
|
||
}
|
||
$data = [
|
||
'id' => (string) $id,
|
||
'nonce' => wp_create_nonce('aksrt_pay_update_withdrawal_' . $id),
|
||
'key' => $this->request_key_for($id, $retry_id, $retry_key),
|
||
'number' => $number !== '' ? $number : '#' . $id,
|
||
'user' => $user ? $user->display_name . ' #' . $user_id : '#' . $user_id,
|
||
'net' => $this->format_money($net),
|
||
'gross' => $this->format_money($gross),
|
||
'source' => $source_label,
|
||
'payee' => $method_label . ($account !== '' ? ' · ' . $account : ''),
|
||
];
|
||
$attrs = '';
|
||
foreach ($data as $key => $value) {
|
||
$attrs .= ' data-' . $key . '="' . esc_attr($value) . '"';
|
||
}
|
||
$buttons = [
|
||
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'aksrt'), 'button-primary'],
|
||
Withdrawal::STATUS_COMPLETED => [__('确认打款', 'aksrt'), 'button-primary'],
|
||
Withdrawal::STATUS_REJECTED => [__('拒绝', 'aksrt'), 'is-danger'],
|
||
];
|
||
echo '<div class="aksrt-wd-actions">';
|
||
foreach ($next as $target) {
|
||
[$label, $class] = $buttons[$target];
|
||
echo '<button type="button" class="button button-small ' . esc_attr($class) . '" data-aksrt-wd-open="' . esc_attr($target) . '"' . $attrs // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped above.
|
||
. ' aria-haspopup="dialog" aria-controls="aksrt-wd-dialog">' . esc_html($label) . '</button>';
|
||
}
|
||
echo '</div></td></tr>';
|
||
}
|
||
|
||
/** 处理流程 + 当前提现策略;有待办时用警示色提醒冻结金额。 */
|
||
private function render_process_callout(array $stats): void {
|
||
$settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_withdrawal_settings() : [];
|
||
$open = (int) ($stats['pending'] ?? 0) + (int) ($stats['processing'] ?? 0);
|
||
$policy = sprintf(
|
||
/* translators: 1 min amount 2 fee percent 3 daily count 4 daily limit */
|
||
__('当前策略:最低 %1$s,手续费 %2$d%%,每日 %3$d 次 / %4$s。', 'aksrt'),
|
||
$this->format_money((string) ($settings['min_amount'] ?? '0.00')),
|
||
(int) ($settings['service_charge_rate'] ?? 0),
|
||
(int) ($settings['daily_count'] ?? 0),
|
||
$this->format_money((string) ($settings['daily_limit'] ?? '0.00'))
|
||
);
|
||
$flow = __('处理流程:开始处理 → 按收款信息线下打款 → 确认打款。确认打款与拒绝都不可撤销。', 'aksrt');
|
||
|
||
echo '<div class="aksrt-native-callout' . ($open > 0 ? ' is-warning' : '') . '">';
|
||
if ($open > 0) {
|
||
echo '<strong>' . esc_html(sprintf(
|
||
/* translators: 1 open count 2 frozen amount */
|
||
__('%1$d 笔申请待办,冻结中 %2$s。', 'aksrt'),
|
||
$open,
|
||
$this->format_money((string) ($stats['open_gross'] ?? '0.00'))
|
||
)) . '</strong> ';
|
||
}
|
||
echo esc_html($flow . ' ' . $policy) . '</div>';
|
||
}
|
||
|
||
private function render_withdrawal_stats(array $stats): void {
|
||
NativeUi::stats([
|
||
['label' => __('待处理', 'aksrt'), 'value' => number_format_i18n((int) ($stats['pending'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PENDING]), 'icon' => 'dashicons-clock', 'tone' => 'warning'],
|
||
['label' => __('处理中', 'aksrt'), 'value' => number_format_i18n((int) ($stats['processing'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_PROCESSING]), 'icon' => 'dashicons-update', 'tone' => 'info'],
|
||
['label' => __('冻结中金额', 'aksrt'), 'value' => $this->format_money((string) ($stats['open_gross'] ?? '0.00')), 'hint' => __('待处理 + 处理中的申请毛额', 'aksrt'), 'icon' => 'dashicons-lock', 'tone' => 'warning'],
|
||
['label' => __('已打款', 'aksrt'), 'value' => $this->format_money((string) ($stats['paid_net'] ?? '0.00')), 'url' => $this->page_url(['status' => Withdrawal::STATUS_COMPLETED]), 'hint' => sprintf(
|
||
/* translators: 1 completed count 2 fee total */
|
||
__('%1$s 笔 · 手续费 %2$s', 'aksrt'),
|
||
number_format_i18n((int) ($stats['completed'] ?? 0)),
|
||
$this->format_money((string) ($stats['paid_fee'] ?? '0.00'))
|
||
), 'icon' => 'dashicons-yes-alt', 'tone' => 'success'],
|
||
['label' => __('已拒绝', 'aksrt'), 'value' => number_format_i18n((int) ($stats['rejected'] ?? 0)), 'url' => $this->page_url(['status' => Withdrawal::STATUS_REJECTED]), 'icon' => 'dashicons-no-alt', 'tone' => 'danger'],
|
||
]);
|
||
}
|
||
|
||
/** @param array<string, int> $counts */
|
||
private function render_status_views(string $current, array $query_args, array $counts): void {
|
||
$base = array_diff_key($query_args, ['status' => true]);
|
||
$views = ['' => [__('全部', 'aksrt'), array_sum($counts)]];
|
||
foreach ($this->status_labels() as $key => $label) {
|
||
$views[$key] = [$label, (int) ($counts[$key] ?? 0)];
|
||
}
|
||
|
||
echo '<nav class="aksrt-native-tabs aksrt-wd-views" aria-label="' . esc_attr__('按处理状态查看', 'aksrt') . '">';
|
||
foreach ($views as $key => [$label, $count]) {
|
||
$url = $this->page_url($base + ($key !== '' ? ['status' => $key] : []));
|
||
$active = $current === $key;
|
||
echo '<a class="aksrt-native-tabs__link' . ($active ? ' is-active' : '') . '" href="' . esc_url($url) . '"' . ($active ? ' aria-current="page"' : '') . '>'
|
||
. esc_html($label) . ' <span class="count">' . esc_html(number_format_i18n($count)) . '</span></a>';
|
||
}
|
||
echo '</nav>';
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $filters
|
||
* @param array<string, string|int> $query_args
|
||
*/
|
||
private function render_filter_card(array $filters, array $query_args): void {
|
||
NativeUi::card_open('', '', false, 'aksrt-wd-filter-card');
|
||
echo '<form method="get" class="aksrt-wd-filters" role="search">';
|
||
echo '<input type="hidden" name="page" value="' . esc_attr(self::PAGE_SLUG) . '" />';
|
||
if ($filters['status'] !== '') {
|
||
echo '<input type="hidden" name="status" value="' . esc_attr($filters['status']) . '" />';
|
||
}
|
||
|
||
echo '<label class="aksrt-wd-filters__search"><span class="screen-reader-text">' . esc_html__('订单号 / 用户 ID', 'aksrt') . '</span>'
|
||
. '<input type="search" name="s" value="' . esc_attr($filters['keyword']) . '" placeholder="' . esc_attr__('搜索订单号 / 用户 ID', 'aksrt') . '" /></label>';
|
||
echo $this->filter_select('source', __('提现来源', 'aksrt'), ['' => __('全部来源', 'aksrt')] + $this->source_labels(), $filters['source']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
echo $this->filter_select('method', __('收款方式', 'aksrt'), ['' => __('全部收款方式', 'aksrt')] + $this->method_labels(), $filters['method']); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||
echo '<span class="aksrt-wd-filters__range">'
|
||
. '<label><span class="screen-reader-text">' . esc_html__('开始日期', 'aksrt') . '</span><input type="date" name="date_from" value="' . esc_attr($filters['date_from']) . '" /></label>'
|
||
. '<span aria-hidden="true">–</span>'
|
||
. '<label><span class="screen-reader-text">' . esc_html__('结束日期', 'aksrt') . '</span><input type="date" name="date_to" value="' . esc_attr($filters['date_to']) . '" /></label>'
|
||
. '</span>';
|
||
echo '<label><span class="screen-reader-text">' . esc_html__('用户 ID', 'aksrt') . '</span><input type="number" min="1" name="user_id" class="aksrt-wd-filters__id" value="'
|
||
. esc_attr($filters['user_id'] > 0 ? (string) $filters['user_id'] : '') . '" placeholder="' . esc_attr__('用户 ID', 'aksrt') . '" /></label>';
|
||
echo '<span class="aksrt-wd-filters__submit"><button type="submit" class="button button-primary">' . esc_html__('筛选', 'aksrt') . '</button>';
|
||
if (array_diff_key($query_args, ['status' => true]) !== []) {
|
||
$reset = $this->page_url($filters['status'] !== '' ? ['status' => $filters['status']] : []);
|
||
echo '<a class="button" href="' . esc_url($reset) . '">' . esc_html__('重置', 'aksrt') . '</a>';
|
||
}
|
||
echo '</span></form>';
|
||
NativeUi::card_close();
|
||
}
|
||
|
||
/** @param array<string, string> $options */
|
||
private function filter_select(string $name, string $label, array $options, string $current): string {
|
||
$html = '<label><span class="screen-reader-text">' . esc_html($label) . '</span><select name="' . esc_attr($name) . '">';
|
||
foreach ($options as $value => $text) {
|
||
$html .= '<option value="' . esc_attr((string) $value) . '"' . selected($current, (string) $value, false) . '>' . esc_html($text) . '</option>';
|
||
}
|
||
return $html . '</select></label>';
|
||
}
|
||
|
||
/**
|
||
* 共享处理弹窗:行内按钮把记录摘要、nonce 与幂等键写进来(admin-withdrawals.js),
|
||
* 三种目标状态各自的说明与备注要求用 data-mode 区分。
|
||
*/
|
||
private function render_process_dialog(): void {
|
||
echo '<dialog class="aksrt-wd-dialog" id="aksrt-wd-dialog" aria-labelledby="aksrt-wd-dialog-title">';
|
||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" class="aksrt-wd-dialog__frame" data-aksrt-wd-form data-busy-label="' . esc_attr__('正在提交…', 'aksrt') . '">';
|
||
echo '<input type="hidden" name="action" value="aksrt_pay_update_withdrawal" />';
|
||
echo '<input type="hidden" name="withdrawal_id" value="" data-field="id" />';
|
||
echo '<input type="hidden" name="new_status" value="" data-field="status" />';
|
||
echo '<input type="hidden" name="idempotency_key" value="" data-field="key" />';
|
||
echo '<input type="hidden" name="_wpnonce" value="" data-field="nonce" />';
|
||
wp_referer_field();
|
||
|
||
$titles = [
|
||
Withdrawal::STATUS_PROCESSING => __('开始处理提现', 'aksrt'),
|
||
Withdrawal::STATUS_COMPLETED => __('确认已打款', 'aksrt'),
|
||
Withdrawal::STATUS_REJECTED => __('拒绝提现', 'aksrt'),
|
||
];
|
||
echo '<header class="aksrt-wd-dialog__head"><h2 id="aksrt-wd-dialog-title">';
|
||
foreach ($titles as $mode => $title) {
|
||
echo '<span data-mode="' . esc_attr($mode) . '" hidden>' . esc_html($title) . '</span>';
|
||
}
|
||
echo '</h2><button type="button" class="aksrt-wd-dialog__close" data-aksrt-wd-close aria-label="' . esc_attr__('关闭弹窗', 'aksrt') . '"><span class="dashicons dashicons-no-alt" aria-hidden="true"></span></button></header>';
|
||
|
||
echo '<div class="aksrt-wd-dialog__body"><dl class="aksrt-wd-summary">';
|
||
foreach ([
|
||
'number' => __('订单号', 'aksrt'),
|
||
'user' => __('用户', 'aksrt'),
|
||
'net' => __('应打款', 'aksrt'),
|
||
'gross' => __('申请金额', 'aksrt'),
|
||
'source' => __('来源', 'aksrt'),
|
||
'payee' => __('收款信息', 'aksrt'),
|
||
] as $field => $label) {
|
||
echo '<div class="aksrt-wd-summary__item aksrt-wd-summary__item--' . esc_attr($field) . '"><dt>' . esc_html($label) . '</dt><dd data-summary="' . esc_attr($field) . '"></dd></div>';
|
||
}
|
||
echo '</dl>';
|
||
|
||
$hints = [
|
||
Withdrawal::STATUS_PROCESSING => [__('标记为处理中后,请按上方收款信息线下打款,完成后回到这里确认打款。', 'aksrt'), ''],
|
||
Withdrawal::STATUS_COMPLETED => [__('请确认已向用户实际打出「应打款」金额。确认后状态不可更改,用户会收到完成通知。', 'aksrt'), 'is-warning'],
|
||
Withdrawal::STATUS_REJECTED => [__('拒绝后,冻结的申请金额会退回用户的原资金池,拒绝原因会通知用户。此操作不可撤销。', 'aksrt'), 'is-danger'],
|
||
];
|
||
foreach ($hints as $mode => [$hint, $tone]) {
|
||
echo '<p class="aksrt-native-callout ' . esc_attr($tone) . '" data-mode="' . esc_attr($mode) . '" hidden>' . esc_html($hint) . '</p>';
|
||
}
|
||
|
||
echo '<label class="aksrt-native-field"><span class="aksrt-native-field__label">'
|
||
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_REJECTED) . '" hidden>' . esc_html__('拒绝原因(必填)', 'aksrt') . '</span>'
|
||
. '<span data-mode="' . esc_attr(Withdrawal::STATUS_PROCESSING) . ' ' . esc_attr(Withdrawal::STATUS_COMPLETED) . '" hidden>' . esc_html__('备注(选填)', 'aksrt') . '</span>'
|
||
. '</span><textarea name="note" rows="3" maxlength="1000" data-aksrt-wd-note placeholder="' . esc_attr__('如打款流水号、拒绝原因等,追加记录在申请上', 'aksrt') . '"></textarea></label>';
|
||
echo '</div>';
|
||
|
||
$submits = [
|
||
Withdrawal::STATUS_PROCESSING => [__('开始处理', 'aksrt'), 'button-primary'],
|
||
Withdrawal::STATUS_COMPLETED => [__('确认已打款', 'aksrt'), 'button-primary'],
|
||
Withdrawal::STATUS_REJECTED => [__('确认拒绝', 'aksrt'), 'button-primary is-danger'],
|
||
];
|
||
echo '<footer class="aksrt-wd-dialog__foot"><button type="button" class="button" data-aksrt-wd-close>' . esc_html__('取消', 'aksrt') . '</button>';
|
||
foreach ($submits as $mode => [$label, $class]) {
|
||
echo '<button type="submit" class="button ' . esc_attr($class) . '" data-mode="' . esc_attr($mode) . '" data-aksrt-wd-submit hidden>' . esc_html($label) . '</button>';
|
||
}
|
||
echo '</footer></form></dialog>';
|
||
}
|
||
|
||
/**
|
||
* 从请求读取并校验筛选条件;非法值一律视为未筛选。
|
||
*
|
||
* @return array{status:string,keyword:string,source:string,method:string,user_id:int,date_from:string,date_to:string}
|
||
*/
|
||
private function list_filters(): array {
|
||
$read = static function (string $key): string {
|
||
return isset($_GET[$key]) ? sanitize_text_field((string) wp_unslash($_GET[$key])) : '';
|
||
};
|
||
$date = static function (string $value): string {
|
||
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1 ? $value : '';
|
||
};
|
||
$status = sanitize_key($read('status'));
|
||
$source = sanitize_key($read('source'));
|
||
$method = sanitize_key($read('method'));
|
||
|
||
return [
|
||
'status' => isset($this->status_labels()[$status]) ? $status : '',
|
||
'keyword' => trim($read('s')),
|
||
'source' => isset($this->source_labels()[$source]) ? $source : '',
|
||
'method' => isset($this->method_labels()[$method]) ? $method : '',
|
||
'user_id' => max(0, (int) $read('user_id')),
|
||
'date_from' => $date($read('date_from')),
|
||
'date_to' => $date($read('date_to')),
|
||
];
|
||
}
|
||
}
|