Browse Source

feat: 增加实体变更记录.

pull/1080/head
colin 1 year ago
parent
commit
ac3ed868f4
  1. 2
      apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts
  2. 44
      apps/vben5/packages/@abp/core/src/hooks/useFeatures.ts
  3. 7
      apps/vben5/packages/@abp/core/src/hooks/useGlobalFeatures.ts
  4. 50
      apps/vben5/packages/@abp/core/src/hooks/useSettings.ts
  5. 6
      apps/vben5/packages/@abp/core/src/hooks/useSimpleStateCheck.ts
  6. 6
      apps/vben5/packages/@abp/core/src/hooks/useValidation.ts
  7. 6
      apps/vben5/packages/@abp/identity/src/components/claim-types/ClaimTypeTable.vue
  8. 9
      apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue
  9. 8
      apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue
  10. 1
      apps/vben5/packages/@abp/openiddict/package.json
  11. 31
      apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue
  12. 89
      apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue

2
apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts

@ -50,7 +50,7 @@ export function useRequireFeaturesSimpleStateChecker<
featureNames: string[], featureNames: string[],
requiresAll: boolean = false, requiresAll: boolean = false,
): ISimpleStateChecker<TState> { ): ISimpleStateChecker<TState> {
const { featureChecker } = useFeatures(); const featureChecker = useFeatures();
return new RequireFeaturesSimpleStateChecker( return new RequireFeaturesSimpleStateChecker(
featureChecker, featureChecker,
featureNames, featureNames,

44
apps/vben5/packages/@abp/core/src/hooks/useFeatures.ts

@ -1,24 +1,40 @@
import type { FeatureValue, IFeatureChecker } from '../types/features'; import type { FeatureValue, IFeatureChecker } from '../types/features';
import { computed } from 'vue'; import { ref, watch } from 'vue';
import { useAbpStore } from '../store/abp'; import { useAbpStore } from '../store/abp';
export function useFeatures() { export function useFeatures(): IFeatureChecker {
const abpStore = useAbpStore(); const abpStore = useAbpStore();
const getFeatures = computed(() => {
const fetures = abpStore.application?.features.values ?? {}; const features = ref<FeatureValue[]>([]);
const fetureValues = Object.keys(fetures).map((key): FeatureValue => {
return { watch(
name: key, () => abpStore.application,
value: fetures[key] ?? '', (application) => {
}; if (!application?.features.values) {
}); features.value = [];
return fetureValues; return;
}); }
const featuresSet: FeatureValue[] = [];
Object.keys(application.features.values).forEach((name) => {
if (application.features.values[name]) {
featuresSet.push({
name,
value: application.features.values[name],
});
}
});
features.value = featuresSet;
},
{
deep: true,
immediate: true,
},
);
function get(name: string): FeatureValue | undefined { function get(name: string): FeatureValue | undefined {
return getFeatures.value.find((feature) => name === feature.name); return features.value.find((feature) => name === feature.name);
} }
function _isEnabled(name: string): boolean { function _isEnabled(name: string): boolean {
@ -52,5 +68,5 @@ export function useFeatures() {
}, },
}; };
return { featureChecker }; return featureChecker;
} }

7
apps/vben5/packages/@abp/core/src/hooks/useGlobalFeatures.ts

@ -4,10 +4,13 @@ import { useAbpStore } from '../store/abp';
import { isNullOrWhiteSpace } from '../utils/string'; import { isNullOrWhiteSpace } from '../utils/string';
export function useGlobalFeatures() { export function useGlobalFeatures() {
const abpStore = useAbpStore();
const getGlobalFeatures = computed(() => { const getGlobalFeatures = computed(() => {
const abpStore = useAbpStore(); if (!abpStore.application) {
return [];
}
const enabledFeatures = const enabledFeatures =
abpStore.application?.globalFeatures.enabledFeatures ?? []; abpStore.application.globalFeatures.enabledFeatures ?? [];
return enabledFeatures; return enabledFeatures;
}); });

50
apps/vben5/packages/@abp/core/src/hooks/useSettings.ts

@ -1,37 +1,47 @@
import type { ISettingProvider, SettingValue } from '../types/settings'; import type { ISettingProvider, SettingValue } from '../types/settings';
import { computed } from 'vue'; import { ref, watch } from 'vue';
import { useAbpStore } from '../store'; import { useAbpStore } from '../store';
export function useSettings(): ISettingProvider { export function useSettings(): ISettingProvider {
const abpStore = useAbpStore(); const abpStore = useAbpStore();
const getSettings = computed(() => {
if (!abpStore.application) { const settings = ref<SettingValue[]>([]);
return [];
} watch(
const { values: settings } = abpStore.application.setting; () => abpStore.application,
const settingValues = Object.keys(settings).map((key): SettingValue => { (application) => {
return { if (!application?.setting.values) {
name: key, settings.value = [];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion return;
value: settings[key]!, }
}; const settingsSet: SettingValue[] = [];
}); Object.keys(application.setting.values).forEach((name) => {
return settingValues; if (application.setting.values[name]) {
}); settingsSet.push({
name,
value: application.setting.values[name],
});
}
});
settings.value = settingsSet;
},
{
deep: true,
immediate: true,
},
);
function get(name: string): SettingValue | undefined { function get(name: string): SettingValue | undefined {
return getSettings.value.find((setting) => name === setting.name); return settings.value.find((setting) => name === setting.name);
} }
function getAll(...names: string[]): SettingValue[] { function getAll(...names: string[]): SettingValue[] {
if (names) { if (names) {
return getSettings.value.filter((setting) => return settings.value.filter((setting) => names.includes(setting.name));
names.includes(setting.name),
);
} }
return getSettings.value; return settings.value;
} }
function getOrDefault<T>(name: string, defaultValue: T): string | T { function getOrDefault<T>(name: string, defaultValue: T): string | T {

6
apps/vben5/packages/@abp/core/src/hooks/useSimpleStateCheck.ts

@ -128,8 +128,6 @@ class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
export function useSimpleStateCheck< export function useSimpleStateCheck<
TState extends IHasSimpleStateCheckers<TState>, TState extends IHasSimpleStateCheckers<TState>,
>() { >(): ISimpleStateCheckerSerializer {
return { return new SimpleStateCheckerSerializer();
serializer: new SimpleStateCheckerSerializer(),
};
} }

6
apps/vben5/packages/@abp/core/src/hooks/useValidation.ts

@ -17,7 +17,7 @@ import { ValidationEnum } from '../constants';
import { isEmail, isPhone } from '../utils/regex'; import { isEmail, isPhone } from '../utils/regex';
import { useLocalization } from './useLocalization'; import { useLocalization } from './useLocalization';
export function useValidation() { export function useValidation(): RuleCreator {
const { L } = useLocalization(['AbpValidation']); const { L } = useLocalization(['AbpValidation']);
function _getFieldName(field: Field) { function _getFieldName(field: Field) {
return __getFieldName( return __getFieldName(
@ -399,7 +399,5 @@ export function useValidation() {
}, },
}; };
return { return ruleCreator;
ruleCreator,
};
} }

6
apps/vben5/packages/@abp/identity/src/components/claim-types/ClaimTypeTable.vue

@ -12,6 +12,7 @@ import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing'; import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { useFeatures } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui'; import { useVbenVxeGrid } from '@abp/ui';
import { import {
DeleteOutlined, DeleteOutlined,
@ -36,6 +37,7 @@ const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined'); const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit'); const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const { cancel, deleteApi, getPagedListApi } = useClaimTypesApi(); const { cancel, deleteApi, getPagedListApi } = useClaimTypesApi();
@ -194,7 +196,7 @@ const onMenuClick = (row: IdentityClaimTypeDto, info: MenuInfo) => {
case 'entity-changes': { case 'entity-changes': {
roleChangeDrawerApi.setData({ roleChangeDrawerApi.setData({
entityId: row.id, entityId: row.id,
entityTypeFullName: 'Volo.Abp.Identity.IdentityRole', entityTypeFullName: 'Volo.Abp.Identity.IdentityClaimType',
subject: row.name, subject: row.name,
}); });
roleChangeDrawerApi.open(); roleChangeDrawerApi.open();
@ -249,7 +251,7 @@ const onMenuClick = (row: IdentityClaimTypeDto, info: MenuInfo) => {
> >
{{ $t('AbpUi.Delete') }} {{ $t('AbpUi.Delete') }}
</Button> </Button>
<Dropdown> <Dropdown v-if="isEnabled('AbpAuditing.Logging.AuditLog')">
<template #overlay> <template #overlay>
<Menu @click="(info) => onMenuClick(row, info)"> <Menu @click="(info) => onMenuClick(row, info)">
<MenuItem <MenuItem

9
apps/vben5/packages/@abp/identity/src/components/roles/RoleTable.vue

@ -12,7 +12,7 @@ import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing'; import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { useAbpStore } from '@abp/core'; import { useAbpStore, useFeatures } from '@abp/core';
import { PermissionModal } from '@abp/permissions'; import { PermissionModal } from '@abp/permissions';
import { useVbenVxeGrid } from '@abp/ui'; import { useVbenVxeGrid } from '@abp/ui';
import { import {
@ -37,7 +37,9 @@ const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const RoleModal = defineAsyncComponent(() => import('./RoleModal.vue')); const RoleModal = defineAsyncComponent(() => import('./RoleModal.vue'));
const ClaimModal = defineAsyncComponent(() => import('./RoleClaimModal.vue')); const ClaimModal = defineAsyncComponent(() => import('./RoleClaimModal.vue'));
const abpStore = useAbpStore(); const abpStore = useAbpStore();
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const { cancel, deleteApi, getPagedListApi } = useRolesApi(); const { cancel, deleteApi, getPagedListApi } = useRolesApi();
@ -251,7 +253,10 @@ const handleMenuClick = async (row: IdentityRoleDto, info: MenuInfo) => {
{{ $t('AppPlatform.Menu:Manage') }} {{ $t('AppPlatform.Menu:Manage') }}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
v-if="hasAccessByCodes([AuditLogPermissions.Default])" v-if="
isEnabled('AbpAuditing.Logging.AuditLog') &&
hasAccessByCodes([AuditLogPermissions.Default])
"
key="entity-changes" key="entity-changes"
:icon="h(AuditLogIcon)" :icon="h(AuditLogIcon)"
> >

8
apps/vben5/packages/@abp/identity/src/components/users/UserTable.vue

@ -12,7 +12,7 @@ import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing'; import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { formatToDateTime, useAbpStore } from '@abp/core'; import { formatToDateTime, useAbpStore, useFeatures } from '@abp/core';
import { PermissionModal } from '@abp/permissions'; import { PermissionModal } from '@abp/permissions';
import { useVbenVxeGrid } from '@abp/ui'; import { useVbenVxeGrid } from '@abp/ui';
import { import {
@ -63,6 +63,7 @@ const getLockEnd = computed(() => {
}); });
const abpStore = useAbpStore(); const abpStore = useAbpStore();
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const { cancel, deleteApi, getPagedListApi, unLockApi } = useUsersApi(); const { cancel, deleteApi, getPagedListApi, unLockApi } = useUsersApi();
@ -353,7 +354,10 @@ const handleMenuClick = async (row: IdentityUserDto, info: MenuInfo) => {
{{ $t('AppPlatform.Menu:Manage') }} {{ $t('AppPlatform.Menu:Manage') }}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
v-if="hasAccessByCodes([AuditLogPermissions.Default])" v-if="
isEnabled('AbpAuditing.Logging.AuditLog') &&
hasAccessByCodes([AuditLogPermissions.Default])
"
key="entity-changes" key="entity-changes"
:icon="h(AuditLogIcon)" :icon="h(AuditLogIcon)"
> >

1
apps/vben5/packages/@abp/openiddict/package.json

@ -20,6 +20,7 @@
} }
}, },
"dependencies": { "dependencies": {
"@abp/auditing": "workspace:*",
"@abp/core": "workspace:*", "@abp/core": "workspace:*",
"@abp/identity": "workspace:*", "@abp/identity": "workspace:*",
"@abp/permissions": "workspace:*", "@abp/permissions": "workspace:*",

31
apps/vben5/packages/@abp/openiddict/src/components/applications/ApplicationTable.vue

@ -7,10 +7,12 @@ import type { OpenIddictApplicationDto } from '../../types/applications';
import { defineAsyncComponent, h } from 'vue'; import { defineAsyncComponent, h } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { useVbenModal } from '@vben/common-ui'; import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons'; import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { useFeatures } from '@abp/core';
import { PermissionModal } from '@abp/permissions'; import { PermissionModal } from '@abp/permissions';
import { useVbenVxeGrid } from '@abp/ui'; import { useVbenVxeGrid } from '@abp/ui';
import { import {
@ -29,11 +31,14 @@ defineOptions({
}); });
const MenuItem = Menu.Item; const MenuItem = Menu.Item;
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const CheckIcon = createIconifyIcon('ant-design:check-outlined'); const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined'); const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const SecretIcon = createIconifyIcon('codicon:gist-secret'); const SecretIcon = createIconifyIcon('codicon:gist-secret');
const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions'); const PermissionsOutlined = createIconifyIcon('icon-park-outline:permissions');
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
const { deleteApi, getPagedListApi } = useApplicationsApi(); const { deleteApi, getPagedListApi } = useApplicationsApi();
@ -148,6 +153,9 @@ const [ApplicationSecretModal, secretModalApi] = useVbenModal({
const [ApplicationPermissionModal, permissionModalApi] = useVbenModal({ const [ApplicationPermissionModal, permissionModalApi] = useVbenModal({
connectedComponent: PermissionModal, connectedComponent: PermissionModal,
}); });
const [ApplicationChangeDrawer, applicationChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const [Grid, { query }] = useVbenVxeGrid({ const [Grid, { query }] = useVbenVxeGrid({
formOptions, formOptions,
@ -179,6 +187,16 @@ const onDelete = (row: OpenIddictApplicationDto) => {
const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => { const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => {
switch (info.key) { switch (info.key) {
case 'entity-changes': {
applicationChangeDrawerApi.setData({
entityId: row.id,
entityTypeFullName:
'Volo.Abp.OpenIddict.Applications.OpenIddictApplication',
subject: row.clientId,
});
applicationChangeDrawerApi.open();
break;
}
case 'permissions': { case 'permissions': {
permissionModalApi.setData({ permissionModalApi.setData({
displayName: row.clientId, displayName: row.clientId,
@ -271,6 +289,16 @@ const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => {
> >
{{ $t('AbpOpenIddict.ManagePermissions') }} {{ $t('AbpOpenIddict.ManagePermissions') }}
</MenuItem> </MenuItem>
<MenuItem
v-if="
isEnabled('AbpAuditing.Logging.AuditLog') &&
hasAccessByCodes([AuditLogPermissions.Default])
"
key="entity-changes"
:icon="h(AuditLogIcon)"
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
</Menu> </Menu>
</template> </template>
<Button :icon="h(EllipsisOutlined)" type="link" /> <Button :icon="h(EllipsisOutlined)" type="link" />
@ -282,6 +310,7 @@ const onMenuClick = (row: OpenIddictApplicationDto, info: MenuInfo) => {
<ApplicationModal @change="() => query()" /> <ApplicationModal @change="() => query()" />
<ApplicationSecretModal @change="() => query()" /> <ApplicationSecretModal @change="() => query()" />
<ApplicationPermissionModal /> <ApplicationPermissionModal />
<ApplicationChangeDrawer />
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

89
apps/vben5/packages/@abp/openiddict/src/components/scopes/ScopeTable.vue

@ -1,22 +1,26 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VbenFormProps, VxeGridProps } from '@abp/ui'; import type { VbenFormProps, VxeGridProps } from '@abp/ui';
import type { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import type { OpenIddictScopeDto } from '../../types/scopes'; import type { OpenIddictScopeDto } from '../../types/scopes';
import { defineAsyncComponent, h } from 'vue'; import { defineAsyncComponent, h } from 'vue';
import { useVbenModal } from '@vben/common-ui'; import { useAccess } from '@vben/access';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons'; import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { formatToDateTime } from '@abp/core'; import { AuditLogPermissions, EntityChangeDrawer } from '@abp/auditing';
import { formatToDateTime, useFeatures } from '@abp/core';
import { useVbenVxeGrid } from '@abp/ui'; import { useVbenVxeGrid } from '@abp/ui';
import { import {
DeleteOutlined, DeleteOutlined,
EditOutlined, EditOutlined,
EllipsisOutlined,
PlusOutlined, PlusOutlined,
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { Button, message, Modal } from 'ant-design-vue'; import { Button, Dropdown, Menu, message, Modal } from 'ant-design-vue';
import { useScopesApi } from '../../api/useScopesApi'; import { useScopesApi } from '../../api/useScopesApi';
import { ScopesPermissions } from '../../constants/permissions'; import { ScopesPermissions } from '../../constants/permissions';
@ -25,9 +29,14 @@ defineOptions({
name: 'ScopeTable', name: 'ScopeTable',
}); });
const MenuItem = Menu.Item;
const AuditLogIcon = createIconifyIcon('fluent-mdl2:compliance-audit');
const CheckIcon = createIconifyIcon('ant-design:check-outlined'); const CheckIcon = createIconifyIcon('ant-design:check-outlined');
const CloseIcon = createIconifyIcon('ant-design:close-outlined'); const CloseIcon = createIconifyIcon('ant-design:close-outlined');
const { isEnabled } = useFeatures();
const { hasAccessByCodes } = useAccess();
const { deleteApi, getPagedListApi } = useScopesApi(); const { deleteApi, getPagedListApi } = useScopesApi();
const formOptions: VbenFormProps = { const formOptions: VbenFormProps = {
@ -109,9 +118,13 @@ const gridOptions: VxeGridProps<OpenIddictScopeDto> = {
zoom: true, zoom: true,
}, },
}; };
const [ScopeModal, modalApi] = useVbenModal({ const [ScopeModal, modalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(() => import('./ScopeModal.vue')), connectedComponent: defineAsyncComponent(() => import('./ScopeModal.vue')),
}); });
const [ScopeChangeDrawer, scopeChangeDrawerApi] = useVbenDrawer({
connectedComponent: EntityChangeDrawer,
});
const [Grid, { query }] = useVbenVxeGrid({ const [Grid, { query }] = useVbenVxeGrid({
formOptions, formOptions,
gridOptions, gridOptions,
@ -139,6 +152,19 @@ const onDelete = (row: OpenIddictScopeDto) => {
title: $t('AbpUi.AreYouSure'), title: $t('AbpUi.AreYouSure'),
}); });
}; };
const onMenuClick = (row: OpenIddictScopeDto, info: MenuInfo) => {
switch (info.key) {
case 'entity-changes': {
scopeChangeDrawerApi.setData({
entityId: row.id,
entityTypeFullName: 'Volo.Abp.OpenIddict.Scopes.OpenIddictScope',
subject: row.name,
});
scopeChangeDrawerApi.open();
break;
}
}
};
</script> </script>
<template> <template>
@ -167,33 +193,44 @@ const onDelete = (row: OpenIddictScopeDto) => {
</template> </template>
<template #action="{ row }"> <template #action="{ row }">
<div class="flex flex-row"> <div class="flex flex-row">
<div class="basis-1/3"> <Button
<Button :icon="h(EditOutlined)"
:icon="h(EditOutlined)" block
block type="link"
type="link" v-access:code="[ScopesPermissions.Update]"
v-access:code="[ScopesPermissions.Update]" @click="onUpdate(row)"
@click="onUpdate(row)" >
> {{ $t('AbpUi.Edit') }}
{{ $t('AbpUi.Edit') }} </Button>
</Button> <Button
</div> :icon="h(DeleteOutlined)"
<div class="basis-1/3"> block
<Button danger
:icon="h(DeleteOutlined)" type="link"
block v-access:code="[ScopesPermissions.Delete]"
danger @click="onDelete(row)"
type="link" >
v-access:code="[ScopesPermissions.Delete]" {{ $t('AbpUi.Delete') }}
@click="onDelete(row)" </Button>
> <Dropdown v-if="isEnabled('AbpAuditing.Logging.AuditLog')">
{{ $t('AbpUi.Delete') }} <template #overlay>
</Button> <Menu @click="(info) => onMenuClick(row, info)">
</div> <MenuItem
v-if="hasAccessByCodes([AuditLogPermissions.Default])"
key="entity-changes"
:icon="h(AuditLogIcon)"
>
{{ $t('AbpAuditLogging.EntitiesChanged') }}
</MenuItem>
</Menu>
</template>
<Button :icon="h(EllipsisOutlined)" type="link" />
</Dropdown>
</div> </div>
</template> </template>
</Grid> </Grid>
<ScopeModal @change="() => query()" /> <ScopeModal @change="() => query()" />
<ScopeChangeDrawer />
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped></style>

Loading…
Cancel
Save