Browse Source

Merge pull request #1144 from colinin/vben5-simple-state-check

feat(vben5): Added a simple state check component
pull/1149/head
yx lin 1 year ago
committed by GitHub
parent
commit
c1161bcfa1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      apps/vben5/apps/app-antd/src/adapter/component/index.ts
  2. 2
      apps/vben5/packages/@abp/core/package.json
  3. 2
      apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireAuthenticatedSimpleStateChecker.ts
  4. 2
      apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireFeaturesSimpleStateChecker.ts
  5. 18
      apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts
  6. 2
      apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequirePermissionsSimpleStateChecker.ts
  7. 27
      apps/vben5/packages/@abp/core/src/hooks/useSimpleStateCheck.ts
  8. 1
      apps/vben5/packages/@abp/core/src/utils/array.ts
  9. 1
      apps/vben5/packages/@abp/core/src/utils/index.ts
  10. 34
      apps/vben5/packages/@abp/core/src/utils/tree.ts
  11. 2
      apps/vben5/packages/@abp/features/src/components/index.ts
  12. 135
      apps/vben5/packages/@abp/features/src/components/state-check/FeatureStateCheck.vue
  13. 65
      apps/vben5/packages/@abp/features/src/components/state-check/GlobalFeatureStateCheck.vue
  14. 33
      apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionModal.vue
  15. 1
      apps/vben5/packages/@abp/permissions/src/components/index.ts
  16. 134
      apps/vben5/packages/@abp/permissions/src/components/state-check/PermissionStateCheck.vue
  17. 4
      apps/vben5/packages/@abp/ui/package.json
  18. 1
      apps/vben5/packages/@abp/ui/src/components/index.ts
  19. 265
      apps/vben5/packages/@abp/ui/src/components/simple-state-checking/SimpleStateChecking.vue
  20. 231
      apps/vben5/packages/@abp/ui/src/components/simple-state-checking/SimpleStateCheckingModal.vue
  21. 2
      apps/vben5/packages/@abp/ui/src/components/simple-state-checking/index.ts
  22. 6
      apps/vben5/packages/@abp/ui/src/components/simple-state-checking/interface.ts
  23. 2
      apps/vben5/pnpm-workspace.yaml

10
apps/vben5/apps/app-antd/src/adapter/component/index.ts

@ -3,14 +3,17 @@
* vben-formvben-modalvben-drawer 使,
*/
import type { Component, SetupContext } from 'vue';
import type { BaseFormComponentType } from '@vben/common-ui';
import type { Component, SetupContext } from 'vue';
import { h } from 'vue';
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { FeatureStateCheck, GlobalFeatureStateCheck } from '@abp/features';
import { PermissionStateCheck } from '@abp/permissions';
import {
AutoComplete,
Button,
@ -18,6 +21,7 @@ import {
CheckboxGroup,
DatePicker,
Divider,
Empty,
Input,
InputNumber,
InputPassword,
@ -123,6 +127,7 @@ async function initComponentAdapter() {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
Divider,
Empty,
IconPicker: (props, { attrs, slots }) => {
return h(
IconPicker,
@ -150,6 +155,9 @@ async function initComponentAdapter() {
TimePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
FeatureStateCheck,
GlobalFeatureStateCheck,
PermissionStateCheck,
};
// 将组件注册到全局共享状态中

2
apps/vben5/packages/@abp/core/package.json

@ -37,12 +37,14 @@
"dependencies": {
"@vueuse/core": "catalog:",
"dayjs": "catalog:",
"lodash.groupby": "catalog:",
"lodash.merge": "catalog:",
"pinia": "catalog:",
"pinia-plugin-persistedstate": "catalog:",
"vue": "catalog:"
},
"devDependencies": {
"@types/lodash.groupby": "catalog:",
"@types/lodash.merge": "catalog:"
}
}

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

@ -14,7 +14,7 @@ export interface RequireAuthenticatedStateChecker {
export class RequireAuthenticatedSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>
implements RequireAuthenticatedStateChecker, ISimpleStateChecker<TState>
implements ISimpleStateChecker<TState>, RequireAuthenticatedStateChecker
{
_currentUser?: CurrentUser;
name = 'A';

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

@ -16,7 +16,7 @@ export interface RequireFeaturesStateChecker {
export class RequireFeaturesSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>
implements RequireFeaturesStateChecker, ISimpleStateChecker<TState>
implements ISimpleStateChecker<TState>, RequireFeaturesStateChecker
{
_featureChecker: IFeatureChecker;
featureNames: string[];

18
apps/vben5/packages/@abp/core/src/hooks/SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker.ts

@ -8,7 +8,7 @@ import type {
import { useGlobalFeatures } from '../useGlobalFeatures';
export interface RequireGlobalFeaturesStateChecker {
featureNames: string[];
globalFeatureNames: string[];
name: string;
requiresAll: boolean;
}
@ -16,24 +16,24 @@ export interface RequireGlobalFeaturesStateChecker {
export class RequireGlobalFeaturesSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>
implements RequireGlobalFeaturesStateChecker, ISimpleStateChecker<TState>
implements ISimpleStateChecker<TState>, RequireGlobalFeaturesStateChecker
{
_globalFeatureChecker: IGlobalFeatureChecker;
featureNames: string[];
globalFeatureNames: string[];
name: string = 'G';
requiresAll: boolean;
constructor(
globalFeatureChecker: IGlobalFeatureChecker,
featureNames: string[],
globalFeatureNames: string[],
requiresAll: boolean = false,
) {
this._globalFeatureChecker = globalFeatureChecker;
this.featureNames = featureNames;
this.globalFeatureNames = globalFeatureNames;
this.requiresAll = requiresAll;
}
isEnabled(_context: SimpleStateCheckerContext<TState>): boolean {
return this._globalFeatureChecker.isEnabled(
this.featureNames,
this.globalFeatureNames,
this.requiresAll,
);
}
@ -41,7 +41,7 @@ export class RequireGlobalFeaturesSimpleStateChecker<
serialize(): string {
return JSON.stringify({
A: this.requiresAll,
N: this.featureNames,
N: this.globalFeatureNames,
T: this.name,
});
}
@ -50,13 +50,13 @@ export class RequireGlobalFeaturesSimpleStateChecker<
export function useRequireGlobalFeaturesSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>(
featureNames: string[],
globalFeatureNames: string[],
requiresAll: boolean = false,
): ISimpleStateChecker<TState> {
const globalFeatureChecker = useGlobalFeatures();
return new RequireGlobalFeaturesSimpleStateChecker(
globalFeatureChecker,
featureNames,
globalFeatureNames,
requiresAll,
);
}

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

@ -37,7 +37,7 @@ export interface RequirePermissionsStateChecker<
export class RequirePermissionsSimpleStateChecker<
TState extends IHasSimpleStateCheckers<TState>,
>
implements RequirePermissionsStateChecker<TState>, ISimpleStateChecker<TState>
implements ISimpleStateChecker<TState>, RequirePermissionsStateChecker<TState>
{
_permissionChecker: IPermissionChecker;
model: RequirePermissionsSimpleBatchStateCheckerModel<TState>;

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

@ -11,8 +11,10 @@ import { useRequireFeaturesSimpleStateChecker } from './SimpleStateChecking/useR
import { useRequireGlobalFeaturesSimpleStateChecker } from './SimpleStateChecking/useRequireGlobalFeaturesSimpleStateChecker';
import { useRequirePermissionsSimpleStateChecker } from './SimpleStateChecking/useRequirePermissionsSimpleStateChecker';
class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
deserialize<TState extends IHasSimpleStateCheckers<TState>>(
export function useSimpleStateCheck<
TState extends IHasSimpleStateCheckers<TState>,
>(): ISimpleStateCheckerSerializer {
function deserialize<TState extends IHasSimpleStateCheckers<TState>>(
jsonObject: any,
state: TState,
): ISimpleStateChecker<TState> | undefined {
@ -72,7 +74,7 @@ class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
}
}
deserializeArray<TState extends IHasSimpleStateCheckers<TState>>(
function deserializeArray<TState extends IHasSimpleStateCheckers<TState>>(
value: string,
state: TState,
): ISimpleStateChecker<TState>[] {
@ -82,22 +84,22 @@ class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
if (Array.isArray(jsonObject)) {
if (jsonObject.length === 0) return [];
return jsonObject
.map((json) => this.deserialize(json, state))
.map((json) => deserialize(json, state))
.filter((checker) => !isNullOrUnDef(checker))
.map((checker) => checker);
}
const stateChecker = this.deserialize(jsonObject, state);
const stateChecker = deserialize(jsonObject, state);
if (!stateChecker) return [];
return [stateChecker];
}
serialize<TState extends IHasSimpleStateCheckers<TState>>(
function serialize<TState extends IHasSimpleStateCheckers<TState>>(
checker: ISimpleStateChecker<TState>,
): string | undefined {
return checker.serialize();
}
serializeArray<TState extends IHasSimpleStateCheckers<TState>>(
function serializeArray<TState extends IHasSimpleStateCheckers<TState>>(
stateCheckers: ISimpleStateChecker<TState>[],
): string | undefined {
if (stateCheckers.length === 0) return undefined;
@ -124,10 +126,11 @@ class SimpleStateCheckerSerializer implements ISimpleStateCheckerSerializer {
? `[${serializedCheckers}]`
: undefined;
}
}
export function useSimpleStateCheck<
TState extends IHasSimpleStateCheckers<TState>,
>(): ISimpleStateCheckerSerializer {
return new SimpleStateCheckerSerializer();
return {
deserialize,
deserializeArray,
serialize,
serializeArray,
};
}

1
apps/vben5/packages/@abp/core/src/utils/array.ts

@ -0,0 +1 @@
export { default as groupBy } from 'lodash.groupby';

1
apps/vben5/packages/@abp/core/src/utils/index.ts

@ -1,3 +1,4 @@
export * from './array';
export * from './date';
export * from './is';
export * from './mitt';

34
apps/vben5/packages/@abp/core/src/utils/tree.ts

@ -18,20 +18,28 @@ export function listToTree<T = any>(
config: Partial<TreeHelperConfig> = {},
): T[] {
const conf = getConfig(config) as TreeHelperConfig;
const nodeMap = new Map();
const result: T[] = [];
const map: { [key: string]: any } = {};
const roots: any[] = [];
const { id, pid, children } = conf;
for (const node of list) {
node[children] = node[children] || [];
nodeMap.set(node[id], node);
}
for (const node of list) {
const parent = nodeMap.get(node[pid]);
(parent ? parent[children] : result).push(node);
if (parent) {
parent.hasChildren = true;
// 将每个元素放入map中,方便通过id查找
list.forEach((item) => {
map[item[id]] = { ...item, [children]: [] };
});
// 构建树形结构
list.forEach((item) => {
const parentId = item[pid];
if (parentId === null || parentId === undefined) {
// 根节点
roots.push(map[item[id]]);
} else {
// 非根节点,将其添加到父节点的children数组中
if (map[parentId]) {
map[parentId][children].push(map[item[id]]);
}
}
}
return result;
});
return roots;
}

2
apps/vben5/packages/@abp/features/src/components/index.ts

@ -1,3 +1,5 @@
export { default as FeatureDefinitionTable } from './definitions/features/FeatureDefinitionTable.vue';
export { default as FeatureGroupDefinitionTable } from './definitions/groups/FeatureGroupDefinitionTable.vue';
export { default as FeatureModal } from './features/FeatureModal.vue';
export { default as FeatureStateCheck } from './state-check/FeatureStateCheck.vue';
export { default as GlobalFeatureStateCheck } from './state-check/GlobalFeatureStateCheck.vue';

135
apps/vben5/packages/@abp/features/src/components/state-check/FeatureStateCheck.vue

@ -0,0 +1,135 @@
<script setup lang="ts">
import type {
FeatureDefinitionDto,
FeatureGroupDefinitionDto,
} from '../../types';
import { computed, onMounted, ref } from 'vue';
import {
listToTree,
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
import { Checkbox, FormItemRest, TreeSelect } from 'ant-design-vue';
import { useFeatureDefinitionsApi } from '../../api/useFeatureDefinitionsApi';
import { useFeatureGroupDefinitionsApi } from '../../api/useFeatureGroupDefinitionsApi';
interface FeatureTreeData {
checkable?: boolean;
children: FeatureTreeData[];
displayName: string;
groupName: string;
name: string;
}
const emits = defineEmits(['blur', 'change']);
const { getListApi: getFeaturesApi } = useFeatureDefinitionsApi();
const { getListApi: getFeatureGroupsApi } = useFeatureGroupDefinitionsApi();
const { deserialize } = useLocalizationSerializer();
const { Lr } = useLocalization();
const features = ref<FeatureDefinitionDto[]>([]);
const featureGroups = ref<FeatureGroupDefinitionDto[]>([]);
const modelValue = defineModel<{
featureNames: string[];
requiresAll: boolean;
}>({
default: {
featureNames: [],
requiresAll: false,
},
});
const getFeatureOptions = computed(() => {
const featureOptions: FeatureTreeData[] = [];
featureGroups.value.forEach((group) => {
featureOptions.push({
checkable: false,
displayName: group.displayName,
groupName: group.name,
name: group.name,
children: listToTree(
features.value.filter((x) => x.groupName === group.name),
{
id: 'name',
pid: 'parentName',
},
),
});
});
return featureOptions;
});
const getRequiredFeatures = computed(() => {
const featureNames = modelValue.value?.featureNames ?? [];
const requiredFeatures = features.value
.filter((feature) => featureNames.includes(feature.name))
.map((feature) => {
return {
label: feature.displayName,
value: feature.name,
};
});
return requiredFeatures;
});
function onChange() {
emits('change', modelValue.value);
}
function onFeaturesChange(value: any[]) {
modelValue.value.featureNames = value.map((item) => item.value);
emits('change', modelValue.value);
}
async function onInit() {
const [featureGroupRes, featuresRes] = await Promise.all([
getFeatureGroupsApi(),
getFeaturesApi(),
]);
featureGroups.value = featureGroupRes.items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
features.value = featuresRes.items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
}
onMounted(onInit);
</script>
<template>
<div class="flex w-full flex-col gap-5">
<FormItemRest>
<Checkbox
v-model:checked="modelValue.requiresAll"
@blur="emits('blur')"
@change="onChange"
>
{{ $t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
<TreeSelect
:tree-data="getFeatureOptions"
allow-clear
tree-checkable
tree-check-strictly
:field-names="{ label: 'displayName', value: 'name' }"
:value="getRequiredFeatures"
@blur="emits('blur')"
@change="onFeaturesChange"
/>
</FormItemRest>
</div>
</template>
<style scoped></style>

65
apps/vben5/packages/@abp/features/src/components/state-check/GlobalFeatureStateCheck.vue

@ -0,0 +1,65 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useAbpStore } from '@abp/core';
import { Checkbox, FormItemRest, Select } from 'ant-design-vue';
const emits = defineEmits(['blur', 'change']);
const modelValue = defineModel<{
globalFeatureNames: string[];
requiresAll: boolean;
}>({
default: {
globalFeatureNames: [],
requiresAll: false,
},
});
const abpStore = useAbpStore();
const getFeatureOptions = computed(() => {
if (!abpStore.application?.globalFeatures) {
return [];
}
return abpStore.application.globalFeatures.enabledFeatures.map((feature) => {
return {
label: feature,
value: feature,
};
});
});
function onChange() {
emits('change', modelValue.value);
}
function onFeaturesChange(value: any) {
modelValue.value.globalFeatureNames = value;
emits('change', modelValue.value);
}
</script>
<template>
<div class="flex w-full flex-col gap-5">
<FormItemRest>
<Checkbox
v-model:checked="modelValue.requiresAll"
@blur="emits('blur')"
@change="onChange"
>
{{ $t('component.simple_state_checking.requireFeatures.requiresAll') }}
</Checkbox>
<Select
:value="modelValue.globalFeatureNames"
:options="getFeatureOptions"
show-search
mode="tags"
@blur="emits('blur')"
@change="onFeaturesChange"
/>
</FormItemRest>
</div>
</template>
<style scoped></style>

33
apps/vben5/packages/@abp/permissions/src/components/definitions/permissions/PermissionDefinitionModal.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { IHasSimpleStateCheckers, ISimpleStateChecker } from '@abp/core';
import type { PropertyInfo } from '@abp/ui';
import type { FormInstance } from 'ant-design-vue';
@ -15,7 +16,7 @@ import {
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
import { LocalizableInput, PropertyTable } from '@abp/ui';
import { LocalizableInput, PropertyTable, SimpleStateChecking } from '@abp/ui';
import {
Checkbox,
Form,
@ -40,6 +41,13 @@ const emits = defineEmits<{
const FormItem = Form.Item;
const TabPane = Tabs.TabPane;
interface PermissionStateChecker
extends IHasSimpleStateCheckers<PermissionStateChecker> {}
class PermissionState implements PermissionStateChecker {
stateCheckers: ISimpleStateChecker<PermissionStateChecker>[] = [];
}
interface PermissionTreeVo {
children: PermissionTreeVo[];
displayName: string;
@ -52,6 +60,7 @@ type TabKeys = 'basic' | 'props';
const defaultModel = {
isEnabled: true,
} as PermissionDefinitionDto;
const permissionState = new PermissionState();
const isEditModel = ref(false);
const activeTab = ref<TabKeys>('basic');
@ -72,6 +81,7 @@ const {
} = usePermissionDefinitionsApi();
const [Modal, modalApi] = useVbenModal({
class: 'w-1/2',
closeOnClickModal: false,
draggable: true,
fullscreenButton: false,
onCancel() {
@ -269,6 +279,26 @@ function onPropDelete(prop: PropertyInfo) {
/>
</FormItem>
</TabPane>
<!-- 状态检查 -->
<TabPane
key="stateCheckers"
:tab="$t('AbpPermissionManagement.StateCheckers')"
>
<FormItem
name="stateCheckers"
label=""
:label-col="{ span: 0 }"
:wrapper-col="{ span: 24 }"
>
<SimpleStateChecking
:disabled="formModel.isStatic"
:allow-delete="true"
:allow-edit="true"
v-model:value="formModel.stateCheckers"
:state="permissionState"
/>
</FormItem>
</TabPane>
<!-- 属性 -->
<TabPane key="props" :tab="$t('AbpPermissionManagement.Properties')">
<PropertyTable
@ -284,4 +314,3 @@ function onPropDelete(prop: PropertyInfo) {
</template>
<style scoped></style>
import { useLocalization, useLocalizationSerializer } from '@abp/core';

1
apps/vben5/packages/@abp/permissions/src/components/index.ts

@ -1,3 +1,4 @@
export { default as PermissionGroupDefinitionTable } from './definitions/groups/PermissionGroupDefinitionTable.vue';
export { default as PermissionDefinitionTable } from './definitions/permissions/PermissionDefinitionTable.vue';
export { default as PermissionModal } from './permissions/PermissionModal.vue';
export { default as PermissionStateCheck } from './state-check/PermissionStateCheck.vue';

134
apps/vben5/packages/@abp/permissions/src/components/state-check/PermissionStateCheck.vue

@ -0,0 +1,134 @@
<script setup lang="ts">
import type { PermissionDefinitionDto } from '../../types/definitions';
import type { PermissionGroupDefinitionDto } from '../../types/groups';
import { computed, onMounted, ref } from 'vue';
import {
listToTree,
useLocalization,
useLocalizationSerializer,
} from '@abp/core';
import { Checkbox, FormItemRest, TreeSelect } from 'ant-design-vue';
import { usePermissionDefinitionsApi } from '../../api/usePermissionDefinitionsApi';
import { usePermissionGroupDefinitionsApi } from '../../api/usePermissionGroupDefinitionsApi';
interface TreeData {
checkable?: boolean;
children: TreeData[];
displayName: string;
groupName: string;
name: string;
}
const emits = defineEmits(['blur', 'change']);
const { getListApi: getPermissionsApi } = usePermissionDefinitionsApi();
const { getListApi: getPermissionGroupsApi } =
usePermissionGroupDefinitionsApi();
const { deserialize } = useLocalizationSerializer();
const { Lr } = useLocalization();
const permissions = ref<PermissionDefinitionDto[]>([]);
const permissionGroups = ref<PermissionGroupDefinitionDto[]>([]);
const modelValue = defineModel<{
permissions: string[];
requiresAll: boolean;
}>({
default: {
permissions: [],
requiresAll: false,
},
});
const getPermissionOptions = computed(() => {
const permissionOptions: TreeData[] = [];
permissionGroups.value.forEach((group) => {
permissionOptions.push({
checkable: false,
displayName: group.displayName,
groupName: group.name,
name: group.name,
children: listToTree(
permissions.value.filter((x) => x.groupName === group.name),
{
id: 'name',
pid: 'parentName',
},
),
});
});
return permissionOptions;
});
const getRequiredPermissions = computed(() => {
const permissionNames = modelValue.value?.permissions ?? [];
return permissions.value
.filter((permission) => permissionNames.includes(permission.name))
.map((permission) => {
return {
label: permission.displayName,
value: permission.name,
};
});
});
function onChange() {
emits('change', modelValue.value);
}
function onPermissionsChange(value: any[]) {
modelValue.value.permissions = value.map((item) => item.value);
emits('change', modelValue.value);
}
async function onInit() {
const [permissionGroupRes, permissionsRes] = await Promise.all([
getPermissionGroupsApi(),
getPermissionsApi(),
]);
permissionGroups.value = permissionGroupRes.items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
permissions.value = permissionsRes.items.map((item) => {
const displayName = deserialize(item.displayName);
return {
...item,
displayName: Lr(displayName.resourceName, displayName.name),
};
});
}
onMounted(onInit);
</script>
<template>
<div class="flex w-full flex-col gap-5">
<FormItemRest>
<Checkbox
v-model:checked="modelValue.requiresAll"
@blur="emits('blur')"
@change="onChange"
>
{{
$t('component.simple_state_checking.requirePermissions.requiresAll')
}}
</Checkbox>
<TreeSelect
:tree-data="getPermissionOptions"
allow-clear
tree-checkable
tree-check-strictly
:field-names="{ label: 'displayName', value: 'name' }"
:value="getRequiredPermissions"
@blur="emits('blur')"
@change="onPermissionsChange"
/>
</FormItemRest>
</div>
</template>
<style scoped></style>

4
apps/vben5/packages/@abp/ui/package.json

@ -35,8 +35,12 @@
"@vben/utils": "workspace:*",
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"lodash.clonedeep": "catalog:",
"vue": "catalog:*",
"vxe-pc-ui": "catalog:",
"vxe-table": "catalog:"
},
"devDependencies": {
"@types/lodash.clonedeep": "catalog:"
}
}

1
apps/vben5/packages/@abp/ui/src/components/index.ts

@ -1,5 +1,6 @@
export { default as LocalizableInput } from './localizable-input/LocalizableInput.vue';
export { default as PropertyTable } from './properties/PropertyTable.vue';
export * from './properties/types';
export * from './simple-state-checking';
export * from './string-value-type';
export type * from './vxe-table';

265
apps/vben5/packages/@abp/ui/src/components/simple-state-checking/SimpleStateChecking.vue

@ -0,0 +1,265 @@
<script setup lang="ts">
import type { ColumnsType } from 'ant-design-vue/lib/table';
import type { PropType } from 'vue';
import type { SimplaCheckStateBase } from './interface';
import { computed, reactive, unref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { createIconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales';
import { isNullOrUnDef, useSimpleStateCheck } from '@abp/core';
import { Button, Card, Col, Row, Table, Tag } from 'ant-design-vue';
import SimpleStateCheckingModal from './SimpleStateCheckingModal.vue';
const props = defineProps({
allowDelete: {
default: false,
type: Boolean,
},
allowEdit: {
default: false,
type: Boolean,
},
disabled: {
default: false,
type: Boolean,
},
state: {
required: true,
type: Object as PropType<SimplaCheckStateBase>,
},
value: {
default: '',
type: String,
},
});
const emits = defineEmits(['change', 'update:value']);
const DeleteOutlined = createIconifyIcon('ant-design:delete-outlined');
const EditOutlined = createIconifyIcon('ant-design:edit-outlined');
const simpleCheckerMap = reactive<{ [key: string]: string }>({
A: $t('component.simple_state_checking.requireAuthenticated.title'),
F: $t('component.simple_state_checking.requireFeatures.title'),
G: $t('component.simple_state_checking.requireGlobalFeatures.title'),
P: $t('component.simple_state_checking.requirePermissions.title'),
});
const { deserialize, deserializeArray, serializeArray } = useSimpleStateCheck();
const [Modal, modalApi] = useVbenModal({
connectedComponent: SimpleStateCheckingModal,
});
const getTableColumns = computed(() => {
const columns: ColumnsType = [
{
align: 'left',
dataIndex: 'name',
fixed: 'left',
key: 'name',
maxWidth: 150,
title: $t('component.simple_state_checking.table.name'),
},
{
align: 'left',
dataIndex: 'properties',
fixed: 'left',
key: 'properties',
title: $t('component.simple_state_checking.table.properties'),
},
];
return [
...columns,
...(props.disabled
? []
: [
{
align: 'center',
dataIndex: 'action',
fixed: 'right',
key: 'action',
title: $t('component.simple_state_checking.table.actions'),
width: 180,
},
]),
];
});
const getSimpleCheckers = computed(() => {
if (isNullOrUnDef(props.value) || props.value.length === 0) {
return [];
}
const simpleCheckers = deserializeArray(props.value, props.state);
return simpleCheckers;
});
const getStateCheckerOptions = computed(() => {
const stateCheckers = unref(getSimpleCheckers);
return Object.keys(simpleCheckerMap).map((key) => {
return {
disabled: stateCheckers.some((x) => (x as any).name === key),
label: simpleCheckerMap[key],
value: key,
};
});
});
function handleAddNew() {
modalApi.setData({
options: getStateCheckerOptions.value,
});
modalApi.open();
}
function handleEdit(record: any) {
modalApi.setData({
options: getStateCheckerOptions.value,
record,
});
modalApi.open();
}
function onChange(record: any) {
const stateChecker = deserialize(record, props.state)!;
const stateCheckers = unref(getSimpleCheckers);
const inputIndex = stateCheckers.findIndex(
(x) => (x as any).name === record.name,
);
if (inputIndex === -1) {
stateCheckers.push(stateChecker);
} else {
stateCheckers[inputIndex] = stateChecker;
}
const updateValue = serializeArray(stateCheckers);
emits('change', updateValue);
emits('update:value', updateValue);
}
function handleDelete(record: any) {
const stateCheckers = unref(getSimpleCheckers);
const filtedStateCheckers = stateCheckers.filter(
(x) => (x as any).name !== record.name,
);
if (filtedStateCheckers.length === 0) {
handleClean();
return;
}
const serializedCheckers = serializeArray(filtedStateCheckers);
emits('change', serializedCheckers);
emits('update:value', serializedCheckers);
}
function handleClean() {
emits('change', undefined);
emits('update:value', undefined);
}
</script>
<template>
<div class="w-full">
<div class="card">
<Card>
<template #title>
<Row>
<Col :span="12">
<span>{{ $t('component.simple_state_checking.title') }}</span>
</Col>
<Col :span="12">
<div class="toolbar" v-if="!props.disabled">
<Button type="primary" @click="handleAddNew">
{{ $t('component.simple_state_checking.actions.create') }}
</Button>
<Button danger @click="handleClean">
{{ $t('component.simple_state_checking.actions.clean') }}
</Button>
</div>
</Col>
</Row>
</template>
<Table :columns="getTableColumns" :data-source="getSimpleCheckers">
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<span>{{ simpleCheckerMap[record.name] }}</span>
</template>
<template v-else-if="column.key === 'properties'">
<div v-if="record.name === 'F'">
<Tag v-for="feature in record.featureNames" :key="feature">
{{ feature }}
</Tag>
</div>
<div v-else-if="record.name === 'G'">
<Tag
v-for="feature in record.globalFeatureNames"
:key="feature"
>
{{ feature }}
</Tag>
</div>
<div v-else-if="record.name === 'P'">
<Tag
v-for="permission in record.model.permissions"
:key="permission"
>
{{ permission }}
</Tag>
</div>
<div v-else-if="record.name === 'A'">
<span>{{
$t(
'component.simple_state_checking.requireAuthenticated.title',
)
}}</span>
</div>
</template>
<template v-else-if="column.key === 'action'">
<div class="flex flex-row">
<Button
v-if="props.allowEdit"
type="link"
@click="() => handleEdit(record)"
>
<template #icon>
<EditOutlined />
</template>
{{ $t('component.simple_state_checking.actions.update') }}
</Button>
<Button
v-if="props.allowDelete"
type="link"
@click="() => handleDelete(record)"
danger
>
<template #icon>
<DeleteOutlined />
</template>
{{ $t('component.simple_state_checking.actions.delete') }}
</Button>
</div>
</template>
</template>
</Table>
</Card>
</div>
</div>
<Modal @change="onChange" />
</template>
<style lang="less" scoped>
.card {
.toolbar {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 8px;
> * {
margin-right: 8px;
}
}
}
</style>

231
apps/vben5/packages/@abp/ui/src/components/simple-state-checking/SimpleStateCheckingModal.vue

@ -0,0 +1,231 @@
<script setup lang="ts">
import { toRaw } from 'vue';
import { useVbenForm, useVbenModal, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
interface ModalState {
options: { disabled?: boolean; label: string; value: string }[];
record: any;
}
const emits = defineEmits(['change']);
const [Modal, modalApi] = useVbenModal({
closeOnClickModal: false,
closeOnPressEscape: false,
onConfirm: onSubmit,
onOpenChange(isOpen) {
if (isOpen) {
onInit();
}
},
});
const [Form, formApi] = useVbenForm({
schema: [
{
component: 'Select',
fieldName: 'name',
label: $t('component.simple_state_checking.form.name'),
rules: 'selectRequired',
},
{
component: 'Empty',
disabledOnChangeListener: false,
fieldName: 'value',
label: '',
},
],
showDefaultActions: false,
});
async function onInit() {
const state = modalApi.getData<ModalState>();
formApi.updateSchema([
{
componentProps: {
disabled: !!state.record,
onChange,
options: state.options,
},
fieldName: 'name',
},
{
component: 'Empty',
fieldName: 'value',
label: '',
rules: z.object({}).optional(),
},
]);
formApi.setValues({
name: state.record?.name,
value: {},
});
if (state.record) {
onRecordChange(state.record);
}
}
async function onRecordChange(record: any) {
await formApi.setFieldValue('name', record.name);
onChange(record.name);
switch (record.name) {
case 'F': {
await formApi.setFieldValue('value', {
featureNames: record.featureNames,
requiresAll: record.requiresAll,
});
break;
}
case 'G': {
await formApi.setFieldValue('value', {
globalFeatureNames: record.globalFeatureNames,
requiresAll: record.requiresAll,
});
break;
}
case 'P': {
await formApi.setFieldValue('value', {
permissions: record.permissions,
requiresAll: record.requiresAll,
});
break;
}
}
}
function onChange(value: string) {
formApi.updateSchema([
{
component: 'Empty',
controlClass: '',
fieldName: 'value',
label: '',
rules: z.object({}).optional(),
},
]);
formApi.setFieldValue('value', {});
switch (value) {
case 'A': {
formApi.updateSchema([
{
controlClass: 'invisible',
fieldName: 'value',
},
]);
break;
}
case 'F': {
formApi.updateSchema([
{
component: 'FeatureStateCheck',
fieldName: 'value',
label: $t(
'component.simple_state_checking.requireFeatures.featureNames',
),
// TODO: vben, 使
rules: z
.object({
featureNames: z.array(z.string()),
requiresAll: z.boolean().optional(),
})
.refine((v) => v.featureNames.length > 0, {
message: `${$t('ui.placeholder.select')}${$t('component.simple_state_checking.requireFeatures.featureNames')}`,
}),
},
]);
formApi.setFieldValue('value', {
featureNames: [],
requiresAll: false,
});
break;
}
case 'G': {
formApi.updateSchema([
{
component: 'GlobalFeatureStateCheck',
fieldName: 'value',
label: $t(
'component.simple_state_checking.requireFeatures.featureNames',
),
// TODO: vben, 使
rules: z
.object({
globalFeatureNames: z.array(z.string()),
requiresAll: z.boolean().optional(),
})
.refine((v) => v.globalFeatureNames.length > 0, {
message: `${$t('ui.placeholder.select')}${$t('component.simple_state_checking.requireFeatures.featureNames')}`,
}),
},
]);
formApi.setFieldValue('value', {
globalFeatureNames: [],
requiresAll: false,
});
break;
}
case 'P': {
formApi.updateSchema([
{
component: 'PermissionStateCheck',
fieldName: 'value',
label: $t(
'component.simple_state_checking.requirePermissions.permissions',
),
// TODO: vben, 使
rules: z
.object({
permissions: z.array(z.string()),
requiresAll: z.boolean().optional(),
})
.refine((v) => v.permissions.length > 0, {
message: `${$t('ui.placeholder.select')}${$t('component.simple_state_checking.requirePermissions.permissions')}`,
}),
},
]);
formApi.setFieldValue('value', {
permissions: [],
requiresAll: false,
});
break;
}
}
}
async function onSubmit() {
const result = await formApi.validate();
if (result.valid) {
const values = await formApi.getValues();
const value = toRaw(values.value);
const stateChecker: { [key: string]: any } = {
A: value.requiresAll,
T: values.name,
};
switch (values.name) {
case 'F': {
stateChecker.N = value.featureNames;
break;
}
case 'G': {
stateChecker.N = value.globalFeatureNames;
break;
}
case 'P': {
stateChecker.N = value.permissions;
break;
}
}
emits('change', stateChecker);
modalApi.close();
}
}
</script>
<template>
<Modal :title="$t('component.simple_state_checking.title')">
<Form />
</Modal>
</template>
<style scoped></style>

2
apps/vben5/packages/@abp/ui/src/components/simple-state-checking/index.ts

@ -0,0 +1,2 @@
export * from './interface';
export { default as SimpleStateChecking } from './SimpleStateChecking.vue';

6
apps/vben5/packages/@abp/ui/src/components/simple-state-checking/interface.ts

@ -0,0 +1,6 @@
import type { IHasSimpleStateCheckers, ISimpleStateChecker } from '@abp/core';
export interface SimplaCheckStateBase
extends IHasSimpleStateCheckers<SimplaCheckStateBase> {
stateCheckers: ISimpleStateChecker<SimplaCheckStateBase>[];
}

2
apps/vben5/pnpm-workspace.yaml

@ -49,6 +49,7 @@ catalog:
'@types/lodash.clonedeep': ^4.5.9
'@types/lodash.debounce': ^4.0.9
'@types/lodash.get': ^4.4.9
'@types/lodash.groupby': ^4.6.9
'@types/lodash.isequal': ^4.5.8
'@types/lodash.isnumber': ^3.0.9
'@types/lodash.merge': ^4.6.9
@ -124,6 +125,7 @@ catalog:
lodash.clonedeep: ^4.5.0
lodash.debounce: ^4.0.8
lodash.get: ^4.4.2
lodash.groupby: ^4.6.0
lodash.isequal: ^4.5.0
lodash.isnumber: ^3.0.3
lodash.merge: ^4.6.2

Loading…
Cancel
Save