feat: expand superadmin edits and minio docs
This commit is contained in:
@@ -39,6 +39,23 @@ export const CreatorService = {
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async getCreatorSettings(tenantID) {
|
||||
if (!tenantID) throw new Error('tenantID is required');
|
||||
return requestJson(`/super/v1/creators/${tenantID}/settings`);
|
||||
},
|
||||
async updateCreatorSettings(tenantID, { name, bio, avatar, cover, description } = {}) {
|
||||
if (!tenantID) throw new Error('tenantID is required');
|
||||
return requestJson(`/super/v1/creators/${tenantID}/settings`, {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
name,
|
||||
bio,
|
||||
avatar,
|
||||
cover,
|
||||
description
|
||||
}
|
||||
});
|
||||
},
|
||||
async listJoinRequests({ page, limit, tenant_id, tenant_code, tenant_name, user_id, username, status, created_at_from, created_at_to } = {}) {
|
||||
const iso = (d) => {
|
||||
if (!d) return undefined;
|
||||
@@ -140,6 +157,31 @@ export const CreatorService = {
|
||||
items: normalizeItems(data?.items)
|
||||
};
|
||||
},
|
||||
async createPayoutAccount(tenantID, { user_id, type, name, account, realname } = {}) {
|
||||
if (!tenantID) throw new Error('tenantID is required');
|
||||
return requestJson(`/super/v1/creators/${tenantID}/payout-accounts`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
user_id,
|
||||
type,
|
||||
name,
|
||||
account,
|
||||
realname
|
||||
}
|
||||
});
|
||||
},
|
||||
async updatePayoutAccount(accountID, { type, name, account, realname } = {}) {
|
||||
if (!accountID) throw new Error('accountID is required');
|
||||
return requestJson(`/super/v1/payout-accounts/${accountID}`, {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
type,
|
||||
name,
|
||||
account,
|
||||
realname
|
||||
}
|
||||
});
|
||||
},
|
||||
async removePayoutAccount(accountID) {
|
||||
if (!accountID) throw new Error('accountID is required');
|
||||
return requestJson(`/super/v1/payout-accounts/${accountID}`, { method: 'DELETE' });
|
||||
|
||||
@@ -98,5 +98,19 @@ export const NotificationService = {
|
||||
is_active
|
||||
}
|
||||
});
|
||||
},
|
||||
async updateTemplate({ id, tenant_id, name, type, title, content, is_active } = {}) {
|
||||
if (!id) throw new Error('template id is required');
|
||||
return requestJson(`/super/v1/notifications/templates/${id}`, {
|
||||
method: 'PATCH',
|
||||
body: {
|
||||
tenant_id,
|
||||
name,
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
is_active
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,6 +68,20 @@ export const UserService = {
|
||||
if (!Array.isArray(roles) || roles.length === 0) throw new Error('roles is required');
|
||||
return requestJson(`/super/v1/users/${userID}/roles`, { method: 'PATCH', body: { roles } });
|
||||
},
|
||||
async updateUserProfile({ userID, nickname, avatar, gender, bio, is_real_name_verified, real_name, id_card } = {}) {
|
||||
if (!userID) throw new Error('userID is required');
|
||||
|
||||
const body = {};
|
||||
if (nickname !== undefined) body.nickname = nickname;
|
||||
if (avatar !== undefined) body.avatar = avatar;
|
||||
if (gender !== undefined) body.gender = gender;
|
||||
if (bio !== undefined) body.bio = bio;
|
||||
if (is_real_name_verified !== undefined) body.is_real_name_verified = is_real_name_verified;
|
||||
if (real_name !== undefined) body.real_name = real_name;
|
||||
if (id_card !== undefined) body.id_card = id_card;
|
||||
|
||||
return requestJson(`/super/v1/users/${userID}`, { method: 'PATCH', body });
|
||||
},
|
||||
async getUserStatistics() {
|
||||
try {
|
||||
const data = await requestJson('/super/v1/users/statistics');
|
||||
|
||||
@@ -34,6 +34,16 @@ const statusUpdating = ref(false);
|
||||
const statusTenant = ref(null);
|
||||
const statusValue = ref(null);
|
||||
|
||||
const settingsDialogVisible = ref(false);
|
||||
const settingsLoading = ref(false);
|
||||
const settingsSubmitting = ref(false);
|
||||
const settingsTenant = ref(null);
|
||||
const settingsName = ref('');
|
||||
const settingsBio = ref('');
|
||||
const settingsAvatar = ref('');
|
||||
const settingsCover = ref('');
|
||||
const settingsDescription = ref('');
|
||||
|
||||
const applicationStatusOptions = computed(() => [{ label: '全部', value: '' }, ...(statusOptions.value || [])]);
|
||||
|
||||
const joinRequests = ref([]);
|
||||
@@ -92,6 +102,11 @@ const payoutStatusOptions = [
|
||||
{ label: '已驳回', value: 'rejected' }
|
||||
];
|
||||
|
||||
const payoutTypeOptions = [
|
||||
{ label: 'bank', value: 'bank' },
|
||||
{ label: 'alipay', value: 'alipay' }
|
||||
];
|
||||
|
||||
const payoutReviewOptions = [
|
||||
{ label: '通过', value: 'approve' },
|
||||
{ label: '驳回', value: 'reject' }
|
||||
@@ -119,6 +134,17 @@ const payoutReviewAction = ref('approve');
|
||||
const payoutReviewReason = ref('');
|
||||
const payoutReviewTarget = ref(null);
|
||||
|
||||
const payoutDialogVisible = ref(false);
|
||||
const payoutDialogMode = ref('create');
|
||||
const payoutDialogSubmitting = ref(false);
|
||||
const payoutDialogEditingID = ref(null);
|
||||
const payoutFormTenantID = ref(null);
|
||||
const payoutFormUserID = ref(null);
|
||||
const payoutFormType = ref('bank');
|
||||
const payoutFormName = ref('');
|
||||
const payoutFormAccount = ref('');
|
||||
const payoutFormRealname = ref('');
|
||||
|
||||
const inviteDialogVisible = ref(false);
|
||||
const inviteSubmitting = ref(false);
|
||||
const inviteTenantID = ref(null);
|
||||
@@ -420,6 +446,54 @@ async function confirmUpdateStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openSettingsDialog(row) {
|
||||
const tenantIDValue = Number(row?.id ?? row?.tenant_id ?? 0);
|
||||
if (!tenantIDValue) return;
|
||||
settingsTenant.value = row;
|
||||
settingsDialogVisible.value = true;
|
||||
settingsLoading.value = true;
|
||||
try {
|
||||
const data = await CreatorService.getCreatorSettings(tenantIDValue);
|
||||
settingsName.value = data?.name ?? '';
|
||||
settingsBio.value = data?.bio ?? '';
|
||||
settingsAvatar.value = data?.avatar ?? '';
|
||||
settingsCover.value = data?.cover ?? '';
|
||||
settingsDescription.value = data?.description ?? '';
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: error?.message || '无法加载创作者设置', life: 4000 });
|
||||
} finally {
|
||||
settingsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmUpdateSettings() {
|
||||
const tenantIDValue = Number(settingsTenant.value?.id ?? settingsTenant.value?.tenant_id ?? 0);
|
||||
if (!tenantIDValue) return;
|
||||
const name = settingsName.value.trim();
|
||||
if (!name) {
|
||||
toast.add({ severity: 'warn', summary: '提示', detail: '名称不能为空', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
settingsSubmitting.value = true;
|
||||
try {
|
||||
await CreatorService.updateCreatorSettings(tenantIDValue, {
|
||||
name,
|
||||
bio: settingsBio.value,
|
||||
avatar: settingsAvatar.value,
|
||||
cover: settingsCover.value,
|
||||
description: settingsDescription.value
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '更新成功', detail: `TenantID: ${tenantIDValue}`, life: 3000 });
|
||||
settingsDialogVisible.value = false;
|
||||
await loadCreators();
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '更新失败', detail: error?.message || '无法更新创作者设置', life: 4000 });
|
||||
} finally {
|
||||
settingsSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openReviewDialog(row, action) {
|
||||
reviewTarget.value = row;
|
||||
reviewAction.value = action || 'approve';
|
||||
@@ -529,6 +603,77 @@ async function confirmPayoutReview() {
|
||||
}
|
||||
}
|
||||
|
||||
function openPayoutDialog(mode, row) {
|
||||
payoutDialogMode.value = mode;
|
||||
if (mode === 'edit' && row) {
|
||||
payoutDialogEditingID.value = row.id;
|
||||
payoutFormTenantID.value = row.tenant_id ?? null;
|
||||
payoutFormUserID.value = row.user_id ?? null;
|
||||
payoutFormType.value = row.type || 'bank';
|
||||
payoutFormName.value = row.name || '';
|
||||
payoutFormAccount.value = row.account || '';
|
||||
payoutFormRealname.value = row.realname || '';
|
||||
} else {
|
||||
payoutDialogEditingID.value = null;
|
||||
payoutFormTenantID.value = null;
|
||||
payoutFormUserID.value = null;
|
||||
payoutFormType.value = 'bank';
|
||||
payoutFormName.value = '';
|
||||
payoutFormAccount.value = '';
|
||||
payoutFormRealname.value = '';
|
||||
}
|
||||
payoutDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmPayoutDialog() {
|
||||
const tenantIDValue = Number(payoutFormTenantID.value ?? 0);
|
||||
const userIDValue = Number(payoutFormUserID.value ?? 0);
|
||||
const name = payoutFormName.value.trim();
|
||||
const account = payoutFormAccount.value.trim();
|
||||
const realname = payoutFormRealname.value.trim();
|
||||
|
||||
if (!name || !account || !realname) {
|
||||
toast.add({ severity: 'warn', summary: '提示', detail: '请完善账户信息', life: 3000 });
|
||||
return;
|
||||
}
|
||||
if (payoutDialogMode.value === 'create' && (!tenantIDValue || !userIDValue)) {
|
||||
toast.add({ severity: 'warn', summary: '提示', detail: 'TenantID 与 UserID 不能为空', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
payoutDialogSubmitting.value = true;
|
||||
try {
|
||||
if (payoutDialogMode.value === 'edit') {
|
||||
const accountID = payoutDialogEditingID.value;
|
||||
if (!accountID) return;
|
||||
await CreatorService.updatePayoutAccount(accountID, {
|
||||
type: payoutFormType.value,
|
||||
name,
|
||||
account,
|
||||
realname
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '更新成功', detail: `账户ID: ${accountID}`, life: 3000 });
|
||||
} else {
|
||||
await CreatorService.createPayoutAccount(tenantIDValue, {
|
||||
user_id: userIDValue,
|
||||
type: payoutFormType.value,
|
||||
name,
|
||||
account,
|
||||
realname
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '已创建', detail: '结算账户已创建', life: 3000 });
|
||||
}
|
||||
payoutDialogVisible.value = false;
|
||||
await loadPayoutAccounts();
|
||||
} catch (error) {
|
||||
const summary = payoutDialogMode.value === 'edit' ? '更新失败' : '创建失败';
|
||||
const detail = payoutDialogMode.value === 'edit' ? '无法更新结算账户' : '无法创建结算账户';
|
||||
toast.add({ severity: 'error', summary, detail: error?.message || detail, life: 4000 });
|
||||
} finally {
|
||||
payoutDialogSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openInviteDialog(row) {
|
||||
inviteTenantID.value = Number(row?.tenant_id ?? row?.tenant?.id ?? 0) || null;
|
||||
inviteMaxUses.value = 1;
|
||||
@@ -663,6 +808,11 @@ onMounted(() => {
|
||||
{{ formatDate(data.created_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<Button label="设置" icon="pi pi-cog" text size="small" class="p-0" @click="openSettingsDialog(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</TabPanel>
|
||||
<TabPanel value="applications">
|
||||
@@ -863,6 +1013,7 @@ onMounted(() => {
|
||||
<h4 class="m-0">结算账户</h4>
|
||||
<span class="text-muted-color">跨租户结算账户审查</span>
|
||||
</div>
|
||||
<Button label="新增账户" icon="pi pi-plus" @click="openPayoutDialog('create')" />
|
||||
</div>
|
||||
|
||||
<SearchPanel :loading="payoutAccountsLoading" @search="onPayoutSearch" @reset="onPayoutReset">
|
||||
@@ -956,6 +1107,7 @@ onMounted(() => {
|
||||
<Column header="操作" style="min-width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Button label="编辑" icon="pi pi-pencil" text size="small" class="p-0 justify-start" @click="openPayoutDialog('edit', data)" />
|
||||
<Button v-if="data?.status === 'pending'" label="通过" icon="pi pi-check" text size="small" class="p-0 justify-start" @click="openPayoutReviewDialog(data, 'approve')" />
|
||||
<Button v-if="data?.status === 'pending'" label="驳回" icon="pi pi-times" severity="danger" text size="small" class="p-0 justify-start" @click="openPayoutReviewDialog(data, 'reject')" />
|
||||
<Button label="删除" icon="pi pi-trash" severity="danger" text size="small" class="p-0 justify-start" @click="openPayoutRemoveDialog(data)" />
|
||||
@@ -968,6 +1120,44 @@ onMounted(() => {
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="settingsDialogVisible" :modal="true" :style="{ width: '620px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">创作者设置</span>
|
||||
<span class="text-muted-color truncate max-w-[240px]">{{ settingsTenant?.name ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="settingsLoading" class="flex items-center justify-center py-10">
|
||||
<ProgressSpinner style="width: 36px; height: 36px" strokeWidth="6" />
|
||||
</div>
|
||||
<div v-else class="grid gap-4">
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">名称</label>
|
||||
<InputText v-model="settingsName" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">头像 URL</label>
|
||||
<InputText v-model="settingsAvatar" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">封面 URL</label>
|
||||
<InputText v-model="settingsCover" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">简介</label>
|
||||
<Textarea v-model="settingsBio" rows="2" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">描述</label>
|
||||
<Textarea v-model="settingsDescription" rows="3" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="settingsDialogVisible = false" :disabled="settingsSubmitting" />
|
||||
<Button label="确认" icon="pi pi-check" @click="confirmUpdateSettings" :loading="settingsSubmitting" :disabled="settingsSubmitting || settingsLoading" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="statusDialogVisible" :modal="true" :style="{ width: '420px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -1060,6 +1250,48 @@ onMounted(() => {
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="payoutDialogVisible" :modal="true" :style="{ width: '560px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">{{ payoutDialogMode === 'edit' ? '编辑结算账户' : '新增结算账户' }}</span>
|
||||
<span v-if="payoutDialogMode === 'edit'" class="text-muted-color">账户ID: {{ payoutDialogEditingID ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">TenantID</label>
|
||||
<InputNumber v-model="payoutFormTenantID" :min="1" placeholder="必填" class="w-full" :disabled="payoutDialogMode === 'edit'" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">UserID</label>
|
||||
<InputNumber v-model="payoutFormUserID" :min="1" placeholder="必填" class="w-full" :disabled="payoutDialogMode === 'edit'" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">类型</label>
|
||||
<Select v-model="payoutFormType" :options="payoutTypeOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">账户名称</label>
|
||||
<InputText v-model="payoutFormName" placeholder="开户行/账户名称" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">账号</label>
|
||||
<InputText v-model="payoutFormAccount" placeholder="收款账号" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">收款人</label>
|
||||
<InputText v-model="payoutFormRealname" placeholder="收款人姓名" class="w-full" />
|
||||
</div>
|
||||
<div v-if="payoutDialogMode === 'edit'" class="text-xs text-muted-color">提示:更新已审核账户将重置为待审核状态。</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="payoutDialogVisible = false" :disabled="payoutDialogSubmitting" />
|
||||
<Button label="确认" icon="pi pi-check" @click="confirmPayoutDialog" :loading="payoutDialogSubmitting" :disabled="payoutDialogSubmitting" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="payoutRemoveDialogVisible" :modal="true" :style="{ width: '420px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
@@ -69,6 +69,8 @@ const templateCreatedAtFrom = ref(null);
|
||||
const templateCreatedAtTo = ref(null);
|
||||
|
||||
const templateDialogVisible = ref(false);
|
||||
const templateDialogMode = ref('create');
|
||||
const templateEditingID = ref(null);
|
||||
const templateSubmitting = ref(false);
|
||||
const createTenantID = ref(null);
|
||||
const createName = ref('');
|
||||
@@ -278,6 +280,8 @@ function onTemplateSort(event) {
|
||||
}
|
||||
|
||||
function openTemplateDialog() {
|
||||
templateDialogMode.value = 'create';
|
||||
templateEditingID.value = null;
|
||||
createTenantID.value = null;
|
||||
createName.value = '';
|
||||
createType.value = 'system';
|
||||
@@ -287,7 +291,20 @@ function openTemplateDialog() {
|
||||
templateDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmCreateTemplate() {
|
||||
function openTemplateEditDialog(row) {
|
||||
if (!row) return;
|
||||
templateDialogMode.value = 'edit';
|
||||
templateEditingID.value = row.id;
|
||||
createTenantID.value = row.tenant_id > 0 ? row.tenant_id : null;
|
||||
createName.value = row.name || '';
|
||||
createType.value = row.type || 'system';
|
||||
createTitle.value = row.title || '';
|
||||
createContent.value = row.content || '';
|
||||
createIsActive.value = row.is_active ?? true;
|
||||
templateDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmTemplate() {
|
||||
const name = createName.value.trim();
|
||||
const title = createTitle.value.trim();
|
||||
const content = createContent.value.trim();
|
||||
@@ -295,19 +312,29 @@ async function confirmCreateTemplate() {
|
||||
|
||||
templateSubmitting.value = true;
|
||||
try {
|
||||
await NotificationService.createTemplate({
|
||||
const payload = {
|
||||
tenant_id: createTenantID.value || undefined,
|
||||
name,
|
||||
type: createType.value,
|
||||
title,
|
||||
content,
|
||||
is_active: createIsActive.value
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '已创建', detail: '模板已创建', life: 3000 });
|
||||
};
|
||||
if (templateDialogMode.value === 'edit') {
|
||||
const templateID = templateEditingID.value;
|
||||
if (!templateID) return;
|
||||
await NotificationService.updateTemplate({ id: templateID, ...payload });
|
||||
toast.add({ severity: 'success', summary: '已更新', detail: `模板ID: ${templateID}`, life: 3000 });
|
||||
} else {
|
||||
await NotificationService.createTemplate(payload);
|
||||
toast.add({ severity: 'success', summary: '已创建', detail: '模板已创建', life: 3000 });
|
||||
}
|
||||
templateDialogVisible.value = false;
|
||||
loadTemplates();
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '创建失败', detail: error?.message || '无法创建模板', life: 4000 });
|
||||
const label = templateDialogMode.value === 'edit' ? '更新失败' : '创建失败';
|
||||
const detail = templateDialogMode.value === 'edit' ? '无法更新模板' : '无法创建模板';
|
||||
toast.add({ severity: 'error', summary: label, detail: error?.message || detail, life: 4000 });
|
||||
} finally {
|
||||
templateSubmitting.value = false;
|
||||
}
|
||||
@@ -483,6 +510,11 @@ loadTemplates();
|
||||
{{ formatDate(data.updated_at) }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<Button label="编辑" icon="pi pi-pencil" text size="small" class="p-0" @click="openTemplateEditDialog(data)" />
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</TabPanel>
|
||||
@@ -526,7 +558,7 @@ loadTemplates();
|
||||
|
||||
<Dialog v-model:visible="templateDialogVisible" modal :style="{ width: '560px' }">
|
||||
<template #header>
|
||||
<span class="font-medium">创建模板</span>
|
||||
<span class="font-medium">{{ templateDialogMode === 'edit' ? '编辑模板' : '创建模板' }}</span>
|
||||
</template>
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@@ -558,7 +590,13 @@ loadTemplates();
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="templateDialogVisible = false" />
|
||||
<Button label="创建" icon="pi pi-check" :loading="templateSubmitting" :disabled="templateSubmitting || !createName.trim() || !createTitle.trim() || !createContent.trim()" @click="confirmCreateTemplate" />
|
||||
<Button
|
||||
:label="templateDialogMode === 'edit' ? '更新' : '创建'"
|
||||
icon="pi pi-check"
|
||||
:loading="templateSubmitting"
|
||||
:disabled="templateSubmitting || !createName.trim() || !createTitle.trim() || !createContent.trim()"
|
||||
@click="confirmTemplate"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
@@ -83,6 +83,12 @@ const orderStatusOptions = [
|
||||
{ label: 'failed', value: 'failed' }
|
||||
];
|
||||
|
||||
const genderOptions = [
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' },
|
||||
{ label: '保密', value: 'secret' }
|
||||
];
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return '-';
|
||||
if (String(value).startsWith('0001-01-01')) return '-';
|
||||
@@ -116,6 +122,19 @@ function formatOrderType(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatGender(value) {
|
||||
switch (value) {
|
||||
case 'male':
|
||||
return '男';
|
||||
case 'female':
|
||||
return '女';
|
||||
case 'secret':
|
||||
return '保密';
|
||||
default:
|
||||
return value || '-';
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusSeverity(status) {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
@@ -387,6 +406,16 @@ async function loadRechargeOrders() {
|
||||
}
|
||||
}
|
||||
|
||||
const profileDialogVisible = ref(false);
|
||||
const profileSubmitting = ref(false);
|
||||
const profileNickname = ref('');
|
||||
const profileAvatar = ref('');
|
||||
const profileGender = ref('secret');
|
||||
const profileBio = ref('');
|
||||
const profileIsVerified = ref(false);
|
||||
const profileRealName = ref('');
|
||||
const profileIDCard = ref('');
|
||||
|
||||
const statusDialogVisible = ref(false);
|
||||
const statusLoading = ref(false);
|
||||
const statusOptionsLoading = ref(false);
|
||||
@@ -457,6 +486,53 @@ async function confirmUpdateRoles() {
|
||||
}
|
||||
}
|
||||
|
||||
async function openProfileDialog() {
|
||||
profileNickname.value = user.value?.nickname ?? '';
|
||||
profileAvatar.value = user.value?.avatar ?? '';
|
||||
profileGender.value = user.value?.gender || 'secret';
|
||||
profileBio.value = user.value?.bio ?? '';
|
||||
profileIsVerified.value = !!user.value?.is_real_name_verified;
|
||||
|
||||
if (!realName.value && !realNameLoading.value) {
|
||||
await loadRealName();
|
||||
}
|
||||
profileRealName.value = realName.value?.real_name ?? '';
|
||||
profileIDCard.value = '';
|
||||
profileDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmUpdateProfile() {
|
||||
const id = userID.value;
|
||||
if (!id) return;
|
||||
|
||||
const payload = {
|
||||
userID: id,
|
||||
nickname: profileNickname.value?.trim() ?? '',
|
||||
avatar: profileAvatar.value?.trim() ?? '',
|
||||
gender: profileGender.value,
|
||||
bio: profileBio.value?.trim() ?? '',
|
||||
is_real_name_verified: profileIsVerified.value
|
||||
};
|
||||
|
||||
const realNameValue = profileRealName.value.trim();
|
||||
if (realNameValue) payload.real_name = realNameValue;
|
||||
const idCardValue = profileIDCard.value.trim();
|
||||
if (idCardValue) payload.id_card = idCardValue;
|
||||
|
||||
profileSubmitting.value = true;
|
||||
try {
|
||||
await UserService.updateUserProfile(payload);
|
||||
toast.add({ severity: 'success', summary: '更新成功', detail: `用户ID: ${id}`, life: 3000 });
|
||||
profileDialogVisible.value = false;
|
||||
await loadUser();
|
||||
await loadRealName();
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '更新失败', detail: error?.message || '无法更新用户资料', life: 4000 });
|
||||
} finally {
|
||||
profileSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const ownedTenantsLoading = ref(false);
|
||||
const ownedTenants = ref([]);
|
||||
const ownedTenantsTotal = ref(0);
|
||||
@@ -748,6 +824,7 @@ onMounted(() => {
|
||||
<span class="text-muted-color">UserID: {{ userID || '-' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button label="资料" icon="pi pi-user-edit" severity="secondary" @click="openProfileDialog" :disabled="loading" />
|
||||
<Button label="角色" icon="pi pi-user-edit" severity="secondary" @click="openRolesDialog" :disabled="loading" />
|
||||
<Button label="状态" icon="pi pi-tag" @click="openStatusDialog" :disabled="loading" />
|
||||
</div>
|
||||
@@ -766,6 +843,22 @@ onMounted(() => {
|
||||
<div class="text-sm text-muted-color">状态</div>
|
||||
<Tag :value="user?.status_description || user?.status || '-'" :severity="getStatusSeverity(user?.status)" />
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<div class="text-sm text-muted-color">昵称</div>
|
||||
<div class="font-medium">{{ user?.nickname || '-' }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<div class="text-sm text-muted-color">性别</div>
|
||||
<div class="font-medium">{{ formatGender(user?.gender) }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<div class="text-sm text-muted-color">头像</div>
|
||||
<div class="font-medium break-all">{{ user?.avatar || '-' }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<div class="text-sm text-muted-color">简介</div>
|
||||
<div class="font-medium">{{ user?.bio || '-' }}</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4">
|
||||
<div class="text-sm text-muted-color">余额</div>
|
||||
<div class="font-medium">{{ formatCny(user?.balance) }}</div>
|
||||
@@ -1581,6 +1674,54 @@ onMounted(() => {
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Dialog v-model:visible="profileDialogVisible" :modal="true" :style="{ width: '560px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">编辑用户资料</span>
|
||||
<span class="text-muted-color truncate max-w-[240px]">{{ user?.username ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid gap-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">昵称</label>
|
||||
<InputText v-model="profileNickname" placeholder="可为空" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">性别</label>
|
||||
<Select v-model="profileGender" :options="genderOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">头像 URL</label>
|
||||
<InputText v-model="profileAvatar" placeholder="可为空" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">个人简介</label>
|
||||
<Textarea v-model="profileBio" rows="3" placeholder="可为空" class="w-full" />
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<Checkbox v-model="profileIsVerified" binary inputId="profileRealNameVerified" />
|
||||
<label for="profileRealNameVerified">已实名认证</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">真实姓名</label>
|
||||
<InputText v-model="profileRealName" placeholder="留空则不修改" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-sm text-muted-color mb-2 block">身份证号</label>
|
||||
<InputText v-model="profileIDCard" placeholder="留空则不修改" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-muted-color">提示:更新实名认证信息将同步写入用户元数据。</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="profileDialogVisible = false" :disabled="profileSubmitting" />
|
||||
<Button label="确认" icon="pi pi-check" @click="confirmUpdateProfile" :loading="profileSubmitting" :disabled="profileSubmitting" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="statusDialogVisible" :modal="true" :style="{ width: '420px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user