7 changed files with 491 additions and 2 deletions
@ -0,0 +1,15 @@ |
|||
<script lang="ts" setup> |
|||
import { Page } from '@vben/common-ui'; |
|||
|
|||
import { TokenTable } from '@abp/openiddict'; |
|||
|
|||
defineOptions({ |
|||
name: 'OpenIddictTokens', |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Page> |
|||
<TokenTable /> |
|||
</Page> |
|||
</template> |
|||
@ -1,3 +1,4 @@ |
|||
export { default as ApplicationTable } from './applications/ApplicationTable.vue'; |
|||
export { default as AuthorizationTable } from './authorizations/AuthorizationTable.vue'; |
|||
export { default as ScopeTable } from './scopes/ScopeTable.vue'; |
|||
export { default as TokenTable } from './tokens/TokenTable.vue'; |
|||
|
|||
@ -0,0 +1,165 @@ |
|||
<script setup lang="ts"> |
|||
import type { OpenIddictTokenDto } from '../../types/tokens'; |
|||
|
|||
import { useAccess } from '@vben/access'; |
|||
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
type IdentityUserDto, |
|||
userLookupApi, |
|||
UserLookupPermissions, |
|||
} from '@abp/identity'; |
|||
import { CodeEditor } from '@abp/ui'; |
|||
|
|||
import { getApi as getApplication } from '../../api/applications'; |
|||
import { getApi as getAuthorization } from '../../api/tokens'; |
|||
|
|||
defineOptions({ |
|||
name: 'TokenModal', |
|||
}); |
|||
|
|||
const { hasAccessByCodes } = useAccess(); |
|||
|
|||
const [Form, formApi] = useVbenForm({ |
|||
commonConfig: { |
|||
// 所有表单项 |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
schema: [ |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'applicationId', |
|||
label: $t('AbpOpenIddict.DisplayName:ApplicationId'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'authorizationId', |
|||
label: $t('AbpOpenIddict.DisplayName:AuthorizationId'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'subject', |
|||
label: $t('AbpOpenIddict.DisplayName:Subject'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'type', |
|||
label: $t('AbpOpenIddict.DisplayName:Type'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'status', |
|||
label: $t('AbpOpenIddict.DisplayName:Status'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'creationDate', |
|||
label: $t('AbpOpenIddict.DisplayName:CreationDate'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'expirationDate', |
|||
label: $t('AbpOpenIddict.DisplayName:ExpirationDate'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'redemptionDate', |
|||
label: $t('AbpOpenIddict.DisplayName:RedemptionDate'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'referenceId', |
|||
label: $t('AbpOpenIddict.DisplayName:ReferenceId'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
componentProps: { |
|||
readonly: true, |
|||
}, |
|||
fieldName: 'payload', |
|||
label: $t('AbpOpenIddict.DisplayName:Payload'), |
|||
}, |
|||
], |
|||
|
|||
showDefaultActions: false, |
|||
}); |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
class: 'w-1/2', |
|||
draggable: true, |
|||
fullscreenButton: false, |
|||
onCancel() { |
|||
modalApi.close(); |
|||
}, |
|||
async onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
try { |
|||
modalApi.setState({ loading: true }); |
|||
const { id } = modalApi.getData<OpenIddictTokenDto>(); |
|||
await onGet(id); |
|||
} finally { |
|||
modalApi.setState({ loading: false }); |
|||
} |
|||
} |
|||
}, |
|||
showConfirmButton: false, |
|||
title: $t('AbpOpenIddict.Tokens'), |
|||
}); |
|||
|
|||
async function onGet(id: string) { |
|||
const authorization = await getAuthorization(id); |
|||
const application = await getApplication(authorization.applicationId!); |
|||
let subjectInfo: IdentityUserDto | undefined; |
|||
if (hasAccessByCodes([UserLookupPermissions.Default])) { |
|||
subjectInfo = await userLookupApi.findByIdApi(authorization.subject!); |
|||
} |
|||
formApi.setValues({ |
|||
...authorization, |
|||
applicationId: `${application.clientId}(${authorization.applicationId})`, |
|||
subject: subjectInfo?.userName |
|||
? `${subjectInfo.userName}(${authorization.subject})` |
|||
: authorization.subject, |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Form> |
|||
<template #payload="{ modelValue }"> |
|||
<CodeEditor :value="modelValue" readonly /> |
|||
</template> |
|||
</Form> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,297 @@ |
|||
<script setup lang="ts"> |
|||
import type { VbenFormProps, VxeGridProps } from '@abp/ui'; |
|||
import type { SelectValue } from 'ant-design-vue/es/select'; |
|||
|
|||
import type { OpenIddictApplicationDto } from '../../types'; |
|||
import type { OpenIddictTokenDto } from '../../types/tokens'; |
|||
|
|||
import { defineAsyncComponent, h, onMounted, ref } from 'vue'; |
|||
|
|||
import { useAccess } from '@vben/access'; |
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { createIconifyIcon } from '@vben/icons'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { formatToDateTime } from '@abp/core'; |
|||
import { useVbenVxeGrid } from '@abp/ui'; |
|||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons-vue'; |
|||
import { Button, message, Modal, Select } from 'ant-design-vue'; |
|||
import debounce from 'lodash.debounce'; |
|||
|
|||
import { getPagedListApi as getApplications } from '../../api/applications'; |
|||
import { deleteApi, getPagedListApi } from '../../api/tokens'; |
|||
import { TokensPermissions } from '../../constants/permissions'; |
|||
|
|||
defineOptions({ |
|||
name: 'TokenTable', |
|||
}); |
|||
|
|||
const CheckIcon = createIconifyIcon('ant-design:check-outlined'); |
|||
const CloseIcon = createIconifyIcon('ant-design:close-outlined'); |
|||
|
|||
const { hasAccessByCodes } = useAccess(); |
|||
|
|||
const applications = ref<OpenIddictApplicationDto[]>([]); |
|||
const formOptions: VbenFormProps = { |
|||
// 默认展开 |
|||
collapsed: true, |
|||
collapsedRows: 2, |
|||
// 所有表单项共用,可单独在表单内覆盖 |
|||
commonConfig: { |
|||
// 在label后显示一个冒号 |
|||
colon: true, |
|||
// 所有表单项 |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
fieldMappingTime: [ |
|||
['creationTime', ['beginCreationTime', 'endCreationTime'], 'YYYY-MM-DD'], |
|||
[ |
|||
'expirationDate', |
|||
['beginExpirationDate', 'endExpirationDate'], |
|||
'YYYY-MM-DD', |
|||
], |
|||
], |
|||
handleReset: onFormReset, |
|||
schema: [ |
|||
{ |
|||
component: 'Select', |
|||
fieldName: 'clientId', |
|||
formItemClass: 'col-span-1/3 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:ClientId'), |
|||
}, |
|||
{ |
|||
component: 'RangePicker', |
|||
fieldName: 'creationTime', |
|||
formItemClass: 'col-span-2 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:CreationTime'), |
|||
}, |
|||
{ |
|||
component: 'RangePicker', |
|||
fieldName: 'expirationDate', |
|||
formItemClass: 'col-span-2 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:ExpirationDate'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'subject', |
|||
formItemClass: 'col-span-1/3 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:Subject'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'status', |
|||
formItemClass: 'col-span-1/3 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:Status'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'type', |
|||
formItemClass: 'col-span-1/3 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:Type'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'referenceId', |
|||
formItemClass: 'col-span-1/3 items-baseline', |
|||
label: $t('AbpOpenIddict.DisplayName:ReferenceId'), |
|||
}, |
|||
{ |
|||
component: 'Input', |
|||
fieldName: 'filter', |
|||
formItemClass: 'col-span-2 items-baseline', |
|||
label: $t('AbpUi.Search'), |
|||
}, |
|||
], |
|||
// 控制表单是否显示折叠按钮 |
|||
showCollapseButton: true, |
|||
// 按下回车时是否提交表单 |
|||
submitOnEnter: true, |
|||
}; |
|||
|
|||
const gridOptions: VxeGridProps<OpenIddictTokenDto> = { |
|||
columns: [ |
|||
{ |
|||
align: 'left', |
|||
field: 'applicationId', |
|||
minWidth: 300, |
|||
title: $t('AbpOpenIddict.DisplayName:ApplicationId'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'subject', |
|||
minWidth: 300, |
|||
title: $t('AbpOpenIddict.DisplayName:Subject'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'type', |
|||
minWidth: 150, |
|||
title: $t('AbpOpenIddict.DisplayName:Type'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'status', |
|||
minWidth: 150, |
|||
title: $t('AbpOpenIddict.DisplayName:Status'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'creationDate', |
|||
formatter: ({ cellValue }) => { |
|||
return cellValue ? formatToDateTime(cellValue) : cellValue; |
|||
}, |
|||
minWidth: 200, |
|||
title: $t('AbpOpenIddict.DisplayName:CreationDate'), |
|||
}, |
|||
{ |
|||
align: 'left', |
|||
field: 'expirationDate', |
|||
formatter: ({ cellValue }) => { |
|||
return cellValue ? formatToDateTime(cellValue) : cellValue; |
|||
}, |
|||
minWidth: 200, |
|||
title: $t('AbpOpenIddict.DisplayName:ExpirationDate'), |
|||
}, |
|||
{ |
|||
field: 'action', |
|||
fixed: 'right', |
|||
slots: { default: 'action' }, |
|||
title: $t('AbpUi.Actions'), |
|||
visible: hasAccessByCodes([TokensPermissions.Delete]), |
|||
width: 220, |
|||
}, |
|||
], |
|||
exportConfig: {}, |
|||
keepSource: true, |
|||
proxyConfig: { |
|||
ajax: { |
|||
query: async ({ page }, formValues) => { |
|||
return await getPagedListApi({ |
|||
maxResultCount: page.pageSize, |
|||
skipCount: (page.currentPage - 1) * page.pageSize, |
|||
...formValues, |
|||
}); |
|||
}, |
|||
}, |
|||
response: { |
|||
total: 'totalCount', |
|||
list: 'items', |
|||
}, |
|||
}, |
|||
toolbarConfig: { |
|||
custom: true, |
|||
export: true, |
|||
// import: true, |
|||
refresh: true, |
|||
zoom: true, |
|||
}, |
|||
}; |
|||
const [TokenModal, modalApi] = useVbenModal({ |
|||
connectedComponent: defineAsyncComponent(() => import('./TokenModal.vue')), |
|||
}); |
|||
|
|||
const [Grid, gridApi] = useVbenVxeGrid({ |
|||
formOptions, |
|||
gridOptions, |
|||
}); |
|||
|
|||
const onSearchClient = debounce(async (filter?: string) => { |
|||
const { items } = await getApplications({ |
|||
filter, |
|||
maxResultCount: 25, |
|||
}); |
|||
applications.value = items; |
|||
}, 500); |
|||
|
|||
function onChangeClient(value?: SelectValue) { |
|||
gridApi.formApi.setFieldValue('clientId', value); |
|||
} |
|||
|
|||
function onUpdate(row: OpenIddictTokenDto) { |
|||
modalApi.setData(row); |
|||
modalApi.open(); |
|||
} |
|||
|
|||
function onDelete(row: OpenIddictTokenDto) { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
content: `${$t('AbpUi.ItemWillBeDeletedMessage')}`, |
|||
onOk: () => { |
|||
return deleteApi(row.id).then(() => { |
|||
message.success($t('AbpUi.SuccessfullyDeleted')); |
|||
gridApi.query(); |
|||
}); |
|||
}, |
|||
title: $t('AbpUi.AreYouSure'), |
|||
}); |
|||
} |
|||
|
|||
function onFormReset() { |
|||
gridApi.formApi.resetForm(); |
|||
gridApi.formApi.submitForm(); |
|||
} |
|||
onMounted(onSearchClient); |
|||
</script> |
|||
|
|||
<template> |
|||
<Grid :table-title="$t('AbpOpenIddict.Tokens')"> |
|||
<template #form-clientId="{ modelValue }"> |
|||
<Select |
|||
:default-active-first-option="false" |
|||
:field-names="{ label: 'clientId', value: 'id' }" |
|||
:filter-option="false" |
|||
:options="applications" |
|||
:placeholder="$t('ui.placeholder.select')" |
|||
:value="modelValue" |
|||
allow-clear |
|||
class="w-full" |
|||
show-search |
|||
@change="onChangeClient" |
|||
@search="onSearchClient" |
|||
/> |
|||
</template> |
|||
<template #required="{ row }"> |
|||
<div class="flex flex-row justify-center"> |
|||
<CheckIcon v-if="row.required" class="text-green-500" /> |
|||
<CloseIcon v-else class="text-red-500" /> |
|||
</div> |
|||
</template> |
|||
<template #static="{ row }"> |
|||
<div class="flex flex-row justify-center"> |
|||
<CheckIcon v-if="row.isStatic" class="text-green-500" /> |
|||
<CloseIcon v-else class="text-red-500" /> |
|||
</div> |
|||
</template> |
|||
<template #action="{ row }"> |
|||
<div class="flex flex-row"> |
|||
<div class="basis-1/2"> |
|||
<Button |
|||
:icon="h(EditOutlined)" |
|||
block |
|||
type="link" |
|||
@click="onUpdate(row)" |
|||
> |
|||
{{ $t('AbpUi.Edit') }} |
|||
</Button> |
|||
</div> |
|||
<div class="basis-1/2"> |
|||
<Button |
|||
:icon="h(DeleteOutlined)" |
|||
block |
|||
danger |
|||
type="link" |
|||
v-access:code="[TokensPermissions.Delete]" |
|||
@click="onDelete(row)" |
|||
> |
|||
{{ $t('AbpUi.Delete') }} |
|||
</Button> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</Grid> |
|||
<TokenModal @change="() => gridApi.query()" /> |
|||
</template> |
|||
|
|||
<style lang="scss" scoped></style> |
|||
Loading…
Reference in new issue