40 changed files with 1291 additions and 129 deletions
@ -1,29 +1,408 @@ |
|||
<template> |
|||
<BasicModal |
|||
@register="registerModal" |
|||
:title="title" |
|||
:title="L('Applications')" |
|||
:can-fullscreen="false" |
|||
:show-ok-btn="false" |
|||
:width="800" |
|||
:height="500" |
|||
:close-func="handleBeforeClose" |
|||
@ok="handleSubmit" |
|||
> |
|||
|
|||
<Form |
|||
ref="formRef" |
|||
:model="state.application" |
|||
:rules="state.formRules" |
|||
:label-col="{ span: 6 }" |
|||
:wrapper-col="{ span: 18 }" |
|||
> |
|||
<Tabs v-model:active-key="state.activeTab" @change="handleTabChange"> |
|||
<!-- Basic --> |
|||
<TabPane key="basic" :tab="L('BasicInfo')"> |
|||
<FormItem name="clientId" :label="L('DisplayName:ClientId')"> |
|||
<Input :disabled="state.isEdit" v-model:value="state.application.clientId" /> |
|||
</FormItem> |
|||
<FormItem name="type" :label="L('DisplayName:Type')"> |
|||
<Input v-model:value="state.application.type" /> |
|||
</FormItem> |
|||
<FormItem name="clientUri" :label="L('DisplayName:ClientUri')"> |
|||
<Input v-model:value="state.application.clientUri" /> |
|||
</FormItem> |
|||
<FormItem name="logoUri" :label="L('DisplayName:LogoUri')"> |
|||
<Input v-model:value="state.application.logoUri" /> |
|||
</FormItem> |
|||
<FormItem |
|||
name="consentType" |
|||
:label="L('DisplayName:ConsentType')" |
|||
:label-col="{ span: 4 }" |
|||
:wrapper-col="{ span: 20 }" |
|||
> |
|||
<Select :options="consentTypes" v-model:value="state.application.consentType" /> |
|||
</FormItem> |
|||
</TabPane> |
|||
<!-- DisplayName --> |
|||
<TabPane key="displayName" :tab="L('DisplayNames')"> |
|||
<FormItem name="displayName" :label="L('DisplayName:DefaultDisplayName')"> |
|||
<Input v-model:value="state.application.displayName" /> |
|||
</FormItem> |
|||
<DisplayNameForm |
|||
:displayNames="state.application.displayNames" |
|||
@create="handleNewDisplayName" |
|||
@delete="handleDelDisplayName" |
|||
/> |
|||
</TabPane> |
|||
<!-- Endpoints --> |
|||
<TabPane key="endpoints"> |
|||
<template #tab> |
|||
<Dropdown> |
|||
<span |
|||
>{{ L('Endpoints') }} |
|||
<DownOutlined /> |
|||
</span> |
|||
<template #overlay> |
|||
<Menu @click="handleClickUrisMenu"> |
|||
<MenuItem |
|||
key="redirectUris" |
|||
> |
|||
{{ L('DisplayName:RedirectUris') }} |
|||
</MenuItem> |
|||
<MenuItem |
|||
key="postLogoutRedirectUris" |
|||
> |
|||
{{ L('DisplayName:PostLogoutRedirectUris') }} |
|||
</MenuItem> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</template> |
|||
<component |
|||
:is="componentsRef[state.endPoint.component]" |
|||
:uris="state.endPoint.uris" |
|||
@create-redirect-uri="handleNewRedirectUri" |
|||
@delete-redirect-uri="handleDelRedirectUri" |
|||
@create-logout-uri="handleNewLogoutUri" |
|||
@delete-logout-uri="handleDelLogoutUri" |
|||
/> |
|||
</TabPane> |
|||
<!-- Scopes --> |
|||
<TabPane key="permissions" :tab="L('Scopes')"> |
|||
<ApplicationScope |
|||
:scopes="state.application.scopes" |
|||
:support-scopes="state.openIdConfiguration?.scopes_supported" |
|||
@create="handleNewScope" |
|||
@delete="handleDelScope" |
|||
/> |
|||
</TabPane> |
|||
<!-- Authorizations --> |
|||
<TabPane key="authorizations" :tab="L('Authorizations')"> |
|||
<FormItem |
|||
name="endpoints" |
|||
:label="L('DisplayName:Endpoints')" |
|||
:label-col="{ span: 4 }" |
|||
:wrapper-col="{ span: 20 }" |
|||
> |
|||
<Select :options="endpoints" mode="tags" v-model:value="state.application.endpoints" /> |
|||
</FormItem> |
|||
<FormItem |
|||
name="grantTypes" |
|||
:label="L('DisplayName:GrantTypes')" |
|||
:label-col="{ span: 4 }" |
|||
:wrapper-col="{ span: 20 }" |
|||
> |
|||
<Select :options="grantTypes" mode="tags" v-model:value="state.application.grantTypes" /> |
|||
</FormItem> |
|||
<FormItem |
|||
name="responseTypes" |
|||
:label="L('DisplayName:ResponseTypes')" |
|||
:label-col="{ span: 4 }" |
|||
:wrapper-col="{ span: 20 }" |
|||
> |
|||
<Select :options="responseTypes" mode="tags" v-model:value="state.application.responseTypes" /> |
|||
</FormItem> |
|||
</TabPane> |
|||
<!-- Propertites --> |
|||
<TabPane key="propertites" :tab="L('Propertites')"> |
|||
<PropertyForm |
|||
:properties="state.application.properties" |
|||
@create="handleNewProperty" |
|||
@delete="handleDelProperty" |
|||
/> |
|||
</TabPane> |
|||
</Tabs> |
|||
</Form> |
|||
</BasicModal> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
import { Form } from 'ant-design-vue'; |
|||
import type { FormInstance } from 'ant-design-vue/lib/form'; |
|||
import { cloneDeep } from 'lodash-es'; |
|||
import { computed, nextTick, ref, unref, reactive, shallowRef, onMounted, watch } from 'vue'; |
|||
import { DownOutlined } from '@ant-design/icons-vue'; |
|||
import { Dropdown, Form, Menu, Input, Select, Tabs } from 'ant-design-vue'; |
|||
import { BasicModal, useModalInner } from '/@/components/Modal'; |
|||
import { useMessage } from '/@/hooks/web/useMessage'; |
|||
import { useValidation } from '/@/hooks/abp/useValidation'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { ApplicationState } from '../types/props'; |
|||
import { GetAsyncById, CreateAsyncByInput, UpdateAsyncByIdAndInput } from '/@/api/openiddict/open-iddict-application'; |
|||
import { OpenIddictApplicationDto } from '/@/api/openiddict/open-iddict-application/model'; |
|||
import { discovery } from '/@/api/identity-server/identityServer'; |
|||
import RedirectUri from './RedirectUri.vue'; |
|||
import PostLogoutRedirectUri from './PostLogoutRedirectUri.vue'; |
|||
import DisplayNameForm from '../../components/DisplayNames/DisplayNameForm.vue'; |
|||
import ApplicationScope from './ApplicationScope.vue'; |
|||
import PropertyForm from '../../components/Properties/PropertyForm.vue'; |
|||
|
|||
const FormItem = Form.Item; |
|||
const TabPane = Tabs.TabPane; |
|||
const MenuItem = Menu.Item; |
|||
|
|||
const { L } = useLocalization('AbpOpenIddict'); |
|||
const [registerModal] = useModalInner((data) => { |
|||
console.log(data); |
|||
const emits = defineEmits(['register', 'change']); |
|||
const { L } = useLocalization(['AbpOpenIddict']); |
|||
const { ruleCreator } = useValidation(); |
|||
const { createMessage, createConfirm } = useMessage(); |
|||
const componentsRef = shallowRef({ |
|||
'redirectUris': RedirectUri, |
|||
'postLogoutRedirectUris': PostLogoutRedirectUri, |
|||
}); |
|||
const formRef = ref<FormInstance>(); |
|||
const state = reactive<ApplicationState>({ |
|||
activeTab: 'basic', |
|||
isEdit: false, |
|||
entityChanged: false, |
|||
application: {} as OpenIddictApplicationDto, |
|||
formRules: { |
|||
clientId: ruleCreator.fieldRequired({ |
|||
name: 'ClientId', |
|||
resourceName: 'AbpOpenIddict', |
|||
trigger: 'blur', |
|||
}), |
|||
}, |
|||
endPoint: { |
|||
component: '', |
|||
uris: [], |
|||
}, |
|||
}); |
|||
watch( |
|||
() => state.application, |
|||
() => { |
|||
state.entityChanged = true; |
|||
}, |
|||
{ |
|||
deep: true, |
|||
}, |
|||
); |
|||
const consentTypes = reactive([ |
|||
{ label: 'explicit', value: 'explicit' }, |
|||
{ label: 'external', value: 'external' }, |
|||
{ label: 'implicit', value: 'implicit' }, |
|||
{ label: 'systematic', value: 'systematic' }, |
|||
]); |
|||
const endpoints = reactive([ |
|||
{ label: 'authorization', value: 'authorization' }, |
|||
{ label: 'token', value: 'token' }, |
|||
{ label: 'logout', value: 'logout' }, |
|||
{ label: 'device', value: 'device' }, |
|||
{ label: 'revocation', value: 'revocation' }, |
|||
{ label: 'introspection', value: 'introspection' }, |
|||
]); |
|||
const grantTypes = computed(() => { |
|||
if (!state.openIdConfiguration) return[]; |
|||
const types = state.openIdConfiguration.grant_types_supported; |
|||
return types.map((type) => { |
|||
return { |
|||
label: type, |
|||
value: type, |
|||
}; |
|||
}); |
|||
}); |
|||
const responseTypes = computed(() => { |
|||
if (!state.openIdConfiguration) return[]; |
|||
const types = state.openIdConfiguration.response_types_supported; |
|||
return types.map((type) => { |
|||
return { |
|||
label: type, |
|||
value: type, |
|||
}; |
|||
}); |
|||
}); |
|||
const title = computed(() => { |
|||
|
|||
const [registerModal, { changeLoading, changeOkLoading, closeModal }] = useModalInner((data) => { |
|||
nextTick(() => { |
|||
fetch(data?.id); |
|||
}); |
|||
}); |
|||
|
|||
onMounted(initOpenidDiscovery); |
|||
|
|||
function initOpenidDiscovery() { |
|||
discovery().then((openIdConfiuration) => { |
|||
state.openIdConfiguration = openIdConfiuration; |
|||
}); |
|||
} |
|||
|
|||
function fetch(id?: string) { |
|||
state.activeTab = 'basic'; |
|||
state.isEdit = false; |
|||
state.entityChanged = false; |
|||
state.application = {} as OpenIddictApplicationDto; |
|||
const form = unref(formRef); |
|||
form?.resetFields(); |
|||
if (!id) { |
|||
nextTick(() => { |
|||
state.isEdit = false; |
|||
state.entityChanged = false; |
|||
}); |
|||
return; |
|||
} |
|||
changeLoading(true); |
|||
GetAsyncById(id).then((application) => { |
|||
state.application = application; |
|||
nextTick(() => { |
|||
state.isEdit = true; |
|||
state.entityChanged = false; |
|||
}); |
|||
}).finally(() => { |
|||
changeLoading(false); |
|||
}); |
|||
} |
|||
|
|||
function handleTabChange(activeKey) { |
|||
state.activeTab = activeKey; |
|||
switch (activeKey) { |
|||
case 'endpoints': |
|||
if (!state.endPoint.component) { |
|||
state.endPoint = { |
|||
component : 'redirectUris', |
|||
uris: state.application?.redirectUris, |
|||
}; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
function handleNewLogoutUri(uri: string) { |
|||
if (!state.application) { |
|||
return; |
|||
} |
|||
state.application.postLogoutRedirectUris ??= []; |
|||
state.application.postLogoutRedirectUris.push(uri); |
|||
} |
|||
|
|||
function handleDelLogoutUri(uri: string) { |
|||
if (!state.application || !state.application.postLogoutRedirectUris) { |
|||
return; |
|||
} |
|||
const index = state.application.postLogoutRedirectUris.findIndex(logoutUri => logoutUri === uri); |
|||
if (!index) { |
|||
state.application.postLogoutRedirectUris.splice(index); |
|||
} |
|||
} |
|||
|
|||
function handleNewRedirectUri(uri: string) { |
|||
if (!state.application) { |
|||
return; |
|||
} |
|||
state.application.redirectUris ??= []; |
|||
state.application.redirectUris.push(uri); |
|||
} |
|||
|
|||
function handleDelRedirectUri(uri: string) { |
|||
if (!state.application || !state.application.redirectUris) { |
|||
return; |
|||
} |
|||
const index = state.application.redirectUris.findIndex(redirectUri => redirectUri === uri); |
|||
if (!index) { |
|||
state.application.redirectUris.splice(index); |
|||
} |
|||
} |
|||
|
|||
function handleNewDisplayName(record) { |
|||
if (!state.application) { |
|||
return; |
|||
} |
|||
state.application.displayNames ??= {}; |
|||
state.application.displayNames[record.culture] = record.displayName; |
|||
} |
|||
|
|||
function handleDelDisplayName(record) { |
|||
if (!state.application || !state.application.displayNames) { |
|||
return; |
|||
} |
|||
delete state.application.displayNames[record.culture]; |
|||
} |
|||
|
|||
function handleNewProperty(record) { |
|||
if (!state.application) { |
|||
return; |
|||
} |
|||
state.application.properties ??= {}; |
|||
state.application.properties[record.key] = record.value; |
|||
} |
|||
|
|||
function handleDelProperty(record) { |
|||
if (!state.application || !state.application.properties) { |
|||
return; |
|||
} |
|||
delete state.application.properties[record.key]; |
|||
} |
|||
|
|||
function handleNewScope(scopes: string[]) { |
|||
if (!state.application) { |
|||
return; |
|||
} |
|||
state.application.scopes ??= []; |
|||
state.application.scopes.push(...scopes); |
|||
} |
|||
|
|||
function handleDelScope(scopes: string[]) { |
|||
if (!state.application || !state.application.scopes) { |
|||
return; |
|||
} |
|||
state.application.scopes = state.application.scopes.filter((scope) => !scopes.includes(scope)); |
|||
} |
|||
|
|||
function handleClickUrisMenu(e) { |
|||
state.endPoint = { |
|||
component : e.key, |
|||
uris: state.application[e.key], |
|||
}; |
|||
state.activeTab = 'endpoints'; |
|||
} |
|||
|
|||
function handleBeforeClose(): Promise<boolean> { |
|||
console.log(state); |
|||
return new Promise((resolve) => { |
|||
if (!state.entityChanged) { |
|||
return resolve(true); |
|||
} |
|||
createConfirm({ |
|||
iconType: 'warning', |
|||
title: L('AreYouSure'), |
|||
content: L('AreYouSureYouWantToCancelEditingWarningMessage'), |
|||
onOk: () => { |
|||
resolve(true); |
|||
}, |
|||
onCancel: () => { |
|||
resolve(false); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function handleSubmit() { |
|||
const form = unref(formRef); |
|||
form?.validate().then(() => { |
|||
changeOkLoading(true); |
|||
const api = state.application.id |
|||
? UpdateAsyncByIdAndInput(state.application.id, cloneDeep(state.application)) |
|||
: CreateAsyncByInput(cloneDeep(state.application)); |
|||
api.then((res) => { |
|||
createMessage.success(L('Successful')); |
|||
emits('change', res); |
|||
closeModal(); |
|||
}).finally(() => { |
|||
changeOkLoading(false); |
|||
}) |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
@ -0,0 +1,68 @@ |
|||
<template> |
|||
<PermissionForm :resources="getSupportScopes" :targetResources="targetResources" @change="handleChange" /> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref, unref, onMounted } from 'vue'; |
|||
import { GetListAsyncByInput } from '/@/api/openiddict/open-iddict-scope'; |
|||
import PermissionForm from '../../components/Permissions/PermissionForm.vue'; |
|||
|
|||
const emits = defineEmits(['create', 'delete']); |
|||
const props = defineProps({ |
|||
scopes: { |
|||
type: [Array] as PropType<string[]>, |
|||
}, |
|||
supportScopes: { |
|||
type: [Array] as PropType<string[]>, |
|||
} |
|||
}); |
|||
|
|||
const resourcesRef = ref<{ key: string; title: string }[]>([]); |
|||
const targetResources = computed(() => { |
|||
if (!props.scopes) return []; |
|||
const targetScopes = getSupportScopes.value.filter((item) => |
|||
props.scopes?.some((scope) => scope === item.key), |
|||
); |
|||
|
|||
return targetScopes.map((item) => item.key); |
|||
}); |
|||
const getSupportScopes = computed(() => { |
|||
const resources = unref(resourcesRef); |
|||
if (props.supportScopes) { |
|||
const supportScopes = props.supportScopes.map(scope => { |
|||
return { |
|||
key: scope, |
|||
title: scope, |
|||
} |
|||
}); |
|||
supportScopes.push(...resources); |
|||
return supportScopes; |
|||
} |
|||
return resources; |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
GetListAsyncByInput({ |
|||
skipCount: 0, |
|||
maxResultCount: 100, |
|||
}).then((res) => { |
|||
resourcesRef.value = res.items.map((item) => { |
|||
return { |
|||
key: item.name, |
|||
title: item.displayName ?? item.name, |
|||
}; |
|||
}); |
|||
}); |
|||
}); |
|||
|
|||
function handleChange(_, direction, moveKeys: string[]) { |
|||
switch (direction) { |
|||
case 'left': |
|||
emits('delete', moveKeys); |
|||
break; |
|||
case 'right': |
|||
emits('create', moveKeys); |
|||
break; |
|||
} |
|||
} |
|||
</script> |
|||
@ -0,0 +1,60 @@ |
|||
<template> |
|||
<UriEditForm |
|||
:title="L('DisplayName:PostLogoutRedirectUris')" |
|||
:schemas="schemas" |
|||
:columns="columns" |
|||
:data-source="dataSource" |
|||
rowKey="uri" |
|||
@create="handleAddNew" |
|||
@delete="handleDelete" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
import { FormSchema } from '/@/components/Form'; |
|||
import { BasicColumn } from '/@/components/Table'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import UriEditForm from '../../components/Uris/UriForm.vue'; |
|||
|
|||
const emits = defineEmits(['create-logout-uri', 'delete-logout-uri']); |
|||
const props = defineProps({ |
|||
uris: { |
|||
type: Array as PropType<String[]>, |
|||
}, |
|||
}); |
|||
const { L } = useLocalization(['AbpOpenIddict']); |
|||
const dataSource = computed(() => { |
|||
if (!props.uris) return []; |
|||
return props.uris.map(uri => { |
|||
return { |
|||
uri: uri, |
|||
}; |
|||
}); |
|||
}); |
|||
const schemas: FormSchema[] = [ |
|||
{ |
|||
field: 'uri', |
|||
component: 'Input', |
|||
label: 'Uri', |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
}, |
|||
]; |
|||
const columns: BasicColumn[] = [ |
|||
{ |
|||
dataIndex: 'uri', |
|||
align: 'left', |
|||
width: 'auto', |
|||
sorter: true, |
|||
}, |
|||
]; |
|||
|
|||
function handleAddNew(record) { |
|||
emits('create-logout-uri', record.uri); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
emits('delete-logout-uri', record.uri); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,60 @@ |
|||
<template> |
|||
<UriEditForm |
|||
:title="L('DisplayName:RedirectUris')" |
|||
:schemas="schemas" |
|||
:columns="columns" |
|||
:data-source="dataSource" |
|||
rowKey="uri" |
|||
@create="handleAddNew" |
|||
@delete="handleDelete" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
import { FormSchema } from '/@/components/Form'; |
|||
import { BasicColumn } from '/@/components/Table'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import UriEditForm from '../../components/Uris/UriForm.vue'; |
|||
|
|||
const emits = defineEmits(['create-redirect-uri', 'delete-redirect-uri']); |
|||
const props = defineProps({ |
|||
uris: { |
|||
type: Array as PropType<String[]>, |
|||
}, |
|||
}); |
|||
const { L } = useLocalization(['AbpOpenIddict']); |
|||
const dataSource = computed(() => { |
|||
if (!props.uris) return []; |
|||
return props.uris.map(uri => { |
|||
return { |
|||
uri: uri, |
|||
}; |
|||
}); |
|||
}); |
|||
const schemas: FormSchema[] = [ |
|||
{ |
|||
field: 'uri', |
|||
component: 'Input', |
|||
label: 'Uri', |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
}, |
|||
]; |
|||
const columns: BasicColumn[] = [ |
|||
{ |
|||
dataIndex: 'uri', |
|||
align: 'left', |
|||
width: 'auto', |
|||
sorter: true, |
|||
}, |
|||
]; |
|||
|
|||
function handleAddNew(record) { |
|||
emits('create-redirect-uri', record.uri); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
emits('delete-redirect-uri', record.uri); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,18 @@ |
|||
import { Rule } from 'ant-design-vue/lib/form'; |
|||
import { OpenIdConfiguration } from '/@/api/identity-server/model/basicModel'; |
|||
import { OpenIddictApplicationDto } from '/@/api/openiddict/open-iddict-application/model'; |
|||
|
|||
export interface EndPointComponent { |
|||
component: string; |
|||
uris?: string[]; |
|||
} |
|||
|
|||
export interface ApplicationState { |
|||
activeTab: string; |
|||
formRules?: Dictionary<string, Rule>, |
|||
application: OpenIddictApplicationDto; |
|||
endPoint: EndPointComponent; |
|||
openIdConfiguration?: OpenIdConfiguration; |
|||
entityChanged: boolean; |
|||
isEdit: boolean; |
|||
} |
|||
@ -0,0 +1,133 @@ |
|||
<template> |
|||
<Card :title="L('DisplayName:DisplayNames')"> |
|||
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
|||
<BasicTable @register="registerTable" :data-source="dataSource"> |
|||
<template #bodyCell="{ column, record }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<TableAction |
|||
:actions="[ |
|||
{ |
|||
color: 'error', |
|||
icon: 'ant-design:delete-outlined', |
|||
label: L('Delete'), |
|||
onClick: handleDelete.bind(null, record), |
|||
}, |
|||
]" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</BasicTable> |
|||
</Card> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref } from 'vue'; |
|||
import { Card } from 'ant-design-vue'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { BasicForm, useForm } from '/@/components/Form'; |
|||
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
|||
import { getList as getLanguages } from '/@/api/localization/languages'; |
|||
|
|||
const emits = defineEmits(['create', 'delete']); |
|||
const props = defineProps({ |
|||
displayNames: { |
|||
type: Object as PropType<Recordable>, |
|||
default: () => {}, |
|||
}, |
|||
}); |
|||
|
|||
const { L } = useLocalization(['AbpOpenIddict', 'AbpLocalization', 'AbpUi']); |
|||
const modelRef = ref({}); |
|||
const [registerForm, { resetFields }] = useForm({ |
|||
model: modelRef, |
|||
schemas: [ |
|||
{ |
|||
field: 'culture', |
|||
component: 'ApiSelect', |
|||
label: L('DisplayName:CultureName'), |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
componentProps: { |
|||
api: getLanguages, |
|||
params: { |
|||
skipCount: 0, |
|||
maxResultCount: 100, |
|||
}, |
|||
resultField: 'items', |
|||
labelField: 'displayName', |
|||
valueField: 'cultureName', |
|||
}, |
|||
}, |
|||
{ |
|||
field: 'displayName', |
|||
component: 'Input', |
|||
label: L('DisplayName:DisplayName'), |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
}, |
|||
], |
|||
showResetButton: false, |
|||
submitButtonOptions: { |
|||
text: L('DisplayName:AddNew'), |
|||
// icon: 'ant-design:plus-outlined', |
|||
}, |
|||
}); |
|||
const [registerTable] = useTable({ |
|||
rowKey: 'culture', |
|||
showHeader: false, |
|||
columns: [ |
|||
{ |
|||
dataIndex: 'culture', |
|||
align: 'left', |
|||
width: 100, |
|||
sorter: true, |
|||
}, |
|||
{ |
|||
dataIndex: 'displayName', |
|||
align: 'left', |
|||
width: 'auto', |
|||
sorter: true, |
|||
}, |
|||
], |
|||
pagination: false, |
|||
striped: false, |
|||
useSearchForm: false, |
|||
showTableSetting: false, |
|||
showIndexColumn: false, |
|||
bordered: false, |
|||
actionColumn: { |
|||
width: 200, |
|||
title: L('Actions'), |
|||
dataIndex: 'action', |
|||
}, |
|||
}); |
|||
const dataSource = computed(() => { |
|||
if (!props.displayNames) { |
|||
return []; |
|||
} |
|||
return Object.keys(props.displayNames).map((key) => { |
|||
return { |
|||
culture: key, |
|||
displayName: props.displayNames[key] |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
function handleSubmit(input) { |
|||
if (!props.displayNames && !props.displayNames[input.culture]) { |
|||
return; |
|||
} |
|||
emits('create', input); |
|||
resetFields(); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
emits('delete', record); |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.title { |
|||
margin-bottom: 20px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,41 @@ |
|||
<template> |
|||
<Transfer |
|||
:dataSource="resources" |
|||
:targetKeys="targetResources" |
|||
:titles="[L('Assigned'), L('Available')]" |
|||
:render="(item) => item.title" |
|||
:list-style="getListStyle" |
|||
@change="handleChange" |
|||
/> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed } from 'vue'; |
|||
import { Transfer } from 'ant-design-vue'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
|
|||
const emits = defineEmits(['change']); |
|||
const props = defineProps({ |
|||
resources: { |
|||
type: [Array] as PropType<{ key: string; title: string }[]>, |
|||
required: true, |
|||
default: () => [], |
|||
}, |
|||
targetResources: { type: [Array] as PropType<string[]>, required: true, default: () => [] }, |
|||
listStyle: { type: Object, required: false }, |
|||
}); |
|||
|
|||
const { L } = useLocalization(['AbpOpenIddict']); |
|||
const defaultListStyle = { |
|||
width: '48%', |
|||
height: '500px', |
|||
minHeight: '500px', |
|||
}; |
|||
const getListStyle = computed(() => { |
|||
return {...defaultListStyle, ...props.listStyle} |
|||
}); |
|||
|
|||
function handleChange(targetKeys, direction, moveKeys) { |
|||
emits('change', targetKeys, direction, moveKeys); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,120 @@ |
|||
<template> |
|||
<Card :title="L('Propertites')"> |
|||
<BasicTable @register="registerTable" :data-source="dataSource"> |
|||
<template #toolbar> |
|||
<Button type="primary" @click="handleAddNew">{{ L('Propertites:New') }}</Button> |
|||
</template> |
|||
<template #bodyCell="{ column, record }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<TableAction |
|||
:actions="[ |
|||
{ |
|||
color: 'error', |
|||
icon: 'ant-design:delete-outlined', |
|||
label: L('Delete'), |
|||
onClick: handleDelete.bind(null, record), |
|||
}, |
|||
]" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</BasicTable> |
|||
<BasicModal v-bind="$attrs" @register="registerModal" @ok="handleSubmit" :title="title"> |
|||
<BasicForm @register="registerForm" /> |
|||
</BasicModal> |
|||
</Card> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { computed, ref } from 'vue'; |
|||
import { Button, Card } from 'ant-design-vue'; |
|||
import { BasicForm, useForm } from '/@/components/Form'; |
|||
import { BasicModal, useModal } from '/@/components/Modal'; |
|||
import { BasicTable, TableAction, useTable } from '/@/components/Table'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
|
|||
const emits = defineEmits(['create', 'delete']); |
|||
const props = defineProps({ |
|||
properties: { |
|||
type: Object as PropType<Dictionary<string, string>>, |
|||
default: () => [], |
|||
}, |
|||
}); |
|||
|
|||
const { L } = useLocalization(['AbpOpenIddict']); |
|||
const title = ref(''); |
|||
const [registerForm, { validate, resetFields }] = useForm({ |
|||
labelWidth: 120, |
|||
showActionButtonGroup: false, |
|||
schemas: [ |
|||
{ |
|||
field: 'key', |
|||
component: 'Input', |
|||
label: L('Propertites:Key'), |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
}, |
|||
{ |
|||
field: 'value', |
|||
component: 'Input', |
|||
label: L('Propertites:Value'), |
|||
colProps: { span: 24 }, |
|||
required: true, |
|||
}, |
|||
], |
|||
}); |
|||
const dataSource = computed(() => { |
|||
if (!props.properties) return []; |
|||
return Object.keys(props.properties).map((key) => { |
|||
return { |
|||
key: key, |
|||
value: props.properties[key], |
|||
}; |
|||
}); |
|||
}); |
|||
const [registerTable] = useTable({ |
|||
rowKey: "key", |
|||
columns: [ |
|||
{ |
|||
title: L('Propertites:Key'), |
|||
dataIndex: 'key', |
|||
align: 'left', |
|||
width: 180, |
|||
sorter: true, |
|||
}, |
|||
{ |
|||
title: L('Propertites:Value'), |
|||
dataIndex: 'value', |
|||
align: 'left', |
|||
width: 180, |
|||
sorter: true, |
|||
}, |
|||
], |
|||
pagination: false, |
|||
showTableSetting: true, |
|||
maxHeight: 230, |
|||
actionColumn: { |
|||
width: 100, |
|||
title: L('Actions'), |
|||
dataIndex: 'action', |
|||
}, |
|||
}); |
|||
const [registerModal, { openModal, closeModal }] = useModal(); |
|||
|
|||
function handleAddNew() { |
|||
title.value = L('Propertites:New'); |
|||
openModal(true); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
emits('delete', record); |
|||
} |
|||
|
|||
function handleSubmit() { |
|||
validate().then((input) => { |
|||
emits('create', input); |
|||
resetFields(); |
|||
closeModal(); |
|||
}); |
|||
} |
|||
</script> |
|||
@ -0,0 +1,124 @@ |
|||
<template> |
|||
<div> |
|||
<BasicTitle class="title">{{ title }}</BasicTitle> |
|||
<BasicForm @register="registerForm" @submit="handleSubmit" /> |
|||
<BasicTable @register="registerTable"> |
|||
<template #bodyCell="{ column, record }"> |
|||
<template v-if="column.key === 'action'"> |
|||
<TableAction |
|||
:actions="[ |
|||
{ |
|||
color: 'error', |
|||
icon: 'ant-design:delete-outlined', |
|||
label: L('Delete'), |
|||
onClick: handleDelete.bind(null, record), |
|||
}, |
|||
]" |
|||
/> |
|||
</template> |
|||
</template> |
|||
</BasicTable> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, watch } from 'vue'; |
|||
import { BasicTitle } from '/@/components/Basic'; |
|||
import { useLocalization } from '/@/hooks/abp/useLocalization'; |
|||
import { BasicForm, FormSchema, useForm } from '/@/components/Form'; |
|||
import { BasicTable, BasicColumn, TableAction, useTable } from '/@/components/Table'; |
|||
|
|||
const emits = defineEmits(['create', 'delete']); |
|||
const props = defineProps({ |
|||
schemas: { |
|||
type: [Array] as PropType<FormSchema[]>, |
|||
required: true, |
|||
default: () => [], |
|||
}, |
|||
labelWidth: { |
|||
type: Number, |
|||
}, |
|||
columns: { |
|||
type: [Array] as PropType<BasicColumn[]>, |
|||
required: true, |
|||
default: () => [], |
|||
}, |
|||
dataSource: { |
|||
type: [Array] as PropType<Recordable[]>, |
|||
required: true, |
|||
default: () => [], |
|||
}, |
|||
rowKey: { |
|||
type: String, |
|||
default: () => 'id', |
|||
}, |
|||
showHeader: { |
|||
type: Boolean, |
|||
default: () => false, |
|||
}, |
|||
title: { |
|||
type: String, |
|||
default: () => '', |
|||
}, |
|||
tableTitle: { |
|||
type: String, |
|||
default: () => '', |
|||
}, |
|||
}); |
|||
|
|||
const { L } = useLocalization(['AbpOpenIddict', 'AbpUi']); |
|||
const modelRef = ref({}); |
|||
const [registerForm, { resetFields }] = useForm({ |
|||
model: modelRef, |
|||
labelWidth: props.labelWidth, |
|||
schemas: props.schemas, |
|||
showResetButton: false, |
|||
submitButtonOptions: { |
|||
text: L('Uri:AddNew'), |
|||
// icon: 'ant-design:plus-outlined', |
|||
}, |
|||
}); |
|||
const [registerTable, { setTableData }] = useTable({ |
|||
rowKey: props.rowKey, |
|||
showHeader: props.showHeader, |
|||
title: props.tableTitle, |
|||
columns: props.columns, |
|||
dataSource: props.dataSource, |
|||
pagination: false, |
|||
striped: false, |
|||
useSearchForm: false, |
|||
showTableSetting: false, |
|||
showIndexColumn: false, |
|||
bordered: false, |
|||
actionColumn: { |
|||
width: 200, |
|||
title: L('Actions'), |
|||
dataIndex: 'action', |
|||
}, |
|||
}); |
|||
|
|||
watch( |
|||
() => props.dataSource, |
|||
(data) => { |
|||
setTableData(data); |
|||
}, |
|||
{ |
|||
deep: true, |
|||
}, |
|||
); |
|||
|
|||
function handleSubmit(input) { |
|||
emits('create', input); |
|||
resetFields(); |
|||
} |
|||
|
|||
function handleDelete(record) { |
|||
emits('delete', record); |
|||
} |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.title { |
|||
margin-bottom: 20px; |
|||
} |
|||
</style> |
|||
@ -1,8 +1,14 @@ |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.OpenIddict.Applications; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationCreateDto : OpenIddictApplicationCreateOrUpdateDto |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(OpenIddictApplicationConsts), nameof(OpenIddictApplicationConsts.ClientIdMaxLength))] |
|||
public string ClientId { get; set; } |
|||
} |
|||
|
|||
@ -1,8 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationUpdateDto : OpenIddictApplicationCreateOrUpdateDto |
|||
public class OpenIddictApplicationUpdateDto : OpenIddictApplicationCreateOrUpdateDto, IHasConcurrencyStamp |
|||
{ |
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
|
|||
@ -1,8 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictScopeUpdateDto : OpenIddictScopeCreateOrUpdateDto |
|||
public class OpenIddictScopeUpdateDto : OpenIddictScopeCreateOrUpdateDto, IHasConcurrencyStamp |
|||
{ |
|||
public string ConcurrencyStamp { get; set; } |
|||
} |
|||
|
|||
Loading…
Reference in new issue