优化每日推送和积分转化多语言样式

This commit is contained in:
2026-07-24 12:01:41 +08:00
parent 4bad91b8a3
commit e3d4ec4624
15 changed files with 204 additions and 65 deletions

View File

@@ -144,9 +144,17 @@ class PlayxConfig extends Backend
} catch (\Throwable $e) {
Db::rollback();
return $this->error($e->getMessage());
return $this->error(__($e->getMessage()));
}
return $this->success(__('Operation completed'), $stats);
return $this->success(
sprintf(
__('Lifetime points calculation completed'),
$stats['processed'],
$stats['updated'],
$stats['skipped']
),
$stats
);
}
}

View File

@@ -28,6 +28,8 @@ return [
'Deleted successfully' => 'Deleted successfully!',
'Parameter error' => 'Parameter error!',
'Lifetime points source is disabled' => 'Lifetime points source is disabled',
'Operation completed' => 'Operation completed!',
'Lifetime points calculation completed' => 'Lifetime conversion done: %d processed, %d updated, %d skipped',
'File uploaded successfully' => 'File uploaded successfully',
'No files were uploaded' => 'No files were uploaded',
'The uploaded file format is not allowed' => 'The uploaded file format is no allowance.',

View File

@@ -28,6 +28,8 @@ return [
'Deleted successfully' => '删除成功!',
'Parameter error' => '参数错误!',
'Lifetime points source is disabled' => '终身总输赢积分来源未启用',
'Operation completed' => '操作完成!',
'Lifetime points calculation completed' => '终身总输赢积分折算完成:共处理 %d 条,更新 %d 条,跳过 %d 条',
'Please use the %s field to sort before operating' => '请使用 %s 字段排序后再操作',
'File uploaded successfully' => '文件上传成功!',
'No files were uploaded' => '没有文件被上传',

View File

@@ -142,13 +142,13 @@ final class MallPointsConversion
public static function recalculateAllLifetimePoints(MallBusinessConfig $row): array
{
if (intval($row->lifetime_points_enabled ?? 0) !== 1) {
throw new \RuntimeException(__('Lifetime points source is disabled'));
throw new \RuntimeException('Lifetime points source is disabled');
}
$config = self::configFromRow($row);
$ratio = floatval($config['lifetime_points_ratio'] ?? 0);
if ($ratio < 0) {
throw new \RuntimeException(__('Parameter error'));
throw new \RuntimeException('Parameter error');
}
$stats = ['processed' => 0, 'updated' => 0, 'skipped' => 0];

View File

@@ -29,13 +29,8 @@ export function save(data: anyObj) {
}
export function calculateLifetimePoints() {
return createAxios(
{
url: actionUrl.get('calculateLifetimePoints'),
method: 'post',
},
{
showSuccessMessage: true,
}
)
return createAxios({
url: actionUrl.get('calculateLifetimePoints'),
method: 'post',
})
}

View File

@@ -13,7 +13,7 @@ export default {
},
dailyPush: {
title: 'About this page',
desc: 'Daily T+1 snapshots pushed by the partner; rows are stored here and drive user-asset related updates.',
desc: 'Daily T+1 snapshots from the partner; each row stores converted points per mall settings and syncs user assets.',
},
claimLog: {
title: 'About this page',
@@ -57,6 +57,6 @@ export default {
},
playxConfig: {
title: 'About this page',
desc: 'Edit return/unlock/points-to-cash ratios used by daily push and asset APIs; values are stored in mall_business_config and apply immediately after save.',
desc: 'Configure daily and lifetime loss-to-points sources (switches and ratios), mall open date, unlock ratio, and points-to-cash ratio. Open date changes take effect the next calendar day.',
},
}

View File

@@ -12,6 +12,7 @@ export default {
calculate_lifetime_btn: 'Calculate lifetime loss points',
calculate_lifetime_tip: 'Batch update from latest daily push lifetime field and ratio above.',
calculate_lifetime_confirm: 'Recalculate all members with the current lifetime ratio (reverses previous lifetime conversion first). Continue?',
calculate_lifetime_success: 'Lifetime conversion done: {processed} processed, {updated} updated, {skipped} skipped',
section_mall_open: 'Mall open date',
mall_open_date: 'Mall open date',
mall_open_date_tip: 'Takes effect the next calendar day. E.g. set Aug 1: from Aug 2, daily push rows with business date on/after Aug 1 convert yesterday loss to points.',

View File

@@ -1,14 +1,14 @@
export default {
id: 'id',
user_id: 'user_id',
date: 'date',
username: 'username',
yesterday_win_loss_net: 'yesterday_win_loss_net',
converted_points: 'converted_points',
yesterday_total_deposit: 'yesterday_total_deposit',
lifetime_total_deposit: 'lifetime_total_deposit',
lifetime_total_withdraw: 'lifetime_total_withdraw',
lifetime_win_loss_net: 'lifetime_win_loss_net',
create_time: 'create_time',
'quick Search Fields': 'id',
id: 'ID',
user_id: 'PlayX user ID',
date: 'Business date',
username: 'Username',
yesterday_win_loss_net: 'Yesterday net win/loss',
converted_points: 'Converted points',
yesterday_total_deposit: 'Yesterday total deposit',
lifetime_total_deposit: 'Lifetime total deposit',
lifetime_total_withdraw: 'Lifetime total withdraw',
lifetime_win_loss_net: 'Lifetime net win/loss',
create_time: 'Created at',
'quick Search Fields': 'ID',
}

View File

@@ -1,17 +1,17 @@
export default {
id: 'id',
username: 'username',
phone: 'phone',
playx_user_id: 'playX_user_id',
locked_points: 'locked_points',
available_points: 'available_points',
id: 'ID',
username: 'Username',
phone: 'Phone',
playx_user_id: 'PlayX user ID',
locked_points: 'Locked points',
available_points: 'Available points',
adjust_available_points: 'Adjust points',
today_limit: 'today_limit',
today_claimed: 'today_claimed',
today_limit_date: 'today_limit_date',
points_claim_cap: 'Points claim cap',
today_limit: 'Daily claim cap',
today_claimed: 'Claimed today',
today_limit_date: 'Daily cap date',
points_claim_cap: 'Lifetime claim cap',
total_points_claimed: 'Total points claimed',
create_time: 'create_time',
update_time: 'update_time',
'quick Search Fields': 'id, playX_user_id, username, phone',
create_time: 'Created at',
update_time: 'Updated at',
'quick Search Fields': 'ID, PlayX user ID, username, phone',
}

View File

@@ -13,7 +13,7 @@ export default {
},
dailyPush: {
title: '页面说明',
desc: '展示合作方 T+1 每日推送的经营数据快照;接口写入后在此留痕,并用于同步用户资产相关字段。',
desc: '展示合作方 T+1 每日推送的经营数据快照;入库时按商城参数折算「转换积分」并同步用户资产。',
},
claimLog: {
title: '页面说明',
@@ -57,6 +57,6 @@ export default {
},
playxConfig: {
title: '页面说明',
desc: '配置每日推送用到的返还比例、解锁比例积分折算现金比例;保存后立即作用于接口逻辑mall_business_config)。',
desc: '配置昨日净输赢、终身总输赢两种积分来源(开关与兑换比例)、商城开放日,以及解锁比例积分折算现金比例;保存后按规则生效(开放日隔日生效)。',
},
}

View File

@@ -12,6 +12,7 @@ export default {
calculate_lifetime_btn: '一键计算终身总输赢',
calculate_lifetime_tip: '按各会员最新每日推送中的终身总输赢与上方比例,批量更新待领取积分与领取上限。无终身总输赢数据的会员将跳过。',
calculate_lifetime_confirm: '将按当前终身总输赢兑换比例重新折算全部会员积分,已折算部分会先扣回再按新比例增加。是否继续?',
calculate_lifetime_success: '终身总输赢积分折算完成:共处理 {processed} 条,更新 {updated} 条,跳过 {skipped} 条',
section_mall_open: '商城开放日',
mall_open_date: '商城开放日',
mall_open_date_tip: '设置后隔日生效。例如设为 8 月 1 日,则从 8 月 2 日起对业务日期为 8 月 1 日及之后的每日推送执行「昨日净输赢」转积分T+1开放日前一日产生的输赢在开放日次日开始折算。',

View File

@@ -70,7 +70,9 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.username'),
prop: 'username',
align: 'center',
minWidth: 95,
operatorPlaceholder: t('Fuzzy query'),
showOverflowTooltip: true,
sortable: false,
operator: 'LIKE',
},
@@ -78,6 +80,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.yesterday_win_loss_net'),
prop: 'yesterday_win_loss_net',
align: 'center',
minWidth: 90,
operator: 'RANGE',
sortable: 'custom',
},
@@ -85,14 +88,15 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.converted_points'),
prop: 'converted_points',
align: 'center',
minWidth: 80,
operator: 'RANGE',
sortable: 'custom',
width: 100,
},
{
label: t('mall.dailyPush.yesterday_total_deposit'),
prop: 'yesterday_total_deposit',
align: 'center',
minWidth: 90,
operator: 'RANGE',
sortable: 'custom',
},
@@ -100,6 +104,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_total_deposit'),
prop: 'lifetime_total_deposit',
align: 'center',
minWidth: 90,
operator: 'RANGE',
sortable: 'custom',
},
@@ -107,7 +112,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_total_withdraw'),
prop: 'lifetime_total_withdraw',
align: 'center',
minWidth: 95,
minWidth: 90,
operator: 'RANGE',
sortable: 'custom',
},
@@ -115,7 +120,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_win_loss_net'),
prop: 'lifetime_win_loss_net',
align: 'center',
minWidth: 95,
minWidth: 90,
operator: 'RANGE',
sortable: 'custom',
},

View File

@@ -113,7 +113,7 @@
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessageBox } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n'
import { calculateLifetimePoints, index, save } from '/@/api/backend/mall/playxConfig'
@@ -204,7 +204,15 @@ const onCalculateLifetime = () => {
}).then(() => {
calculating.value = true
calculateLifetimePoints()
.then(() => {
.then((res) => {
const stats = res.data ?? {}
ElMessage.success(
t('mall.playxConfig.calculate_lifetime_success', {
processed: stats.processed ?? 0,
updated: stats.updated ?? 0,
skipped: stats.skipped ?? 0,
})
)
loadData()
})
.finally(() => {

View File

@@ -35,22 +35,94 @@ const baTable = new baTableClass(
column: [
{ type: 'selection', align: 'center', operator: false },
{ label: t('mall.playxDailyPush.id'), prop: 'id', align: 'center', width: 70, operator: 'RANGE', sortable: 'custom' },
{ label: t('mall.playxDailyPush.user_id'), prop: 'user_id', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
{ label: t('mall.playxDailyPush.date'), prop: 'date', align: 'center', render: 'date', operator: 'RANGE', comSearchRender: 'date', sortable: 'custom', width: 120, operatorPlaceholder: t('Fuzzy query') },
{ label: t('mall.playxDailyPush.username'), prop: 'username', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' },
{ label: t('mall.playxDailyPush.yesterday_win_loss_net'), prop: 'yesterday_win_loss_net', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.playxDailyPush.converted_points'), prop: 'converted_points', align: 'center', operator: 'RANGE', sortable: false, width: 100 },
{ label: t('mall.playxDailyPush.yesterday_total_deposit'), prop: 'yesterday_total_deposit', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.playxDailyPush.lifetime_total_deposit'), prop: 'lifetime_total_deposit', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.playxDailyPush.lifetime_total_withdraw'), prop: 'lifetime_total_withdraw', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.playxDailyPush.lifetime_win_loss_net'), prop: 'lifetime_win_loss_net', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.playxDailyPush.create_time'), prop: 'create_time', align: 'center', render: 'datetime', operator: 'RANGE', comSearchRender: 'datetime', sortable: 'custom', width: 160, timeFormat: 'yyyy-mm-dd hh:MM:ss' },
{
label: t('mall.playxDailyPush.user_id'),
prop: 'user_id',
align: 'center',
operatorPlaceholder: t('Fuzzy query'),
sortable: false,
operator: 'LIKE',
},
{
label: t('mall.playxDailyPush.date'),
prop: 'date',
align: 'center',
render: 'date',
operator: 'RANGE',
comSearchRender: 'date',
sortable: 'custom',
width: 120,
operatorPlaceholder: t('Fuzzy query'),
},
{
label: t('mall.playxDailyPush.username'),
prop: 'username',
align: 'center',
operatorPlaceholder: t('Fuzzy query'),
sortable: false,
operator: 'LIKE',
},
{
label: t('mall.playxDailyPush.yesterday_win_loss_net'),
prop: 'yesterday_win_loss_net',
align: 'center',
operator: 'RANGE',
sortable: false,
},
{
label: t('mall.playxDailyPush.converted_points'),
prop: 'converted_points',
align: 'center',
minWidth: 160,
operator: 'RANGE',
sortable: false,
width: 100,
},
{
label: t('mall.playxDailyPush.yesterday_total_deposit'),
prop: 'yesterday_total_deposit',
align: 'center',
operator: 'RANGE',
sortable: false,
},
{
label: t('mall.playxDailyPush.lifetime_total_deposit'),
prop: 'lifetime_total_deposit',
align: 'center',
operator: 'RANGE',
sortable: false,
},
{
label: t('mall.playxDailyPush.lifetime_total_withdraw'),
prop: 'lifetime_total_withdraw',
align: 'center',
operator: 'RANGE',
sortable: false,
},
{
label: t('mall.playxDailyPush.lifetime_win_loss_net'),
prop: 'lifetime_win_loss_net',
align: 'center',
operator: 'RANGE',
sortable: false,
},
{
label: t('mall.playxDailyPush.create_time'),
prop: 'create_time',
align: 'center',
render: 'datetime',
operator: 'RANGE',
comSearchRender: 'datetime',
sortable: 'custom',
width: 160,
timeFormat: 'yyyy-mm-dd hh:MM:ss',
},
],
dblClickNotEditColumn: [undefined],
},
{
defaultItems: {},
},
}
)
provide('baTable', baTable)
@@ -66,4 +138,3 @@ onMounted(() => {
</script>
<style scoped lang="scss"></style>

View File

@@ -45,6 +45,7 @@ const baTable = new baTableClass(
prop: 'username',
align: 'center',
operatorPlaceholder: t('Fuzzy query'),
showOverflowTooltip: true,
sortable: false,
operator: 'LIKE',
},
@@ -52,7 +53,9 @@ const baTable = new baTableClass(
label: t('mall.userAsset.phone'),
prop: 'phone',
align: 'center',
minWidth: 100,
operatorPlaceholder: t('Fuzzy query'),
showOverflowTooltip: true,
sortable: false,
operator: 'LIKE',
},
@@ -60,16 +63,59 @@ const baTable = new baTableClass(
label: t('mall.userAsset.playx_user_id'),
prop: 'playx_user_id',
align: 'center',
minWidth: 120,
operatorPlaceholder: t('Fuzzy query'),
sortable: false,
operator: 'LIKE',
},
{ label: t('mall.userAsset.locked_points'), prop: 'locked_points', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.userAsset.available_points'), prop: 'available_points', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.userAsset.today_limit'), prop: 'today_limit', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.userAsset.today_claimed'), prop: 'today_claimed', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.userAsset.points_claim_cap'), prop: 'points_claim_cap', align: 'center', operator: 'RANGE', sortable: false },
{ label: t('mall.userAsset.total_points_claimed'), prop: 'total_points_claimed', align: 'center', operator: 'RANGE', sortable: false },
{
label: t('mall.userAsset.locked_points'),
prop: 'locked_points',
align: 'center',
minWidth: 120,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.available_points'),
prop: 'available_points',
align: 'center',
minWidth: 120,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.today_limit'),
prop: 'today_limit',
align: 'center',
minWidth: 160,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.today_claimed'),
prop: 'today_claimed',
align: 'center',
minWidth: 120,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.points_claim_cap'),
prop: 'points_claim_cap',
align: 'center',
minWidth: 160,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.total_points_claimed'),
prop: 'total_points_claimed',
align: 'center',
minWidth: 160,
operator: 'RANGE',
sortable: true,
},
{
label: t('mall.userAsset.today_limit_date'),
prop: 'today_limit_date',