16. مثال [nuxt-13]: التحكم في التنقل في [nuxt-12]
في هذا المثال، نهتم بالتنقل في [nuxt-12]. لم نقم بذلك في [nuxt-12] لأن التحقق من التنقل كان سيجعل المثال المعقد أصلاً أكثر تعقيداً.
الهدف: نريد أن يقتصر المستخدم على القيام بالإجراءات المصرح بها فقط:
- إذا لم تبدأ الجلسة jSON، فإن الجلسات المسموح بها هي URL و[/] فقط؛
- إذا بدأت الجلسة jSON ولكن لم يتم مصادقة المستخدم، فإن الجلسات URL و [/authentification] هي المسموح بها فقط؛
- إذا بدأت الجلسة jSON وتم مصادقة المستخدم، فإن الجلسات URL و [/get-admindata, /fin-session] هي المسموح بها فقط؛
- عندما لا تكون وجهة التوجيه الحالية مسموحة، يتم إعادة التوجيه إلى URL المسموح بها؛
يتم الحصول على المثال [nuxt-13] مبدئيًا عن طريق نسخ المثال [nuxt-12]:

ستتم التعديلات في مجلد التوجيه [middleware].
16.1. توجيه التطبيق [nuxt]
يتم تكوين توجيه التطبيق على النحو التالي في الملف [nuxt.config]:
// الموجه
router: {
// جذر URL للتطبيق
base: '/nuxt-13/',
// برمجيات التوجيه الوسيطة
middleware: ['routing']
},
- السطر 6: يتم التحكم في توجيه التطبيق بواسطة الملف [middleware/routing]؛
الملف [middleware/routing] هو التالي:
/* eslint-disable no-console */
// نقوم باستيراد برامج الوسيطة من الخادم والعميل
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// من الذي ينفذ هذا الكود؟
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// توجيه الخادم
serverRouting(context)
} else {
// توجيه العميل
clientRouting(context)
}
}
- الأسطر 10-16: يتم التعامل مع توجيه العميل والخادم [nuxt] بشكل مختلف. هذه نقطة تختلف فيها بشكل كبير؛
- السطر 4: يتم تنفيذ توجيه الخادم بواسطة البرنامج النصي [middleware/server/routing]؛
- السطر 5: يتم تنفيذ توجيه العميل بواسطة البرنامج النصي [middleware/client/routing]؛
16.2. توجيه العميل [nuxt]
يظل توجيه العميل [nuxt] كما كان في [nuxt-12]:
/* eslint-disable no-console */
export default function(context) {
// من الذي ينفذ هذا الكود؟
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// إدارة ملف تعريف الارتباط الخاص بالجلسة PHP في المتصفح
// يجب أن تكون ملفات تعريف الارتباط للجلسة PHP في المتصفح مطابقة لتلك الموجودة في جلسة nuxt
// تتلقى العملية [fin-session] ملف تعريف ارتباط جديد PHP (الخادم كعميل nuxt)
// إذا كان الخادم هو الذي يستلمه، يجب على العميل نقله إلى المتصفح
// من أجل تبادلاته الخاصة مع الخادم PHP
// نحن هنا في توجيه عميل
// نسترد ملف تعريف الارتباط الخاص بالجلسة PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// إذا كان موجودًا، يتم تعيين ملف تعريف ارتباط الجلسة PHP للمتصفح
document.cookie = phpSessionCookie
}
...
}
لتجنب دخول العميل إلى مسارات غير مصرح بها، سنعرض له في قائمة التنقل الخاصة بالعميل المسارات المصرح بها فقط. يصبح المكون [components/navigation] كما يلي:
<template>
<!-- قائمة Bootstrap ذات الخيارات الثلاثة -->
<b-nav vertical>
<b-nav-item v-if="$store.state.jsonSessionStarted && !$store.state.userAuthenticated" to="/authentification" exact exact-active-class="active">
Authentification
</b-nav-item>
<b-nav-item
v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated && !$store.state.adminData"
to="/get-admindata"
exact
exact-active-class="active"
>
Requête AdminData
</b-nav-item>
<b-nav-item v-if="$store.state.jsonSessionStarted && $store.state.userAuthenticated" to="/fin-session" exact exact-active-class="active">
Fin session impôt
</b-nav-item>
</b-nav>
</template>
- السطر 4: لا يتم عرض الخيار [Authentification] إلا إذا كانت الجلسة jSON قد بدأت ولكن المستخدم لم يتم توثيقه. إذا لم تبدأ الجلسة jSON أو كان المستخدم قد تم توثيقه بالفعل، فلن يتم عرض الخيار؛
- السطور 7-11: لا يتم عرض الخيار [Requête AdminData] إلا إذا كانت الجلسة jSON قد بدأت، وكان المستخدم قد تمت مصادقته، ولم يتم استرداد البيانات [AdminData] بعد. إذا لم يتم استيفاء أي من هذه الشروط الثلاثة (لم تبدأ الجلسة jSON، أو لم يتم توثيق المستخدم، أو تم استرداد البيانات [AdminData] بالفعل)، لا يتم عرض الخيار؛
- السطر 15: يتم تقديم الخيار [Fin session impôt] بمجرد بدء الجلسة jSON وتوثيق المستخدم، وإلا فلن يتم تقديمه؛
16.3. توجيه الخادم [nuxt]
عادةً ما يكون توجيه الخادم أكثر تعقيدًا من توجيه العميل لأن المستخدم يمكنه كتابة أي عنوان URL في شريط العناوين بمتصفحه. يمكننا ترك الأمر على حاله (ففي النهاية ليس من المفترض أن يقوم المستخدم بذلك) أو محاولة التحكم في الأمور. وهذا ما سنفعله هنا، على سبيل المثال، لأنه في حالة تطبيق [nuxt-12]، يمكننا الاستغناء عن ذلك تمامًا لأن خادم حساب الضريبة محمي جيدًا ضد هذه العناوين اليدوية URL ويعرف كيفية إرسال رسائل الخطأ المناسبة. لقد رأينا ذلك في [next-12] حيث لم يكن هناك أي تحكم في التوجيه.
يختلف توجيه خادم [nuxt] اختلافًا كبيرًا عن عميل [nuxt] فيما يتعلق بمفهوم إعادة التوجيه:
- عندما يتم إعادة توجيه خادم [nuxt]، فإنه يرسل أمر إعادة توجيه إلى متصفح العميل مع هدف إعادة التوجيه. ثم يقوم المتصفح بإرسال طلب جديد إلى الخادم [nuxt] يطلب منه الهدف الذي تم إرساله إليه. يحدث كل شيء كما لو أن المستخدم قد كتب يدويًا عنوان URL لوجهة إعادة التوجيه: يتم إعادة تشغيل التطبيق [nuxt] بالكامل وبالتالي دورة حياته بالكامل (المكونات الإضافية للخادم، المتجر، توجيه الخادم، الصفحات)؛
- عندما يتم إعادة توجيه عميل [nuxt]، لا يحدث شيء من هذا القبيل. هناك تغيير بسيط في الصفحة، وهو نفس التغيير الذي كان سيتم الحصول عليه لو أن المستخدم نقر على رابط يؤدي إلى هدف إعادة التوجيه. يكون دورة الحياة مختلفة عندئذٍ (توجيه العميل، عرض هدف المسار)؛
لهذا السبب، من الأفضل فصل توجيه العميل عن توجيه الخادم حتى لو بدا كلا الكودين متشابهين.
سيكون نص توجيه الخادم [middleware/server/routing] كما يلي:
/* eslint-disable no-console */
export default function(context) {
// من الذي ينفذ هذا الكود؟
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// نسترد بعض المعلومات من المخزن [nuxt]
const store = context.store
// من أين أتينا؟
const from = store.state.from || 'nowhere'
...
}
- في توجيه العميل، تتلقى وظيفة التوجيه السياق [context] مع الخاصية [context.from] التي تمثل مسار الصفحة التي أتينا منها. يتم الحصول على المسار الذي سنذهب إليه بواسطة [context.route]؛
- في توجيه الخادم، تتلقى وظيفة التوجيه السياق [context] بدون الخاصية [context.from]. لا يتدخل توجيه الخادم إلا عندما يُطلب URL يدويًا من الخادم [nuxt]. من المعروف أنه عندئذ يتم إعادة تعيين التطبيق [nuxt] بالكامل. وكأننا نبدأ من الصفر، وبالتالي لا يوجد مفهوم "الصفحة السابقة"؛
- بفضل الجلسة [nuxt]، نعلم أن الخادم يمكنه استرداد هذه الجلسة وبالتالي عدم البدء من الصفر. لذلك، في هذه الجلسة [nuxt]، وبشكل أكثر تحديدًا في مخزن هذه الجلسة، سنقوم بتخزين اسم آخر صفحة عرضها متصفح العميل قبل أن يُطلب URL من الخادم [nuxt]؛
- الأسطر 7-9: نسترد اسم آخر صفحة عرضها متصفح العميل. عند بدء تشغيل التطبيق، لا توجد هذه المعلومة [from] في المخزن. ثم يتم تعيين الاسم [nowhere] للمتغير [from]؛
لكي يتمكن الخادم [nuxt] من استرداد اسم آخر صفحة عرضها متصفح العميل من المخزن، يجب أن يقوم العميل [nuxt] أيضًا بوضع هذه المعلومة في المخزن. وبالتالي، يتم استكمال برنامج التوجيه الخاص بالعميل [nuxt] على النحو التالي:
/* eslint-disable no-console */
export default function(context) {
// من يقوم بتنفيذ هذا الكود؟
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// إدارة ملف تعريف الارتباط الخاص بالجلسة PHP في المتصفح
// يجب أن تكون ملفات تعريف الارتباط للجلسة PHP في المتصفح مطابقة لتلك الموجودة في جلسة nuxt
// تتلقى العملية [fin-session] ملف تعريف ارتباط جديد PHP (الخادم كعميل nuxt)
// إذا كان الخادم هو الذي يستلمه، يجب على العميل نقله إلى المتصفح
// من أجل تبادلاته الخاصة مع الخادم PHP
// نحن هنا في توجيه عميل
// نسترد ملف تعريف الارتباط الخاص بالجلسة PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// إذا كان موجودًا، يتم تعيين ملف تعريف ارتباط الجلسة PHP للمتصفح
document.cookie = phpSessionCookie
}
// يتم إدراج اسم الصفحة المقصودة في الجلسة - بدون إعادة توجيه من الخادم
context.store.commit('replace', { serverRedirection: false, from: context.route.name })
// يتم حفظ المتجر في الجلسة [nuxt]
const session = context.app.$session()
session.value.store = context.store.state
session.save(context)
}
- تتم إضافة الأسطر 19-24؛
- السطر 20: يتم إدراج اسم الصفحة [context.route.name] التي سيتم عرضها في المخزن، والتي ستكون بالتالي، عند التوجيه التالي، الصفحة التي أتينا منها. من ناحية أخرى، سنرى أنه في توجيه الخادم [nuxt]، يحتاج هذا الخادم إلى معرفة ما إذا كان التوجيه الحالي ناتجًا عن إعادة توجيه سابقة من الخادم [nuxt]. وهذا ليس هو الحال هنا، ولذلك نضع الخاصية [serverRedirection] في [false]؛
- الأسطر 22-24: يتم وضع حالة المخزن في الجلسة [nuxt] (السطر 23) ثم يتم حفظ الجلسة [nuxt] في ملف تعريف ارتباط (السطر 24) والذي سيتم حفظه بدوره في متصفح العميل [nuxt]؛
لنعد إلى البرنامج النصي لتوجيه الخادم [nuxt]:
/* eslint-disable no-console */
export default function(context) {
// من الذي ينفذ هذا الكود؟
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// نسترد بعض المعلومات من المخزن [nuxt]
const store = context.store
// من أين أتينا؟
const from = store.state.from || 'nowhere'
// إلى أين نحن ذاهبون؟
const to = context.route.name
// إعادة توجيه محتملة
let redirection = ''
// انتهت إدارة التوجيه
let done = false
// هل نحن بالفعل في عملية إعادة توجيه من الخادم [nuxt]؟
if (store.state.serverRedirection) {
// لا شيء للقيام به
done = true
}
// هل هذه عملية إعادة تحميل للصفحة؟
if (to === from) {
// لا شيء
done = true
}
// التحكم في تنقل الخادم [nuxt]
// يتم الاستلهام من تنقل العميل في المكون [navigation]
// في حالة عدم بدء الجلسة PHP
if (!done && !store.state.jsonSessionStarted && to !== 'index') {
// إعادة التوجيه
redirection = 'index'
// انتهى العمل
done = true
}
// في حالة عدم مصادقة المستخدم
if (!done && store.state.jsonSessionStarted && !store.state.userAuthenticated && to !== 'authentification') {
// إعادة التوجيه
redirection = from
// انتهى العمل
done = true
}
// حالة تم فيها توثيق المستخدم
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && to !== 'get-admindata' && to !== 'fin-session') {
// البقاء على نفس الصفحة
redirection = from
// انتهى العمل
done = true
}
// في حالة الحصول على [adminData]
if (!done && store.state.jsonSessionStarted && store.state.userAuthenticated && store.state.adminData && to !== 'fin-session') {
// يتم البقاء على نفس الصفحة
redirection = from
// تم الانتهاء من العمل
done = true
}
// تم إجراء جميع الفحوصات ---------------------
// إعادة توجيه؟
if (redirection) {
// تم تسجيل إعادة التوجيه في المتجر
store.commit('replace', { serverRedirection: true })
} else {
// لا توجد إعادة توجيه
store.commit('replace', { serverRedirection: false, from: to })
}
// يتم حفظ المتجر في الجلسة [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// يتم إجراء إعادة التوجيه المحتملة
if (redirection) {
context.redirect({ name: redirection })
}
}
- الأسطر 6-9: يتم استرداد قيمة [from] من مخزن الخادم [nuxt]؛
- السطر 11: يتم تسجيل هدف التوجيه الحالي؛
- السطر 13: قد يؤدي التوجيه إلى إعادة توجيه متصفح العميل. سيكون [redirection] هو هدف إعادة التوجيه هذه؛
- السطر 15: يشير [done] إلى [true] إلى أن التوجيه قد اكتمل؛
- الأسطر 17-21: يتم أولاً التحقق مما إذا كان التوجيه الحالي ناتجًا عن طلب إعادة توجيه مرسل إلى متصفح العميل. يتم تخزين هذه المعلومات في الخاصية [serverRedirection] في المخزن. إذا كانت هذه الخاصية صحيحة، فهذا يعني أن الخادم [nuxt] قد أرسل إعادة توجيه إلى متصفح العميل أثناء الطلب السابق إلى الخادم [nuxt]. في هذه الحالة، لا يوجد توجيه يجب القيام به. أثناء الطلب السابق، قرر موجه الخادم [nuxt] أنه يجب إعادة توجيه متصفح العميل. لا يجب إعادة النظر في هذا القرار من خلال توجيه جديد؛
- السطور 23-27: يتم التحقق مما إذا كان التوجيه الحالي عبارة عن إعادة تحميل للصفحة. إذا كان الأمر كذلك، يتم السماح بذلك؛
- ابتداءً من السطر 29، يتم استئناف القواعد المطبقة في المكون [navigation] التابع للعميل [nuxt] (انظر الفقرة السابقة)؛
- السطور 32-38: يتم التعامل مع الحالة التي لم تبدأ فيها الجلسة jSON ولم تكن وجهة التوجيه هي الصفحة [index]. في هذه الحالة، يتم إعادة توجيه متصفح العميل إلى الصفحة [index]؛
- الأسطر 40-46: يتم التعامل مع الحالة التي تكون فيها الجلسة jSON قد بدأت، والمستخدم غير مصدق، ووجهة التوجيه الحالية ليست الصفحة [authentification]. في هذه الحالة، يتم رفض التوجيه والبقاء في المكان الذي كنا فيه؛
- السطور 48-54: نعالج الحالة التي بدأت فيها الجلسة jSON، وتم مصادقة المستخدم، ووجهة التوجيه الحالية ليست الصفحة [get-admindata]، ولا الصفحة [fin-session]، وهما الوجهتان الوحيدتان المحتملتان. في هذه الحالة، يتم رفض التوجيه المطلوب والعودة إلى حيث كنا سابقًا؛
- الأسطر 56-62: يتم معالجة الحالة التي تم فيها الحصول على [adminData]. في هذه الحالة، لا يوجد سوى هدف واحد ممكن للتوجيه: الصفحة [fin-session]. إذا لم تكن هي المطلوبة، يتم رفض التوجيه والعودة إلى حيث كنا سابقًا؛
- الأسطر 64-72: إذا حدثت إعادة توجيه، يتم تسجيلها في مخزن الخادم [nuxt]: [serverRedirection: true]. تجدر الإشارة إلى أنه لا يتم تعيين قيمة للخاصية [from] في المخزن. والسبب في ذلك هو أنه سيكون هناك إعادة توجيه من متصفح العميل وقد رأينا أنه في هذه الحالة، لم يكن هناك توجيه (الأسطر 17-20) ولا يتم استخدام الخاصية [from] في المخزن؛
- الأسطر 66-69: إذا لم تكن هناك إعادة توجيه، فإننا نسجل ذلك أيضًا في مخزن الخادم [nuxt]: [serverRedirection: false]. بالإضافة إلى ذلك، سيؤدي التوجيه الجاري إلى عرض الصفحة [to] التي ستصبح الصفحة السابقة للطلب التالي (العميل أو الخادم [nuxt]). ولهذا السبب نكتب [from: to]؛
- الأسطر 73-76: يتم حفظ المخزن في الجلسة [nuxt] التي تم حفظها بدورها في ملف تعريف ارتباط؛
- الأسطر 77-80: إذا لم يكن [redirection] فارغًا، فإننا نطلب من المتصفح إعادة التوجيه. وإلا (لا نرى ذلك هنا)، فسوف يستمر دورة حياة الخادم [nuxt]: ستتم معالجة الصفحة [to] بواسطة الخادم [nuxt] وإرسالها إلى متصفح العميل [nuxt] مع ملف تعريف الارتباط للجلسة [nuxt]؛
التوجيه المختار هنا للخادم [nuxt] هو توجيه تعسفي. كان من الممكن اختيار توجيه آخر أو، كما قيل، عدم القيام بأي توجيه على الإطلاق. يتميز التوجيه المختار أعلاه بأنه يبقي التطبيق دائمًا في حالة مستقرة بغض النظر عن URL التي يطلبها المستخدم.
يمكن تحسين نقطة واحدة عندما تكون الصفحة التي يتم تحميلها في النهاية هي الصفحة الأصلية. هناك حالتان:
- قام المستخدم بإعادة تحميل الصفحة (to===from)؛
- وجود عمليات إعادة توجيه إلى الصفحة الأصلية (redirection===from)؛
في كلتا الحالتين، سيتم تنفيذ الصفحة الأصلية مرة أخرى مع استدعائها غير المتزامن لخادم حساب الضريبة. لنأخذ مثالاً. إذا قام المستخدم، بعد المصادقة، بإعادة تحميل الصفحة (F5). في هذه الحالة، في التوجيه أعلاه، يكون لدينا: [to]=[from]=[authentification]. لا يوجد إعادة توجيه. سيتم تنفيذ الصفحة [to=authentification] بواسطة الخادم [nuxt]. إذا لم نفعل شيئًا، فستُنفَّذ الدالة [asyncData] مرة أخرى. وهذا غير ضروري لأن المصادقة قد تمت بالفعل.
يمكن تحسين الوضع عن طريق إجراء تعديل بسيط على الصفحة [authentification]:
// بيانات غير متزامنة
async asyncData(context) {
// السجل
console.log('[authentification asyncData started]')
// لا يتم تكرار العملية إذا تم طلب الصفحة مسبقًا
if (process.server && context.store.state.userAuthenticated) {
console.log('[authentification asyncData canceled]')
return { result: '[succès]' }
}
// العميل [nuxt]
if (process.client) {
// بدء الانتظار
context.app.$eventBus().$emit('loading', true)
// لا توجد أخطاء
context.app.$eventBus().$emit('errorLoading', false)
}
try {
// يتم المصادقة لدى الخادم
...
- الأسطر 6-9: إذا تم تنفيذ الصفحة بواسطة الخادم [nuxt] واكتشفنا في المخزن أن المصادقة قد تمت بالفعل، فإننا نعيد النتيجة المطلوبة مباشرة (السطر 8)؛
نقوم بنفس الشيء لجميع الصفحات:
الصفحة [index]:
// بيانات غير متزامنة
async asyncData(context) {
// سجل
console.log('[index asyncData started]')
// لا يتم تكرار العملية إذا تم طلب الصفحة من قبل
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
...
الصفحة [get-admindata]
// بيانات غير متزامنة
async asyncData(context) {
// سجل
console.log('[get-admindata asyncData started]')
// لا يتم تنفيذ العملية مرتين إذا تم طلب الصفحة بالفعل
if (process.server && context.store.state.adminData) {
console.log('[get-admindata asyncData canceled]')
return { result: context.store.state.adminData }
}
// العميل
if (process.client) {
// بدء الانتظار
context.app.$eventBus().$emit('loading', true)
// لا توجد أخطاء
context.app.$eventBus().$emit('errorLoading', false)
}
try {
...
الصفحة [fin-session]
// بيانات غير متزامنة
async asyncData(context) {
// سجل
console.log('[fin-session asyncData started]')
// لا نكرر العملية إذا كانت الصفحة قد طُلبت بالفعل
if (process.server && context.store.state.jsonSessionStarted && !context.store.state.userAuthenticated) {
console.log('[fin-session asyncData canceled]')
return { result: "[succès]. La session jSON reste initialisée mais vous n'êtes plus authentifié(e)." }
}
// حالة العميل [nuxt]
if (process.client) {
// بدء الانتظار
context.app.$eventBus().$emit('loading', true)
// لا توجد أخطاء
context.app.$eventBus().$emit('errorLoading', false)
}
try {
16.4. Exécution
لتنفيذ هذا المثال، يجب الحرص قبل التنفيذ على حذف ملف تعريف الارتباط الخاص بالجلسة [nuxt] وملف تعريف الارتباط PHP من المتصفح الذي يقوم بتشغيل العميل [nuxt] من أجل البدء من وضع نظيف. فيما يلي مثال باستخدام متصفح Chrome:

16.5. Conclusion
يعد توجيه الخادم [nuxt] معقدًا لأنه يتعين توقع جميع URL التي قد يكتبها المستخدم يدويًا. هذه حالة نموذجية. تطبيق [nuxt] ليس مخصصًا للاستخدام بهذه الطريقة. بمجرد تقديم الصفحة [index] بواسطة جهاز التوجيه الخاص بالخادم [nuxt]، يمكن إعادة توجيه الطلبات التالية الموجهة إلى الخادم إلى صفحة خطأ.
في الحالة المحددة لمثالنا [nuxt-13]، كان توجيه الخادم [nuxt] غير ضروري. كان التوجيه الافتراضي (غياب التوجيه في الواقع) في المثال [nuxt-12] مناسبًا تمامًا.