feat: 添加功能和修复问题
- 添加全局 API Token 认证支持 (环境变量 API_TOKEN) - Team 页面添加直接邀请按钮 - Team 页面添加随机邀请按钮 - 修复已邀请用户列表字段名不匹配问题 - 修复数据库为空时错误显示 toast 的问题
This commit is contained in:
@@ -54,7 +54,8 @@ import {
|
||||
} from '@/components/ui/pagination'
|
||||
import { useAccountsStore } from '@/stores/accounts'
|
||||
import { createAccount, refreshAccount, deleteAccount, type Account } from '@/api/accounts'
|
||||
import { Plus, RefreshCw, Users, Loader2, Eye, EyeOff, Trash2 } from 'lucide-vue-next'
|
||||
import { inviteByAdmin } from '@/api/invite'
|
||||
import { Plus, RefreshCw, Users, Loader2, Eye, EyeOff, Trash2, UserPlus, Shuffle } from 'lucide-vue-next'
|
||||
|
||||
const router = useRouter()
|
||||
const accountsStore = useAccountsStore()
|
||||
@@ -67,6 +68,18 @@ const showToken = ref(false)
|
||||
|
||||
// Delete confirmation
|
||||
const deleteDialogOpen = ref(false)
|
||||
|
||||
// Invite dialog
|
||||
const inviteDialogOpen = ref(false)
|
||||
const invitingAccountId = ref<number | null>(null)
|
||||
const invitingAccountName = ref('')
|
||||
const inviteEmail = ref('')
|
||||
const inviting = ref(false)
|
||||
|
||||
// Random invite dialog
|
||||
const randomInviteDialogOpen = ref(false)
|
||||
const randomInviteEmail = ref('')
|
||||
const randomInviting = ref(false)
|
||||
const pendingDelete = ref<Account | null>(null)
|
||||
|
||||
// Pagination
|
||||
@@ -185,6 +198,74 @@ function viewInvites(account: Account) {
|
||||
router.push(`/admin/teams/${account.id}/invites`)
|
||||
}
|
||||
|
||||
function openInviteDialog(account: Account) {
|
||||
invitingAccountId.value = account.id
|
||||
invitingAccountName.value = account.name || account.team_account_id
|
||||
inviteEmail.value = ''
|
||||
inviteDialogOpen.value = true
|
||||
}
|
||||
|
||||
async function handleInvite() {
|
||||
if (!inviteEmail.value.trim()) {
|
||||
toast.error('请输入邮箱地址')
|
||||
return
|
||||
}
|
||||
if (!invitingAccountId.value) return
|
||||
|
||||
inviting.value = true
|
||||
try {
|
||||
const response = await inviteByAdmin({
|
||||
email: inviteEmail.value.trim(),
|
||||
account_id: invitingAccountId.value,
|
||||
})
|
||||
if (response.data.success) {
|
||||
toast.success('邀请发送成功')
|
||||
inviteDialogOpen.value = false
|
||||
inviteEmail.value = ''
|
||||
// Refresh account to update seats
|
||||
if (invitingAccountId.value) {
|
||||
await handleRefresh(accountsStore.accounts.find(a => a.id === invitingAccountId.value)!)
|
||||
}
|
||||
} else {
|
||||
toast.error(response.data.message || '邀请失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e.response?.data?.message || '邀请失败')
|
||||
} finally {
|
||||
inviting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Random invite - auto select available team
|
||||
async function handleRandomInvite() {
|
||||
if (!randomInviteEmail.value.trim()) {
|
||||
toast.error('请输入邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
randomInviting.value = true
|
||||
try {
|
||||
// Use account_id = 0 to let backend auto-select
|
||||
const response = await inviteByAdmin({
|
||||
email: randomInviteEmail.value.trim(),
|
||||
account_id: 0,
|
||||
})
|
||||
if (response.data.success) {
|
||||
toast.success(`邀请发送成功,已分配到: ${response.data.account_name || 'Team'}`)
|
||||
randomInviteDialogOpen.value = false
|
||||
randomInviteEmail.value = ''
|
||||
// Refresh all accounts to update seats
|
||||
await accountsStore.fetchAccounts()
|
||||
} else {
|
||||
toast.error(response.data.message || '邀请失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e.response?.data?.message || '邀请失败')
|
||||
} finally {
|
||||
randomInviting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goToPage(page: number) {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
currentPage.value = page
|
||||
@@ -203,6 +284,7 @@ function handlePageSizeChange(value: any) {
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold">Team 管理</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button>
|
||||
@@ -268,6 +350,43 @@ function handlePageSizeChange(value: any) {
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Random Invite Button and Dialog -->
|
||||
<Dialog v-model:open="randomInviteDialogOpen">
|
||||
<DialogTrigger as-child>
|
||||
<Button variant="outline">
|
||||
<Shuffle class="h-4 w-4 mr-2" />
|
||||
随机邀请
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>随机邀请</DialogTitle>
|
||||
<DialogDescription>
|
||||
系统将自动选择有空位的 Team 发送邀请
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="handleRandomInvite" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="random_invite_email">邮箱地址 *</Label>
|
||||
<Input
|
||||
id="random_invite_email"
|
||||
v-model="randomInviteEmail"
|
||||
type="email"
|
||||
placeholder="user@example.com"
|
||||
:disabled="randomInviting"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" :disabled="randomInviting">
|
||||
<Loader2 v-if="randomInviting" class="h-4 w-4 mr-2 animate-spin" />
|
||||
{{ randomInviting ? '邀请中...' : '发送邀请' }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card class="min-h-[600px] flex flex-col">
|
||||
@@ -329,7 +448,10 @@ function handlePageSizeChange(value: any) {
|
||||
]"
|
||||
/>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="viewInvites(account)">
|
||||
<Button variant="outline" size="sm" @click="openInviteDialog(account)" title="直接邀请">
|
||||
<UserPlus class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" @click="viewInvites(account)" title="查看已邀请用户">
|
||||
<Users class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -410,5 +532,35 @@ function handlePageSizeChange(value: any) {
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- Invite dialog -->
|
||||
<Dialog v-model:open="inviteDialogOpen">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>邀请用户</DialogTitle>
|
||||
<DialogDescription>
|
||||
邀请用户加入 Team: {{ invitingAccountName }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form @submit.prevent="handleInvite" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="invite_email">邮箱地址 *</Label>
|
||||
<Input
|
||||
id="invite_email"
|
||||
v-model="inviteEmail"
|
||||
type="email"
|
||||
placeholder="user@example.com"
|
||||
:disabled="inviting"
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" :disabled="inviting">
|
||||
<Loader2 v-if="inviting" class="h-4 w-4 mr-2 animate-spin" />
|
||||
{{ inviting ? '邀请中...' : '发送邀请' }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user