优化每日推送和积分转化逻辑
This commit is contained in:
@@ -5,6 +5,7 @@ export const url = '/admin/mall.PlayxConfig/'
|
||||
export const actionUrl = new Map([
|
||||
['index', url + 'index'],
|
||||
['save', url + 'save'],
|
||||
['calculateLifetimePoints', url + 'calculateLifetimePoints'],
|
||||
])
|
||||
|
||||
export function index() {
|
||||
@@ -26,3 +27,15 @@ export function save(data: anyObj) {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function calculateLifetimePoints() {
|
||||
return createAxios(
|
||||
{
|
||||
url: actionUrl.get('calculateLifetimePoints'),
|
||||
method: 'post',
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ export default {
|
||||
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',
|
||||
export_excel: 'Export Excel',
|
||||
|
||||
@@ -1,8 +1,24 @@
|
||||
export default {
|
||||
return_ratio: 'Return ratio (return_ratio)',
|
||||
return_ratio_tip: 'In daily push, when yesterday net win/loss is negative: locked increment = |net| × this ratio.',
|
||||
section_daily_source: 'Source 1: yesterday net win/loss',
|
||||
daily_points_enabled: 'Enable daily loss to points',
|
||||
daily_points_enabled_tip: 'Convert yesterday net win/loss from daily push to locked points; only losses (negative net) count.',
|
||||
return_ratio: 'Yesterday loss conversion ratio',
|
||||
return_ratio_tip: 'On daily push: converted points = floor(|yesterday net|) × ratio (losses only, non-negative integer). Requires mall open date and enabled source.',
|
||||
section_lifetime_source: 'Source 2: lifetime net win/loss',
|
||||
lifetime_points_enabled: 'Enable lifetime loss to points',
|
||||
lifetime_points_enabled_tip: 'Uses the latest daily push row per member; only losses. Skips when lifetime field is missing.',
|
||||
lifetime_points_ratio: 'Lifetime loss conversion ratio',
|
||||
lifetime_points_ratio_tip: 'One-click job: locked points from |lifetime net| × ratio. Re-run reverses prior lifetime conversion then applies the new ratio.',
|
||||
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?',
|
||||
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.',
|
||||
active_mall_open_date: 'Active open date',
|
||||
section_other: 'Other settings',
|
||||
unlock_ratio: 'Unlock ratio (unlock_ratio)',
|
||||
unlock_ratio_tip: 'In daily push: daily claim cap = yesterday total deposit × this ratio.',
|
||||
unlock_ratio_tip: 'Daily push: daily claim cap = yesterday total deposit × this ratio.',
|
||||
points_to_cash_ratio: 'Points to cash ratio',
|
||||
points_to_cash_ratio_tip: 'For withdrawable cash display: cash ≈ available points × this ratio.',
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ export default {
|
||||
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',
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
today_limit: 'today_limit',
|
||||
today_claimed: 'today_claimed',
|
||||
today_limit_date: 'today_limit_date',
|
||||
points_claim_cap: 'Points 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',
|
||||
|
||||
@@ -4,9 +4,11 @@ export default {
|
||||
date: '业务日期',
|
||||
username: '用户名',
|
||||
yesterday_win_loss_net: '昨日净输赢',
|
||||
converted_points: '转换积分',
|
||||
yesterday_total_deposit: '昨日总充值',
|
||||
lifetime_total_deposit: '历史总充值',
|
||||
lifetime_total_withdraw: '历史总提现',
|
||||
lifetime_total_deposit: '终身总充值',
|
||||
lifetime_total_withdraw: '终身总提现',
|
||||
lifetime_win_loss_net: '终身总输赢',
|
||||
create_time: '创建时间',
|
||||
'quick Search Fields': 'ID',
|
||||
export_excel: 'Excel导出',
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
export default {
|
||||
return_ratio: '返还比例(return_ratio)',
|
||||
return_ratio_tip: '每日推送中,仅当昨日净输赢为负时:待领取增量 = |净输赢| × 本比例。',
|
||||
section_daily_source: '积分来源一:昨日净输赢',
|
||||
daily_points_enabled: '启用昨日净输赢转积分',
|
||||
daily_points_enabled_tip: '根据每日推送中的「昨日净输赢」折算待领取积分;仅统计会员输的钱(净输赢为负),赢的不计。',
|
||||
return_ratio: '昨日净输赢兑换比例',
|
||||
return_ratio_tip: '每日推送入库时:转换积分 = floor(|昨日净输赢|) × 本比例(仅负值,结果为非负整数)。需满足商城开放日规则且本来源已启用。',
|
||||
section_lifetime_source: '积分来源二:终身总输赢',
|
||||
lifetime_points_enabled: '启用终身总输赢转积分',
|
||||
lifetime_points_enabled_tip: '使用每日推送中每位会员最新一条记录的「终身总输赢」字段;仅统计输的钱。第三方未传该字段时跳过。',
|
||||
lifetime_points_ratio: '终身总输赢兑换比例',
|
||||
lifetime_points_ratio_tip: '一键计算时:待领取增量按 |终身总输赢| × 本比例折算。重复执行会先按已记入的终身折算额扣回,再按新比例增加。',
|
||||
calculate_lifetime_btn: '一键计算终身总输赢',
|
||||
calculate_lifetime_tip: '按各会员最新每日推送中的终身总输赢与上方比例,批量更新待领取积分与领取上限。无终身总输赢数据的会员将跳过。',
|
||||
calculate_lifetime_confirm: '将按当前终身总输赢兑换比例重新折算全部会员积分,已折算部分会先扣回再按新比例增加。是否继续?',
|
||||
section_mall_open: '商城开放日',
|
||||
mall_open_date: '商城开放日',
|
||||
mall_open_date_tip: '设置后隔日生效。例如设为 8 月 1 日,则从 8 月 2 日起对业务日期为 8 月 1 日及之后的每日推送执行「昨日净输赢」转积分(T+1:开放日前一日产生的输赢在开放日次日开始折算)。',
|
||||
active_mall_open_date: '当前生效的开放日',
|
||||
section_other: '其他参数',
|
||||
unlock_ratio: '解锁比例(unlock_ratio)',
|
||||
unlock_ratio_tip: '每日推送中:今日可领取上限 = 昨日总充值 × 本比例。',
|
||||
points_to_cash_ratio: '积分折算现金比例',
|
||||
|
||||
@@ -4,9 +4,11 @@ export default {
|
||||
date: '业务日期',
|
||||
username: '用户名',
|
||||
yesterday_win_loss_net: '昨日净输赢',
|
||||
converted_points: '转换积分',
|
||||
yesterday_total_deposit: '昨日总充值',
|
||||
lifetime_total_deposit: '历史总充值',
|
||||
lifetime_total_withdraw: '历史总提现',
|
||||
lifetime_total_deposit: '终身总充值',
|
||||
lifetime_total_withdraw: '终身总提现',
|
||||
lifetime_win_loss_net: '终身总输赢',
|
||||
create_time: '创建时间',
|
||||
'quick Search Fields': 'ID',
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ export default {
|
||||
today_limit: '今日可领取上限',
|
||||
today_claimed: '今日已领取',
|
||||
today_limit_date: '今日上限日期',
|
||||
points_claim_cap: '领取积分上限',
|
||||
total_points_claimed: '累计已领取积分',
|
||||
create_time: '创建时间',
|
||||
update_time: '修改时间',
|
||||
'quick Search Fields': 'ID、playX用户ID、用户名、手机号',
|
||||
|
||||
@@ -92,9 +92,11 @@ const fieldOptions = computed(() => [
|
||||
{ value: 'date', label: t('mall.dailyPush.date') },
|
||||
{ value: 'username', label: t('mall.dailyPush.username') },
|
||||
{ value: 'yesterday_win_loss_net', label: t('mall.dailyPush.yesterday_win_loss_net') },
|
||||
{ value: 'converted_points', label: t('mall.dailyPush.converted_points') },
|
||||
{ value: 'yesterday_total_deposit', label: t('mall.dailyPush.yesterday_total_deposit') },
|
||||
{ value: 'lifetime_total_deposit', label: t('mall.dailyPush.lifetime_total_deposit') },
|
||||
{ value: 'lifetime_total_withdraw', label: t('mall.dailyPush.lifetime_total_withdraw') },
|
||||
{ value: 'lifetime_win_loss_net', label: t('mall.dailyPush.lifetime_win_loss_net') },
|
||||
{ value: 'create_time', label: t('mall.dailyPush.create_time') },
|
||||
])
|
||||
|
||||
|
||||
@@ -81,6 +81,14 @@ const baTable = new baTableClass(
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
},
|
||||
{
|
||||
label: t('mall.dailyPush.converted_points'),
|
||||
prop: 'converted_points',
|
||||
align: 'center',
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
label: t('mall.dailyPush.yesterday_total_deposit'),
|
||||
prop: 'yesterday_total_deposit',
|
||||
@@ -103,6 +111,14 @@ const baTable = new baTableClass(
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
},
|
||||
{
|
||||
label: t('mall.dailyPush.lifetime_win_loss_net'),
|
||||
prop: 'lifetime_win_loss_net',
|
||||
align: 'center',
|
||||
minWidth: 95,
|
||||
operator: 'RANGE',
|
||||
sortable: 'custom',
|
||||
},
|
||||
{
|
||||
label: t('mall.dailyPush.create_time'),
|
||||
prop: 'create_time',
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
@submit.prevent=""
|
||||
@keyup.enter="onSubmit()"
|
||||
>
|
||||
<el-divider content-position="left">{{ t('mall.playxConfig.section_daily_source') }}</el-divider>
|
||||
<el-form-item :label="t('mall.playxConfig.daily_points_enabled')" prop="daily_points_enabled">
|
||||
<el-switch v-model="form.daily_points_enabled" />
|
||||
<div class="form-tip">{{ t('mall.playxConfig.daily_points_enabled_tip') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.playxConfig.return_ratio')" prop="return_ratio">
|
||||
<el-input-number
|
||||
v-model="form.return_ratio"
|
||||
@@ -23,9 +28,57 @@
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
class="w100"
|
||||
:disabled="!form.daily_points_enabled"
|
||||
/>
|
||||
<div class="form-tip">{{ t('mall.playxConfig.return_ratio_tip') }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ t('mall.playxConfig.section_lifetime_source') }}</el-divider>
|
||||
<el-form-item :label="t('mall.playxConfig.lifetime_points_enabled')" prop="lifetime_points_enabled">
|
||||
<el-switch v-model="form.lifetime_points_enabled" />
|
||||
<div class="form-tip">{{ t('mall.playxConfig.lifetime_points_enabled_tip') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('mall.playxConfig.lifetime_points_ratio')" prop="lifetime_points_ratio">
|
||||
<el-input-number
|
||||
v-model="form.lifetime_points_ratio"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
class="w100"
|
||||
:disabled="!form.lifetime_points_enabled"
|
||||
/>
|
||||
<div class="form-tip">{{ t('mall.playxConfig.lifetime_points_ratio_tip') }}</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="warning"
|
||||
:loading="calculating"
|
||||
:disabled="!form.lifetime_points_enabled"
|
||||
@click="onCalculateLifetime()"
|
||||
>
|
||||
{{ t('mall.playxConfig.calculate_lifetime_btn') }}
|
||||
</el-button>
|
||||
<div class="form-tip">{{ t('mall.playxConfig.calculate_lifetime_tip') }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ t('mall.playxConfig.section_mall_open') }}</el-divider>
|
||||
<el-form-item :label="t('mall.playxConfig.mall_open_date')" prop="mall_open_date">
|
||||
<el-date-picker
|
||||
v-model="form.mall_open_date"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="w100"
|
||||
:placeholder="t('Please select field', { field: t('mall.playxConfig.mall_open_date') })"
|
||||
/>
|
||||
<div class="form-tip">{{ t('mall.playxConfig.mall_open_date_tip') }}</div>
|
||||
<div v-if="activeMallOpenDate" class="form-tip">
|
||||
{{ t('mall.playxConfig.active_mall_open_date') }}:{{ activeMallOpenDate }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">{{ t('mall.playxConfig.section_other') }}</el-divider>
|
||||
<el-form-item :label="t('mall.playxConfig.unlock_ratio')" prop="unlock_ratio">
|
||||
<el-input-number
|
||||
v-model="form.unlock_ratio"
|
||||
@@ -60,9 +113,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { onMounted, reactive, ref, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { index, save } from '/@/api/backend/mall/playxConfig'
|
||||
import { calculateLifetimePoints, index, save } from '/@/api/backend/mall/playxConfig'
|
||||
import MallPageIntro from '/@/components/mall/MallPageIntro.vue'
|
||||
|
||||
defineOptions({
|
||||
@@ -73,18 +127,25 @@ const { t } = useI18n()
|
||||
const formRef = useTemplateRef<FormInstance>('formRef')
|
||||
const loading = ref(true)
|
||||
const submitting = ref(false)
|
||||
const calculating = ref(false)
|
||||
const remark = ref('')
|
||||
const activeMallOpenDate = ref('')
|
||||
|
||||
const form = reactive({
|
||||
return_ratio: 0.1,
|
||||
unlock_ratio: 0.1,
|
||||
points_to_cash_ratio: 0.1,
|
||||
daily_points_enabled: true,
|
||||
lifetime_points_enabled: false,
|
||||
lifetime_points_ratio: 0.1,
|
||||
mall_open_date: '' as string | null,
|
||||
})
|
||||
|
||||
const rules: FormRules = {
|
||||
return_ratio: [{ required: true, message: t('Please input field', { field: t('mall.playxConfig.return_ratio') }) }],
|
||||
unlock_ratio: [{ required: true, message: t('Please input field', { field: t('mall.playxConfig.unlock_ratio') }) }],
|
||||
points_to_cash_ratio: [{ required: true, message: t('Please input field', { field: t('mall.playxConfig.points_to_cash_ratio') }) }],
|
||||
lifetime_points_ratio: [{ required: true, message: t('Please input field', { field: t('mall.playxConfig.lifetime_points_ratio') }) }],
|
||||
}
|
||||
|
||||
const loadData = () => {
|
||||
@@ -93,6 +154,7 @@ const loadData = () => {
|
||||
.then((res) => {
|
||||
remark.value = res.data.remark ?? ''
|
||||
const row = res.data.row ?? {}
|
||||
activeMallOpenDate.value = row.active_mall_open_date ?? ''
|
||||
if (row.return_ratio !== undefined && row.return_ratio !== null) {
|
||||
form.return_ratio = parseFloat(String(row.return_ratio))
|
||||
}
|
||||
@@ -102,6 +164,12 @@ const loadData = () => {
|
||||
if (row.points_to_cash_ratio !== undefined && row.points_to_cash_ratio !== null) {
|
||||
form.points_to_cash_ratio = parseFloat(String(row.points_to_cash_ratio))
|
||||
}
|
||||
form.daily_points_enabled = Number(row.daily_points_enabled ?? 1) === 1
|
||||
form.lifetime_points_enabled = Number(row.lifetime_points_enabled ?? 0) === 1
|
||||
if (row.lifetime_points_ratio !== undefined && row.lifetime_points_ratio !== null) {
|
||||
form.lifetime_points_ratio = parseFloat(String(row.lifetime_points_ratio))
|
||||
}
|
||||
form.mall_open_date = row.mall_open_date ? String(row.mall_open_date).slice(0, 10) : null
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
@@ -116,6 +184,10 @@ const onSubmit = () => {
|
||||
return_ratio: form.return_ratio,
|
||||
unlock_ratio: form.unlock_ratio,
|
||||
points_to_cash_ratio: form.points_to_cash_ratio,
|
||||
daily_points_enabled: form.daily_points_enabled ? 1 : 0,
|
||||
lifetime_points_enabled: form.lifetime_points_enabled ? 1 : 0,
|
||||
lifetime_points_ratio: form.lifetime_points_ratio,
|
||||
mall_open_date: form.mall_open_date || null,
|
||||
})
|
||||
.then(() => {
|
||||
loadData()
|
||||
@@ -126,6 +198,21 @@ const onSubmit = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onCalculateLifetime = () => {
|
||||
ElMessageBox.confirm(t('mall.playxConfig.calculate_lifetime_confirm'), t('Reminder'), {
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
calculating.value = true
|
||||
calculateLifetimePoints()
|
||||
.then(() => {
|
||||
loadData()
|
||||
})
|
||||
.finally(() => {
|
||||
calculating.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
@@ -39,9 +39,11 @@ const baTable = new baTableClass(
|
||||
{ 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' },
|
||||
],
|
||||
dblClickNotEditColumn: [undefined],
|
||||
|
||||
@@ -68,6 +68,8 @@ const baTable = new baTableClass(
|
||||
{ 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.today_limit_date'),
|
||||
prop: 'today_limit_date',
|
||||
|
||||
Reference in New Issue
Block a user