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

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) { } catch (\Throwable $e) {
Db::rollback(); 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!', 'Deleted successfully' => 'Deleted successfully!',
'Parameter error' => 'Parameter error!', 'Parameter error' => 'Parameter error!',
'Lifetime points source is disabled' => 'Lifetime points source is disabled', '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', 'File uploaded successfully' => 'File uploaded successfully',
'No files were uploaded' => 'No files were uploaded', 'No files were uploaded' => 'No files were uploaded',
'The uploaded file format is not allowed' => 'The uploaded file format is no allowance.', 'The uploaded file format is not allowed' => 'The uploaded file format is no allowance.',

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' => '操作完成!',
'Lifetime points calculation completed' => '终身总输赢积分折算完成:共处理 %d 条,更新 %d 条,跳过 %d 条',
'Please use the %s field to sort before operating' => '请使用 %s 字段排序后再操作', 'Please use the %s field to sort before operating' => '请使用 %s 字段排序后再操作',
'File uploaded successfully' => '文件上传成功!', 'File uploaded successfully' => '文件上传成功!',
'No files were uploaded' => '没有文件被上传', 'No files were uploaded' => '没有文件被上传',

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ export default {
}, },
dailyPush: { dailyPush: {
title: 'About this page', 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: { claimLog: {
title: 'About this page', title: 'About this page',
@@ -57,6 +57,6 @@ export default {
}, },
playxConfig: { playxConfig: {
title: 'About this page', 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_btn: 'Calculate lifetime loss points',
calculate_lifetime_tip: 'Batch update from latest daily push lifetime field and ratio above.', 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_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', section_mall_open: 'Mall open date',
mall_open_date: '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.', 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 { export default {
id: 'id', id: 'ID',
user_id: 'user_id', user_id: 'PlayX user ID',
date: 'date', date: 'Business date',
username: 'username', username: 'Username',
yesterday_win_loss_net: 'yesterday_win_loss_net', yesterday_win_loss_net: 'Yesterday net win/loss',
converted_points: 'converted_points', converted_points: 'Converted points',
yesterday_total_deposit: 'yesterday_total_deposit', yesterday_total_deposit: 'Yesterday total deposit',
lifetime_total_deposit: 'lifetime_total_deposit', lifetime_total_deposit: 'Lifetime total deposit',
lifetime_total_withdraw: 'lifetime_total_withdraw', lifetime_total_withdraw: 'Lifetime total withdraw',
lifetime_win_loss_net: 'lifetime_win_loss_net', lifetime_win_loss_net: 'Lifetime net win/loss',
create_time: 'create_time', create_time: 'Created at',
'quick Search Fields': 'id', 'quick Search Fields': 'ID',
} }

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ export default {
calculate_lifetime_btn: '一键计算终身总输赢', calculate_lifetime_btn: '一键计算终身总输赢',
calculate_lifetime_tip: '按各会员最新每日推送中的终身总输赢与上方比例,批量更新待领取积分与领取上限。无终身总输赢数据的会员将跳过。', calculate_lifetime_tip: '按各会员最新每日推送中的终身总输赢与上方比例,批量更新待领取积分与领取上限。无终身总输赢数据的会员将跳过。',
calculate_lifetime_confirm: '将按当前终身总输赢兑换比例重新折算全部会员积分,已折算部分会先扣回再按新比例增加。是否继续?', calculate_lifetime_confirm: '将按当前终身总输赢兑换比例重新折算全部会员积分,已折算部分会先扣回再按新比例增加。是否继续?',
calculate_lifetime_success: '终身总输赢积分折算完成:共处理 {processed} 条,更新 {updated} 条,跳过 {skipped} 条',
section_mall_open: '商城开放日', section_mall_open: '商城开放日',
mall_open_date: '商城开放日', mall_open_date: '商城开放日',
mall_open_date_tip: '设置后隔日生效。例如设为 8 月 1 日,则从 8 月 2 日起对业务日期为 8 月 1 日及之后的每日推送执行「昨日净输赢」转积分T+1开放日前一日产生的输赢在开放日次日开始折算。', 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'), label: t('mall.dailyPush.username'),
prop: 'username', prop: 'username',
align: 'center', align: 'center',
minWidth: 95,
operatorPlaceholder: t('Fuzzy query'), operatorPlaceholder: t('Fuzzy query'),
showOverflowTooltip: true,
sortable: false, sortable: false,
operator: 'LIKE', operator: 'LIKE',
}, },
@@ -78,6 +80,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.yesterday_win_loss_net'), label: t('mall.dailyPush.yesterday_win_loss_net'),
prop: 'yesterday_win_loss_net', prop: 'yesterday_win_loss_net',
align: 'center', align: 'center',
minWidth: 90,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
}, },
@@ -85,14 +88,15 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.converted_points'), label: t('mall.dailyPush.converted_points'),
prop: 'converted_points', prop: 'converted_points',
align: 'center', align: 'center',
minWidth: 80,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
width: 100,
}, },
{ {
label: t('mall.dailyPush.yesterday_total_deposit'), label: t('mall.dailyPush.yesterday_total_deposit'),
prop: 'yesterday_total_deposit', prop: 'yesterday_total_deposit',
align: 'center', align: 'center',
minWidth: 90,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
}, },
@@ -100,6 +104,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_total_deposit'), label: t('mall.dailyPush.lifetime_total_deposit'),
prop: 'lifetime_total_deposit', prop: 'lifetime_total_deposit',
align: 'center', align: 'center',
minWidth: 90,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
}, },
@@ -107,7 +112,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_total_withdraw'), label: t('mall.dailyPush.lifetime_total_withdraw'),
prop: 'lifetime_total_withdraw', prop: 'lifetime_total_withdraw',
align: 'center', align: 'center',
minWidth: 95, minWidth: 90,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
}, },
@@ -115,7 +120,7 @@ const baTable = new baTableClass(
label: t('mall.dailyPush.lifetime_win_loss_net'), label: t('mall.dailyPush.lifetime_win_loss_net'),
prop: 'lifetime_win_loss_net', prop: 'lifetime_win_loss_net',
align: 'center', align: 'center',
minWidth: 95, minWidth: 90,
operator: 'RANGE', operator: 'RANGE',
sortable: 'custom', sortable: 'custom',
}, },

View File

@@ -113,7 +113,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' 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 { onMounted, reactive, ref, useTemplateRef } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { calculateLifetimePoints, index, save } from '/@/api/backend/mall/playxConfig' import { calculateLifetimePoints, index, save } from '/@/api/backend/mall/playxConfig'
@@ -204,7 +204,15 @@ const onCalculateLifetime = () => {
}).then(() => { }).then(() => {
calculating.value = true calculating.value = true
calculateLifetimePoints() 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() loadData()
}) })
.finally(() => { .finally(() => {

View File

@@ -35,22 +35,94 @@ const baTable = new baTableClass(
column: [ column: [
{ type: 'selection', align: 'center', operator: false }, { 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.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.user_id'),
{ label: t('mall.playxDailyPush.username'), prop: 'username', align: 'center', operatorPlaceholder: t('Fuzzy query'), sortable: false, operator: 'LIKE' }, prop: 'user_id',
{ label: t('mall.playxDailyPush.yesterday_win_loss_net'), prop: 'yesterday_win_loss_net', align: 'center', operator: 'RANGE', sortable: false }, align: 'center',
{ label: t('mall.playxDailyPush.converted_points'), prop: 'converted_points', align: 'center', operator: 'RANGE', sortable: false, width: 100 }, operatorPlaceholder: t('Fuzzy query'),
{ label: t('mall.playxDailyPush.yesterday_total_deposit'), prop: 'yesterday_total_deposit', align: 'center', operator: 'RANGE', sortable: false }, sortable: false,
{ label: t('mall.playxDailyPush.lifetime_total_deposit'), prop: 'lifetime_total_deposit', align: 'center', operator: 'RANGE', sortable: false }, operator: 'LIKE',
{ 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.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], dblClickNotEditColumn: [undefined],
}, },
{ {
defaultItems: {}, defaultItems: {},
}, }
) )
provide('baTable', baTable) provide('baTable', baTable)
@@ -66,4 +138,3 @@ onMounted(() => {
</script> </script>
<style scoped lang="scss"></style> <style scoped lang="scss"></style>

View File

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