admin user and ui panel
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
|
||||
<div class="nav-right">
|
||||
<a-dropdown>
|
||||
<a-dropdown trigger="click" placement="bottomRight" :overlay-class-name="'user-dropdown-overlay'">
|
||||
<span class="user-chip">
|
||||
<a-avatar size="small" style="background-color: #1c2d56">
|
||||
{{ initial }}
|
||||
@@ -32,13 +32,39 @@
|
||||
<span class="user-name">{{ displayName }}</span>
|
||||
</span>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item key="my-data" @click="router.push('/my-data')">
|
||||
اطلاعات من
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="onLogout">خروج</a-menu-item>
|
||||
</a-menu>
|
||||
<div class="user-card">
|
||||
<div class="user-card__head">
|
||||
<a-avatar size="large" style="background-color: #1c2d56">
|
||||
{{ initial }}
|
||||
</a-avatar>
|
||||
<div class="user-card__id">
|
||||
<span class="user-card__name">{{ displayName }}</span>
|
||||
<span class="user-card__mobile">{{ auth.user?.mobile }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-divider class="user-card__divider" />
|
||||
|
||||
<a-menu class="user-card__menu">
|
||||
<a-menu-item key="my-data" @click="router.push('/my-data')">
|
||||
<UserOutlined />
|
||||
اطلاعات من
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="canSeePermissions" key="permission" @click="router.push('/permission')">
|
||||
<SafetyCertificateOutlined />
|
||||
دسترسی ها
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
||||
<a-divider class="user-card__divider" />
|
||||
|
||||
<a-menu class="user-card__menu">
|
||||
<a-menu-item key="logout" @click="onLogout">
|
||||
<LogoutOutlined />
|
||||
خروج
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
@@ -48,8 +74,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
SafetyCertificateOutlined,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useAuthStore } from '../../stores/auth';
|
||||
import { useUsersStore } from '../../stores/users';
|
||||
|
||||
interface Crumb {
|
||||
title: string;
|
||||
@@ -63,11 +96,13 @@ defineEmits<{ (e: 'toggle-sidebar'): void }>();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const auth = useAuthStore();
|
||||
const users = useUsersStore();
|
||||
|
||||
const pageTitle = computed(() => (route.meta?.pageTitle as string) ?? '');
|
||||
const breadcrumb = computed(() => route.meta?.breadcrumb as Crumb[] | undefined);
|
||||
const displayName = computed(() => auth.user?.name ?? auth.user?.fullName ?? 'کاربر');
|
||||
const initial = computed(() => displayName.value.trim().charAt(0) || 'ک');
|
||||
const canSeePermissions = computed(() => Boolean(users.hasPermission('user.permission')));
|
||||
|
||||
function onLogout() {
|
||||
auth.logout();
|
||||
@@ -105,3 +140,84 @@ function onLogout() {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--
|
||||
Unscoped: ant-design-vue's <a-dropdown> teleports its #overlay to <body>, so
|
||||
a `scoped` block (which relies on a component-instance data attribute) never
|
||||
matches it. Reach is contained instead by :overlay-class-name="'user-dropdown-overlay'"
|
||||
above, so every selector below is namespaced under that one class.
|
||||
-->
|
||||
<style>
|
||||
.user-dropdown-overlay .ant-dropdown-menu {
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card {
|
||||
width: 220px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 28px -8px rgba(28, 45, 86, 0.28);
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 2px 4px 8px;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__id {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1c2d56;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__mobile {
|
||||
font-size: 12px;
|
||||
color: #8a909c;
|
||||
direction: ltr;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__divider {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__menu.ant-menu {
|
||||
border-inline-end: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__menu .ant-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
margin: 0;
|
||||
padding-inline: 6px !important;
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__menu .ant-menu-item:hover {
|
||||
background: #f4f5f6;
|
||||
color: #1c2d56;
|
||||
}
|
||||
|
||||
.user-dropdown-overlay .user-card__menu .anticon {
|
||||
font-size: 15px;
|
||||
color: #8a909c;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,10 +12,19 @@
|
||||
// A "header" node is a section divider carrying `items`, not `submenu`.
|
||||
//
|
||||
// Legacy quirks preserved deliberately (see docs/ADMIN-PORT-PLAN.md):
|
||||
// - two nodes have their permissionId COMMENTED OUT (banker-list, shopkeeper-list),
|
||||
// so they are visible to everyone
|
||||
// - "newsletters.newsletters" and "wallet.list" never match any granted rid (#7, #8)
|
||||
// - several nodes repeat the same permissionId or reuse another view's `name`
|
||||
//
|
||||
// Two further legacy quirks were fixed rather than preserved, since both were
|
||||
// bugs (not intentional behaviour) and each has since caused real confusion:
|
||||
// - banker-list / shopkeeper-list had their permissionId commented out, so
|
||||
// the items showed for everyone even though the backing endpoints
|
||||
// (admin/customers-omde/banker|shopkeeper/list) require
|
||||
// persons.list.client.omde and 403'd on an unauthorized click. Filled in.
|
||||
// - "newsletters.newsletters" never matched any rid in ALL_RIDS (the real
|
||||
// one is "newsletters"), so the item was invisible to every user,
|
||||
// including super-admin. Corrected to "newsletters".
|
||||
// "wallet.list" is a real, seeded rid (see acl.constants.ts) and was never
|
||||
// actually broken — the old note above was itself mistaken.
|
||||
import { isBranch } from '../utils';
|
||||
|
||||
export interface NavMenuNode {
|
||||
@@ -57,9 +66,16 @@ export const adminMenu: NavMenuNode[] = [
|
||||
i18n: 'اشخاص',
|
||||
permissionId:"persons",
|
||||
submenu: [
|
||||
// 'اضافه کردن شخص' removed: adding a wholesale customer is now the
|
||||
// add button on the list page itself (OmdeList), so the nav entry
|
||||
// duplicated it. The /customer/add route still works.
|
||||
// Restored per request: the add button on OmdeList only covers
|
||||
// wholesale customers, so removing this entry left no nav path to
|
||||
// the general "add person" form.
|
||||
{
|
||||
url: '/customer/add',
|
||||
name: 'AddCustomer',
|
||||
slug: 'add-customer',
|
||||
i18n: 'اضافه کردن شخص',
|
||||
permissionId:"persons.create"
|
||||
},
|
||||
{
|
||||
url: '/customer/reagent-list',
|
||||
name: 'List',
|
||||
@@ -99,8 +115,13 @@ export const adminMenu: NavMenuNode[] = [
|
||||
url: '/customer/banker-list',
|
||||
name: 'List',
|
||||
slug: 'banker-list',
|
||||
i18n: 'لیست بنکداران'
|
||||
// permissionId:"persons.list.client."
|
||||
i18n: 'لیست بنکداران',
|
||||
// Legacy left this rid commented out, so the item showed for
|
||||
// everyone regardless of permission — a bug, not a feature: the
|
||||
// API route it points at (admin/customers-omde/banker/list) is
|
||||
// gated on exactly this rid, so an unauthorized click 403'd.
|
||||
// Filled in to match what the server actually enforces.
|
||||
permissionId:"persons.list.client.omde"
|
||||
},
|
||||
{
|
||||
// legacy navMenuItems.js:90 spelled this with a capital S while the route
|
||||
@@ -109,8 +130,10 @@ export const adminMenu: NavMenuNode[] = [
|
||||
url: '/customer/shopkeeper-list',
|
||||
name: 'List',
|
||||
slug: 'shopkeeper-list',
|
||||
i18n: 'لیست مغازه داران حضوری'
|
||||
// permissionId:"persons.list.client."
|
||||
i18n: 'لیست مغازه داران حضوری',
|
||||
// Same fix as banker-list above — the backing endpoint
|
||||
// (admin/customers-omde/shopkeeper/list) requires this rid.
|
||||
permissionId:"persons.list.client.omde"
|
||||
},
|
||||
]
|
||||
},
|
||||
@@ -398,7 +421,12 @@ export const adminMenu: NavMenuNode[] = [
|
||||
name: 'newsletters',
|
||||
icon: 'FileTextIcon',
|
||||
i18n: 'خبرنامه',
|
||||
permissionId:"newsletters.newsletters",
|
||||
// Legacy typo'd this as "newsletters.newsletters", a rid that never
|
||||
// exists in ALL_RIDS — so the item was permanently invisible to
|
||||
// everyone, including super-admin. The route's own guard
|
||||
// (POST newsletter/list, admin-misc.controller.ts) requires the
|
||||
// plain "newsletters" rid; matched here.
|
||||
permissionId:"newsletters",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user