基于naive ui重构设置-权限页面及弹窗
This commit is contained in:
parent
b0172b9aa8
commit
81f6620bf1
@ -15,7 +15,7 @@
|
|||||||
<n-form-item :label="$t('Endpoint')" :required="true">
|
<n-form-item :label="$t('Endpoint')" :required="true">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="endpoint"
|
v-model:value="endpoint"
|
||||||
:placeholder="$t('Enter webhook endpoint URL')"
|
placeholder=""
|
||||||
:size="inputSize"
|
:size="inputSize"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<n-form-item :label="$t('Secret Key')">
|
<n-form-item :label="$t('Secret Key')">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="secret"
|
v-model:value="secret"
|
||||||
:placeholder="$t('Enter secret key (optional)')"
|
placeholder=""
|
||||||
:size="inputSize"
|
:size="inputSize"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,138 +1,198 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog
|
<n-modal
|
||||||
v-if="role"
|
v-if="role"
|
||||||
:options="{ title: `${role.title}`, size: 'xl' }"
|
v-model:show="show"
|
||||||
v-model="show"
|
preset="card"
|
||||||
|
:title="role.title"
|
||||||
|
:style="modalStyle"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
class="role-configure-modal"
|
||||||
>
|
>
|
||||||
<template v-slot:body-content>
|
<template #header>
|
||||||
<FTabs
|
<span class="text-lg font-semibold">{{ role.title }}</span>
|
||||||
:tabs="[
|
</template>
|
||||||
{
|
<n-tabs v-model:value="activeTab" type="line" animated>
|
||||||
label: $t('Members'),
|
<n-tab-pane name="members" :tab="$t('Members')">
|
||||||
value: 'members',
|
<n-space vertical :size="20">
|
||||||
},
|
<div class="flex gap-2 items-end">
|
||||||
{
|
<n-select
|
||||||
label: $t('Settings'),
|
v-model:value="selectedMember"
|
||||||
value: 'settings',
|
:options="autoCompleteList"
|
||||||
},
|
:placeholder="$t('Select member to add')"
|
||||||
]"
|
:size="inputSize"
|
||||||
v-model="tabIndex"
|
class="flex-1"
|
||||||
>
|
filterable
|
||||||
<TabList v-slot="{ tab, selected }" class="pl-0">
|
|
||||||
<div
|
|
||||||
class="flex cursor-pointer items-center gap-1.5 border-b border-transparent py-3 text-base text-gray-600 duration-300 ease-in-out hover:border-gray-400 hover:text-gray-900 focus:outline-none focus:transition-none [&>div]:pl-0"
|
|
||||||
:class="{ 'text-gray-900': selected }"
|
|
||||||
>
|
|
||||||
<span>{{ tab.label }}</span>
|
|
||||||
</div>
|
|
||||||
</TabList>
|
|
||||||
<TabPanel v-slot="{ tab }">
|
|
||||||
<div v-if="tab.value === 'members'" class="text-base">
|
|
||||||
<div class="my-4 flex gap-2">
|
|
||||||
<div class="flex-1">
|
|
||||||
<Autocomplete
|
|
||||||
:options="autoCompleteList"
|
|
||||||
v-model="member"
|
|
||||||
:placeholder="$t('Select member to add')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="solid"
|
|
||||||
:label="$t('Add Member')"
|
|
||||||
:disabled="!member?.value"
|
|
||||||
:loading="$resources.role.addUser?.loading"
|
|
||||||
@click="() => addUser(member.value)"
|
|
||||||
/>
|
/>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="!selectedMember"
|
||||||
|
:loading="$resources.role.addUser?.loading"
|
||||||
|
@click="() => addUser(selectedMember)"
|
||||||
|
:size="buttonSize"
|
||||||
|
>
|
||||||
|
{{ $t('Add Member') }}
|
||||||
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded border px-3">
|
<n-card>
|
||||||
<div class="mt-2 text-gray-600">{{ $t('Members') }}</div>
|
<div class="mb-3 text-gray-600 font-medium">{{ $t('Members') }}</div>
|
||||||
<div
|
<div
|
||||||
v-if="roleUsers.length === 0"
|
v-if="roleUsers.length === 0"
|
||||||
class="p-6 text-center text-gray-500"
|
class="p-6 text-center text-gray-500"
|
||||||
>
|
>
|
||||||
<span>{{ $t('No members have been added to this role yet.') }}</span>
|
<span>{{ $t('No members have been added to this role yet.') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col divide-y">
|
<div v-else class="space-y-2">
|
||||||
<div
|
<div
|
||||||
v-for="user in roleUsers"
|
v-for="user in roleUsers"
|
||||||
class="flex justify-between py-3"
|
:key="user.user"
|
||||||
|
class="flex justify-between items-center py-2 border-b border-gray-100 last:border-0"
|
||||||
|
>
|
||||||
|
<UserWithAvatarCell
|
||||||
|
:avatarImage="user.user_image"
|
||||||
|
:fullName="user.full_name"
|
||||||
|
:email="user.user"
|
||||||
|
/>
|
||||||
|
<n-button
|
||||||
|
tertiary
|
||||||
|
circle
|
||||||
|
size="small"
|
||||||
|
@click="() => removeUser(user.user)"
|
||||||
>
|
>
|
||||||
<UserWithAvatarCell
|
<template #icon>
|
||||||
:avatarImage="user.user_image"
|
<n-icon><XIcon /></n-icon>
|
||||||
:fullName="user.full_name"
|
</template>
|
||||||
:email="user.user"
|
</n-button>
|
||||||
:key="user.user"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<Button variant="ghost" @click="() => removeUser(user.user)">
|
</n-card>
|
||||||
<template #icon>
|
</n-space>
|
||||||
<i-lucide-x class="h-4 w-4 text-gray-600" />
|
</n-tab-pane>
|
||||||
</template>
|
<n-tab-pane name="settings" :tab="$t('Settings')">
|
||||||
</Button>
|
<n-space vertical :size="20">
|
||||||
|
<n-card>
|
||||||
|
<n-form-item>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-base font-medium text-gray-900 mb-1">
|
||||||
|
{{ $t('Admin Access') }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
{{ $t('Grant members permissions similar to team owner. Includes access to all pages and settings.') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<n-switch
|
||||||
</div>
|
:value="adminAccess"
|
||||||
</div>
|
@update:value="(val) => adminAccess = val"
|
||||||
<div v-else-if="tab.value === 'settings'" class="mt-4 text-base">
|
size="medium"
|
||||||
<div class="space-y-3">
|
|
||||||
<div class="rounded border p-4">
|
|
||||||
<Switch
|
|
||||||
class="ml-2"
|
|
||||||
v-model="adminAccess"
|
|
||||||
:label="$t('Admin Access')"
|
|
||||||
:description="$t('Grant members permissions similar to team owner. Includes access to all pages and settings.')"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 rounded border p-4">
|
</n-form-item>
|
||||||
<h2 class="mb-2 ml-2 font-semibold">{{ $t('Page Access Permissions') }}</h2>
|
</n-card>
|
||||||
<Switch
|
<n-card>
|
||||||
v-model="allowBilling"
|
<h2 class="mb-4 text-base font-semibold">{{ $t('Page Access Permissions') }}</h2>
|
||||||
:label="$t('Allow Billing Access')"
|
<n-space vertical :size="16">
|
||||||
:disabled="adminAccess"
|
<n-form-item>
|
||||||
/>
|
<div class="flex items-center justify-between w-full">
|
||||||
<Switch
|
<span class="text-base">{{ $t('Allow Billing Access') }}</span>
|
||||||
v-model="allowApps"
|
<n-switch
|
||||||
:label="$t('Allow Apps Access')"
|
:value="allowBilling"
|
||||||
:disabled="adminAccess"
|
@update:value="(val) => allowBilling = val"
|
||||||
/>
|
:disabled="adminAccess"
|
||||||
<Switch
|
size="medium"
|
||||||
v-if="$team.pg.jerp_partner"
|
/>
|
||||||
v-model="allowPartner"
|
</div>
|
||||||
:label="$t('Allow Partner Access')"
|
</n-form-item>
|
||||||
:disabled="adminAccess"
|
<n-form-item>
|
||||||
/>
|
<div class="flex items-center justify-between w-full">
|
||||||
<Switch
|
<span class="text-base">{{ $t('Allow Apps Access') }}</span>
|
||||||
v-model="allowSiteCreation"
|
<n-switch
|
||||||
:label="$t('Allow Site Creation')"
|
:value="allowApps"
|
||||||
:disabled="adminAccess"
|
@update:value="(val) => allowApps = val"
|
||||||
/>
|
:disabled="adminAccess"
|
||||||
<Switch
|
size="medium"
|
||||||
v-model="allowBenchCreation"
|
/>
|
||||||
:label="$t('Allow Release Group Creation')"
|
</div>
|
||||||
:disabled="adminAccess"
|
</n-form-item>
|
||||||
/>
|
<n-form-item v-if="$team.pg.jerp_partner">
|
||||||
<Switch
|
<div class="flex items-center justify-between w-full">
|
||||||
v-model="allowServerCreation"
|
<span class="text-base">{{ $t('Allow Partner Access') }}</span>
|
||||||
:label="$t('Allow Server Creation')"
|
<n-switch
|
||||||
:disabled="adminAccess"
|
:value="allowPartner"
|
||||||
/>
|
@update:value="(val) => allowPartner = val"
|
||||||
<Switch
|
:disabled="adminAccess"
|
||||||
v-model="allowWebhookConfiguration"
|
size="medium"
|
||||||
:label="$t('Allow Webhook Configuration')"
|
/>
|
||||||
:disabled="adminAccess"
|
</div>
|
||||||
/>
|
</n-form-item>
|
||||||
</div>
|
<n-form-item>
|
||||||
</div>
|
<div class="flex items-center justify-between w-full">
|
||||||
</div>
|
<span class="text-base">{{ $t('Allow Site Creation') }}</span>
|
||||||
</TabPanel>
|
<n-switch
|
||||||
</FTabs>
|
:value="allowSiteCreation"
|
||||||
</template>
|
@update:value="(val) => allowSiteCreation = val"
|
||||||
</Dialog>
|
:disabled="adminAccess"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span class="text-base">{{ $t('Allow Release Group Creation') }}</span>
|
||||||
|
<n-switch
|
||||||
|
:value="allowBenchCreation"
|
||||||
|
@update:value="(val) => allowBenchCreation = val"
|
||||||
|
:disabled="adminAccess"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span class="text-base">{{ $t('Allow Server Creation') }}</span>
|
||||||
|
<n-switch
|
||||||
|
:value="allowServerCreation"
|
||||||
|
@update:value="(val) => allowServerCreation = val"
|
||||||
|
:disabled="adminAccess"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item>
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<span class="text-base">{{ $t('Allow Webhook Configuration') }}</span>
|
||||||
|
<n-switch
|
||||||
|
:value="allowWebhookConfiguration"
|
||||||
|
@update:value="(val) => allowWebhookConfiguration = val"
|
||||||
|
:disabled="adminAccess"
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
</n-card>
|
||||||
|
</n-space>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Switch, Tabs, TabList, TabPanel } from 'jingrow-ui';
|
import {
|
||||||
|
NModal,
|
||||||
|
NTabs,
|
||||||
|
NTabPane,
|
||||||
|
NSpace,
|
||||||
|
NSelect,
|
||||||
|
NButton,
|
||||||
|
NIcon,
|
||||||
|
NCard,
|
||||||
|
NFormItem,
|
||||||
|
NSwitch,
|
||||||
|
} from 'naive-ui';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import UserWithAvatarCell from '../UserWithAvatarCell.vue';
|
import UserWithAvatarCell from '../UserWithAvatarCell.vue';
|
||||||
|
import XIcon from '~icons/lucide/x';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
@ -140,16 +200,24 @@ export default {
|
|||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
UserWithAvatarCell,
|
UserWithAvatarCell,
|
||||||
FTabs: Tabs,
|
NModal,
|
||||||
TabPanel,
|
NTabs,
|
||||||
TabList,
|
NTabPane,
|
||||||
Switch,
|
NSpace,
|
||||||
|
NSelect,
|
||||||
|
NButton,
|
||||||
|
NIcon,
|
||||||
|
NCard,
|
||||||
|
NFormItem,
|
||||||
|
NSwitch,
|
||||||
|
XIcon,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
member: {},
|
selectedMember: null,
|
||||||
show: true,
|
show: true,
|
||||||
tabIndex: 0,
|
activeTab: 'members',
|
||||||
|
windowWidth: window.innerWidth,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
resources: {
|
resources: {
|
||||||
@ -180,6 +248,21 @@ export default {
|
|||||||
?.filter(({ user }) => isNotGroupMember(user))
|
?.filter(({ user }) => isNotGroupMember(user))
|
||||||
.map(({ user }) => ({ label: user, value: user }));
|
.map(({ user }) => ({ label: user, value: user }));
|
||||||
},
|
},
|
||||||
|
isMobile() {
|
||||||
|
return this.windowWidth <= 768;
|
||||||
|
},
|
||||||
|
modalStyle() {
|
||||||
|
return {
|
||||||
|
width: this.isMobile ? '95vw' : '900px',
|
||||||
|
maxWidth: this.isMobile ? '95vw' : '90vw',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
inputSize() {
|
||||||
|
return this.isMobile ? 'medium' : 'large';
|
||||||
|
},
|
||||||
|
buttonSize() {
|
||||||
|
return this.isMobile ? 'medium' : 'medium';
|
||||||
|
},
|
||||||
adminAccess: {
|
adminAccess: {
|
||||||
get() {
|
get() {
|
||||||
return !!this.role?.admin_access;
|
return !!this.role?.admin_access;
|
||||||
@ -286,14 +369,16 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleResize() {
|
||||||
|
this.windowWidth = window.innerWidth;
|
||||||
|
},
|
||||||
addUser(user) {
|
addUser(user) {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
if (this.$resources.role.addUser.loading) return;
|
if (this.$resources.role.addUser.loading) return;
|
||||||
|
|
||||||
toast.success(this.$t('Added {user} to {role}', { user, role: this.role.title }), { duration: 2000 });
|
toast.success(this.$t('Added {user} to {role}', { user, role: this.role.title }), { duration: 2000 });
|
||||||
this.member = {};
|
this.selectedMember = null;
|
||||||
this.$resources.role.addUser.submit({ user });
|
this.$resources.role.addUser.submit({ user });
|
||||||
// 刷新角色数据
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.$resources.role) {
|
if (this.$resources.role) {
|
||||||
this.$resources.role.reload();
|
this.$resources.role.reload();
|
||||||
@ -306,7 +391,6 @@ export default {
|
|||||||
|
|
||||||
toast.success(this.$t('Removed {user} from {role}', { user, role: this.role.title }), { duration: 2000 });
|
toast.success(this.$t('Removed {user} from {role}', { user, role: this.role.title }), { duration: 2000 });
|
||||||
this.$resources.role.removeUser.submit({ user });
|
this.$resources.role.removeUser.submit({ user });
|
||||||
// 刷新角色数据
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.$resources.role) {
|
if (this.$resources.role) {
|
||||||
this.$resources.role.reload();
|
this.$resources.role.reload();
|
||||||
@ -314,5 +398,57 @@ export default {
|
|||||||
}, 500);
|
}, 500);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.handleResize();
|
||||||
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('resize', this.handleResize);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.role-configure-modal .n-card) {
|
||||||
|
width: 900px;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-tabs) {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-tab-pane) {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.role-configure-modal .n-card) {
|
||||||
|
width: 95vw !important;
|
||||||
|
max-width: 95vw !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-card-body) {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-card__header) {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.role-configure-modal .n-tab-pane) {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,20 +1,86 @@
|
|||||||
<template>
|
<template>
|
||||||
<ObjectList :options="listOptions" />
|
<div class="role-list-container">
|
||||||
|
<n-card class="settings-card">
|
||||||
|
<ObjectList :options="listOptions" />
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 创建角色弹窗 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showCreateRoleDialog"
|
||||||
|
preset="card"
|
||||||
|
:title="$t('Create Role')"
|
||||||
|
:style="modalStyle"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
class="create-role-modal"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span class="text-lg font-semibold">{{ $t('Create Role') }}</span>
|
||||||
|
</template>
|
||||||
|
<n-space vertical :size="20">
|
||||||
|
<n-form-item :label="$t('Role')" :required="true">
|
||||||
|
<n-input
|
||||||
|
v-model:value="newRoleTitle"
|
||||||
|
placeholder=""
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
@keyup.enter="handleCreateRole"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
<template #action>
|
||||||
|
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||||
|
<n-button @click="showCreateRoleDialog = false" :block="isMobile" :size="buttonSize">
|
||||||
|
{{ $t('Cancel') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:loading="createRoleLoading"
|
||||||
|
:disabled="!newRoleTitle"
|
||||||
|
@click="handleCreateRole"
|
||||||
|
:block="isMobile"
|
||||||
|
:size="buttonSize"
|
||||||
|
>
|
||||||
|
{{ $t('Confirm') }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
|
||||||
|
<!-- 删除角色确认弹窗 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showDeleteRoleDialog"
|
||||||
|
preset="dialog"
|
||||||
|
:title="$t('Delete Role')"
|
||||||
|
:positive-text="$t('Delete')"
|
||||||
|
:positive-button-props="{ type: 'error' }"
|
||||||
|
:loading="deleteRoleLoading"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
@positive-click="handleDeleteRole"
|
||||||
|
>
|
||||||
|
<div class="py-4">
|
||||||
|
<p class="text-base" v-html="$t('Are you sure you want to delete role <b>{role}</b>?', { role: selectedRole?.title || '' })"></p>
|
||||||
|
</div>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="jsx">
|
<script setup lang="jsx">
|
||||||
import { h, ref, getCurrentInstance } from 'vue';
|
import { h, ref, getCurrentInstance, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { FeatherIcon } from 'jingrow-ui';
|
import { FeatherIcon } from 'jingrow-ui';
|
||||||
import { icon, renderDialog, confirmDialog } from '../../utils/components';
|
import { NCard, NModal, NSpace, NFormItem, NInput, NButton } from 'naive-ui';
|
||||||
|
import { icon, renderDialog } from '../../utils/components';
|
||||||
import ObjectList from '../ObjectList.vue';
|
import ObjectList from '../ObjectList.vue';
|
||||||
import RoleConfigureDialog from './RoleConfigureDialog.vue';
|
import RoleConfigureDialog from './RoleConfigureDialog.vue';
|
||||||
import router from '../../router';
|
import { useRouter } from 'vue-router';
|
||||||
import UserAvatarGroup from '../AvatarGroup.vue';
|
import UserAvatarGroup from '../AvatarGroup.vue';
|
||||||
import { getToastErrorMessage } from '../../utils/toast';
|
import { getToastErrorMessage } from '../../utils/toast';
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
|
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const listOptions = ref({
|
const listOptions = ref({
|
||||||
pagetype: 'Jcloud Role',
|
pagetype: 'Jcloud Role',
|
||||||
@ -75,22 +141,9 @@ const listOptions = ref({
|
|||||||
label: $t('Delete Role'),
|
label: $t('Delete Role'),
|
||||||
onClick() {
|
onClick() {
|
||||||
if (roleListResource.delete.loading) return;
|
if (roleListResource.delete.loading) return;
|
||||||
confirmDialog({
|
selectedRole.value = row;
|
||||||
title: $t('Delete Role'),
|
roleListResourceRef.value = roleListResource;
|
||||||
message: $t('Are you sure you want to delete role <b>{role}</b>?', { role: row.title }),
|
showDeleteRoleDialog.value = true;
|
||||||
onSuccess({ hide }) {
|
|
||||||
if (roleListResource.delete.loading) return;
|
|
||||||
toast.promise(roleListResource.delete.submit(row.name), {
|
|
||||||
loading: $t('Deleting role...'),
|
|
||||||
success: () => {
|
|
||||||
roleListResource.reload();
|
|
||||||
hide();
|
|
||||||
return $t('Role {role} deleted', { role: row.title });
|
|
||||||
},
|
|
||||||
error: (e) => getToastErrorMessage(e),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -102,42 +155,158 @@ const listOptions = ref({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
primaryAction({ listResource: groups }) {
|
primaryAction({ listResource: groups }) {
|
||||||
|
roleListResourceRef.value = groups;
|
||||||
return {
|
return {
|
||||||
label: $t('New Role'),
|
label: $t('New Role'),
|
||||||
variant: 'solid',
|
|
||||||
slots: {
|
slots: {
|
||||||
prefix: icon('plus'),
|
prefix: icon('plus'),
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
confirmDialog({
|
newRoleTitle.value = '';
|
||||||
title: $t('Create Role'),
|
showCreateRoleDialog.value = true;
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
fieldname: 'title',
|
|
||||||
label: $t('Role'),
|
|
||||||
autocomplete: 'off',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
primaryAction: {
|
|
||||||
label: $t('Confirm'),
|
|
||||||
variant: 'solid'
|
|
||||||
},
|
|
||||||
onSuccess({ hide, values }) {
|
|
||||||
if (values.title) {
|
|
||||||
return groups.insert.submit(
|
|
||||||
{ title: values.title },
|
|
||||||
{ onSuccess: hide },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const windowWidth = ref(window.innerWidth);
|
||||||
|
const showCreateRoleDialog = ref(false);
|
||||||
|
const showDeleteRoleDialog = ref(false);
|
||||||
|
const newRoleTitle = ref('');
|
||||||
|
const selectedRole = ref(null);
|
||||||
|
const roleListResourceRef = ref(null);
|
||||||
|
const createRoleLoading = ref(false);
|
||||||
|
const deleteRoleLoading = ref(false);
|
||||||
|
|
||||||
|
const isMobile = computed(() => windowWidth.value <= 768);
|
||||||
|
const modalStyle = computed(() => ({
|
||||||
|
width: isMobile.value ? '95vw' : '700px',
|
||||||
|
maxWidth: isMobile.value ? '95vw' : '90vw',
|
||||||
|
}));
|
||||||
|
const inputSize = computed(() => isMobile.value ? 'medium' : 'large');
|
||||||
|
const buttonSize = computed(() => isMobile.value ? 'medium' : 'medium');
|
||||||
|
|
||||||
|
function handleResize() {
|
||||||
|
windowWidth.value = window.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleCreateRole() {
|
||||||
|
if (!newRoleTitle.value || !roleListResourceRef.value) return;
|
||||||
|
if (createRoleLoading.value) return;
|
||||||
|
|
||||||
|
createRoleLoading.value = true;
|
||||||
|
try {
|
||||||
|
await roleListResourceRef.value.insert.submit({ title: newRoleTitle.value });
|
||||||
|
toast.success($t('Role created successfully'));
|
||||||
|
showCreateRoleDialog.value = false;
|
||||||
|
newRoleTitle.value = '';
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(getToastErrorMessage(error));
|
||||||
|
} finally {
|
||||||
|
createRoleLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDeleteRole() {
|
||||||
|
if (!selectedRole.value || !roleListResourceRef.value) return;
|
||||||
|
if (deleteRoleLoading.value) return;
|
||||||
|
|
||||||
|
const roleTitle = selectedRole.value.title;
|
||||||
|
deleteRoleLoading.value = true;
|
||||||
|
try {
|
||||||
|
await toast.promise(
|
||||||
|
roleListResourceRef.value.delete.submit(selectedRole.value.name),
|
||||||
|
{
|
||||||
|
loading: $t('Deleting role...'),
|
||||||
|
success: () => {
|
||||||
|
roleListResourceRef.value.reload();
|
||||||
|
showDeleteRoleDialog.value = false;
|
||||||
|
selectedRole.value = null;
|
||||||
|
return $t('Role {role} deleted', { role: roleTitle });
|
||||||
|
},
|
||||||
|
error: (e) => getToastErrorMessage(e),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
deleteRoleLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function configureRole(row) {
|
function configureRole(row) {
|
||||||
renderDialog(h(RoleConfigureDialog, { roleId: row.name }));
|
renderDialog(h(RoleConfigureDialog, { roleId: row.name }));
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.role-list-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card:hover {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card) {
|
||||||
|
width: 700px;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card__action) {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.create-role-modal .n-card) {
|
||||||
|
width: 95vw !important;
|
||||||
|
max-width: 95vw !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card-body) {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card__header) {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-card__action) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.create-role-modal .n-button) {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,58 +1,158 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mb-5 flex items-center gap-2">
|
<div class="role-permissions-container">
|
||||||
<Tooltip :text="$t('All Roles')">
|
<div class="mb-5 flex items-center gap-2">
|
||||||
<Button :route="{ name: 'SettingsPermissionRoles' }">
|
<n-tooltip>
|
||||||
<template #icon>
|
<template #trigger>
|
||||||
<i-lucide-arrow-left class="h-4 w-4 text-gray-700" />
|
<n-button
|
||||||
|
quaternary
|
||||||
|
@click="router.push({ name: 'SettingsPermissionRoles' })"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><ArrowLeftIcon /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
{{ $t('All Roles') }}
|
||||||
</Tooltip>
|
</n-tooltip>
|
||||||
<h3 class="text-lg font-medium text-gray-900">
|
<h3 class="text-lg font-medium text-gray-900">
|
||||||
{{ role.pg?.title }}
|
{{ role.pg?.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<Tooltip :text="$t('Admin Role')" v-if="role.pg.admin_access">
|
<n-tooltip v-if="role.pg?.admin_access">
|
||||||
<FeatherIcon name="shield" class="h-5 w-5 text-gray-700" />
|
<template #trigger>
|
||||||
</Tooltip>
|
<n-icon class="h-5 w-5 text-gray-700">
|
||||||
|
<ShieldIcon />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ $t('Admin Role') }}
|
||||||
|
</n-tooltip>
|
||||||
|
</div>
|
||||||
|
<n-card class="settings-card">
|
||||||
|
<ObjectList
|
||||||
|
:options="rolePermissions"
|
||||||
|
@update:selections="(e) => (selectedItems = e)"
|
||||||
|
>
|
||||||
|
<template #header-left="{ listResource }">
|
||||||
|
<n-dropdown :options="getDropdownOptions(listResource)" trigger="click">
|
||||||
|
<n-button>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon><AppWindowIcon /></n-icon>
|
||||||
|
</template>
|
||||||
|
{{ currentDropdownOption.label }}
|
||||||
|
<template #suffix>
|
||||||
|
<n-icon><ChevronDownIcon /></n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</n-dropdown>
|
||||||
|
</template>
|
||||||
|
</ObjectList>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 添加权限弹窗 -->
|
||||||
|
<n-modal
|
||||||
|
v-model:show="showAddPermissionDialog"
|
||||||
|
preset="card"
|
||||||
|
:title="$t('Add Permission')"
|
||||||
|
:style="modalStyle"
|
||||||
|
:mask-closable="true"
|
||||||
|
:close-on-esc="true"
|
||||||
|
class="add-permission-modal"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span class="text-lg font-semibold">{{ $t('Add Permission') }}</span>
|
||||||
|
</template>
|
||||||
|
<n-space vertical :size="20">
|
||||||
|
<n-form-item :label="$t('Select {type}', { type: currentDropdownOption.pagetype === 'Release Group' ? $t('Release Group') : currentDropdownOption.pagetype })">
|
||||||
|
<!-- 这里需要使用 LinkControl 或类似的组件来选择文档 -->
|
||||||
|
<n-input
|
||||||
|
v-model:value="selectedDocument"
|
||||||
|
:placeholder="$t('Select {type}', { type: currentDropdownOption.pagetype === 'Release Group' ? $t('Release Group') : currentDropdownOption.pagetype })"
|
||||||
|
:size="inputSize"
|
||||||
|
class="w-full"
|
||||||
|
readonly
|
||||||
|
@click="showDocumentSelector = true"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-space>
|
||||||
|
<template #action>
|
||||||
|
<n-space :vertical="isMobile" :size="isMobile ? 12 : 16" class="w-full">
|
||||||
|
<n-button @click="showAddPermissionDialog = false" :block="isMobile" :size="buttonSize">
|
||||||
|
{{ $t('Cancel') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
:loading="addPermissionLoading"
|
||||||
|
:disabled="!selectedDocument"
|
||||||
|
@click="handleAddPermission"
|
||||||
|
:block="isMobile"
|
||||||
|
:size="buttonSize"
|
||||||
|
>
|
||||||
|
{{ $t('Add') }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
</div>
|
</div>
|
||||||
<ObjectList
|
|
||||||
:options="rolePermissions"
|
|
||||||
@update:selections="(e) => (selectedItems = e)"
|
|
||||||
>
|
|
||||||
<template #header-left="{ listResource }">
|
|
||||||
<Dropdown :options="getDropdownOptions(listResource)">
|
|
||||||
<Button>
|
|
||||||
<template #prefix>
|
|
||||||
<LucideAppWindow class="h-4 w-4 text-gray-500" />
|
|
||||||
</template>
|
|
||||||
{{ currentDropdownOption.label }}
|
|
||||||
<template #suffix>
|
|
||||||
<FeatherIcon name="chevron-down" class="h-4 w-4 text-gray-500" />
|
|
||||||
</template>
|
|
||||||
</Button>
|
|
||||||
</Dropdown>
|
|
||||||
</template>
|
|
||||||
</ObjectList>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
Dropdown,
|
|
||||||
createDocumentResource,
|
createDocumentResource,
|
||||||
createResource,
|
createResource,
|
||||||
} from 'jingrow-ui';
|
} from 'jingrow-ui';
|
||||||
import { computed, h, ref, getCurrentInstance } from 'vue';
|
import { computed, h, ref, getCurrentInstance, onMounted, onBeforeUnmount } from 'vue';
|
||||||
import LucideAppWindow from '~icons/lucide/app-window';
|
import {
|
||||||
|
NCard,
|
||||||
|
NButton,
|
||||||
|
NDropdown,
|
||||||
|
NTooltip,
|
||||||
|
NIcon,
|
||||||
|
NModal,
|
||||||
|
NSpace,
|
||||||
|
NFormItem,
|
||||||
|
NInput,
|
||||||
|
} from 'naive-ui';
|
||||||
|
import ArrowLeftIcon from '~icons/lucide/arrow-left';
|
||||||
|
import ShieldIcon from '~icons/lucide/shield';
|
||||||
|
import AppWindowIcon from '~icons/lucide/app-window';
|
||||||
|
import ChevronDownIcon from '~icons/lucide/chevron-down';
|
||||||
import ObjectList from '../ObjectList.vue';
|
import ObjectList from '../ObjectList.vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import { getToastErrorMessage } from '../../utils/toast';
|
import { getToastErrorMessage } from '../../utils/toast';
|
||||||
import { confirmDialog, icon, renderDialog } from '../../utils/components';
|
import { icon, renderDialog } from '../../utils/components';
|
||||||
import RoleConfigureDialog from './RoleConfigureDialog.vue';
|
import RoleConfigureDialog from './RoleConfigureDialog.vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
|
const $t = instance?.appContext.config.globalProperties.$t || ((key) => key);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
let selectedItems = ref(new Set());
|
let selectedItems = ref(new Set());
|
||||||
|
const windowWidth = ref(window.innerWidth);
|
||||||
|
const showAddPermissionDialog = ref(false);
|
||||||
|
const showDocumentSelector = ref(false);
|
||||||
|
const selectedDocument = ref('');
|
||||||
|
const addPermissionLoading = ref(false);
|
||||||
|
const permissionsResourceRef = ref(null);
|
||||||
|
|
||||||
|
const isMobile = computed(() => windowWidth.value <= 768);
|
||||||
|
const modalStyle = computed(() => ({
|
||||||
|
width: isMobile.value ? '95vw' : '700px',
|
||||||
|
maxWidth: isMobile.value ? '95vw' : '90vw',
|
||||||
|
}));
|
||||||
|
const inputSize = computed(() => isMobile.value ? 'medium' : 'large');
|
||||||
|
const buttonSize = computed(() => isMobile.value ? 'medium' : 'medium');
|
||||||
|
|
||||||
|
function handleResize() {
|
||||||
|
windowWidth.value = window.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
roleId: { type: String, required: true },
|
roleId: { type: String, required: true },
|
||||||
@ -86,7 +186,8 @@ const currentDropdownOption = ref(dropdownOptions[0]);
|
|||||||
function getDropdownOptions(listResource) {
|
function getDropdownOptions(listResource) {
|
||||||
return dropdownOptions.map((option) => {
|
return dropdownOptions.map((option) => {
|
||||||
return {
|
return {
|
||||||
...option,
|
label: option.label,
|
||||||
|
key: option.pagetype,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
currentDropdownOption.value = option;
|
currentDropdownOption.value = option;
|
||||||
let filters = {
|
let filters = {
|
||||||
@ -179,59 +280,109 @@ const rolePermissions = ref({
|
|||||||
prefix: icon('plus'),
|
prefix: icon('plus'),
|
||||||
},
|
},
|
||||||
onClick() {
|
onClick() {
|
||||||
const pagetypeLabel = currentDropdownOption.value.pagetype === 'Release Group'
|
permissionsResourceRef.value = permissions;
|
||||||
? $t('Release Group')
|
selectedDocument.value = '';
|
||||||
: currentDropdownOption.value.pagetype;
|
showAddPermissionDialog.value = true;
|
||||||
confirmDialog({
|
|
||||||
title: $t('Add Permission'),
|
|
||||||
message: '',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: $t('Select {type}', { type: pagetypeLabel }),
|
|
||||||
type: 'link',
|
|
||||||
fieldname: 'document_name',
|
|
||||||
options: {
|
|
||||||
pagetype: currentDropdownOption.value.pagetype,
|
|
||||||
filters: {
|
|
||||||
name: [
|
|
||||||
'not in',
|
|
||||||
permissions.data.map(
|
|
||||||
(p) => p[currentDropdownOption.value.fieldname],
|
|
||||||
) || '',
|
|
||||||
],
|
|
||||||
status: ['!=', 'Archived'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
primaryAction: {
|
|
||||||
label: $t('Add'),
|
|
||||||
onClick({ values }) {
|
|
||||||
let key = currentDropdownOption.value.fieldname;
|
|
||||||
|
|
||||||
toast.promise(
|
|
||||||
docInsert.submit({
|
|
||||||
pg: {
|
|
||||||
pagetype: 'Jcloud Role Permission',
|
|
||||||
role: props.roleId,
|
|
||||||
[key]: values.document_name,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
loading: $t('Adding permission...'),
|
|
||||||
success() {
|
|
||||||
permissions.reload();
|
|
||||||
return $t('Permission added successfully');
|
|
||||||
},
|
|
||||||
error: (e) => getToastErrorMessage(e),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].filter((action) => (action.condition ? action.condition() : true));
|
].filter((action) => (action.condition ? action.condition() : true));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function handleAddPermission() {
|
||||||
|
if (!selectedDocument.value || !permissionsResourceRef.value) return;
|
||||||
|
if (addPermissionLoading.value) return;
|
||||||
|
|
||||||
|
const key = currentDropdownOption.value.fieldname;
|
||||||
|
addPermissionLoading.value = true;
|
||||||
|
try {
|
||||||
|
await toast.promise(
|
||||||
|
docInsert.submit({
|
||||||
|
pg: {
|
||||||
|
pagetype: 'Jcloud Role Permission',
|
||||||
|
role: props.roleId,
|
||||||
|
[key]: selectedDocument.value,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
loading: $t('Adding permission...'),
|
||||||
|
success() {
|
||||||
|
permissionsResourceRef.value.reload();
|
||||||
|
showAddPermissionDialog.value = false;
|
||||||
|
selectedDocument.value = '';
|
||||||
|
return $t('Permission added successfully');
|
||||||
|
},
|
||||||
|
error: (e) => getToastErrorMessage(e),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
addPermissionLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.role-permissions-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card {
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
transition: box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-card:hover {
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card) {
|
||||||
|
width: 700px;
|
||||||
|
max-width: 90vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card-body) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-form-item) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-input) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card__action) {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
:deep(.add-permission-modal .n-card) {
|
||||||
|
width: 95vw !important;
|
||||||
|
max-width: 95vw !important;
|
||||||
|
margin: 20px auto;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card-body) {
|
||||||
|
padding: 20px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card__header) {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-card__action) {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.add-permission-modal .n-button) {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,7 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-5">
|
<div class="permissions-settings-container">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup></script>
|
<script setup></script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.permissions-settings-container {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user