初始化2
This commit is contained in:
326
app/pages/console/invites/index.vue
Normal file
326
app/pages/console/invites/index.vue
Normal file
@@ -0,0 +1,326 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { message, Modal } from 'ant-design-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
listPendingInvites,
|
||||
acceptInvite,
|
||||
rejectInvite,
|
||||
type AppUser
|
||||
} from '@/api/app/appUser'
|
||||
import ConsoleLayout from '@/layouts/console.vue'
|
||||
|
||||
definePageMeta({
|
||||
layout: 'console'
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const invites = ref<AppUser[]>([])
|
||||
const loading = ref(false)
|
||||
const activeTab = ref('pending')
|
||||
|
||||
// 待确认邀请
|
||||
const pendingInvites = computed(() => invites.value)
|
||||
|
||||
// 加载邀请列表
|
||||
async function loadInvites() {
|
||||
try {
|
||||
loading.value = true
|
||||
invites.value = await listPendingInvites()
|
||||
} catch (error) {
|
||||
console.error('加载邀请列表失败:', error)
|
||||
message.error('加载邀请列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 接受邀请
|
||||
async function handleAccept(invite: AppUser) {
|
||||
if (!invite.id) return
|
||||
try {
|
||||
await acceptInvite(invite.id)
|
||||
message.success('已接受邀请,加入应用成功')
|
||||
invites.value = invites.value.filter(i => i.id !== invite.id)
|
||||
// 刷新页面或跳转到应用
|
||||
setTimeout(() => {
|
||||
router.push('/developer/apps')
|
||||
}, 500)
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '接受邀请失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 拒绝邀请
|
||||
async function handleReject(invite: AppUser) {
|
||||
if (!invite.id) return
|
||||
Modal.confirm({
|
||||
title: '确认拒绝邀请?',
|
||||
content: `拒绝后将无法加入应用「${invite.productName || '未知应用'}」`,
|
||||
okText: '确认拒绝',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
async onOk() {
|
||||
try {
|
||||
await rejectInvite(invite.id!)
|
||||
message.success('已拒绝邀请')
|
||||
invites.value = invites.value.filter(i => i.id !== invite.id)
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '拒绝邀请失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取角色标签文本
|
||||
function getRoleLabel(role?: string) {
|
||||
const map: Record<string, string> = {
|
||||
owner: '所有者',
|
||||
admin: '管理员',
|
||||
developer: '开发者',
|
||||
viewer: '访客'
|
||||
}
|
||||
return map[role || ''] || role || '未知'
|
||||
}
|
||||
|
||||
// 获取角色标签颜色
|
||||
function getRoleColor(role?: string) {
|
||||
const map: Record<string, string> = {
|
||||
owner: 'orange',
|
||||
admin: 'blue',
|
||||
developer: 'green',
|
||||
viewer: 'purple'
|
||||
}
|
||||
return map[role || ''] || 'default'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadInvites()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConsoleLayout>
|
||||
<div class="invites-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">应用邀请</h1>
|
||||
<p class="page-desc">管理您收到的应用加入邀请</p>
|
||||
</div>
|
||||
|
||||
<a-card class="invites-card">
|
||||
<a-tabs v-model:activeKey="activeTab">
|
||||
<a-tab-pane key="pending" tab="待确认">
|
||||
<a-spin :spinning="loading">
|
||||
<div v-if="pendingInvites.length === 0" class="empty-state">
|
||||
<a-empty description="暂无待确认的邀请">
|
||||
<template #extra>
|
||||
<p class="empty-tip">
|
||||
当有人邀请您加入应用时,邀请将显示在这里
|
||||
</p>
|
||||
</template>
|
||||
</a-empty>
|
||||
</div>
|
||||
<div v-else class="invite-list">
|
||||
<div
|
||||
v-for="invite in pendingInvites"
|
||||
:key="invite.id"
|
||||
class="invite-item"
|
||||
>
|
||||
<div class="invite-main">
|
||||
<a-avatar
|
||||
:src="invite.icon || '/logo.png'"
|
||||
:size="64"
|
||||
class="app-icon"
|
||||
/>
|
||||
<div class="invite-info">
|
||||
<div class="info-header">
|
||||
<h3 class="app-name">{{ invite.productName || '未知应用' }}</h3>
|
||||
<a-tag :color="getRoleColor(invite.role)">
|
||||
{{ getRoleLabel(invite.role) }}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div class="info-meta">
|
||||
<span class="meta-item">
|
||||
<UserOutlined />
|
||||
邀请人:{{ invite.username || '未知用户' }}
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<ClockCircleOutlined />
|
||||
邀请时间:{{ invite.inviteTime }}
|
||||
</span>
|
||||
<span v-if="invite.inviteExpireTime" class="meta-item expire">
|
||||
<ExclamationCircleOutlined />
|
||||
有效期至:{{ invite.inviteExpireTime }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invite-actions">
|
||||
<a-button
|
||||
size="large"
|
||||
@click="handleReject(invite)"
|
||||
>
|
||||
拒绝
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="handleAccept(invite)"
|
||||
>
|
||||
接受邀请
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="history" tab="历史记录" disabled>
|
||||
<a-empty description="功能开发中" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</ConsoleLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.invites-page {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.invites-card {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
color: #8c8c8c;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.invite-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.invite-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #f0f0f0;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.invite-item:hover {
|
||||
background: #f5f5f5;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.invite-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
flex-shrink: 0;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.invite-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.info-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #595959;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.meta-item.expire {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.invite-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.invites-page {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.invite-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.invite-actions {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.info-meta {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user