这是基于vue-vben-admin 模板适用于abp vNext的前端管理项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

464 lines
14 KiB

<template>
<BasicModal
@register="registerModal"
:title="L('Applications')"
:can-fullscreen="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="applicationType" :label="L('DisplayName:ApplicationType')">
<Select
default-value="web"
:options="applicationTypes"
v-model:value="state.application.applicationType"
/>
</FormItem>
<FormItem name="clientId" :label="L('DisplayName:ClientId')">
<Input :disabled="state.isEdit" v-model:value="state.application.clientId" />
</FormItem>
<FormItem name="clientType" :label="L('DisplayName:ClientType')">
<Select
:disabled="state.isEdit"
default-value="public"
:options="clientTypes"
v-model:value="state.application.clientType"
/>
</FormItem>
<FormItem v-if="getShowSecret" name="clientSecret" :label="L('DisplayName:ClientSecret')">
<Input v-model:value="state.application.clientSecret" />
</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"
default-value="explicit"
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 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 emits = defineEmits(['register', 'change']);
const { L } = useLocalization(['AbpOpenIddict', 'AbpUi']);
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',
prefix: 'DisplayName',
resourceName: 'AbpOpenIddict',
trigger: 'blur',
}),
clientSecret: ruleCreator.fieldRequired({
name: 'ClientSecret',
prefix: 'DisplayName',
resourceName: 'AbpOpenIddict',
trigger: 'blur',
}),
displayName: ruleCreator.fieldRequired({
name: 'DisplayName',
prefix: 'DisplayName',
resourceName: 'AbpOpenIddict',
trigger: 'blur',
}),
},
endPoint: {
component: '',
uris: [],
},
});
watch(
() => state.application,
() => {
state.entityChanged = true;
},
{
deep: true,
},
);
const clientTypes = reactive([
{ label: 'public', value: 'public' },
{ label: 'confidential', value: 'confidential' },
]);
const applicationTypes = reactive([
{ label: 'Web', value: 'web' },
{ label: 'Native', value: 'native' },
]);
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 getShowSecret = computed(() => {
return !state.isEdit && state.application.clientType === 'confidential';
});
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 [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,
);
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);
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> {
return new Promise((resolve) => {
if (!state.entityChanged) {
const form = unref(formRef);
form?.resetFields();
return resolve(true);
}
createConfirm({
iconType: 'warning',
title: L('AreYouSure'),
content: L('AreYouSureYouWantToCancelEditingWarningMessage'),
onOk: () => {
const form = unref(formRef);
form?.resetFields();
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>