add action permissions

This commit is contained in:
xue jiahao
2024-12-30 14:14:54 +08:00
parent a4d59de2bc
commit 9d0cabcffa
11 changed files with 97 additions and 28 deletions

View File

@@ -13,22 +13,63 @@ export const MenuMapping = {
userDomain: null,
};
export const Actions = {
user: {
show: 'user_list',
add: 'user_add',
edit: 'user_edit',
delete: 'user_delete',
},
role: {
show: 'role_list',
},
domain: {
show: 'domain_list',
add: 'domain_add',
edit: 'domain_edit',
delete: 'domain_delete',
grantUse: {
list: 'domain_grant_use_list',
edit: 'domain_grant_use_edit',
},
grantManage: {
list: 'domain_grant_manage_list',
edit: 'domain_grant_manage_edit',
},
},
};
const store = useAuthStore();
export default boot(({ app }) => {
app.directive('permissions', {
mounted(el: HTMLElement, binding: DirectiveBinding) {
const { value } = binding;
if (!value || store.isSuperAdmin) {
return;
if (!hasPermission(binding.value)) {
hideElement(el);
}
if (!store.permissions[value]) {
if (el.parentNode) {
el.parentNode.removeChild(el);
} else {
el.style.display = 'none';
}
}
},
},
});
app.config.globalProperties.$hasPermission = hasPermission;
});
function hasPermission(value: any) {
if (!value || store.isSuperAdmin) {
return true;
}
if (typeof value === 'string') {
return store.permissions[value];
} else if (typeof value === 'object') {
return Object.values(value).some((permission: any) => store.permissions[permission]);
} else if (Array.isArray(value)) {
return value.some((permission) => store.permissions[permission]);
}
}
function hideElement(el: HTMLElement) {
if (el.parentNode) {
el.parentNode.removeChild(el);
} else {
el.style.display = 'none';
}
}

View File

@@ -36,7 +36,7 @@ export interface EssentialLinkProps {
isSeparator?: boolean;
target?:string;
disable?:boolean;
permission?: string
permission?: string|null;
}
withDefaults(defineProps<EssentialLinkProps>(), {
caption: '',

View File

@@ -8,6 +8,7 @@
<q-card-section class="q-mx-md " >
<q-select
v-permissions="props.actionPermissions.edit"
class="q-mt-md"
:disable="loading||!domain.domainActive"
filled
@@ -53,7 +54,7 @@
</template>
<template v-slot:actions="{ row }">
<q-btn round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
<q-btn v-permissions="props.actionPermissions.edit" round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
</template>
</sharing-user-list>
@@ -76,7 +77,6 @@ import { useAuthStore } from 'stores/useAuthStore';
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
import { Dialog } from 'quasar'
import { IResponse } from 'src/types/BaseTypes';
const authStore = useAuthStore();
@@ -89,6 +89,7 @@ interface Props {
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
actionPermissions: { 'list': string, 'edit': string };
}
interface IUserDisplayWithShareRole extends IUserDisplay {

View File

@@ -8,6 +8,7 @@
:get-shared-api="getSharedApi"
:is-action-disable="(row) => row.id === authStore.userId"
:model-value="modelValue"
:action-permissions="Actions.domain.grantManage"
@update:modelValue="updateModelValue"
@close="close"
/>
@@ -19,6 +20,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import { IUserDisplay } from '../../types/UserTypes';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
import { useAuthStore } from 'src/stores/useAuthStore';
import { IResponse } from 'src/types/BaseTypes';

View File

@@ -7,6 +7,7 @@
:remove-shared-api="removeSharedApi"
:get-shared-api="getSharedApi"
:model-value="modelValue"
:action-permissions="Actions.domain.grantUse"
@update:modelValue="updateModelValue"
@close="close"
/>
@@ -18,6 +19,7 @@ import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import { IUserDisplay } from '../../types/UserTypes';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
interface Props {
modelValue: boolean;

View File

@@ -1,9 +1,9 @@
<template>
<q-btn flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
<q-btn v-if="hasPermission()" flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
<q-menu :max-width="maxWidth">
<q-list dense :style="{ 'min-width': minWidth }">
<template v-for="(item, index) in actions" :key="index" >
<q-item v-if="isAction(item)" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
<q-item v-if="isAction(item)" v-permissions="item.permission" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
:class="item.class" clickable v-close-popup @click="item.action(row)">
<q-item-section side style="color: inherit;">
<q-icon size="1.2em" :name="item.icon" />
@@ -21,7 +21,7 @@
</template>
<script lang="ts">
import { PropType, Ref } from 'vue';
import { PropType, getCurrentInstance } from 'vue';
import { IDomainOwnerDisplay } from '../types/DomainTypes';
interface Action {
@@ -29,6 +29,7 @@ interface Action {
icon?: string;
tooltip?: string|((row: IDomainOwnerDisplay) => string);
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
permission?: string|object;
action: (row: any) => void|Promise<void>;
class?: string;
}
@@ -64,6 +65,18 @@ export default {
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
return typeof item === 'function';
},
hasPermission() {
const proxy = getCurrentInstance()?.proxy;
if (!proxy) {
return false;
}
for (const item of this.actions) {
if (this.isAction(item) && proxy.$hasPermission(item.permission)) {
return true;
}
}
}
}
};

View File

@@ -111,7 +111,6 @@ const essentialLinks: EssentialLinkProps[] = reactive([
icon: 'assignment_ind',
link: '/#/userDomain',
target: '_self',
id: 'userDomain',
permission: MenuMapping.userDomain
},
// {

View File

@@ -120,7 +120,7 @@ const columns = [
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' },
{ name: 'actions', label: '操作', field: 'actions', align: 'right' }
{ name: 'actions', label: '', field: 'actions', align: 'right' }
];
const statusFilterOptions = [

View File

@@ -8,7 +8,7 @@
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
<q-btn v-permissions="Actions.domain.add" color="primary" :disable="loading" label="新規" @click="addRow" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
@@ -154,6 +154,7 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
import { useAuthStore } from 'stores/useAuthStore';
import ShareUsageDialog from 'components/ShareDomain/ShareUsageDialog.vue';
import ShareManageDialog from 'components/ShareDomain/ShareManageDialog.vue';
@@ -233,15 +234,16 @@ const SHARE_USE = 'use';
const SHARE_MANAGE = 'manage';
const actionList = [
{ label: '編集', icon: 'edit_note', action: editRow },
{ label: '利用権限設定', icon: 'person_add_alt', action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
{ label: '管理権限設定', icon: 'add_moderator',
{ label: '編集', icon: 'edit_note', permission: Actions.domain.edit, action: editRow },
{ label: '利用権限設定', icon: 'person_add_alt', permission: Actions.domain.grantUse,
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
{ label: '管理権限設定', icon: 'add_moderator', permission: Actions.domain.grantManage,
disable: (row: IDomainOwnerDisplay) => !isOwner(row),
tooltip: (row: IDomainOwnerDisplay) => isOwner(row) ? '' : 'ドメイン所有者でないため、操作できません',
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_MANAGE, row)}
},
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
{ label: '削除', icon: 'delete_outline', permission: Actions.domain.delete, class: 'text-red', action: removeRow },
];
const isOwner = (row: IDomainOwnerDisplay) => row.owner.id === Number(authStore.userId);

View File

@@ -9,7 +9,7 @@
:pagination="pagination" >
<template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
<q-btn v-permissions="Actions.user.add" color="primary" :disable="loading" label="新規" @click="addRow" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
@@ -161,6 +161,7 @@ import { ref, onMounted, computed } from 'vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import { useUserStore } from 'stores/useUserStore';
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
import { Actions } from 'boot/permissions';
const userStore = useUserStore();
@@ -171,7 +172,7 @@ const columns = [
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' },
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
{ name: 'actions', label: '操作', field: 'actions' }
{ name: 'actions', label: '', field: 'actions' }
];
const statusFilterOptions = [
@@ -205,9 +206,9 @@ const editId = ref(0);
const isCreate = computed(() => editId.value <= 0);
const actionList = [
{ label: '編集', icon: 'edit_note', action: editRow },
{ label: '編集', icon: 'edit_note', permission: Actions.user.edit, action: editRow },
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: showDeleteUserConfirm },
{ label: '削除', icon: 'delete_outline', permission: Actions.user.delete, class: 'text-red', action: showDeleteUserConfirm },
];
const getUsers = async () => {

View File

@@ -8,3 +8,11 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}
import { ComponentCustomProperties } from 'vue';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$hasPermission: (permission: any) => boolean;
}
}