feat(admin): 从已有玩家升级代理、修复 i18n 与过期 .js 冲突

- 新建一级代理改为选择已有玩家;新建用户可选一级代理

- 操作日志/注单等扁平 key 翻译;清理 src 内误生成 .js,Vite 优先解析 .ts

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-03 15:42:15 +08:00
parent cbfa18d1d3
commit 3b739982a1
27 changed files with 625 additions and 165 deletions

View File

@@ -11,11 +11,13 @@ import {
emptyAgentEditForm,
editFormFromAgentDetail,
buildCreateAgentPayload,
applyPromotableUserToForm,
type AgentRow,
type AgentDetail,
type AgentCreateForm,
type AgentEditForm,
} from './agent-form';
type PromotableUserOption,
} from './agent-form.ts';
import {
formatAmount,
formatAmountFull,
@@ -45,6 +47,9 @@ const editLoading = ref(false);
const creditLoading = ref(false);
const createForm = ref<AgentCreateForm>(emptyAgentCreateForm());
const promotableUsers = ref<PromotableUserOption[]>([]);
const promotableLoading = ref(false);
const promotableKeyword = ref('');
const editForm = ref<AgentEditForm>(emptyAgentEditForm());
const detail = ref<AgentDetail | null>(null);
const editingId = ref('');
@@ -76,9 +81,38 @@ function onSizeChange(size: number) {
load();
}
async function loadPromotableUsers() {
promotableLoading.value = true;
try {
const { data } = await api.get('/admin/users/promotable-for-agent', {
params: { keyword: promotableKeyword.value.trim() || undefined },
});
promotableUsers.value = data.data as PromotableUserOption[];
} finally {
promotableLoading.value = false;
}
}
function openCreate() {
createForm.value = emptyAgentCreateForm();
promotableKeyword.value = '';
createVisible.value = true;
loadPromotableUsers();
}
function onPromotableSearch(q: string) {
promotableKeyword.value = q;
void loadPromotableUsers();
}
function onPromotableUserChange(userId: string) {
const user = promotableUsers.value.find((u) => u.id === userId);
if (user) applyPromotableUserToForm(createForm.value, user);
}
function formatPromotableLabel(u: PromotableUserOption) {
const parent = u.parentUsername ? ` · ${u.parentUsername}` : '';
return `${u.username} (#${u.id})${parent}`;
}
async function openDetail(userId: string) {
@@ -270,14 +304,25 @@ function creditTypeLabel(type: string) {
<el-dialog v-model="createVisible" :title="t('agent.dialog.create')" width="520px" destroy-on-close>
<el-form label-width="100px">
<el-form-item :label="t('user.col.username')" required>
<el-input v-model="createForm.username" :placeholder="t('user.ph.username_unique')" />
</el-form-item>
<el-form-item :label="t('user.field.password')" required>
<el-input v-model="createForm.password" type="text" autocomplete="off" />
</el-form-item>
<el-form-item :label="t('user.field.confirm_password')" required>
<el-input v-model="createForm.confirmPassword" type="text" autocomplete="off" />
<el-form-item :label="t('agent.field.select_user')" required>
<el-select
v-model="createForm.userId"
filterable
remote
:remote-method="onPromotableSearch"
:loading="promotableLoading"
:placeholder="t('agent.ph.select_user')"
style="width: 100%"
@change="onPromotableUserChange"
>
<el-option
v-for="u in promotableUsers"
:key="u.id"
:label="formatPromotableLabel(u)"
:value="u.id"
/>
</el-select>
<div class="field-hint">{{ t('agent.hint.select_user') }}</div>
</el-form-item>
<el-form-item :label="t('agent.field.credit_limit')" required>
<el-input-number