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 ApplicationTable } from './applications/ApplicationTable.vue'; |
||||
export { default as AuthorizationTable } from './authorizations/AuthorizationTable.vue'; |
export { default as AuthorizationTable } from './authorizations/AuthorizationTable.vue'; |
||||
export { default as ScopeTable } from './scopes/ScopeTable.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