نظام الأوضاع المتعددة
نظرة عامة
فصل كامل — كل ملف نظيف 100% بدون شروط. الـ mode logic موجود في 4 أماكن فقط في النظام بأكمله.
بعض الصفحات خاصة بنظام معين — يحميها middleware تلقائياً
تظهر أو تختفي حسب النظام عبر filterByMode
نفس الصفحة بحقول/سكيمة/state مختلف لكل نظام
كشف النظام — useMode.js
| المصدر | القيمة | النتيجة |
|---|---|---|
| privateCompanyMode | true | private |
| privateCompanyMode | false | government |
| mode | 1 | private |
| mode | 2 | government |
| mode | 3 | mixed |
| fallback | — | private |
export const MODES = {
PRIVATE: 'private',
GOVERNMENT: 'government',
MIXED: 'mixed'
}
export const useMode = () => {
const { data: session } = useAuth()
const mode = computed(() => {
const raw = session.value
if (raw?.mode !== undefined) {
return {
1: MODES.PRIVATE,
2: MODES.GOVERNMENT,
3: MODES.MIXED
}[raw.mode] || MODES.PRIVATE
}
if (raw?.privateCompanyMode !== undefined) {
return raw.privateCompanyMode
? MODES.PRIVATE : MODES.GOVERNMENT
}
return MODES.PRIVATE
})
const isMode = (...modes) => modes.includes(mode.value)
const filterByMode = (items) => items.filter(item => {
if (!item.modes || item.modes.length === 0) return true
return item.modes.includes(mode.value)
})
return { mode, isMode, filterByMode, MODES }
}مرجع الـ API
MODESObjectثوابت أسماء الأنظمة — PRIVATE, GOVERNMENT, MIXED.
import { MODES } from '~/composables/core/useMode'
MODES.PRIVATE // 'private'
MODES.GOVERNMENT // 'government'
MODES.MIXED // 'mixed'modeComputedRefالـ mode الحالي كـ string — يتحدث تلقائياً عند تغيير session.
const { mode } = useMode()
console.log(mode.value)
// 'private' | 'government' | 'mixed'isMode(...modes)Functionيتحقق هل النظام الحالي ضمن القائمة المحددة — يقبل عدة أنظمة.
const { isMode } = useMode()
isMode('private') // true لو خاص
isMode('private', 'mixed') // true لو خاص أو مختلط
isMode('government') // true لو حكوميfilterByMode(items)Functionيصفي array من العناصر حسب خاصية modes الاختيارية — عنصر بدون modes يظهر دائماً.
const { filterByMode } = useMode()
const items = [
{ label: 'Dashboard' },
{ label: 'Invoices', modes: ['private'] },
{ label: 'Tenders', modes: ['government'] },
]
filterByMode(items)
// → العناصر المناسبة للنظام الحاليالمكوّنات
يستقبل map من المكوّنات ويرسم الصحيح تلقائياً حسب الـ mode. يمرر $attrs و $slots للمكوّن المختار.
<script setup>
import FormPrivate from '~/components/.../FormPrivate.vue'
import FormGovernment from '~/components/.../FormGovernment.vue'
const formComponents = {
private: FormPrivate,
government: FormGovernment
}
</script>
<template>
<BaseModeSwitch :is="formComponents" />
</template>للحالات البسيطة — إظهار/إخفاء محتوى حسب الـ mode. يُستخدم خارج الفورمات فقط.
<!-- إظهار للنظام الخاص فقط -->
<BaseModeShow :modes="['private']">
<div>محتوى خاص بالنظام الخاص</div>
</BaseModeShow>
<!-- إظهار للجميع ما عدا الحكومي -->
<BaseModeShow :except="['government']">
<div>يظهر لكل الأنظمة إلا الحكومي</div>
</BaseModeShow>حماية الصفحات — Middleware
مسموحة لكل الأنظمة — لا حاجة لتحديد modes
مسموحة فقط للأنظمة المحددة — باقي الأنظمة يتم تحويلها لـ /
// صفحة متاحة لكل الأنظمة
definePageMeta({
title: 'pages.activities'
})
// صفحة خاصة بالنظام الخاص فقط
definePageMeta({
title: 'pages.invoices',
modes: ['private']
})
// صفحة متاحة للخاص والمختلط
definePageMeta({
title: 'pages.tax',
modes: ['private', 'mixed']
})تصفية الـ Sidebar
القاعدة: عنصر بدون modes = يظهر في كل الأنظمة. عنصر مع modes = يظهر فقط في الأنظمة المحددة.
export const useSidebar = () => {
const { filterByMode } = useMode()
const filterLinks = (items) => {
return filterByMode(items).map(item => {
if (item.children)
return { ...item, children: filterByMode(item.children) }
return item
}).filter(item => !(item.children?.length === 0))
}
const rawLinks = computed(() => [
{
label: t('sidebar.dashboard'),
icon: 'i-lucide-house',
to: localePath('/')
// بدون modes → يظهر دائماً
},
{
label: t('sidebar.invoices'),
icon: 'i-lucide-file-text',
to: localePath('/invoices'),
modes: ['private'] // ← خاص فقط
},
// ...
])
const links = computed(() => [filterLinks(rawLinks.value), []])
return { links }
}نمط الموديول — الفصل الكامل
composables/modules/settings/signatures/
├── index.js ← الأوركسترا
├── schema.private.js ← سكيمة نظيفة
├── schema.government.js ← سكيمة نظيفة
├── form.private.js ← state نظيف
├── form.government.js ← state نظيف
├── table.js ← مشترك
└── columns.js ← مشترك
components/modules/settings/signatures/
├── SignaturesFormPrivate.vue
├── SignaturesFormGovernment.vue
└── SignaturesTable.vuecomposables/modules/settings/activities/
├── index.js
├── schema.js ← ملف واحد
├── form.js ← ملف واحد
├── table.js
└── columns.js
components/modules/settings/activities/
├── ActivitiesForm.vue ← ملف واحد
└── ActivitiesTable.vue| النوع | المشترك | الخاص بنظام |
|---|---|---|
| Schema composable | schema.js | schema.private.js / schema.government.js |
| Form composable | form.js | form.private.js / form.government.js |
| Columns composable | columns.js | columns.private.js / columns.government.js |
| Vue Component | SignaturesForm.vue | SignaturesFormPrivate.vue / SignaturesFormGovernment.vue |
| Export function | useSignaturesSchema() | useSignaturesSchemaPrivate() / ...Government() |
نمط الأوركسترا — index.js
export const useSignatures = () => {
const { mode } = useMode()
// ← الشرط الوحيد: اختيار النسخة الصحيحة
const schemaMap = {
private: useSignaturesSchemaPrivate,
government: useSignaturesSchemaGovernment
}
const formMap = {
private: useSignaturesFormPrivate,
government: useSignaturesFormGovernment
}
const { schema } = schemaMap[mode.value]()
const form = formMap[mode.value]()
// ← مشترك — بدون أي شرط
const table = useSignaturesTable()
const { createColumns } = useSignaturesColumns()
const editHandler = (item) => {
form.state.id = item.id
}
const columns = createColumns({ onEdit: editHandler })
const resetState = () => form.resetForm()
const submitHandler = async () => {
table.loadingSave.value = true
try {
const body = form.prepareSubmitData()
await table.updateSignature(form.state.id, body)
form.resetForm()
} catch {
// handled by base service
} finally {
table.loadingSave.value = false
}
}
return {
schema, state: form.state,
reportsList: form.reportsList,
items: table.items,
loadingSave: table.loadingSave,
columns, resetState, submitHandler
}
}أين يوجد Mode Logic؟
composables/core/useMode.jsقراءة الـ mode من session وتوحيد الصيغة
middleware/mode.global.jsحماية الصفحات المقيدة بنظام معين
composables/layout/useSidebar.jsتصفية عناصر الـ sidebar حسب النظام
composables/modules/*/index.jsاختيار schema/form/component الصحيح (الأوركسترا)
إضافة نظام جديد
إضافة النظام الجديد للثوابت وقاعدة التحويل.
export const MODES = {
PRIVATE: 'private',
GOVERNMENT: 'government',
MIXED: 'mixed' // ← جديد
}
// في computed:
{ 1: MODES.PRIVATE, 2: MODES.GOVERNMENT, 3: MODES.MIXED }لكل موديول مختلف — schema, form, component.
schema.mixed.js
form.mixed.js
SignaturesFormMixed.vueإضافة النظام الجديد في الـ map.
const schemaMap = {
private: useSignaturesSchemaPrivate,
government: useSignaturesSchemaGovernment,
mixed: useSignaturesSchemaMixed // ← جديد
}إضافة المكوّن الجديد في formComponents.
const formComponents = {
private: FormPrivate,
government: FormGovernment,
mixed: FormMixed // ← جديد
}إضافة عناصر جديدة مع modes: ['mixed'].
{
label: t('sidebar.mixedReport'),
to: localePath('/mixed-report'),
modes: ['mixed'] // ← جديد
}حماية الصفحات المقيدة بالنظام الجديد.
definePageMeta({
title: 'pages.mixedReport',
modes: ['mixed'] // ← حماية بالـ middleware
})القرارات التصميمية
| المعيار | ModeShow / شروط | فصل بالملفات |
|---|---|---|
| مع مئات الحقول | مئات الشروط في template | كل ملف نظيف 100% |
| مع 3+ أنظمة | تعقيد أسّي | ملف جديد فقط |
| فهم نظام واحد | فلترة ذهنية لكل سطر | فتح ملف واحد = كل شيء |
| إضافة نظام جديد | تعديل ملفات موجودة | إضافة ملفات فقط |
| ما يتكرر | خطورته |
|---|---|
| HTML الحقول في template | منخفضة — تصريحي وليس لوجك |
| Labels | صفر — من i18n (ملف واحد مشترك) |
| سلوك الحقول | صفر — في Base Components |
| Schema | صفر — ملف مستقل لكل نظام |
| Form state | صفر — ملف مستقل لكل نظام |
| Services/API | صفر — مشترك دائماً |
| Save/Submit | صفر — مشترك في الأوركسترا |
| البديل | المشكلة |
|---|---|
| Config-Driven (حقول كـ array) | يفقد مرونة الـ template — لا slots مخصصة، لا layouts معقدة، كل نوع حقل جديد يحتاج case |
| Nuxt Layers (طبقة لكل نظام) | الـ mode يأتي runtime من API بعد تسجيل الدخول — Layers تعمل build-time فقط |