البحث في الموقع
المحتوى عن 'vue'.
-
سنوضح في مقال اليوم الخطوات اللازمة لربط الواجهة الخلفية backend مع الواجهة الأمامية frontend من سلسلة مقالات تطوير تطبيق مدونة حديث باستخدام جانغو وفيو. هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue: إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول. إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني. إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث. إن الشائع حاليًا في ربط الواجهتين الأمامية والخلفية لتطبيق ما هو استخدام تقنية تسمى REST API. ومصطلح API هو اختصار للواجهة البرمجية للتطبيقات Application Programming Interface، وهي تشير إلى الاتصال بين تطبيقين برمجيين، أما REST فهو اختصار لنقل الحالة التمثيلية Representational State Transfer، وهو يشير إلى بنية محددة يتبعها هذا النوع من الاتصال. يتكون طلب REST API عادةً من نقطة وصول endpoint تتصل مع الخادم، إضافةً إلى طريقة HTTP وترويسة Head وجسم Body. تحتوي الترويسة على عناصر التعريف meta، مثل التخبئة caching واستيثاق المستخدم user authentication واختبار AB، بينما يحتوي الجسم على البيانات التي يريد العميل إرسالها إلى الخادم. ومع ذلك، فإنه ثمة خلل بسيط في REST API، وهو أنه من المستحيل تصميم واجهات برمجية تجلب البيانات التي يحتاجها العميل بالضبط، إذ من الشائع أن تجلب REST API بيانات زائدة أو ناقصة. ولذلك، طُوّرت لغة الاستعلام غراف كيو إل GraphQL بهدف حل هذه المشكلة، إذ تستخدم المخططات schemas للتأكد من جلب البيانات المحددة فقط لكل طلب، وسنرى كيف يحدث ذلك لاحقًا. إعداد غراف كيو إل GraphQL باستخدام جانغو Django لنبدأ بإعداد غراف كيو إل GraphQL في الواجهة الخلفية، ستحتاج إلى تثبيت حزمة جديدة تسمى graphene-django وهي حزمة توفر مجموعة من الأدوات والميزات التي تسهل عملية تطوير واجهات GraphQL داخل تطبيقات Django عبر تفعيل الأمر التالي: pip install graphene-django انتقل بعد ذلك إلى مجلد settings.py وابحث عن المتغير INSTALLED_APPS. يجب عليك إضافة graphene-django داخل المجلد لكي يتمكن جانغو من العثور على هذه الحزمة. INSTALLED_APPS = [ . . . "blog", "graphene_django", ] إعداد graphene-django ما زال هناك بعض المهام التي يجب عليك إنجازها قبل أن تتمكن من استخدام GraphQL. أولاً، ستحتاج إلى إعداد نمط الرابط URL pattern لخدمة الواجهات البرمجية للتطبيقات APIs في GraphQL، والذي يحدد كيفية تطابق عناوين URL مع عرض معين في تطبيق Django ويستخدم لتحويل طلبات المستخدم إلى العرض المناسب لمعالجتها. للقيام بذلك انتقل إلى الملف urls.py وأضف الشيفرة التالية: from django.views.decorators.csrf import csrf_exempt from graphene_django.views import GraphQLView urlpatterns = [ . . . path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))), ] وبعد ذلك، أنشئ المخططات schemas وحدد لجانغو مكان العثور عليها في مجلد settings.py. تتبع مخططات GraphQL نمطًا يسمح لجانغو بترجمة نماذج قاعدة البيانات database إلى GraphQL وبالعكس، ولنأخذ فيما يلي نموذج الموقع Site model كمثال: class Site(models.Model): name = models.CharField(max_length=200) description = models.TextField() logo = models.ImageField(upload_to='site/logo/') class Meta: verbose_name = 'site' verbose_name_plural = '1. Site' def __str__(self): return self.name أنشئ ملف schema.py داخل مجلد المدونة blog. import graphene from graphene_django import DjangoObjectType from blog import models # Define type class SiteType(DjangoObjectType): class Meta: model = models.Site # The Query class class Query(graphene.ObjectType): site = graphene.Field(types.SiteType) def resolve_site(root, info): return ( models.Site.objects.first() ) كما هو واضح، يُقسم هذا الملف إلى ثلاثة أجزاء. أولًا، يجب عليك استيراد الحزم والنماذج اللازمة. ثم يُصرّح عن صنف SiteType ويُربط مع نموذج الموقع. وأخيرًا، يستخدم الصنف Query الذي يسمح لك باسترداد المعلومات عبر GraphQL API. أما لإنشاء معلومات أو تحديثها، فستحتاج إلى استخدام صنف آخر يُدعى Mutation والذي سنتحدث عنه بالتفصيل في المقال الثالث من السلسلة. توجد الدالة Resolve_site داخل الصنف Query وهي تعمل على استرجاع السجل الأول من النموذج Site model (أي تعيد الكائن الأول الموجود في قاعدة البيانات المرتبطة بهذا النموذج). ترتبط هذه الدالة تلقائيًا مع متغير يحمل نفس الاسم site، هذا يعني أنه عند استدعاء الدالة، ستكون النتيجة متاحة مباشرةً من خلال المتغير site دون الحاجة إلى تحديد اسم جديد له، إذ يعمل هذا الجزء تمامًا مثل Django QuerySet لاسترجاع البيانات من قاعدة البيانات. إذَا عندما تستدعي الدالة Resolve_site فإنها سترجع لك السجل الأول من نموذج Site ووتخزنها في المتغير site تلقائيًا، مما يسهل الوصول إلى هذه البيانات لاحقًا. إنشاء المخططات Schemas يمكنك الآن تكرار نفس الطريقة لجميع النماذج، ولكي تتجنب الحجم الكبير لملف المخطط، يمكنك فصله إلى ثلاثة ملفات، وهي: schema.py types.py queries.py ملف schema.py import graphene from blog import queries schema = graphene.Schema(query=queries.Query) ملف types.py import graphene from graphene_django import DjangoObjectType from blog import models class SiteType(DjangoObjectType): class Meta: model = models.Site class UserType(DjangoObjectType): class Meta: model = models.User class CategoryType(DjangoObjectType): class Meta: model = models.Category class TagType(DjangoObjectType): class Meta: model = models.Tag class PostType(DjangoObjectType): class Meta: model = models.Post queries.py import graphene from blog import models from blog import types # The Query class class Query(graphene.ObjectType): site = graphene.Field(types.SiteType) all_posts = graphene.List(types.PostType) all_categories = graphene.List(types.CategoryType) all_tags = graphene.List(types.TagType) posts_by_category = graphene.List(types.PostType, category=graphene.String()) posts_by_tag = graphene.List(types.PostType, tag=graphene.String()) post_by_slug = graphene.Field(types.PostType, slug=graphene.String()) def resolve_site(root, info): return ( models.Site.objects.first() ) def resolve_all_posts(root, info): return ( models.Post.objects.all() ) def resolve_all_categories(root, info): return ( models.Category.objects.all() ) def resolve_all_tags(root, info): return ( models.Tag.objects.all() ) def resolve_posts_by_category(root, info, category): return ( models.Post.objects.filter(category__slug__iexact=category) ) def resolve_posts_by_tag(root, info, tag): return ( models.Post.objects.filter(tag__slug__iexact=tag) ) def resolve_post_by_slug(root, info, slug): return ( models.Post.objects.get(slug__iexact=slug) ) والآن، يجب عليك إخبار جانغو بمكان العثور على ملف المخطط، لذا انتقل إلى settings.py وأضف الشيفرة التالية: # Configure GraphQL GRAPHENE = { "SCHEMA": "blog.schema.schema", } ولكي تتأكد من عمل المخططات على النحو الصحيح، افتح المتصفح وانتقل إلى http://127.0.0.1:8000/graphql، يجب أن تشاهد واجهة GraphiQL على النحو التالي: لاحظ كيف نسترجع المعلومات في هذا المثال، هذه لغة GraphQL، وهذه هي الطريقة التي سنسترجع بها البيانات في الواجهة الأمامية، والتي سنشرحها لاحقًا. إعداد CORS قبل أن ننتقل إلى الواجهة الأمامية، ثمة شيء يجب الاهتمام به. افتراضيًا، لا يمكن نقل البيانات إلا داخل التطبيق نفسه لأسباب الحماية، ولكننا نحتاج إلى تدفق البيانات بين تطبيقين مختلفين، ولمعالجة هذه المشكلة، يجب تمكين وظيفة CORS، وهي اختصار لـ Cross Origin Resource Sharing والتي تعني مشاركة الموارد ذات الأصول المختلفة. أولاً، ثبِّت حزمة Django-cors-headers. ومن داخل تطبيق الواجهة الخلفية، فعِّل الأمر التالي: pip install django-cors-headers أضف "corsheaders" إلى المتغير INSTALLED_APPS: INSTALLED_APPS = [ . . . "corsheaders", ] ثم أضف "corsheaders.middleware.CorsMiddleware" إلى المتغير MIDDLEWARE لتمكين دعم CORS في تطبيق جانغو: MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", . . . ] وأخيرًا، أضف الشيفرة التالية إلى settings.py: CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_WHITELIST = ("http://localhost:8080",) # Matches the port that Vue.js is using إعداد Apollo باستخدام Vue.js والآن، حان الوقت للانتقال إلى الواجهة الأمامية. أولًا، يجب تثبيت مكتبة Apollo، إذ تتيح لك استخدام GraphQL في تطبيق Vue. ويمكنك تثبيتها من خلال تفعيل الأمر التالي: npm install --save graphql graphql-tag @apollo/client ضمن مجلد src، أنشئ ملف جديد باسم apollo-config.js وأضف له الشيفرة التالية: import { ApolloClient, createHttpLink, InMemoryCache, } from "@apollo/client/core"; // HTTP connection to the API const httpLink = createHttpLink({ uri: "http://127.0.0.1:8000/graphql", // Matches the url and port that Django is using }); // Cache implementation const cache = new InMemoryCache(); // Create the apollo client const apolloClient = new ApolloClient({ link: httpLink, cache, }); ثم انتقل إلى main.js واستورد apolloClient كي تتيح لتطبيقك القدرة على التفاعل مع واجهات GraphQL واسترجاع البيانات بشكل ديناميكي: import { apolloClient } from "@/apollo-config"; createApp(App).use(router).use(apolloClient).mount("#app"); أصبح بإمكانك الآن استخدام لغة GraphQL لاسترجاع البيانات من الواجهة الخلفية، ولتطبيق مثال عملي على ذلك، سننتقل إلى App.vue وهناك سنستعيد اسم موقعنا بكتابة الكود التالي: <template> <div class="container mx-auto max-w-3xl px-4 sm:px-6 xl:max-w-5xl xl:px-0"> <div class="flex flex-col justify-between h-screen"> <header class="flex flex-row items-center justify-between py-10"> <div class="nav-logo text-2xl font-bold"> <router-link to="/" v-if="mySite">{{ mySite.name }}</router-link> </div> . . . </header> . . . </div> </div> </template> <script> import gql from "graphql-tag"; export default { data() { return { mySite: null, }; }, async created() { const siteInfo = await this.$apollo.query({ query: gql` query { site { name } } `, }); this.mySite = siteInfo.data.site; }, }; </script> يُفضِّل البعض إنشاء ملف منفصل لجميع الاستعلامات queries، ومن ثم استيراده إلى ملف فيو كما في الكود التالي: ملف src/queries.js import gql from "graphql-tag"; export const SITE_INFO = gql` query { site { name } } `; ملف App.vue . . . <script> import { SITE_INFO } from "@/queries"; export default { data() { return { mySite: null, }; }, async created() { const siteInfo = await this.$apollo.query({ query: SITE_INFO, }); this.mySite = siteInfo.data.site; }, }; </script> صفحة الصنف Category تبقى لدينا الآن مشكلة من المقال السابق، عندما نستدعي موجه router، فكيف لهذا الموجه أن يعرف الصفحة التي يجب إعادتها؟ فعلى سبيل المثال، عندما نضغط على رابط Category One يجب إرجاع قائمة المنشورات التي تنتمي إلى الصنف الأول Category One، ولكن كيف يمكن للموجه القيام بذلك؟ دعونا نرى مثالًا. أولًا، في ملف router/index.js الذي عرفنا فيه جميع الوجهات لدينا، يجب علينا تعيين جزء من نمط الرابط URL pattern كمتغير. ففي المثال التالي، سيتم تعيين الكلمة بعد /category/ كمتغير Category. وسيكون بالإمكان الوصول إلى هذا المتغير في مكون CategoryView. import { createRouter, createWebHistory } from "vue-router"; . . . const routes = [ { path: "/", name: "Home", component: HomeView, }, { path: "/category/:category", name: "Category", component: CategoryView, }, . . . ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router; وبعد ذلك، في عرض AllCategories (الذي سيُظهر قائمة بجميع التصنيفات)، سنعمل على تمرير بعض المعلومات إلى المتغير Category. <template> <div class="flex flex-col place-content-center place-items-center"> <div class="py-8 border-b-2"> <h1 class="text-5xl font-extrabold">All Categories</h1> </div> <div class="flex flex-wrap py-8"> <router-link v-for="category in this.allCategories" :key="category.name" class=". . ." :to="`/category/${category.slug}`" >{{ category.name }}</router-link > </div> </div> </template> وفي عرض Category يمكننا الوصول إلى المتغير Category باستخدام الخاصية this.$route. <script> // @ is an alias to /src import PostList from "@/components/PostList.vue"; import { POSTS_BY_CATEGORY } from "@/queries"; export default { components: { PostList }, name: "CategoryView", data() { return { postsByCategory: null, }; }, async created() { const posts = await this.$apollo.query({ query: POSTS_BY_CATEGORY, variables: { category: this.$route.params.category, }, }); this.postsByCategory = posts.data.postsByCategory; }, }; </script> وأخيرًا، يمكن استرجاع المنشورات باستخدام الاستعلام POSTS_BY_CATEGORY. export const POSTS_BY_CATEGORY = gql` query ($category: String!) { postsByCategory(category: $category) { title slug content isPublished isFeatured createdAt } } `; ومع هذا المثال، من المفترض أن تكون قادرًا الآن على إنشاء صفحتي المنشور post التي تعرض محتوى منشو أو مقالة معينة، وصفحة الوسم tag التي تظهر قائمة بالمنشورات المرتبطة بوسم معين. إنشاء وتحديث المعلومات باستخدام الطفرات Mutations ذكرنا في المقال السابق من هذه السلسلة أنه يمكننا استخدام الاستعلامات queries لاسترجاع المعلومات من الواجهة الخلفية وإرسالها إلى الواجهة الأمامية. ومع ذلك، من الشائع في تطبيقات الويب الحديثة أن ترسل المعلومات من الواجهة الأمامية إلى الواجهة الخلفية. وللقيام بذلك، لا بُدّ من الحديث عن مفهوم جديد في GraphQL يُسمى الطفرة mutation. ملاحظة: تُستخدم الطفرات mutations لتغيير البيانات في الخادم. فإذا أراد المستخدم مثلًا إضافة أو تحديث أو حذف بيانات عليه أن يستخدم mutation لإرسال طلب بالتعديل المطلوب والخادم يقوم بتحديث البيانات وإرجاع النتيجة. نعود إلى الواجهة الخلفية وننتقل إلى ملف cd في مجلد blog وننشئ ملف باسم Mutations.py. سنكتشف في المثال التالي كيفية نقل البيانات إلى الواجهة الخلفية لإنشاء مستخدم جديد. import graphene from blog import models, types # Mutation sends data to the database class CreateUser(graphene.Mutation): user = graphene.Field(types.UserType) class Arguments: username = graphene.String(required=True) password = graphene.String(required=True) email = graphene.String(required=True) def mutate(self, info, username, password, email): user = models.User( username=username, email=email, ) user.set_password(password) user.save() return CreateUser(user=user) لاحظ في السطر 7 من الكود أعلاه أن UserType مرتبط بنموذج المستخدم User model. أما في الأسطر من 9 إلى 12، فلاحظ أنه لإنشاء مستخدم جديد، يجب إضافة ثلاث عناصر هي اسم المستخدم وكلمة المرور والبريد الإلكتروني. أما الأسطر من 15 إلى 18، فيجب أن تكون واضحة لك، فهي ذات الطريقة التي تنشئ من خلالها عنصرًا جديدًا باستخدام Django QuerySet. السطر 19 مسؤول عن تعيين كلمة المرور، ولأسباب تتعلق الحماية لا يمكنك حفظ كلمة المرور الأصلية للمستخدم في قاعدة البيانات لديك، ويمكن تشفيرها من خلال التابع set_password(). والآن، يجب عليك تضمين ملف Mutation.py في مخطط GraphQL، لذا انتقل إلى schema.py: import graphene from blog import queries, mutations schema = graphene.Schema(query=queries.Query, mutation=mutations.Mutation) ولتتأكد من أنه يعمل على النحو الصحيح، افتح المتصفح وانتقل إلى http://127.0.0.1:8000/graphql للوصول إلى واجهة GraphiQL. mutation { createUser( username: "testuser2022" email: "testuser2022@test.com" password: "testuser2022" ) { user { id username } } } من المفترض أنك تعرف كيفية استخدام ذلك في الواجهة الأمامية. على سبيل المثال: <script> import { USER_SIGNUP } from "@/mutations"; export default { name: "SignUpView", data() {. . .}, methods: { async userSignUp() { // Register user const user = await this.$apollo.mutate({ mutation: USER_SIGNUP, variables: { username: this.signUpDetails.username, email: this.signUpDetails.email, password: this.signUpDetails.password, }, }); // Do something with the variable user . . . }, }, }; </script> src/mutations.js import gql from "graphql-tag"; export const USER_SIGNUP = gql` mutation ($username: String!, $email: String!, $password: String!) { createUser(username: $username, email: $email, password: $password) { user { id username } } } `; استيثاق المستخدم User authentication مع جانغو وفيو جي إس والآن بعد أن تعرفت على كيفية إرسال البيانات إلى الواجهة الخلفية، لن تواجه صعوبةً كبيرةً في استيثاق المستخدم User authentication. في البداية، ستطلب من المستخدم إدخال اسم المستخدم وكلمة المرور الخاصين به وإرسال تلك المعلومات إلى الواجهة الخلفية، ثم في الواجهة الخلفية، سيعثر جانغو على المستخدم بناءً على الاسم، وسيحاول مقارنة كلمة المرور المُدخلة مع كلمة المرور المخزنة في قاعدة البيانات، وإذا تطابقت الكلمتان، سيكون بإمكان المستخدم تسجيل الدخول. ومع ذلك، قد تواجه هذه العملية بعض العقبات في الممارسة العملية. فبدايةً، تُعد عملية إرسال كلمة مرور المستخدم ذهابًا وإيابًا عملية غير آمنة تمامًا. لذا ستحتاج إلى طريقة ما لتشفير البيانات. الطريقة الأكثر استخدامًا هي تقنية JWT، وهو اختصار لـ JSON Web Token. إذ تعمل هذه الطريقة على تشفير بيانات بصيغة JSON إلى ترميز بصيغة token. يمكنك رؤية مثال عليها من هنا. سيُحتفظ بهذا الترميز ضمن التخزين المحلي للمتصفح، وطالما بقي هذا الترميز ضمن ذاكرة المتصفح، سيبقى تسجيل الدخول قائمًا. المشكلة الثانية سببها نظام مكونات فيو Vue. فكما تعلم كل مكون مستقل بذاته فإذا تغير أحد المكونات، فإنه لا يؤثر على المكونات الأخرى. لكننا في هذه الحالة نريد أن تشترك جميع المكونات في نفس الوضعية، فإذا سجل المستخدم دخوله، نريد أن تتعرف جميع المكونات الأخرى على حالة المستخدم أثناء تسجيل الدخول. لذلك ستحتاج إلى مكان مركزي لتخزين هذه المعلومات حين يقوم المستخدم بتسجيل الدخول، ويجب أن تكون جميع المكونات قادرة على قراءة البيانات من ذلك المكان. ولتحقيق بذلك، ستحتاج إلى استخدام Pinia، وهي مكتبة Vue جديدة أُنشئت من خلال Vuex. دمج JWT في الواجهة الخلفية في البداية، سندمج JWT مع الواجهة الخلفية لجانغو، لذلك ستحتاج إلى تثبيت حزمة أخرى باسم django-graphql-jwt: pip install django-graphql-jwt انتقل الآن إلى ملف settings.py وأضف برنامجًا وسيطًا، وكذلك الاستيثاق في الواجهة الخلفية. ستحل هذه الإعدادات مكان الإعدادات الافتراضية لجانغو، وهو ما يتيح استخدام JWT. MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", ] # Configure GraphQL GRAPHENE = { "SCHEMA": "blog.schema.schema", 'MIDDLEWARE': [ 'graphql_jwt.middleware.JSONWebTokenMiddleware', ], } # Auth Backends AUTHENTICATION_BACKENDS = [ 'graphql_jwt.backends.JSONWebTokenBackend', 'django.contrib.auth.backends.ModelBackend', ] لاستخدام هذه الحزمة، انتقل إلى موقع muts.py وأضف الشيفرة التالية: import graphql_jwt class Mutation(graphene.ObjectType): token_auth = graphql_jwt.ObtainJSONWebToken.Field() verify_token = graphql_jwt.Verify.Field() refresh_token = graphql_jwt.Refresh.Field() يمكننا الآن اختباره في واجهة GraphiQL: في حال كلمة مرور خاطئة في حال مستخدم موثوق كما هو واضح، فإن متطلبات تسجيل الدخول هي اسم المستخدم وكلمة المرور، وإذا تم التحقق من وثوقية هذا المستخدم بنجاح (أي أدخل اسم مستخدم وكلمة مرور صحيحة) سيُرسل من الواجهة الخلفية ترميز مشفر token. ويمكن حفظ هذا الترميز في ذاكرة المتصفح. يمكنك أيضًا تخصيص سلوك ObtainJSONWebToken من خلال العودة إلى Mutions.py: # Customize the ObtainJSONWebToken behavior to include the user info class ObtainJSONWebToken(graphql_jwt.JSONWebTokenMutation): user = graphene.Field(types.UserType) @classmethod def resolve(cls, root, info, **kwargs): return cls(user=info.context.user) class Mutation(graphene.ObjectType): token_auth = ObtainJSONWebToken.Field() لاحظ أن ObtainJSONWebToken يتوسع إلى JSONWebTokenMutation الافتراضي، ومن ثم في صنف Mutation، ويمكنك استخدام ObtainJSONWebToken عوضًا عن ذلك. يمكن الآن الحصول على مزيد من المعلومات حول المستخدم عبر GraphQL. استخدام حزمة بينيا Pinia في الواجهة الأمامية حان الوقت الآن لحل المشكلة الثانية في الواجهة الأمامية. والخطوة الأولى هي تثبيت بينيا Pinia. npm install pinia اذهب الآن إلى main.js وتأكد من أن تطبيقك يستخدم بينيا Pinia. import { createPinia } from "pinia"; createApp(App).use(createPinia()).use(router).use(apolloProvider).mount("#app"); ارجع الآن إلى مجلد src وأنشئ مجلد باسم stores، وهو المجلد الذي سنخزن فيه جميع بيانات التطبيق. في الوقت الحالي، كل ما تحتاج إليه هو تخزين القيم الخاصة بالمستخدم (بيانات تسجيل الدخول) وسنحافظ على المعلومات فيه حتى بعد تحديث الصفحة، لذا أنشئ ملف باسم user.js من أجل التعامل مع التخزين كما يلي: import { defineStore } from "pinia"; export const useUserStore = defineStore({ id: "user", state: () => ({ token: localStorage.getItem("token") || null, user: localStorage.getItem("user") || null, }), getters: { getToken: (state) => state.token, getUser: (state) => JSON.parse(state.user), }, actions: { setToken(token) { this.token = token; // Save token to local storage localStorage.setItem("token", this.token); }, removeToken() { this.token = null; // Delete token from local storage localStorage.removeItem("token"); }, setUser(user) { this.user = JSON.stringify(user); // Save user to local storage localStorage.setItem("user", this.user); }, removeUser() { this.user = null; // Delete user from local storage localStorage.removeItem("user"); }, }, }); لاحظ أن هذا مخزن البيانات يتكون من ثلاثة أقسام، وهم state و getters و actions. تساعد هذه الأقسام الثلاثة في إدارة حالة التطبيق بطريقة منظمة وسهلة وإذا كنت على معرفة جيدة بكيفية إنشاء تطبيق فيو Vue فيُفترض أن يكون ذلك مفهومًا بالنسبة لك. تشبه state في Pinia الدالة data() في مكون فيو Vue، إذ تعمل على تحديد المتغيرات، باستثناء أن يمكن لجميع المكونات الوصول إلى هذه المتغيرات. في مثالنا، سيحاول فيو Vue الحصول على الترميز المشفر في ذاكرة المتصفح، فإذا لم يكن موجودًا، سيأخذ المتغير قيمة null. أما getters فهي توازي المتغيرات computed، إذ تنفذ إجراءات بسيطة، وعادةً ما يقتصر عملها على إرجاع قيمة state فحسب. وهنا أيضًا يمكن لجميع المكونات والصفحات الوصول إلى المتغيرات. وأخيرًا، يمكن تشبيه actions بـ methods الموجودة في مكون فيو Vue. كما أنها تؤدي بعض الإجراءات باستخدام الحالات states. وفي هذه الحالة، أنت تعمل على حفظ أو إزالة الترميز المشفر للمستخدم ومعلوماته. ثمة شيء آخر عليك ملاحظته، وهو أنه لا يمكنك حفظ الكائنات objects في الذاكرة المحلية، وإنما يمكنك حفظ السلاسل النصية strings فقط. لذا يجب عليك استخدام stringify() و parse() لتحويل البيانات إلى سلسلة، ومن ثم إعادتها إلى كائن. ستحتاج بعد ذلك إلى استخدام هذا المخزن عند تسجيل دخول المستخدم، وقد أنشأنا ملف SignIn.vue مثل هذا: <script> import { useUserStore } from "@/stores/user"; import { USER_SIGNIN } from "@/mutations"; export default { name: "SignInView", setup() { const userStore = useUserStore(); return { userStore }; }, data() { return { signInDetails: { username: "", password: "", }, }; }, methods: { async userSignIn() { const user = await this.$apollo.mutate({ mutation: USER_SIGNIN, variables: { username: this.signInDetails.username, password: this.signInDetails.password, }, }); this.userStore.setToken(user.data.tokenAuth.token); this.userStore.setUser(user.data.tokenAuth.user); }, }, }; </script> نلاحظ في السطر 2 استيراد مخزن المستخدم User store الذي أنشأته. في السطور 9-12، استدعاء مخزن المستخدم User store في الخطاف setup، وهو ما يجعل التعامل مع بينيا Pinia أسهل دون أي وظائف خريطة إضافية. في السطرين 32-33، استدعاء الإجراءين setToken() و setUser() اللذيْن أنشأناهما منذ قليل، وهو ما سيحفظ المعلومات داخل الذاكرة المحلية. هذه هي الطريقة التي يمكنك من خلالها تسجيل دخول المستخدم، ولكن ماذا لو كان المستخدم قد سجل دخوله أساسًا، لنلقي نظرة على هذا المثال: <script> import { SITE_INFO } from "@/queries"; import { useUserStore } from "@/stores/user"; export default { setup() { const userStore = useUserStore(); return { userStore }; }, data() { return { menuOpen: false, mySite: null, user: { isAuthenticated: false, token: this.userStore.getToken || "", info: this.userStore.getUser || {}, }, dataLoaded: false, }; }, async created() { const siteInfo = await this.$apollo.query({ query: SITE_INFO, }); this.mySite = siteInfo.data.site; if (this.user.token) { this.user.isAuthenticated = true; } }, methods: { userSignOut() { this.userStore.removeToken(); this.userStore.removeUser(); }, }, }; </script> نحاول في السطرين 18- 19 الحصول على الترميز المشفر token ومعلومات المستخدم user info من المخزن Store. في السطور 31-33، إذا كان الترميز المشفر token موجودًا، فسيُعد المستخدم مستوثقًا authentication . في السطور 38-41، ستعمل الطريقة userSignOut() على تسجيل خروج المستخدم عند استدعائها. بعد أن تعرفنا على كيفية استرجاع البيانات باستخدام الاستعلامات queries ووضحنا كيفية إرسال البيانات باستخدام الطفرات mutations، سنشرح في المقال التالي طريقة إنشاء نظام تعليقات وتفاعلات إعجاب لتطبيق المدونة الخاص بنا. ترجمة -وبتصرّف- للمقال Create a Modern Application with Django and Vue #2. اقرأ أيضًا المقال السابق: إنشاء تطبيق حديث باستخدام Django و Vue | الجزء الأول: الأساسيات تطوير الواجهة الأمامية لمواقع الويب Frontend Web Development تطوير الواجهة الخلفية لمواقع الويب Backend Web Development مقارنة بين أطر الواجهات الأمامية: Angular و React و Vue
-
يعد إطار العمل جانغو Django إطار ويب كامل full-stack قائم على لغة بايثون Python، ويتبع نمط تصميم MTV (اختصار للنمط البنائي للبرمجيات نموذج-قالب-عرض Model-Template-View). والمقصود بمصطلح إطار ويب متكامل full-stack أنه يمكننا من خلاله إنشاء كل من الواجهة الأمامية frontend والواجهة الخلفية backend معًا. ومع ذلك، ثمة عيب صغير في هذا الحل، فعندما يطلب المستخدم النهائي صفحة ويب، هنا يجب تصيير rendering الصفحة أولًا في الواجهة الخلفية، ومن ثم تُرسل صفحة HTML المُكوَّنة إلى المستخدم. وفي هذه الحالة، عندما يكون لديك عدد كبير من المستخدمين، سيضع ذلك ضغطًا كبيرًا على خادمك. لحل هذه المشكلة، عادةً ما يُقسِّم المطورون التطبيق إلى قسمين؛ الواجهة الخلفية والواجهة الأمامية. وفي هذه الحالة، عندما يطلب المستخدم صفحة ويب، لن تُكوِّن الواجهة الخلفية صفحة الويب، بل يكفي أن تجمع البيانات الضرورية فقط وتنقلها إلى الواجهة الأمامية. وهنا يأتي دور جهاز العميل الذي يتمتع عادةً بقدرات حاسوبية عالية، إذ يستخدم هذه البيانات لعرض صفحة الويب داخل المتصفح مباشرةً، وبالتالي تخفيف الضغط على الخادم. سنتحدث في سلسلة المقالات هذه حول كيفية إنشاء تطبيق حديث من صفحة واحدة باستخدام جانغو Django كواجهة خلفية، وفيو Vue كواجهة أمامية، وغراف كيو إل GraphQL كلغة معالجة API تربطهما معًا. هذا المقال هو جزء من سلسلة مقالات تتحدث حول كيفية إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue: إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الأول. إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثاني. إنشاء تطبيق حديث باستخدام جانغو Django وفيو Vue - الجزء الثالث. مراجعة سريعة عن جانغو لنبدأ بمراجعة سريعة حول إطار عمل جانغو Django، جانغو هو إطار عمل ويب يعتمد على لغة بايثون ويتبع النمط البنائي MTV، الذي هو اختصار لثلاث كلمات: النموذج M = Model: عبارة عن واجهة تتيح لنا التفاعل مع قاعدة البيانات، مثل استرجاع السجلات أو إنشائها أو تحديثها أو حذفها. القالب T = Template: يمثل الواجهة الأمامية من إطار العمل، وهو الجزء الذي سيراه المستخدمون النهائيون. العرض V = View: يمثل الواجهة الخلفية للتطبيق، إذ يستخدم النموذج Model للتفاعل مع قاعدة البيانات، مثل استرجاع البيانات التي يطلبها المستخدم، ثم يعمل العرض View على معالجة هذه البيانات بطريقة ما، وتظهر النتيجة للمستخدم عبر القالب Template الذي عادةً ما يكون مخصصًا. في سلسلة المقالات الحالية؛ سنستخدم جانغو للواجهة الخلفية فقط، أي لن نستخدم القوالب والعروض في جانغو، ونستستخدم عوضًا عن ذلك Vue.js لبناء الواجهة الأمامية و GraphQL كطريقة للتواصل بين الواجهة الأمامية والخلفية. إنشاء مشروع جانغو جديد الطريقة التي سنعتمد عليها هي فصل مجلد الواجهة الخلفية عن مجلد الواجهة الأمامية، لذا فإن أول خطوة هي إنشاء هيكل المشروع بالطريقة التالية: blog ├── backend └── frontend انتقل إلى مجلد backend، وأنشئ بيئة بايثون افتراضية جديدة. وبيئة بايثون الافتراضية هي بيئة معزولة لكل تثبيت بايثون جديد، لذا عندما تعمل على تثبيت حزم داخل هذه البيئة الافتراضية لن يؤثر ذلك على بيئة بايثون الخاصة بنظامك، وهو أمر بالغ الأهمية لاسيما إذا كنت تستخدم نظام تشغيل لينكس Linux أو ماك أو إس macOS، ولا تريد التأثير على ملفاتهما الأساسية. cd backend python3 -m venv env سيؤدي الأمر السابق إلى إنشاء مجلد جديد يُدعى env مع إنشاء البيئة الافتراضية بداخله. ولتنشيط هذه البيئة الافتراضية، استخدم الأمر التالي: source env/bin/activate إذا كنت تستخدم نظام تشغيل ويندوز Windows، فاستخدم الأمر التالي عوضًا عن السابق، كما يُنصح بتثبيت WSL: env/Scripts/activate بعد تنشيط البيئة الافتراضية، ستبدو الطرفية Terminal كما في الشكل أدناه. لاحظ وجود (env) أمام اسم المستخدم، منا يدل على أنك تعمل حاليًا في البيئة الافتراضية. يمكنك الآن إنشاء مشروع جانغو جديد. python -m pip install Django django-admin startproject backend بعدها يمكنك إنشاء تطبيق جديد باسم blog: python manage.py startapp blog في النهاية، يجب أن يكون هيكل المشروع كما يلي: . ├── backend │ ├── backend │ ├── blog │ ├── manage.py │ └── requirements.txt └── frontend إنشاء النماذج Models تذكر أن النماذج هي عبارة عن واجهة يمكننا استخدامها للتفاعل مع قاعدة البيانات، وواحدة من المزايا الرائعة في جانغو هي أنه يمكنك تلقائيًا معرفة التغييرات التي أجريتها على النماذج، وتوليد ملفات التهجير migration files المقابلة، والتي يمكن استخدامها لإجراء تغييرات على قاعدة البيانات. نموذج الموقع Site Model لنبدأ بالنموذج Site الذي يخزن المعلومات الأساسية لموقعك على الويب. class Site(models.Model): name = models.CharField(max_length=200) description = models.TextField() logo = models.ImageField(upload_to='site/logo/') class Meta: verbose_name = 'site' verbose_name_plural = '1. Site' def __str__(self): return self.name نلاحظ في السطر الرابع الأمر ImageField الذي يخبر جانغو بتحميل الصورة إلى مجلد 'site/logo/' ولتحقيق ذلك، عليك القيام بشيئين اثنين. أولاً، يجب عليك تثبيت حزمة Pillow، إذ يحتاجها جانغو لمعالجة الصور. python -m pip install Pillow ثانيًا، ستحتاج إلى إعداد توجيه جديد لمكان التخزين في settings.py إذ يجب أن تخبر جانغو بالمكان الذي ستخزن فيه هذه الوسائط، وما هي عناوين URL التي ستستخدمها للوصول إلى هذه الملفات. import os # Media Files MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles') MEDIA_URL = '/media/' تشير الإعدادات السابقة إلى أن ملفات الوسائط ستُخزن داخل ملف /mediafiles وسنحتاج إلى استخدام البادئة /media/ قبل عنوان URL للوصول إلى الملفات. فعلى سبيل المثال، سيؤدي عنوان URL التالي http://localhost:3000/media/example.png إلى استرجاع الصورة /mediafiles/example.png. نموذج المستخدم User Model النموذج التالي هو نموذج المستخدم User model، ولحسن الحظ، فإن جانغو يأتي مزودًا بنموذج مستخدم مدمج، والذي يوفر الوظائف الأساسية المتعلقة بالأذونات permission والتراخيص authorization. ومع ذلك، سنجرب في مشروعنا هذا شيئًا أكثر تعقيدًا، مثل إضافة صور أفاتار avatar للملف الشخصي وإضافة سيرة ذاتية وبعض المعلومات الأخرى، ولتحقيق ذلك، ستحتاج إلى إنشاء نماذج مستخدم جديدة، والتي نوسع فيها الصنف AbstractUser، ومعنى نوسعه extends أي نرث منه صنف جديد لنخصصه وفق حاجتنا أو نضيف له المزيد من الميزات. from django.contrib.auth.models import AbstractUser # New user model class User(AbstractUser): avatar = models.ImageField( upload_to='users/avatars/%Y/%m/%d/', default='users/avatars/default.jpg' ) bio = models.TextField(max_length=500, null=True) location = models.CharField(max_length=30, null=True) website = models.CharField(max_length=100, null=True) joined_date = models.DateField(auto_now_add=True) class Meta: verbose_name = 'user' verbose_name_plural = '2. Users' def __str__(self): return self.username هكذا يبدو صنف AbstractUser في جانغو: from django.contrib.auth.models import AbstractUser # New user model class User(AbstractUser): avatar = models.ImageField( upload_to='users/avatars/%Y/%m/%d/', default='users/avatars/default.jpg' ) bio = models.TextField(max_length=500, null=True) location = models.CharField(max_length=30, null=True) website = models.CharField(max_length=100, null=True) joined_date = models.DateField(auto_now_add=True) class Meta: verbose_name = 'user' verbose_name_plural = '2. Users' def __str__(self): return self.username كما هو واضح، يوفر هذا النموذج بعض الحقول الأساسية، مثل الاسم الأول واسم العائلة ونحو ذلك. ستحتاج بعد ذلك إلى التأكد من أن جانغو يستخدم نموذج المستخدم الجديد كنموذج مستخدم افتراضي، لذلك انتقل إلى ملف إعدادات التطبيق settings.py وأضف له التوجيه directive التالي: # Change Default User Model AUTH_USER_MODEL = 'blog.User' نماذج الفئة Category والوسم Tag والمنشور Post class Category(models.Model): name = models.CharField(max_length=200) slug = models.SlugField() description = models.TextField() class Meta: verbose_name = 'category' verbose_name_plural = '3. Categories' def __str__(self): return self.name class Tag(models.Model): name = models.CharField(max_length=200) slug = models.SlugField() description = models.TextField() class Meta: verbose_name = 'tag' verbose_name_plural = '4. Tags' def __str__(self): return self.name class Post(models.Model): title = models.CharField(max_length=200) slug = models.SlugField() content = RichTextField() featured_image = models.ImageField( upload_to='posts/featured_images/%Y/%m/%d/') is_published = models.BooleanField(default=False) is_featured = models.BooleanField(default=False) created_at = models.DateField(auto_now_add=True) modified_at = models.DateField(auto_now=True) # Each post can receive likes from multiple users, and each user can like multiple posts likes = models.ManyToManyField(User, related_name='post_like') # Each post belong to one user and one category. # Each post has many tags, and each tag has many posts. category = models.ForeignKey( Category, on_delete=models.SET_NULL, null=True) tag = models.ManyToManyField(Tag) user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) class Meta: verbose_name = 'post' verbose_name_plural = '5. Posts' def __str__(self): return self.title def get_number_of_likes(self): return self.likes.count() لاحظ كيف تنفذ ميزة الإعجاب بالمنشورات (Like system) في السطر 13، وهو لا يعتمد على النوع البسيط IntegerField (وهو حقل لتخزين القيم الصحيحة) ولكنه يعمل تمامًا مثل الوسوم Tags. ويمكنك استخدام الدالة get_number_of_likes() لتحصل على عدد الإعجابات لكل منشور. نموذج التعليقات Comment model والآن دعونا نخطو خطوةً نحو الأمام، وننشئ قسمًا مخصصًا للتعليقات لهذا التطبيق: class Comment(models.Model): content = models.TextField(max_length=1000) created_at = models.DateField(auto_now_add=True) is_approved = models.BooleanField(default=False) # Each comment can receive likes from multiple users, and each user can like multiple comments likes = models.ManyToManyField(User, related_name='comment_like') # Each comment belongs to one user and one post user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) post = models.ForeignKey(Post, on_delete=models.SET_NULL, null=True) class Meta: verbose_name = 'comment' verbose_name_plural = '6. Comments' def __str__(self): if len(self.content) > 50: comment = self.content[:50] + '...' else: comment = self.content return comment def get_number_of_likes(self): return self.likes.count() إعداد لوحة التحكم في جانغو والآن حان الوقت لإعداد لوحة التحكم أو لوحة الإدارة في جانغو، لذا افتح ملف admin.py واكتب فيه التالي: from django.contrib import admin from .models import * # Register your models here. class UserAdmin(admin.ModelAdmin): list_display = ('username', 'first_name', 'last_name', 'email', 'date_joined') class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} class TagAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('name',)} class PostAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('title',)} list_display = ('title', 'is_published', 'is_featured', 'created_at') class CommentAdmin(admin.ModelAdmin): list_display = ('__str__', 'is_approved', 'created_at') admin.site.register(Site) admin.site.register(User, UserAdmin) admin.site.register(Category, CategoryAdmin) admin.site.register(Tag, TagAdmin) admin.site.register(Post, PostAdmin) admin.site.register(Comment, CommentAdmin) فيما يتعلق بـ CommentAdmin، فإن __str__ تشير إلى الدالة __str__() في نموذج التعليق. والتي ستعيد أول 50 حرفًا في السلسلة وتدمجها مع السلسلة .... والآن، فعِّل خادم التطوير وتأكد ما إذا كانت كل وظيفة تعمل على النحو الصحيح: python manage.py runserver وقبل الانتقال إلى الخطوة التالية، لا تنسَ إضافة بعض المعلومات الجانبية إلى مدونتك. مراجعة سريعة حول فيو جي إس Vue.js والآن بعد أن انتهينا من إعداد الواجهة الخلفية، حان الوقت للانتقال إلى الواجهة الأمامية، وبذلك الانتقال سنبدأ باستخدام فيو جي إس Vue.js في الجزء الثاني من مقالنا هذا لإعداد الواجهة الأمامية في تطبيقنا. قبل الخوض عميقًا، سنبدأ بمراجعة سريعة حول فيو جي إس Vue.js في حال لم تستخدمه من قبل، ويمكنك أيضًا الاطلاع على مقال مقدمة إلى Vue.js. فيو جي إس Vue.js هو أحد إطارات عمل جافا سكريبت JavaScript المخصصة لبناء الواجهات الأمامية، إذ يوفر لك نظامًا بسيطًا معتمدًا على المكونات، ما يسمح لك بإنشاء واجهات مستخدم تفاعلية. المقصود بـ "معتمدًا على المكونات" يعني أن المكون الجذري (App.vue) بإمكانه استيراد مكونات أخرى، وهي الملفات ذات الامتداد .vue، ويمكن لهذه المكونات استيراد المزيد من المكونات الأخرى، ما يسمح لك بإنشاء أنظمة معقدة ومتطورة للغاية. يحتوي الملف النموذجي ذو الامتداد .vue على ثلاثة أقسام، القسم الأول هو <template> يتضمن شيفرات بلغة HTML أما القسم الثاني فهو <script> ويتضمن شيفرات بلغة JavaScript، والقسم الأخير <style> يتضمن شيفرات بلغة CSS. في قسم <script> يمكنك التصريح عن ارتباطات bindings جديدة داخل التابع data() ، ويمكنك بعد ذلك عرض هذه الارتباطات داخل قسم <template> باستخدام الأقواس المزدوجة المتعرجة {{Binding}}. ستُغطى هذه الارتباطات المُصرَّح عنها في التابع data() تلقائيًا داخل نظام تفاعل فيو Vue، وهذا يعني أنه عندما تتغير قيمة الارتباط Binding، سيتغير المكون المقابل تلقائيًا، دون الحاجة إلى تحديث الصفحة. يمكن أن يحتوي القسم <script> أيضًا على توابع أخرى غير data() ، مثل computed وprops. كما يتيح لنا <template> ربط البيانات باستخدام توجيهات مثل v-bind و v-on و v-model. إنشاء مشروع فيو جي إس Vue.js جديد يمكن تثبيت وإنشاء تطبيق Vue باستخدام أداة سطر الأوامر Vue، لكننا في هذه المرة سنتبع طريقةً مختلفةً، إذ سنستخدم أداة إنشاء واجهات أمامية تسمى Vite، مع ملاحظة أن الكلمة تُنطق "veet" وتعني سريع باللغة الفرنسية، والتي أنشأها ذات الشخص الذي أنشأ Vue.js. انتقل الآن إلى مجلد frontend ونفذ الأمر التالي: npm init vue@latest سيُطلب منك العديد من الخيارات، ولهذا المشروع، كل ما عليك فعله إضافة Vue Router: ✔ Project name: … <your_project_name> ✔ Add TypeScript? … No / Yes ✔ Add JSX Support? … No / Yes ✔ Add Vue Router for Single Page Application development? … No / Yes ✔ Add Pinia for state management? … No / Yes ✔ Add Vitest for Unit testing? … No / Yes ✔ Add Cypress for both Unit and End-to-End testing? … No / Yes ✔ Add ESLint for code quality? … No / Yes ✔ Add Prettier for code formating? … No / Yes Scaffolding project in ./<your_project_name>. . . Done. إذا كنت ترتاح أكثر مع لغة برمجة جيدة، فيمكنك اختيار تثبيت TypeScript وإن كنت تحتاج إلى التصحيح التلقائي والتنسيق التلقائي للتعليمات البرمجية التي تكتبها، فيمكنك تثبيت ESlint وPrettier، ستؤدي عملية التثبيت هذه إلى إنشاء ملف package.json في دليل مشروعك، والذي يقوم بتخزين الحزم المطلوبة وإصداراتها. ستحتاج بعد ذلك إلى تثبيت هذه الحزم داخل مشروعك. cd <your_project_name> npm install npm run dev ثمة شيء أخير قبل أن نبدأ في إنشاء الواجهة الأمامية للتطبيق، نحن نستخدم في مشروعنا هذا إطار عمل بلغة CSS يُدعى TailwindCSS، لذا لتثبيته، نفذ الأمر التالي: npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p سيؤدي ذلك إلى إنشاء ملفين، الأول هو tailwind.config.js والثاني postcss.config.js، وإذا كنت تريد معرفة المزيد عن Tailwind فيمكنك الاطلاع على مقال مقارنة بين Bootstrap و Tailwind CSS، كما يمكنك الاطلاع على الدليل الرسمي لـ Tailwind. انتقل إلى tailwind.config.js وأضف المسار إلى جميع ملفات قالبك: module.exports = { content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], }; أنشئ ملف ./src/index.css وأضف توجيهات @tailwind لكل طبقة من طبقات Tailwind’s layers. @tailwind base; @tailwind components; @tailwind utilities; والآن، استورد الملف الذي أنشأته حديثًا ./src/index.css إلى ملف ./src/main.js. import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import "./index.css"; const app = createApp(App); app.use(router); app.mount("#app"); والآن، من المفترض أن تكون قادرًا على استخدام Tailwind داخل ملفات .vue، وللتأكد، دعنا نختبر ذلك. <template> <header> . . . <div class="wrapper"> <HelloWorld msg="You did it!" /> <h1 class="text-3xl font-bold underline">Hello world!</h1> . . . </div> </header> . . . </template> لاحظ أننا أضفنا عنوان <h1> بعد <HelloWorld>، إذ يستخدم العنوان أصناف Tailwind. مكتبة Vue Router لاحظ أن مجلد مشروعك مختلف قليلًا في هذه المرة: يوجد داخل المجلد الرئيسي src مجلد router ومجلد views، إذ يحتوي مجلد router على ملف index.js، والذي يمكنك من خلاله تحديد وُجهات مختلفة، بحيث تشير كل وجهة route إلى مكون view موجود داخل مجلد views، ويمكن أن يوسع كل view بعد ذلك إلى مكونات أخرى داخل مجلد المكونات Components. تزودنا Vue بمثال على ذلك في ملف index.js: import { createRouter, createWebHistory } from "vue-router"; import HomeView from "../views/HomeView.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: "/", name: "home", component: HomeView, }, { path: "/about", name: "about", // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("../views/AboutView.vue"), }, ], }); export default router; لاستدعاء موجه router ما، انظر في داخل ملف App.vue، وعوضًا عن الوسم <a>، يُستخدم الوسم <RouterLink> المُستورد من حزمة vue-router لإدارة التنقل بين الصفحات في تطبيق Vue.js. <script setup> import { RouterLink, RouterView } from "vue-router"; . . . </script> <template> <header> . . . <div class="wrapper"> . . . <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/about">About</RouterLink> </nav> </div> </header> <RouterView /> </template> عند إخراج الصفحة، سيُستبدل الوسم <RouterView/> بالعرض المطابق، وإذا لم تكن ترغب في استيراد هذه المكونات، فكل ما عليك هو استخدام الوسمين router-link to="">> وكذلك <router-view> عوضًا عن ذلك. إنشاء وُجهات routes باستخدام موجه فيو Vue router سنحتاج في مدونة تطبيقنا إلى إنشاء 6 صفحات على الأقل، سنحتاج إلى صفحة رئيسية والتي تعرض قائمة بأحدث الصفحات في المدونة، كما سنحتاج إلى صفحة الأقسام والتي تعرض جميع الأقسام أو الوسوم في المدونة، وصفحة أخرى لتعرض قائمة بالمقالات التي تنتمي إلى الأقسام أو الوسوم، وأخيرًا صفحة المقال التي تعرض محتوى المقال إضافةً إلى التعليقات. فيما يلي الموجهات التي أنشأناها، إذ يوجه الوسم @ نحو مجلد src. import { createRouter, createWebHistory } from "vue-router"; import HomeView from "@/views/main/Home.vue"; import PostView from "@/views/main/Post.vue"; import CategoryView from "@/views/main/Category.vue"; import TagView from "@/views/main/Tag.vue"; import AllCategoriesView from "@/views/main/AllCategories.vue"; import AllTagsView from "@/views/main/AllTags.vue"; const routes = [ { path: "/", name: "Home", component: HomeView, }, { path: "/category", name: "Category", component: CategoryView, }, { path: "/tag", name: "Tag", component: TagView, }, { path: "/post", name: "Post", component: PostView, }, { path: "/categories", name: "Categories", component: AllCategoriesView, }, { path: "/tags", name: "Tags", component: AllTagsView, }, ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router; يرجى ملاحظة أننا نعمل في هذا المقال على إنشاء الواجهة الأمامية فقط، ولم نصل إلى مرحلة نقل البيانات حتى الآن، لذا لا تقلق حول كيفية العثور على المنشورات أو الأقسام أو الوسوم في الوقت الحالي. إنشاء العروض والصفحات والمكونات توضح الصور التالية واجهة المستخدم الأمامية التي أنشأناها لهذا المشروع، ويمكنك إنشاؤها لديك إما باعتماد الشيفرات البرمجية في هذا المقال، أو كتابة الشيفرات البرمجية الخاصة الخاص بك كما يمكنك العثور على الكود البرمجي الكامل من هنا. الصفحة الرئيسية Home Page جميع التصنيفات All Categories جميع الوسوم All Tags صفحة تسجيل الدخول Sign In صفحة إنشاء حساب Sign Up صفحة المنشور المفرد Post قسم التعليقات على المنشور Comments صفحة الملف الشخصي للمستخدم Profile قسم تعليقات المستخدمين User Comments ترجمة -وبتصرّف- للمقال Create a Modern Application with Django and Vue #1 اقرأ أيضًا تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية معالجة طلبات الويب عبر العروض views في تطبيق جانغو بناء تطبيق ويب لإدارة معلومات العملاء باستخدام جانغو Django وريآكت React إنشاء تطبيق بسيط من خلال Vue.js إنشاء تطبيق جانغو وتوصيله بقاعدة بيانات