feat: portal tenant apply flow

This commit is contained in:
2025-12-25 11:12:11 +08:00
parent 81240fa0d1
commit 03117b827b
15 changed files with 691 additions and 12 deletions

View File

@@ -0,0 +1,122 @@
<script setup>
import { applyTenantApplication, getTenantApplication } from '@/service/tenantApply';
import { useSession } from '@/service/session';
import { useToast } from 'primevue/usetoast';
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const toast = useToast();
const router = useRouter();
const route = useRoute();
const { isLoggedIn, isTenantApproved, state: sessionState } = useSession();
const submitting = ref(false);
const loading = ref(false);
const name = ref('');
const code = ref('');
const application = computed(() => sessionState.tenantApplication);
const hasApplication = computed(() => Boolean(application.value?.hasApplication));
const statusLabel = computed(() => application.value?.statusDescription || '');
const statusSeverity = computed(() => {
const s = application.value?.status;
if (s === 'verified') return 'success';
if (s === 'pending_verify') return 'warn';
if (s === 'banned') return 'danger';
return 'secondary';
});
async function refresh() {
loading.value = true;
try {
await getTenantApplication();
} finally {
loading.value = false;
}
}
async function submit() {
if (submitting.value) return;
if (!name.value.trim() || !code.value.trim()) {
toast.add({ severity: 'warn', summary: '请完善信息', detail: '请填写租户名称和租户 ID', life: 2500 });
return;
}
try {
submitting.value = true;
await applyTenantApplication({ name: name.value.trim(), code: code.value.trim() });
toast.add({ severity: 'success', summary: '申请已提交', detail: '等待管理员审核', life: 2000 });
await router.push('/tenant/apply/status');
} catch (err) {
const payload = err?.payload;
const message = String(payload?.message || err?.message || '').trim();
toast.add({ severity: 'error', summary: '提交失败', detail: message || '请稍后重试', life: 3500 });
} finally {
submitting.value = false;
}
}
onMounted(async () => {
if (!isLoggedIn.value) {
const redirect = typeof route.fullPath === 'string' ? route.fullPath : '/tenant/apply';
await router.push(`/auth/login?redirect=${encodeURIComponent(redirect)}`);
return;
}
await refresh();
});
</script>
<template>
<div class="card">
<div class="flex items-start justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold">申请创作者</h1>
<div class="text-muted-color mt-2">提交租户信息后等待后台管理员审核一个用户仅可申请一个租户创作者</div>
</div>
<Button label="刷新" icon="pi pi-refresh" severity="secondary" outlined :loading="loading" @click="refresh" />
</div>
<Divider class="my-6" />
<div v-if="isTenantApproved" class="flex flex-col gap-3">
<Message severity="success">你已成为创作者无需重复申请</Message>
<div v-if="application?.hasApplication" class="text-sm">
<div>租户名称{{ application.tenantName }}</div>
<div>租户 ID{{ application.tenantCode }}</div>
<div>状态<Tag :severity="statusSeverity" :value="statusLabel" /></div>
</div>
</div>
<div v-else-if="hasApplication" class="flex flex-col gap-4">
<Message :severity="statusSeverity">
<span v-if="application?.status === 'pending_verify'">申请已提交正在审核中</span>
<span v-else-if="application?.status === 'verified'">审核已通过你已成为创作者</span>
<span v-else>申请状态{{ statusLabel }}</span>
</Message>
<div class="text-sm flex flex-col gap-2">
<div>租户名称{{ application.tenantName }}</div>
<div>租户 ID{{ application.tenantCode }}</div>
<div>状态<Tag :severity="statusSeverity" :value="statusLabel" /></div>
</div>
</div>
<div v-else class="max-w-2xl flex flex-col gap-6">
<div>
<label for="tenantName" class="block text-surface-900 dark:text-surface-0 text-xl font-medium mb-1">租户名称</label>
<InputText id="tenantName" v-model="name" size="large" class="w-full text-xl py-3" placeholder="请输入租户名称" autocomplete="organization" />
</div>
<div>
<label for="tenantCode" class="block text-surface-900 dark:text-surface-0 text-xl font-medium mb-1">租户 ID</label>
<InputText id="tenantCode" v-model="code" size="large" class="w-full text-xl py-3" placeholder="3-64 位小写字母/数字/_/-" autocapitalize="off" autocomplete="off" />
<small class="text-muted-color">将用于 URL例如 `/t/&lt;tenantCode&gt;/...`提交后不可随意变更</small>
</div>
<Button label="提交申请" icon="pi pi-send" size="large" class="w-full" :loading="submitting" @click="submit" />
</div>
</div>
</template>