chore: commit pending admin-panel, api auth and parity doc changes

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-14 04:33:24 +03:30
co-authored by Claude Haiku 5.5
parent 55856cac89
commit cf014e1f6c
41 changed files with 3471 additions and 969 deletions
@@ -5,7 +5,7 @@
"کیف پول" nesting "لیست کیف پول‌های شارژ شده") — ant-design-vue's a-sub-menu
nests natively, the fixed two-level template just didn't recurse into it.
-->
<a-sub-menu v-if="node.submenu" :key="menuKey">
<a-sub-menu v-if="node.submenu" :key="menuKey" popup-class-name="nav-popup">
<template #icon><component :is="resolveIcon(node.icon)" v-if="node.icon" /></template>
<template #title>{{ node.i18n || node.name }}</template>
<NavMenuNode
@@ -33,6 +33,7 @@ const users = useUsersStore();
// Same visibility rule as VerticalNavMenu.vue: no permissionId = always visible.
function canSee(node: NavMenuNodeType): boolean {
if (node.superAdminOnly && !users.isSuperAdmin) return false;
if (!node.permissionId) return true;
return Boolean(users.hasPermission(node.permissionId));
}
@@ -17,11 +17,18 @@
class="vertical-nav-menu"
theme="light"
>
<div class="nav-header">
<span v-if="!collapsed" class="nav-title">{{ title }}</span>
<div class="nav-header" :class="{ 'is-collapsed': collapsed }">
<span class="nav-logo">
<img src="../../assets/images/logo/montzar-icon.png" alt="" />
</span>
<span v-if="!collapsed" class="nav-brand">
<span class="nav-title">{{ title }}</span>
<span class="nav-subtitle">پنل مدیریت</span>
</span>
</div>
<a-menu
class="nav-menu"
mode="inline"
theme="light"
:inline-collapsed="collapsed"
@@ -78,6 +85,7 @@ const users = useUsersStore();
* Preserved deliberately — see docs/ADMIN-PORT-PLAN.md.
*/
function canSee(node: NavMenuNodeData): boolean {
if (node.superAdminOnly && !users.isSuperAdmin) return false;
if (!node.permissionId) return true;
return Boolean(users.hasPermission(node.permissionId));
}
@@ -176,17 +184,246 @@ function onClick({ key }: { key: string | number }) {
<style scoped>
.vertical-nav-menu {
height: 100vh;
overflow-y: auto; /* pinned in place; its own menu scrolls if it's taller than the viewport */
border-left: 1px solid #f0f0f0;
border-left: 1px solid #eef0f5;
box-shadow: -4px 0 24px -16px rgba(16, 24, 40, 0.12);
}
.vertical-nav-menu :deep(.ant-layout-sider-children) {
display: flex;
flex-direction: column;
height: 100%;
}
.nav-header {
height: 61px; /* legacy ara/ant.less @layout-header-height */
flex-shrink: 0;
display: flex;
align-items: center;
gap: 12px;
padding: 0 20px;
border-bottom: 1px solid #eef0f5;
}
.nav-header.is-collapsed {
justify-content: center;
padding: 0;
}
.nav-logo {
width: 38px;
height: 38px;
flex-shrink: 0;
display: grid;
place-items: center;
border-radius: 11px;
background: #fff;
border: 1px solid #eef0f5;
box-shadow: 0 4px 12px -6px rgba(28, 45, 86, 0.35);
}
.nav-logo img {
width: 64%;
}
.nav-brand {
display: flex;
flex-direction: column;
min-width: 0;
line-height: 1.3;
}
.nav-title {
font-size: 18px;
font-size: 16px;
font-weight: 500;
color: #1c2d56;
}
.nav-subtitle {
font-size: 11px;
color: #8a94a6;
}
/* pinned in place; only the menu scrolls if it's taller than the viewport */
.nav-menu {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 12px 0 24px;
scrollbar-width: thin;
scrollbar-color: #e3e7ef transparent;
}
.nav-menu::-webkit-scrollbar {
width: 6px;
}
.nav-menu::-webkit-scrollbar-thumb {
border-radius: 6px;
background: #e3e7ef;
}
</style>
<!--
Unscoped: the mobile drawer teleports outside #app and ant's collapsed popups
teleport to <body>. :is(#app, ...) also out-ranks utilities.less's
`body #app .ant-menu-item-selected`.
-->
<style>
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .nav-menu.ant-menu {
border-inline-end: none !important;
background: transparent;
font-size: 13.5px;
}
/* section titles */
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item-group-title {
padding: 18px 24px 6px;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.3px;
color: #a0a8b8;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-inline-collapsed .ant-menu-item-group-title {
padding: 12px 0 4px;
text-align: center;
font-size: 0;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-inline-collapsed .ant-menu-item-group-title::after {
content: '';
display: inline-block;
width: 18px;
height: 2px;
border-radius: 2px;
background: #e3e7ef;
}
/* items + submenu titles */
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-inline .ant-menu-item,
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-inline .ant-menu-submenu-title {
height: 42px;
line-height: 42px;
margin: 2px 12px;
width: calc(100% - 24px);
border-radius: 10px;
color: #475467;
transition: background 0.2s ease, color 0.2s ease;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item .ant-menu-item-icon,
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-submenu-title .ant-menu-item-icon {
font-size: 16px;
color: #98a2b3;
transition: color 0.2s ease;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item:not(.ant-menu-item-selected):hover,
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-submenu-title:hover {
background: #f5f7fb !important;
color: #1c2d56 !important;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item:hover .ant-menu-item-icon,
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-submenu-title:hover .ant-menu-item-icon {
color: #1c2d56;
}
/* active item */
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item-selected {
position: relative;
background: rgba(28, 45, 86, 0.08) !important;
color: #1c2d56 !important;
font-weight: 500;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-item-selected .ant-menu-item-icon {
color: #1c2d56;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-inline .ant-menu-item-selected::after {
content: '';
position: absolute;
inset-inline-start: 0;
inset-inline-end: auto;
top: 10px;
bottom: 10px;
width: 3px;
border: none;
border-radius: 3px;
background: #1c2d56;
opacity: 1;
transform: none;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-submenu-selected > .ant-menu-submenu-title,
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-submenu-selected > .ant-menu-submenu-title .ant-menu-item-icon {
color: #1c2d56;
font-weight: 500;
}
/* nested lists: no grey slab, a soft guide line instead */
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-sub.ant-menu-inline {
position: relative;
background: transparent !important;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-sub.ant-menu-inline::before {
content: '';
position: absolute;
inset-inline-start: 32px;
top: 4px;
bottom: 4px;
width: 1px;
background: #e8ebf2;
}
:is(#app, .mobile-nav-drawer) .vertical-nav-menu .ant-menu-sub .ant-menu-item {
height: 38px;
line-height: 38px;
font-size: 13px;
}
/* collapsed rail: submenus open as popups teleported to <body> */
.nav-popup.ant-menu-submenu-popup > .ant-menu {
padding: 6px;
border-radius: 14px;
background: #fff;
border: 1px solid #eef0f5;
box-shadow: 0 12px 32px -12px rgba(16, 24, 40, 0.25);
}
.nav-popup .ant-menu-item,
.nav-popup .ant-menu-submenu-title {
height: 40px;
line-height: 40px;
margin: 2px 0;
width: 100%;
border-radius: 10px;
color: #475467;
font-size: 13px;
transition: background 0.2s ease, color 0.2s ease;
}
.nav-popup .ant-menu-item .ant-menu-item-icon,
.nav-popup .ant-menu-submenu-title .ant-menu-item-icon {
color: #98a2b3;
}
.nav-popup .ant-menu-item:not(.ant-menu-item-selected):hover,
.nav-popup .ant-menu-submenu-title:hover {
background: #f5f7fb !important;
color: #1c2d56 !important;
}
.nav-popup .ant-menu-item-selected {
position: relative;
background: rgba(28, 45, 86, 0.08) !important;
color: #1c2d56 !important;
font-weight: 500;
}
.nav-popup .ant-menu-item-selected .ant-menu-item-icon {
color: #1c2d56;
}
.nav-popup .ant-menu-item-selected::after {
content: '';
position: absolute;
inset-inline-start: 0;
inset-inline-end: auto;
top: 10px;
bottom: 10px;
width: 3px;
border: none;
border-radius: 3px;
background: #1c2d56;
opacity: 1;
transform: none;
}
.nav-popup .ant-menu-submenu-selected > .ant-menu-submenu-title {
color: #1c2d56;
font-weight: 500;
}
/* collapsed rail: the icon of the group holding the active page */
#app .vertical-nav-menu .ant-menu-inline-collapsed .ant-menu-submenu-selected > .ant-menu-submenu-title,
#app .vertical-nav-menu .ant-menu-inline-collapsed > .ant-menu-item-selected {
background: rgba(28, 45, 86, 0.08) !important;
color: #1c2d56 !important;
}
</style>
+8 -1
View File
@@ -12,7 +12,12 @@
sidebar/navbar that used to scroll away with it.
-->
<a-layout class="layout--main" :class="{ 'no-scroll': isAppPage }">
<VerticalNavMenu v-if="!layout.isSmallScreen" :collapsed="collapsed" />
<VerticalNavMenu
v-if="!layout.isSmallScreen"
:collapsed="collapsed"
show-toggle
@toggle="onToggleSidebar"
/>
<a-drawer
v-else
@@ -131,9 +136,11 @@ watch(
<style>
.mobile-nav-drawer .ant-drawer-body {
padding: 0;
background: #ffffff;
}
.mobile-nav-drawer .vertical-nav-menu {
height: 100%;
border-left: none;
background: #ffffff !important;
}
</style>
+15 -6
View File
@@ -36,6 +36,8 @@ export interface NavMenuNode {
tag?: string;
tagColor?: string;
permissionId?: string;
/** Shown to the super admin only, whatever the rid (see utils/superAdmin.ts). */
superAdminOnly?: boolean;
header?: string;
items?: NavMenuNode[];
submenu?: NavMenuNode[];
@@ -109,7 +111,8 @@ export const adminMenu: NavMenuNode[] = [
name: 'List',
slug: 'app-user-list',
i18n: 'لیست مشتریان تک',
permissionId:"persons.list.client.single"
permissionId:"persons.list.client.single",
superAdminOnly: true
},
{
url: '/customer/banker-list',
@@ -121,7 +124,8 @@ export const adminMenu: NavMenuNode[] = [
// 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"
permissionId:"persons.list.client.omde",
superAdminOnly: true
},
{
// legacy navMenuItems.js:90 spelled this with a capital S while the route
@@ -133,7 +137,8 @@ export const adminMenu: NavMenuNode[] = [
i18n: 'لیست مغازه داران حضوری',
// Same fix as banker-list above — the backing endpoint
// (admin/customers-omde/shopkeeper/list) requires this rid.
permissionId:"persons.list.client.omde"
permissionId:"persons.list.client.omde",
superAdminOnly: true
},
]
},
@@ -145,6 +150,7 @@ export const adminMenu: NavMenuNode[] = [
name: 'branch and workshop',
icon: 'GridIcon',
i18n: 'شعب و کارگاه',
superAdminOnly: true,
submenu: [
{
url: null,
@@ -227,14 +233,16 @@ export const adminMenu: NavMenuNode[] = [
name: 'orderOmdeList',
slug: 'order-omde-list',
i18n: 'ثبت سفارش تک',
permissionId:"order.create"
permissionId:"order.create",
superAdminOnly: true
},
{
url: '/order/list',
name: 'orderList',
slug: 'order-list',
i18n: 'لیست سفارش های تک',
permissionId:"order.list"
permissionId:"order.list",
superAdminOnly: true
},
{
url: '/order/make',
@@ -461,7 +469,8 @@ export const adminMenu: NavMenuNode[] = [
name: 'wallet Management',
slug: 'manage-const',
i18n: 'مدیریت کیف پول',
permissionId:"config.wallet"
permissionId:"config.wallet",
superAdminOnly: true
},
// D27 — Tahesab / Zarrin integration.
{