feat: add payout account review flow
This commit is contained in:
@@ -110,7 +110,7 @@ export const CreatorService = {
|
||||
body: { action, reason }
|
||||
});
|
||||
},
|
||||
async listPayoutAccounts({ page, limit, tenant_id, tenant_code, tenant_name, user_id, username, type, created_at_from, created_at_to } = {}) {
|
||||
async listPayoutAccounts({ page, limit, tenant_id, tenant_code, tenant_name, user_id, username, type, status, created_at_from, created_at_to } = {}) {
|
||||
const iso = (d) => {
|
||||
if (!d) return undefined;
|
||||
const date = d instanceof Date ? d : new Date(d);
|
||||
@@ -127,6 +127,7 @@ export const CreatorService = {
|
||||
user_id,
|
||||
username,
|
||||
type,
|
||||
status,
|
||||
created_at_from: iso(created_at_from),
|
||||
created_at_to: iso(created_at_to)
|
||||
};
|
||||
@@ -143,6 +144,16 @@ export const CreatorService = {
|
||||
if (!accountID) throw new Error('accountID is required');
|
||||
return requestJson(`/super/v1/payout-accounts/${accountID}`, { method: 'DELETE' });
|
||||
},
|
||||
async reviewPayoutAccount(accountID, { action, reason } = {}) {
|
||||
if (!accountID) throw new Error('accountID is required');
|
||||
return requestJson(`/super/v1/payout-accounts/${accountID}/review`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action,
|
||||
reason
|
||||
}
|
||||
});
|
||||
},
|
||||
async createInvite(tenantID, { max_uses, expires_at, remark } = {}) {
|
||||
if (!tenantID) throw new Error('tenantID is required');
|
||||
return requestJson(`/super/v1/tenants/${tenantID}/invites`, {
|
||||
|
||||
@@ -74,6 +74,7 @@ const payoutTenantName = ref('');
|
||||
const payoutUserID = ref(null);
|
||||
const payoutUsername = ref('');
|
||||
const payoutType = ref('');
|
||||
const payoutStatus = ref('');
|
||||
const payoutCreatedAtFrom = ref(null);
|
||||
const payoutCreatedAtTo = ref(null);
|
||||
|
||||
@@ -84,6 +85,18 @@ const joinStatusOptions = [
|
||||
{ label: '已驳回', value: 'rejected' }
|
||||
];
|
||||
|
||||
const payoutStatusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '待审核', value: 'pending' },
|
||||
{ label: '已通过', value: 'approved' },
|
||||
{ label: '已驳回', value: 'rejected' }
|
||||
];
|
||||
|
||||
const payoutReviewOptions = [
|
||||
{ label: '通过', value: 'approve' },
|
||||
{ label: '驳回', value: 'reject' }
|
||||
];
|
||||
|
||||
const reviewDialogVisible = ref(false);
|
||||
const reviewSubmitting = ref(false);
|
||||
const reviewAction = ref('approve');
|
||||
@@ -100,6 +113,12 @@ const payoutRemoveDialogVisible = ref(false);
|
||||
const payoutRemoveSubmitting = ref(false);
|
||||
const payoutRemoveTarget = ref(null);
|
||||
|
||||
const payoutReviewDialogVisible = ref(false);
|
||||
const payoutReviewSubmitting = ref(false);
|
||||
const payoutReviewAction = ref('approve');
|
||||
const payoutReviewReason = ref('');
|
||||
const payoutReviewTarget = ref(null);
|
||||
|
||||
const inviteDialogVisible = ref(false);
|
||||
const inviteSubmitting = ref(false);
|
||||
const inviteTenantID = ref(null);
|
||||
@@ -140,6 +159,19 @@ function getJoinStatusSeverity(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPayoutStatusSeverity(value) {
|
||||
switch (value) {
|
||||
case 'pending':
|
||||
return 'warn';
|
||||
case 'approved':
|
||||
return 'success';
|
||||
case 'rejected':
|
||||
return 'danger';
|
||||
default:
|
||||
return 'secondary';
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureStatusOptionsLoaded() {
|
||||
if (statusOptions.value.length > 0) return;
|
||||
statusOptionsLoading.value = true;
|
||||
@@ -240,6 +272,7 @@ async function loadPayoutAccounts() {
|
||||
user_id: payoutUserID.value || undefined,
|
||||
username: payoutUsername.value,
|
||||
type: payoutType.value || undefined,
|
||||
status: payoutStatus.value || undefined,
|
||||
created_at_from: payoutCreatedAtFrom.value || undefined,
|
||||
created_at_to: payoutCreatedAtTo.value || undefined
|
||||
});
|
||||
@@ -345,6 +378,7 @@ function onPayoutReset() {
|
||||
payoutUserID.value = null;
|
||||
payoutUsername.value = '';
|
||||
payoutType.value = '';
|
||||
payoutStatus.value = '';
|
||||
payoutCreatedAtFrom.value = null;
|
||||
payoutCreatedAtTo.value = null;
|
||||
payoutAccountsPage.value = 1;
|
||||
@@ -466,6 +500,35 @@ async function confirmRemovePayoutAccount() {
|
||||
}
|
||||
}
|
||||
|
||||
function openPayoutReviewDialog(row, action) {
|
||||
payoutReviewTarget.value = row;
|
||||
payoutReviewAction.value = action || 'approve';
|
||||
payoutReviewReason.value = '';
|
||||
payoutReviewDialogVisible.value = true;
|
||||
}
|
||||
|
||||
async function confirmPayoutReview() {
|
||||
const targetID = payoutReviewTarget.value?.id;
|
||||
if (!targetID) return;
|
||||
const reason = payoutReviewReason.value.trim();
|
||||
if (payoutReviewAction.value === 'reject' && !reason) {
|
||||
toast.add({ severity: 'warn', summary: '请输入原因', detail: '驳回时需填写原因', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
payoutReviewSubmitting.value = true;
|
||||
try {
|
||||
await CreatorService.reviewPayoutAccount(targetID, { action: payoutReviewAction.value, reason });
|
||||
toast.add({ severity: 'success', summary: '审核完成', detail: `账户ID: ${targetID}`, life: 3000 });
|
||||
payoutReviewDialogVisible.value = false;
|
||||
await loadPayoutAccounts();
|
||||
} catch (error) {
|
||||
toast.add({ severity: 'error', summary: '审核失败', detail: error?.message || '无法审核结算账户', life: 4000 });
|
||||
} finally {
|
||||
payoutReviewSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openInviteDialog(row) {
|
||||
inviteTenantID.value = Number(row?.tenant_id ?? row?.tenant?.id ?? 0) || null;
|
||||
inviteMaxUses.value = 1;
|
||||
@@ -826,6 +889,9 @@ onMounted(() => {
|
||||
<SearchField label="类型">
|
||||
<InputText v-model="payoutType" placeholder="bank/alipay" class="w-full" @keyup.enter="onPayoutSearch" />
|
||||
</SearchField>
|
||||
<SearchField label="状态">
|
||||
<Select v-model="payoutStatus" :options="payoutStatusOptions" optionLabel="label" optionValue="value" placeholder="请选择" class="w-full" />
|
||||
</SearchField>
|
||||
<SearchField label="创建时间 From">
|
||||
<DatePicker v-model="payoutCreatedAtFrom" showIcon showButtonBar placeholder="开始时间" class="w-full" />
|
||||
</SearchField>
|
||||
@@ -873,6 +939,15 @@ onMounted(() => {
|
||||
<Column field="name" header="账户名称" style="min-width: 14rem" />
|
||||
<Column field="account" header="账号" style="min-width: 14rem" />
|
||||
<Column field="realname" header="收款人" style="min-width: 12rem" />
|
||||
<Column header="状态" style="min-width: 12rem">
|
||||
<template #body="{ data }">
|
||||
<div class="flex flex-col">
|
||||
<Tag :value="data?.status_description || data?.status || '-'" :severity="getPayoutStatusSeverity(data?.status)" />
|
||||
<span v-if="data?.review_reason" class="text-xs text-muted-color mt-1 truncate max-w-[220px]">原因:{{ data.review_reason }}</span>
|
||||
<span v-if="data?.reviewed_at" class="text-xs text-muted-color">审核时间:{{ formatDate(data.reviewed_at) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="created_at" header="创建时间" style="min-width: 14rem">
|
||||
<template #body="{ data }">
|
||||
{{ formatDate(data.created_at) }}
|
||||
@@ -880,7 +955,11 @@ onMounted(() => {
|
||||
</Column>
|
||||
<Column header="操作" style="min-width: 8rem">
|
||||
<template #body="{ data }">
|
||||
<Button label="删除" icon="pi pi-trash" severity="danger" text size="small" class="p-0" @click="openPayoutRemoveDialog(data)" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<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)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
@@ -1001,6 +1080,30 @@ onMounted(() => {
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="payoutReviewDialogVisible" :modal="true" :style="{ width: '520px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">结算账户审核</span>
|
||||
<span class="text-muted-color">账户ID: {{ payoutReviewTarget?.id ?? '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-sm text-muted-color">审核结算账户信息,请确认处理动作与备注。</div>
|
||||
<div>
|
||||
<label class="block font-medium mb-2">审核动作</label>
|
||||
<Select v-model="payoutReviewAction" :options="payoutReviewOptions" optionLabel="label" optionValue="value" class="w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block font-medium mb-2">审核说明</label>
|
||||
<InputText v-model="payoutReviewReason" placeholder="驳回时建议填写原因" class="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button label="取消" icon="pi pi-times" text @click="payoutReviewDialogVisible = false" :disabled="payoutReviewSubmitting" />
|
||||
<Button label="确认审核" icon="pi pi-check" severity="success" @click="confirmPayoutReview" :loading="payoutReviewSubmitting" :disabled="payoutReviewSubmitting || (payoutReviewAction === 'reject' && !payoutReviewReason.trim())" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:visible="inviteDialogVisible" :modal="true" :style="{ width: '520px' }">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
|
||||
Reference in New Issue
Block a user