16 changed files with 442 additions and 8 deletions
@ -1,4 +1,5 @@ |
|||||
export { useAccountApi } from './useAccountApi'; |
export { useAccountApi } from './useAccountApi'; |
||||
|
export { useMySessionApi } from './useMySessionApi'; |
||||
export { useProfileApi } from './useProfileApi'; |
export { useProfileApi } from './useProfileApi'; |
||||
export { useTokenApi } from './useTokenApi'; |
export { useTokenApi } from './useTokenApi'; |
||||
export { useUserInfoApi } from './useUserInfoApi'; |
export { useUserInfoApi } from './useUserInfoApi'; |
||||
|
|||||
@ -0,0 +1,41 @@ |
|||||
|
import type { PagedResultDto } from '@abp/core'; |
||||
|
import type { GetMySessionsInput, IdentitySessionDto } from '@abp/identity'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useMySessionApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
/** |
||||
|
* 查询会话列表 |
||||
|
* @param { GetMySessionsInput } input 查询参数 |
||||
|
* @returns { Promise<PagedResultDto<IdentitySessionDto>> } 用户会话列表 |
||||
|
*/ |
||||
|
function getSessionsApi( |
||||
|
input?: GetMySessionsInput, |
||||
|
): Promise<PagedResultDto<IdentitySessionDto>> { |
||||
|
return request<PagedResultDto<IdentitySessionDto>>( |
||||
|
'/api/account/my-profile/sessions', |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
/** |
||||
|
* 撤销会话 |
||||
|
* @param { string } sessionId 会话id |
||||
|
* @returns { Promise<void> } |
||||
|
*/ |
||||
|
function revokeSessionApi(sessionId: string): Promise<void> { |
||||
|
return request(`/api/account/my-profile/sessions/${sessionId}/revoke`, { |
||||
|
method: 'DELETE', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
getSessionsApi, |
||||
|
revokeSessionApi, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { IdentitySessionDto } from '@abp/identity'; |
||||
|
|
||||
|
import { onMounted, ref } from 'vue'; |
||||
|
|
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { SessionTable } from '@abp/identity'; |
||||
|
import { Card, message, Modal } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useMySessionApi } from '../../api/useMySessionApi'; |
||||
|
|
||||
|
const { cancel, getSessionsApi, revokeSessionApi } = useMySessionApi(); |
||||
|
|
||||
|
const sessions = ref<IdentitySessionDto[]>([]); |
||||
|
|
||||
|
async function getSessions() { |
||||
|
const { items } = await getSessionsApi(); |
||||
|
sessions.value = items; |
||||
|
} |
||||
|
|
||||
|
async function onRevoke(session: IdentitySessionDto) { |
||||
|
Modal.confirm({ |
||||
|
centered: true, |
||||
|
content: $t('AbpIdentity.SessionWillBeRevokedMessage'), |
||||
|
iconType: 'warning', |
||||
|
onCancel: () => { |
||||
|
cancel(); |
||||
|
}, |
||||
|
onOk: async () => { |
||||
|
await revokeSessionApi(session.sessionId); |
||||
|
message.success($t('AbpIdentity.SuccessfullyRevoked')); |
||||
|
await getSessions(); |
||||
|
}, |
||||
|
title: $t('AbpUi.AreYouSure'), |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
onMounted(getSessions); |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Card :bordered="false" :title="$t('abp.account.settings.sessionSettings')"> |
||||
|
<SessionTable :sessions="sessions" @revoke="onRevoke" /> |
||||
|
</Card> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,42 @@ |
|||||
|
import type { PagedResultDto } from '@abp/core'; |
||||
|
|
||||
|
import type { GetUserSessionsInput, IdentitySessionDto } from '../types'; |
||||
|
|
||||
|
import { useRequest } from '@abp/request'; |
||||
|
|
||||
|
export function useUserSessionsApi() { |
||||
|
const { cancel, request } = useRequest(); |
||||
|
|
||||
|
/** |
||||
|
* 查询会话列表 |
||||
|
* @param { GetUserSessionsInput } input 查询参数 |
||||
|
* @returns { Promise<PagedResultDto<IdentitySessionDto>> } 用户会话列表 |
||||
|
*/ |
||||
|
function getSessionsApi( |
||||
|
input?: GetUserSessionsInput, |
||||
|
): Promise<PagedResultDto<IdentitySessionDto>> { |
||||
|
return request<PagedResultDto<IdentitySessionDto>>( |
||||
|
'/api/identity/sessions', |
||||
|
{ |
||||
|
method: 'GET', |
||||
|
params: input, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
/** |
||||
|
* 撤销会话 |
||||
|
* @param { string } sessionId 会话id |
||||
|
* @returns { Promise<void> } |
||||
|
*/ |
||||
|
function revokeSessionApi(sessionId: string): Promise<void> { |
||||
|
return request(`/api/identity/sessions/${sessionId}/revoke`, { |
||||
|
method: 'DELETE', |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
cancel, |
||||
|
getSessionsApi, |
||||
|
revokeSessionApi, |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,158 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { VxeGridProps } from '@abp/ui'; |
||||
|
|
||||
|
import type { IdentitySessionDto } from '../../types/sessions'; |
||||
|
|
||||
|
import { computed, nextTick, reactive, watch } from 'vue'; |
||||
|
|
||||
|
import { useAccess } from '@vben/access'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { useAbpStore } from '@abp/core'; |
||||
|
import { useVbenVxeGrid } from '@abp/ui'; |
||||
|
import { Button, Descriptions, Tag } from 'ant-design-vue'; |
||||
|
|
||||
|
import { IdentitySessionPermissions } from '../../constants/permissions'; |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
sessions: IdentitySessionDto[]; |
||||
|
}>(); |
||||
|
const emits = defineEmits<{ |
||||
|
(event: 'revoke', session: IdentitySessionDto): void; |
||||
|
}>(); |
||||
|
const DescriptionItem = Descriptions.Item; |
||||
|
|
||||
|
const { hasAccessByCodes } = useAccess(); |
||||
|
const abpStore = useAbpStore(); |
||||
|
/** 获取登录用户会话Id */ |
||||
|
const getMySessionId = computed(() => { |
||||
|
return abpStore.application?.currentUser.sessionId; |
||||
|
}); |
||||
|
/** 获取是否允许撤销会话 */ |
||||
|
const getAllowRevokeSession = computed(() => { |
||||
|
return (session: IdentitySessionDto) => { |
||||
|
if (getMySessionId.value === session.sessionId) { |
||||
|
return false; |
||||
|
} |
||||
|
return hasAccessByCodes([IdentitySessionPermissions.Revoke]); |
||||
|
}; |
||||
|
}); |
||||
|
|
||||
|
const gridOptions = reactive<VxeGridProps<IdentitySessionDto>>({ |
||||
|
columns: [ |
||||
|
{ |
||||
|
align: 'left', |
||||
|
slots: { content: 'deviceInfo' }, |
||||
|
type: 'expand', |
||||
|
width: 50, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'device', |
||||
|
slots: { default: 'device' }, |
||||
|
title: $t('AbpIdentity.DisplayName:Device'), |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
align: 'left', |
||||
|
field: 'signedIn', |
||||
|
minWidth: 200, |
||||
|
title: $t('AbpIdentity.DisplayName:SignedIn'), |
||||
|
}, |
||||
|
{ |
||||
|
field: 'action', |
||||
|
fixed: 'right', |
||||
|
slots: { default: 'action' }, |
||||
|
title: $t('AbpUi.Actions'), |
||||
|
width: 120, |
||||
|
}, |
||||
|
], |
||||
|
expandConfig: { |
||||
|
padding: true, |
||||
|
trigger: 'default', |
||||
|
}, |
||||
|
exportConfig: {}, |
||||
|
keepSource: true, |
||||
|
pagerConfig: { |
||||
|
autoHidden: true, |
||||
|
}, |
||||
|
toolbarConfig: {}, |
||||
|
}); |
||||
|
|
||||
|
const [Grid, gridApi] = useVbenVxeGrid({ gridOptions }); |
||||
|
|
||||
|
watch( |
||||
|
() => props.sessions, |
||||
|
(sessions) => { |
||||
|
nextTick(() => { |
||||
|
gridApi.setGridOptions({ |
||||
|
data: sessions, |
||||
|
}); |
||||
|
}); |
||||
|
}, |
||||
|
{ |
||||
|
immediate: true, |
||||
|
}, |
||||
|
); |
||||
|
|
||||
|
function onDelete(session: IdentitySessionDto) { |
||||
|
emits('revoke', session); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Grid> |
||||
|
<template #device="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<span>{{ row.device }}</span> |
||||
|
<div class="pl-[5px]"> |
||||
|
<Tag v-if="row.sessionId === getMySessionId" color="#87d068"> |
||||
|
{{ $t('AbpIdentity.CurrentSession') }} |
||||
|
</Tag> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template #deviceInfo="{ row }"> |
||||
|
<Descriptions :colon="false" :column="2" bordered size="small"> |
||||
|
<DescriptionItem |
||||
|
:label="$t('AbpIdentity.DisplayName:SessionId')" |
||||
|
:span="2" |
||||
|
> |
||||
|
{{ row.sessionId }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:Device')"> |
||||
|
{{ row.device }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:DeviceInfo')"> |
||||
|
{{ row.deviceInfo }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:ClientId')"> |
||||
|
{{ row.clientId }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:IpAddresses')"> |
||||
|
{{ row.ipAddresses }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:SignedIn')"> |
||||
|
{{ row.signedIn }} |
||||
|
</DescriptionItem> |
||||
|
<DescriptionItem :label="$t('AbpIdentity.DisplayName:LastAccessed')"> |
||||
|
{{ row.lastAccessed }} |
||||
|
</DescriptionItem> |
||||
|
</Descriptions> |
||||
|
</template> |
||||
|
<template #action="{ row }"> |
||||
|
<div class="flex flex-row"> |
||||
|
<Button |
||||
|
v-if="getAllowRevokeSession(row)" |
||||
|
danger |
||||
|
size="small" |
||||
|
@click="onDelete(row)" |
||||
|
> |
||||
|
{{ $t('AbpIdentity.RevokeSession') }} |
||||
|
</Button> |
||||
|
</div> |
||||
|
</template> |
||||
|
</Grid> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,67 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import type { IdentitySessionDto, IdentityUserDto } from '../../types'; |
||||
|
|
||||
|
import { defineAsyncComponent, ref } from 'vue'; |
||||
|
|
||||
|
import { useVbenDrawer } from '@vben/common-ui'; |
||||
|
import { $t } from '@vben/locales'; |
||||
|
|
||||
|
import { message, Modal } from 'ant-design-vue'; |
||||
|
|
||||
|
import { useUserSessionsApi } from '../../api/useUserSessionsApi'; |
||||
|
|
||||
|
const SessionTable = defineAsyncComponent( |
||||
|
() => import('../sessions/SessionTable.vue'), |
||||
|
); |
||||
|
|
||||
|
const sessions = ref<IdentitySessionDto[]>([]); |
||||
|
|
||||
|
const { cancel, getSessionsApi, revokeSessionApi } = useUserSessionsApi(); |
||||
|
|
||||
|
const [Drawer, drawerApi] = useVbenDrawer({ |
||||
|
class: 'w-[800px]', |
||||
|
onBeforeClose: cancel, |
||||
|
onCancel() { |
||||
|
drawerApi.close(); |
||||
|
}, |
||||
|
onConfirm: async () => {}, |
||||
|
onOpenChange: async (isOpen: boolean) => { |
||||
|
isOpen && (await onRefresh()); |
||||
|
}, |
||||
|
title: $t('AbpIdentity.IdentitySessions'), |
||||
|
}); |
||||
|
async function onRefresh() { |
||||
|
try { |
||||
|
drawerApi.setState({ loading: true }); |
||||
|
const dto = drawerApi.getData<IdentityUserDto>(); |
||||
|
const { items } = await getSessionsApi({ userId: dto.id }); |
||||
|
sessions.value = items; |
||||
|
} finally { |
||||
|
drawerApi.setState({ loading: false }); |
||||
|
} |
||||
|
} |
||||
|
async function onRevoke(session: IdentitySessionDto) { |
||||
|
Modal.confirm({ |
||||
|
centered: true, |
||||
|
content: $t('AbpIdentity.SessionWillBeRevokedMessage'), |
||||
|
iconType: 'warning', |
||||
|
onCancel: () => { |
||||
|
cancel(); |
||||
|
}, |
||||
|
onOk: async () => { |
||||
|
await revokeSessionApi(session.sessionId); |
||||
|
message.success($t('AbpIdentity.SuccessfullyRevoked')); |
||||
|
await onRefresh(); |
||||
|
}, |
||||
|
title: $t('AbpUi.AreYouSure'), |
||||
|
}); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<Drawer> |
||||
|
<SessionTable :sessions="sessions" @revoke="onRevoke" /> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped></style> |
||||
@ -0,0 +1,25 @@ |
|||||
|
import type { EntityDto, PagedAndSortedResultRequestDto } from '@abp/core'; |
||||
|
|
||||
|
interface IdentitySessionDto extends EntityDto<string> { |
||||
|
clientId?: string; |
||||
|
device: string; |
||||
|
deviceInfo: string; |
||||
|
ipAddresses?: string; |
||||
|
lastAccessed?: Date; |
||||
|
sessionId: string; |
||||
|
signedIn: Date; |
||||
|
userId: string; |
||||
|
} |
||||
|
|
||||
|
interface GetUserSessionsInput extends PagedAndSortedResultRequestDto { |
||||
|
clientId?: string; |
||||
|
device?: string; |
||||
|
userId?: string; |
||||
|
} |
||||
|
|
||||
|
interface GetMySessionsInput extends PagedAndSortedResultRequestDto { |
||||
|
clientId?: string; |
||||
|
device?: string; |
||||
|
} |
||||
|
|
||||
|
export type { GetMySessionsInput, GetUserSessionsInput, IdentitySessionDto }; |
||||
Loading…
Reference in new issue