feat: 开户备注、账单展示优化与后台代理管理增强
- 新增初始上分备注(日常上分/开户赠金/自定义)及前后台校验与展示 - 优化钱包流水类型与备注显示,区分管理员/代理/玩家上下分 - 修复登录后语言被后端覆盖的问题,登录时同步当前语言到服务端 - 后台代理/玩家表格操作栏重构,充值订单增加备注列 - 前台个人中心、充值、账单与验证码组件体验优化 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch, h } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox, ElDatePicker } from 'element-plus';
|
||||
import { useAdminLocale } from '../../composables/useAdminLocale';
|
||||
import api from '../../api';
|
||||
import { ensureLeagueExpanded } from '../../utils/matchesListState';
|
||||
@@ -165,9 +165,79 @@ function canPublishRow(row: unknown) {
|
||||
function canCloseRow(row: unknown) {
|
||||
return matchStatus(row) === 'PUBLISHED';
|
||||
}
|
||||
function canReopenRow(row: unknown) {
|
||||
return matchStatus(row) === 'CLOSED';
|
||||
}
|
||||
function canSettleRow(row: unknown) {
|
||||
return matchStatus(row) !== 'DRAFT';
|
||||
}
|
||||
function settleButtonLabel(row: unknown) {
|
||||
return matchStatus(row) === 'SETTLED' ? t('common.resettle') : t('common.settle');
|
||||
}
|
||||
function kickoffPassed(row: unknown) {
|
||||
return new Date(String(rowOf(row).startTime)) <= new Date();
|
||||
}
|
||||
|
||||
async function promptReopenKickoff(): Promise<string | null> {
|
||||
const kickoff = ref('');
|
||||
try {
|
||||
await ElMessageBox({
|
||||
title: t('match.reopen_kickoff_title'),
|
||||
message: () =>
|
||||
h('div', { class: 'reopen-kickoff-prompt' }, [
|
||||
h(
|
||||
'p',
|
||||
{
|
||||
style: 'margin: 0 0 12px; font-size: 13px; color: var(--el-text-color-secondary)',
|
||||
},
|
||||
t('match.reopen_kickoff_hint'),
|
||||
),
|
||||
h(ElDatePicker, {
|
||||
modelValue: kickoff.value,
|
||||
'onUpdate:modelValue': (v: string) => {
|
||||
kickoff.value = v;
|
||||
},
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DDTHH:mm:ss',
|
||||
style: 'width: 100%',
|
||||
}),
|
||||
]),
|
||||
showCancelButton: true,
|
||||
confirmButtonText: t('common.confirm'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
beforeClose: (action, _instance, done) => {
|
||||
if (action === 'confirm') {
|
||||
if (!kickoff.value || new Date(kickoff.value) <= new Date()) {
|
||||
ElMessage.warning(t('match.reopen_kickoff_invalid'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
done();
|
||||
},
|
||||
});
|
||||
return kickoff.value || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function reopenRow(row: unknown) {
|
||||
const id = matchId(row);
|
||||
let startTime: string | undefined;
|
||||
if (kickoffPassed(row)) {
|
||||
const picked = await promptReopenKickoff();
|
||||
if (!picked) return;
|
||||
startTime = picked;
|
||||
}
|
||||
try {
|
||||
await api.post(`/admin/matches/${id}/reopen`, startTime ? { startTime } : {});
|
||||
ElMessage.success(t('msg.reopened'));
|
||||
notifyParent();
|
||||
} catch (e) {
|
||||
const err = e as { response?: { data?: { error?: string } } };
|
||||
ElMessage.error(err.response?.data?.error ?? t('msg.save_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(row: unknown) {
|
||||
const id = matchId(row);
|
||||
@@ -271,13 +341,22 @@ defineExpose({ reload: load });
|
||||
>
|
||||
{{ t('common.close_betting') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="success"
|
||||
plain
|
||||
:disabled="!canReopenRow(row)"
|
||||
@click="reopenRow(row)"
|
||||
>
|
||||
{{ t('common.reopen_betting') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="!canSettleRow(row)"
|
||||
@click="settle(matchId(row))"
|
||||
>
|
||||
{{ t('common.settle') }}
|
||||
{{ settleButtonLabel(row) }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button
|
||||
|
||||
Reference in New Issue
Block a user