feat(user-center): redesign sidebar and wallet tips
This commit is contained in:
@@ -140,6 +140,10 @@ class SchemaPostList {
|
||||
['key' => 'post_wechat_share', 'label' => '微信分享', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_rewards_s', 'label' => '用户打赏', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'post_rewards_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'],
|
||||
['key' => 'post_rewards_amounts', 'label' => '快捷金额', 'type' => 'input', 'default' => '5,10,20,50,100', 'description' => '英文逗号分隔,赞赏直接进入作者站内余额'],
|
||||
['key' => 'post_rewards_custom', 'label' => '允许自定义金额', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_rewards_min', 'label' => '最低金额', 'type' => 'number', 'default' => 1, 'min' => 0.01, 'max' => 1000, 'step' => 0.01],
|
||||
['key' => 'post_rewards_max', 'label' => '最高金额', 'type' => 'number', 'default' => 1000, 'min' => 1, 'max' => 100000, 'step' => 0.01],
|
||||
['key' => 'post_button_toptext', 'label' => '按钮上方文案', 'type' => 'input', 'default' => '喜欢就支持一下吧'],
|
||||
]],
|
||||
['key' => 'content', 'title' => '内容处理', 'fields' => [
|
||||
|
||||
@@ -585,14 +585,7 @@ class SchemaUsers {
|
||||
['key' => 'user_privacy_ip', 'label' => '隐藏IP归属地', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_location', 'label' => '隐藏位置', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'rewards', 'title' => '打赏设置', 'fields' => [
|
||||
['key' => 'post_rewards_s', 'label' => '启用文章打赏', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'post_rewards_text', 'label' => '打赏文案', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'post_rewards_img', 'label' => '打赏二维码', 'type' => 'media', 'default' => ''],
|
||||
['key' => 'post_rewards_btn_text', 'label' => '打赏按钮文案', 'type' => 'input', 'default' => '赞赏'],
|
||||
['key' => 'post_rewards_amounts', 'label' => '打赏金额', 'type' => 'input', 'default' => '5,10,20,50,100'],
|
||||
['key' => 'post_rewards_custom', 'label' => '自定义金额', 'type' => 'switch', 'default' => true],
|
||||
]],
|
||||
|
||||
],
|
||||
],
|
||||
'verify' => [
|
||||
|
||||
@@ -244,6 +244,105 @@ class Balance {
|
||||
return !empty(\AKSRT\Settings\Settings::get_all()['pay_balance_s']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically transfer cash balance between two users.
|
||||
*
|
||||
* @param array<string,mixed> $ref
|
||||
* @return array{sender_balance:float,recipient_balance:float}|\WP_Error
|
||||
*/
|
||||
public static function transfer(int $sender_id, int $recipient_id, float $amount, string $reason_out = 'tip_sent', string $reason_in = 'tip_received', array $ref = [], string $remark = '') {
|
||||
global $wpdb;
|
||||
|
||||
$amount = round($amount, 2);
|
||||
if ($sender_id <= 0 || $recipient_id <= 0 || $sender_id === $recipient_id) {
|
||||
return new \WP_Error('aksrt_balance_transfer_user', __('转账用户无效', 'aksrt'));
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
return new \WP_Error('aksrt_balance_transfer_amount', __('转账金额必须大于零', 'aksrt'));
|
||||
}
|
||||
if (!class_exists('\AKSRT\Core\DBMigrator')) {
|
||||
return new \WP_Error('aksrt_balance_transfer_unavailable', __('余额服务不可用', 'aksrt'));
|
||||
}
|
||||
|
||||
foreach ([$sender_id, $recipient_id] as $user_id) {
|
||||
if (!metadata_exists('user', $user_id, self::META_KEY)) {
|
||||
add_user_meta($user_id, self::META_KEY, '0', true);
|
||||
}
|
||||
}
|
||||
|
||||
$meta_table = $wpdb->usermeta;
|
||||
$log_table = \AKSRT\Core\DBMigrator::table('balance_log');
|
||||
$locked_ids = [$sender_id, $recipient_id];
|
||||
sort($locked_ids, SORT_NUMERIC);
|
||||
|
||||
$wpdb->query('START TRANSACTION');
|
||||
try {
|
||||
foreach ($locked_ids as $user_id) {
|
||||
$wpdb->get_var($wpdb->prepare(
|
||||
"SELECT umeta_id FROM {$meta_table} WHERE user_id = %d AND meta_key = %s FOR UPDATE",
|
||||
$user_id,
|
||||
self::META_KEY
|
||||
));
|
||||
}
|
||||
|
||||
$sender_before = (float) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT meta_value FROM {$meta_table} WHERE user_id = %d AND meta_key = %s LIMIT 1",
|
||||
$sender_id,
|
||||
self::META_KEY
|
||||
));
|
||||
if ($sender_before < $amount) {
|
||||
$wpdb->query('ROLLBACK');
|
||||
return new \WP_Error('aksrt_balance_insufficient', __('余额不足', 'aksrt'));
|
||||
}
|
||||
|
||||
$sender_after = round($sender_before - $amount, 2);
|
||||
$recipient_before = (float) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT meta_value FROM {$meta_table} WHERE user_id = %d AND meta_key = %s LIMIT 1",
|
||||
$recipient_id,
|
||||
self::META_KEY
|
||||
));
|
||||
$recipient_after = round($recipient_before + $amount, 2);
|
||||
|
||||
$sender_updated = $wpdb->update($meta_table, ['meta_value' => (string) $sender_after], ['user_id' => $sender_id, 'meta_key' => self::META_KEY], ['%s'], ['%d', '%s']);
|
||||
$recipient_updated = $wpdb->update($meta_table, ['meta_value' => (string) $recipient_after], ['user_id' => $recipient_id, 'meta_key' => self::META_KEY], ['%s'], ['%d', '%s']);
|
||||
if ($sender_updated !== 1 || $recipient_updated !== 1) {
|
||||
throw new \RuntimeException('balance_update_failed');
|
||||
}
|
||||
|
||||
$ref_type = isset($ref['type']) ? sanitize_key((string) $ref['type']) : 'tip';
|
||||
$ref_id = isset($ref['id']) ? (int) $ref['id'] : null;
|
||||
$created_at = current_time('mysql', true);
|
||||
$log_rows = [
|
||||
[$sender_id, -$amount, $sender_after, $reason_out],
|
||||
[$recipient_id, $amount, $recipient_after, $reason_in],
|
||||
];
|
||||
foreach ($log_rows as [$user_id, $delta, $balance_after, $reason]) {
|
||||
$inserted = $wpdb->insert($log_table, [
|
||||
'user_id' => $user_id,
|
||||
'delta' => $delta,
|
||||
'balance_after' => $balance_after,
|
||||
'reason' => sanitize_key($reason),
|
||||
'remark' => $remark !== '' ? sanitize_text_field(substr($remark, 0, 255)) : null,
|
||||
'ref_type' => $ref_type,
|
||||
'ref_id' => $ref_id,
|
||||
'created_at' => $created_at,
|
||||
], ['%d', '%f', '%f', '%s', '%s', '%s', '%d', '%s']);
|
||||
if ($inserted !== 1) {
|
||||
throw new \RuntimeException('balance_log_failed');
|
||||
}
|
||||
}
|
||||
|
||||
$wpdb->query('COMMIT');
|
||||
wp_cache_delete($sender_id, 'user_meta');
|
||||
wp_cache_delete($recipient_id, 'user_meta');
|
||||
do_action('aksrt_balance_transferred', $sender_id, $recipient_id, $amount, $ref);
|
||||
return ['sender_balance' => $sender_after, 'recipient_balance' => $recipient_after];
|
||||
} catch (\Throwable $e) {
|
||||
$wpdb->query('ROLLBACK');
|
||||
return new \WP_Error('aksrt_balance_transfer_failed', __('打赏入账失败,请稍后重试', 'aksrt'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format yuan for display.
|
||||
*/
|
||||
@@ -262,6 +361,8 @@ class Balance {
|
||||
'recharge' => __('余额充值', 'aksrt'),
|
||||
'transfer_in' => __('转入', 'aksrt'),
|
||||
'transfer_out' => __('转出', 'aksrt'),
|
||||
'tip_sent' => __('打赏支出', 'aksrt'),
|
||||
'tip_received' => __('收到打赏', 'aksrt'),
|
||||
'admin' => __('管理员调整', 'aksrt'),
|
||||
'exchange_from_credits' => __('积分兑换', 'aksrt'),
|
||||
'withdraw_hold' => __('提现冻结', 'aksrt'),
|
||||
|
||||
@@ -391,9 +391,11 @@ trait OrderRepositoryCore {
|
||||
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) as paid,
|
||||
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered,
|
||||
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed,
|
||||
SUM(CASE WHEN status = 'pending_review' THEN 1 ELSE 0 END) as pending_review,
|
||||
SUM(CASE WHEN status = 'refunding' THEN 1 ELSE 0 END) as refunding,
|
||||
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled,
|
||||
SUM(CASE WHEN status = 'refunded' THEN 1 ELSE 0 END) as refunded,
|
||||
SUM(CASE WHEN status IN ('paid','delivered','completed') THEN amount ELSE 0 END) as revenue
|
||||
SUM(CASE WHEN status IN ('paid','delivered','completed','pending_review') THEN amount ELSE 0 END) as revenue
|
||||
FROM {$this->table()} {$where}";
|
||||
|
||||
if (!empty($params)) {
|
||||
@@ -406,9 +408,11 @@ trait OrderRepositoryCore {
|
||||
'total' => (int) ($row['total'] ?? 0),
|
||||
'pending' => (int) ($row['pending'] ?? 0),
|
||||
'paid' => (int) ($row['paid'] ?? 0),
|
||||
'delivered' => (int) ($row['delivered'] ?? 0),
|
||||
'completed' => (int) ($row['completed'] ?? 0),
|
||||
'cancelled' => (int) ($row['cancelled'] ?? 0),
|
||||
'delivered' => (int) ($row['delivered'] ?? 0),
|
||||
'completed' => (int) ($row['completed'] ?? 0),
|
||||
'pending_review' => (int) ($row['pending_review'] ?? 0),
|
||||
'refunding' => (int) ($row['refunding'] ?? 0),
|
||||
'cancelled' => (int) ($row['cancelled'] ?? 0),
|
||||
'refunded' => (int) ($row['refunded'] ?? 0),
|
||||
'revenue' => (float) ($row['revenue'] ?? 0),
|
||||
];
|
||||
|
||||
@@ -237,11 +237,19 @@ class PostDisplay {
|
||||
$authordesc = !isset($settings['post_authordesc_s']) || !empty($settings['post_authordesc_s']);
|
||||
$hide_time = !empty($settings['post_single_hide_time_s']);
|
||||
$share_enabled = !isset($settings['share_s']) || !empty($settings['share_s']);
|
||||
$rewards_enabled = !empty($settings['post_rewards_s']) || !empty($settings['post_rewards_enabled']);
|
||||
$rewards_enabled = !empty($settings['post_rewards_s']);
|
||||
$rewards_text = sanitize_text_field((string) ($settings['post_rewards_text'] ?? ''));
|
||||
if ($rewards_text === '') {
|
||||
$rewards_text = '赞赏';
|
||||
}
|
||||
$rewards_amounts = array_values(array_filter(array_map(static function ($value): float {
|
||||
return round((float) $value, 2);
|
||||
}, explode(',', (string) ($settings['post_rewards_amounts'] ?? '5,10,20,50,100'))), static fn(float $value): bool => $value > 0));
|
||||
if ($rewards_amounts === []) {
|
||||
$rewards_amounts = [5, 10, 20, 50, 100];
|
||||
}
|
||||
$rewards_min = max(0.01, round((float) ($settings['post_rewards_min'] ?? 1), 2));
|
||||
$rewards_max = max($rewards_min, round((float) ($settings['post_rewards_max'] ?? 1000), 2));
|
||||
$author_card_limit = max(0, min(12, (int) ($settings['post_author_card_limit'] ?? 6)));
|
||||
$author_card_orderby = sanitize_key((string) ($settings['post_author_card_orderby'] ?? 'views'));
|
||||
if (!in_array($author_card_orderby, ['views', 'date', 'modified', 'comment_count'], true)) {
|
||||
@@ -332,11 +340,15 @@ class PostDisplay {
|
||||
'author_card_limit' => $author_card_limit,
|
||||
'author_card_orderby' => $author_card_orderby,
|
||||
'author_card_style' => $author_card_style,
|
||||
// Rewards (Zibll post_rewards_s) — per-user QR only.
|
||||
// On-site wallet tipping.
|
||||
'rewards_enabled' => $rewards_enabled,
|
||||
'post_rewards_s' => $rewards_enabled,
|
||||
'rewards_text' => $rewards_text,
|
||||
'rewards_btn_text' => $rewards_text,
|
||||
'rewards_amounts' => $rewards_amounts,
|
||||
'rewards_custom' => !isset($settings['post_rewards_custom']) || !empty($settings['post_rewards_custom']),
|
||||
'rewards_min' => $rewards_min,
|
||||
'rewards_max' => $rewards_max,
|
||||
// Content max height (Zibll article_maxheight_kg / article_maxheight).
|
||||
'maxheight_enabled' => $maxheight_enabled,
|
||||
'maxheight' => $maxheight,
|
||||
|
||||
Reference in New Issue
Block a user