استخدام Vue.js لإنشاء لوحة معلومات تفاعلية للطقس مع واجهات برمجة التطبيقات

نشرت: 2022-03-10
ملخص سريع ↬ غالبًا ما يكون إنشاء لوحة معلومات باستخدام بيانات واجهة برمجة التطبيقات أمرًا معقدًا. قد يصبح اختيار مكدس التكنولوجيا الخاص بك ودمج واجهات برمجة التطبيقات واختيار المخططات المناسبة والتجميل باستخدام أنماط CSS أمرًا صعبًا. هذا البرنامج التعليمي هو دليل خطوة بخطوة حول كيفية مساعدتك في إنشاء لوحة معلومات الطقس في Vue.js باستخدام بيانات API.

(هذه مقالة برعاية.) في هذا البرنامج التعليمي ، ستقوم ببناء لوحة معلومات بسيطة للطقس من البداية. سيكون تطبيقًا خاصًا بالعميل ليس مثالًا على "Hello World" ، ولا مخيفًا جدًا في حجمه وتعقيده.

سيتم تطوير المشروع بأكمله باستخدام أدوات من النظام البيئي Node.js + npm. على وجه الخصوص ، سوف نعتمد بشدة على Dark Sky API للبيانات ، و Vue.js لجميع عمليات الرفع الثقيلة ، و FusionCharts لتصور البيانات.

المتطلبات الأساسية

نتوقع أن تكون على دراية بما يلي:

  • HTML5 و CSS3 (سنستخدم أيضًا الميزات الأساسية التي يوفرها Bootstrap ؛
  • JavaScript (خاصة طريقة ES6 في استخدام اللغة) ؛
  • Node.js و npm (أساسيات إدارة البيئة والحزم على ما يرام).

بصرف النظر عن تلك المذكورة أعلاه ، سيكون من الرائع أن تكون لديك معرفة بـ Vue.js أو أي إطار عمل JavaScript آخر مشابه. لا نتوقع منك أن تعرف المزيد عن FusionCharts - فهي سهلة الاستخدام لدرجة أنك ستتعلمها سريعًا!

الدروس المتوقعة

ستكون أهم ما تعلمته من هذا المشروع هو:

  1. كيف تخطط لتنفيذ لوحة تحكم جيدة
  2. كيفية تطوير التطبيقات باستخدام Vue.js
  3. كيفية إنشاء تطبيقات تعتمد على البيانات
  4. كيفية تصور البيانات باستخدام FusionCharts

على وجه الخصوص ، يأخذك كل قسم من الأقسام خطوة أقرب إلى أهداف التعلم:

  1. مقدمة إلى لوحة معلومات الطقس
    يمنحك هذا الفصل نظرة عامة على الجوانب المختلفة للمهمة.
  2. أنشئ المشروع
    في هذا القسم ، ستتعرف على كيفية إنشاء مشروع من البداية باستخدام أداة سطر الأوامر Vue.
  3. تخصيص هيكل المشروع الافتراضي
    سقالات المشروع الافتراضية التي تحصل عليها في القسم السابق ليست كافية ؛ هنا تتعلم الأشياء الإضافية اللازمة للمشروع من وجهة نظر هيكلية.
  4. الحصول على البيانات ومعالجتها
    هذا القسم هو لحم المشروع. يتم عرض جميع التعليمات البرمجية المهمة للحصول على البيانات ومعالجتها من واجهة برمجة التطبيقات هنا. توقع قضاء أقصى وقت في هذا القسم.
  5. تصور البيانات مع FusionCharts
    بمجرد أن نحصل على جميع البيانات والأجزاء المتحركة الأخرى من المشروع مستقرة ، فإن هذا القسم مخصص لتصور البيانات باستخدام FusionCharts وقليلًا من CSS.

1. سير عمل لوحة القيادة

قبل التعمق في التنفيذ ، من المهم أن نكون واضحين بشأن خطتنا. نقسم خطتنا إلى أربعة جوانب متميزة:

متطلبات

ما هي متطلباتنا لهذا المشروع؟ بمعنى آخر ، ما الأشياء التي نريد عرضها من خلال لوحة معلومات الطقس؟ مع الأخذ في الاعتبار أن جمهورنا المستهدف ربما يكون مجرد بشر بأذواق بسيطة ، نود أن نظهر لهم ما يلي:

  • تفاصيل الموقع الذي يريدون رؤية الطقس فيه ، بالإضافة إلى بعض المعلومات الأساسية حول الطقس. نظرًا لعدم وجود متطلبات صارمة ، سنكتشف التفاصيل المملة لاحقًا. ومع ذلك ، في هذه المرحلة ، من المهم ملاحظة أنه سيتعين علينا تزويد الجمهور بمربع بحث ، حتى يتمكنوا من تقديم مدخلات لموقع اهتمامهم.
  • معلومات رسومية عن الطقس في موقع اهتمامهم ، مثل:
    • اختلاف درجة الحرارة ليوم الاستعلام
    • أبرز أحداث الطقس اليوم:
      • سرعة الرياح واتجاهها
      • الرؤية
      • مؤشر الأشعة فوق البنفسجية

ملاحظة : البيانات التي تم الحصول عليها من API توفر معلومات تتعلق بالعديد من الجوانب الأخرى للطقس. نختار عدم استخدامهم جميعًا من أجل الحفاظ على الحد الأدنى من الشفرة.

هيكل

بناءً على المتطلبات ، يمكننا هيكلة لوحة القيادة الخاصة بنا كما هو موضح أدناه:

هيكل لوحة القيادة
(معاينة كبيرة)

بيانات

لوحة القيادة الخاصة بنا جيدة مثل البيانات التي نحصل عليها ، لأنه لن يكون هناك تصورات جميلة بدون بيانات مناسبة. هناك الكثير من واجهات برمجة التطبيقات العامة التي توفر بيانات الطقس - بعضها مجاني والبعض الآخر ليس كذلك. بالنسبة لمشروعنا ، سنجمع البيانات من Dark Sky API. ومع ذلك ، لن نتمكن من استقصاء نقطة نهاية واجهة برمجة التطبيقات من طرف العميل مباشرةً. لا تقلق ، لدينا حل بديل سيتم الكشف عنه في الوقت المناسب! بمجرد أن نحصل على البيانات الخاصة بالموقع الذي تم البحث عنه ، سنقوم ببعض معالجة البيانات وتنسيقها - كما تعلم ، نوع الإجراءات الفنية التي تساعدنا في دفع الفواتير.

التصور

بمجرد أن نحصل على بيانات نظيفة ومنسقة ، نقوم بتوصيلها في FusionCharts. يوجد عدد قليل جدًا من مكتبات JavaScript في العالم قادرة على مثل FusionCharts. من بين العدد الهائل من العروض من FusionCharts ، سنستخدم عددًا قليلاً فقط - كلها مكتوبة بلغة JavaScript ، ولكنها تعمل بسلاسة عند دمجها مع غلاف Vue لـ FusionCharts.

مسلحين بالصورة الأكبر ، دعنا نتسخ أيدينا - حان الوقت لجعل الأمور ملموسة! في القسم التالي ، ستنشئ مشروع Vue الأساسي ، والذي سنقوم فوقه ببناء المزيد.

2. إنشاء المشروع

لإنشاء المشروع قم بتنفيذ الخطوات التالية:

  1. قم بتثبيت Node.js + npm
    ( إذا كان لديك Node.js مثبتًا على جهاز الكمبيوتر الخاص بك ، فتخط هذه الخطوة. )
    يأتي Node.js مع npm مرفقًا به ، لذلك لا تحتاج إلى تثبيت npm بشكل منفصل. اعتمادًا على نظام التشغيل ، قم بتنزيل Node.js وتثبيته وفقًا للإرشادات الواردة هنا.

    بمجرد التثبيت ، ربما يكون من الجيد التحقق مما إذا كان البرنامج يعمل بشكل صحيح ، وما هي إصداراته. لاختبار ذلك ، افتح سطر الأوامر / المحطة وقم بتنفيذ الأوامر التالية:
     node --version npm --version
  2. تثبيت الحزم مع npm
    بمجرد تشغيل npm ، قم بتنفيذ الأمر التالي لتثبيت الحزم الأساسية اللازمة لمشروعنا.
     npm install -g vue@2 vue-cli@2
  3. بدء مشروع السقالات باستخدام vue-cli
    بافتراض أن الخطوة السابقة سارت بشكل جيد ، فإن الخطوة التالية هي استخدام vue-cli - أداة سطر أوامر من Vue.js ، لتهيئة المشروع. للقيام بذلك ، قم بتنفيذ ما يلي:
    • ابدأ السقالات باستخدام قالب webpack البسيط.
       vue init webpack-simple vue_weather_dashboard
      سيتم طرح مجموعة من الأسئلة عليك - قبول الإعدادات الافتراضية للجميع ولكن السؤال الأخير سيكون جيدًا بما يكفي لهذا المشروع ؛ الجواب N لآخر واحد.
      لقطة شاشة لسطر الأوامر / المحطة
      (معاينة كبيرة)
      ضع في اعتبارك أنه على الرغم من أن webpack-simple ممتاز للنماذج الأولية السريعة والتطبيق الخفيف مثلنا ، إلا أنه غير مناسب بشكل خاص للتطبيقات الجادة أو نشر الإنتاج. إذا كنت ترغب في استخدام أي نموذج آخر (على الرغم من أننا ننصح بعدم استخدامه إذا كنت مبتدئًا) ، أو ترغب في تسمية مشروعك بشيء آخر ، فإن بناء الجملة هو:
       vue init [template-name] [project-name]
    • انتقل إلى الدليل الذي تم إنشاؤه بواسطة vue-cli للمشروع.
       cd vue_weather_dashboard
    • قم بتثبيت جميع الحزم المذكورة في package.json ، التي تم إنشاؤها بواسطة أداة vue-cli webpack-simple .
       npm install
    • ابدأ خادم التطوير وشاهد مشروع Vue الافتراضي الخاص بك يعمل في المتصفح!
       npm run dev

إذا كنت جديدًا على Vue.js ، فتوقف لحظة لتستمتع بأحدث إنجازاتك - لقد قمت بإنشاء تطبيق Vue صغير وتشغيله على localhost: 8080!

لقطة شاشة لموقع Vue.js
(معاينة كبيرة)

شرح موجز لهيكل المشروع الافتراضي

حان الوقت لإلقاء نظرة على الهيكل داخل الدليل vue_weather_dashboard ، بحيث يكون لديك فهم للأساسيات قبل أن نبدأ في تعديله.

يبدو الهيكل مثل هذا:

 vue_weather_dashboard |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src | |--- App.vue | |--- assets | | |--- logo.png | |--- main.js

على الرغم من أنه قد يكون من المغري تخطي التعرف على الملفات والأدلة الافتراضية ، إذا كنت جديدًا على Vue ، فإننا نوصي بشدة بإلقاء نظرة على محتويات الملفات على الأقل. يمكن أن تكون جلسة تعليمية جيدة وتثير أسئلة يجب عليك متابعتها بنفسك ، خاصة الملفات التالية:

  • package.json ، وإلقاء نظرة سريعة على ابن عمها package-lock.json
  • webpack.config.js
  • index.html
  • src/main.js
  • src/App.vue

فيما يلي شرح موجز لكل ملف من الملفات والأدلة الموضحة في مخطط الشجرة:

  • README.md
    لا توجد جائزة للتخمين - إنه في المقام الأول للبشر قراءة وفهم الخطوات اللازمة لإنشاء سقالات المشروع.
  • node_modules /
    هذا هو الدليل الذي تقوم فيه npm بتنزيل الحزم اللازمة لبدء المشروع. تتوفر المعلومات حول الحزم الضرورية في ملف package.json .
  • package.json
    يتم إنشاء هذا الملف بواسطة أداة vue-cli بناءً على متطلبات قالب webpack-simple ، ويحتوي على معلومات حول حزم npm (بما في ذلك إصداراتها وتفاصيل أخرى) التي يجب تثبيتها. ألق نظرة فاحصة على محتوى هذا الملف - هذا هو المكان الذي يجب عليك زيارته وربما تعديله لإضافة / حذف الحزم اللازمة للمشروع ، ثم قم بتشغيل تثبيت npm. اقرأ المزيد عن package.json هنا.
  • الحزمة-lock.json
    تم إنشاء هذا الملف بواسطة npm نفسه ، وهو مخصص بشكل أساسي للاحتفاظ بسجل للأشياء التي يتم تنزيلها وتثبيتها في npm.
  • webpack.config.js
    هذا ملف JavaScript يحتوي على تكوين webpack - أداة تجمع جوانب مختلفة من مشروعنا معًا (التعليمات البرمجية والأصول الثابتة والتكوين والبيئات وطريقة الاستخدام وما إلى ذلك) ، ويتم تصغيرها قبل تقديمها للمستخدم. الميزة هي أن جميع الأشياء مرتبطة ببعضها البعض تلقائيًا ، وتتحسن تجربة المستخدم بشكل كبير بسبب التحسن في أداء التطبيق (يتم تقديم الصفحات بسرعة ويتم تحميلها بشكل أسرع على المتصفح). كما قد تصادف لاحقًا ، هذا هو الملف الذي يجب فحصه عندما لا يعمل شيء ما في نظام البناء بالطريقة التي من المفترض أن يكون عليها. أيضًا ، عندما تريد نشر التطبيق ، يعد هذا أحد الملفات الرئيسية التي تحتاج إلى تعديل (اقرأ المزيد هنا).
  • index.html
    يعمل ملف HTML هذا كمصفوفة (أو يمكنك القول ، قالب) حيث يتم تضمين البيانات والرمز ديناميكيًا (هذا ما يفعله Vue في المقام الأول) ، ثم يتم تقديمه للمستخدم.
  • src / main.js
    يحتوي ملف JavaScript هذا على تعليمات برمجية تدير بشكل أساسي التبعيات على مستوى المشروع / أعلى ، ويحدد مكون Vue ذي المستوى الأعلى. باختصار ، يقوم بتنسيق JavaScript للمشروع بأكمله ، ويعمل كنقطة دخول للتطبيق. قم بتحرير هذا الملف عندما تحتاج إلى الإعلان عن التبعيات على مستوى المشروع على وحدات عقدة معينة ، أو إذا كنت تريد تغيير شيء ما حول مكون Vue الأعلى في المشروع.
  • src / App.vue
    في النقطة السابقة ، عندما كنا نتحدث عن "مكون Vue العلوي" ، كنا نتحدث بشكل أساسي عن هذا الملف. يعد كل ملف .vue في المشروع مكونًا ، وترتبط المكونات بشكل هرمي. في البداية ، لدينا ملف .vue واحد فقط ، مثل App.vue ، باعتباره المكون الوحيد لدينا. لكن سنضيف قريبًا المزيد من المكونات إلى مشروعنا (باتباع بنية لوحة القيادة بشكل أساسي) ، وربطها وفقًا للتسلسل الهرمي المطلوب ، مع كون App.vue هو سلف الجميع. ستحتوي ملفات .vue هذه على رمز بتنسيق يريد Vue منا كتابته. لا تقلق ، فهي عبارة عن كود JavaScript مكتوب للحفاظ على هيكل يمكن أن يبقينا عاقلين ومنظمين. لقد تم تحذيرك - بنهاية هذا المشروع ، إذا كنت جديدًا على Vue ، فقد تصبح مدمنًا على template — script — style template — script — style طريقة template — script — style لتنظيم الكود!

الآن بعد أن أنشأنا الأساس ، حان الوقت لـ:

  • قم بتعديل القوالب وتعديل ملفات التكوين قليلاً ، بحيث يعمل المشروع بالطريقة التي نريدها تمامًا.
  • إنشاء ملفات .vue جديدة ، وتنفيذ بنية لوحة المعلومات مع كود Vue.

سوف نتعلمهم في القسم التالي ، والذي سيكون طويلًا بعض الشيء ويتطلب بعض الاهتمام. إذا كنت بحاجة إلى الكافيين أو الماء ، أو تريد التخلص منه - فقد حان الوقت الآن!

3. تخصيص هيكل المشروع الافتراضي

حان الوقت للتلاعب بالأساس الذي منحنا إياه مشروع السقالة. قبل أن تبدأ ، تأكد من تشغيل خادم التطوير المقدم من webpack . تتمثل ميزة تشغيل هذا الخادم بشكل مستمر في أن أي تغييرات تجريها في الكود المصدري - أي تغييرات تقوم بحفظها وتحديث صفحة الويب - تنعكس فورًا على المتصفح.

إذا كنت ترغب في بدء تشغيل خادم التطوير ، فما عليك سوى تنفيذ الأمر التالي من المحطة (بافتراض أن دليلك الحالي هو دليل المشروع):

 npm run dev

في الأقسام التالية سنقوم بتعديل بعض الملفات الموجودة وإضافة بعض الملفات الجديدة. سيتبعها شروحات موجزة لمحتوى تلك الملفات ، بحيث يكون لديك فكرة عما يجب أن تفعله هذه التغييرات.

تعديل الملفات الموجودة

index.html

تطبيقنا هو تطبيق صفحة واحدة حرفيًا ، نظرًا لوجود صفحة ويب واحدة فقط يتم عرضها على المتصفح. سنتحدث عن هذا لاحقًا ، ولكن لنقم أولاً بإجراء التغيير الأول - تعديل النص داخل علامة <title> .

بهذه المراجعة الصغيرة ، يبدو ملف HTML كما يلي:

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <!-- Modify the text of the title tag below --> <title>Vue Weather Dashboard</title> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

توقف لحظة لتحديث صفحة الويب على localhost:8080 ، وشاهد التغيير ينعكس على شريط العنوان بعلامة التبويب في المتصفح - يجب أن تقول "Vue Weather Dashboard". ومع ذلك ، كان هذا فقط لتوضيح عملية إجراء التغييرات والتحقق مما إذا كانت تعمل أم لا. لدينا المزيد من الأشياء لنفعلها!

تفتقر صفحة HTML البسيطة هذه إلى العديد من الأشياء التي نريدها في مشروعنا ، وخاصة ما يلي:

  • بعض المعلومات الوصفية
  • روابط CDN إلى Bootstrap (إطار عمل CSS)
  • رابط إلى ورقة الأنماط المخصصة (لم تتم إضافتها في المشروع بعد)
  • مؤشرات إلى واجهة برمجة تطبيقات تحديد الموقع الجغرافي لخرائط Google من علامة <script>

بعد إضافة هذه الأشياء ، يحتوي ملف index.html النهائي على المحتوى التالي:

 <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="src/css/style.css"> <title>Weather Dashboard</title> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC-lCjpg1xbw-nsCc11Si8Ldg2LKYizqI4&libraries=places"></script> </head> <body> <div></div> <script src="/dist/build.js"></script> </body> </html>

احفظ الملف وقم بتحديث صفحة الويب. ربما لاحظت وجود نتوء طفيف أثناء تحميل الصفحة - يرجع ذلك أساسًا إلى حقيقة أن نمط الصفحة يتم التحكم فيه الآن بواسطة Bootstrap ، وأن عناصر النمط مثل الخطوط والتباعد وما إلى ذلك تختلف عن الافتراضي الذي كان لدينا سابقًا (إذا لم تكن متأكدًا ، فارجع إلى الإعداد الافتراضي وشاهد الفرق).

لقطة شاشة عندما تقوم بتحديث صفحة الويب باستخدام المضيف المحلي: 8080
(معاينة كبيرة)

ملاحظة : شيء واحد مهم قبل المضي قدمًا - يحتوي عنوان URL الخاص بواجهة برمجة تطبيقات خرائط Google على مفتاح خاص ببرنامج FusionCharts. في الوقت الحالي ، يمكنك استخدام هذا المفتاح لبناء المشروع ، لأننا لا نريدك أن تتورط في هذا النوع من التفاصيل الدقيقة (والتي يمكن أن تكون مصدر إلهاء وأنت جديد). ومع ذلك ، فإننا نحثك بشدة على إنشاء واستخدام مفتاح API لخرائط Google الخاص بك بمجرد إحراز بعض التقدم والشعور بالراحة في الانتباه إلى هذه التفاصيل الدقيقة.

package.json

في وقت كتابة هذا ، استخدمنا إصدارات معينة من حزم npm لمشروعنا ، ونحن نعلم على وجه اليقين أن هذه الأشياء تعمل معًا. ومع ذلك ، بحلول الوقت الذي تقوم فيه بتنفيذ المشروع ، من المحتمل جدًا ألا تكون أحدث الإصدارات المستقرة من الحزم التي يتم تنزيلها من أجلك npm هي نفسها التي استخدمناها ، وقد يؤدي ذلك إلى كسر الكود (أو القيام بأشياء تتجاوز سيطرتنا). وبالتالي ، من المهم جدًا أن يكون لديك نفس ملف package.json بالضبط الذي تم استخدامه لبناء هذا المشروع ، بحيث تكون التعليمات البرمجية / التفسيرات والنتائج التي تحصل عليها متسقة.

يجب أن يكون محتوى ملف package.json :

 { "name": "vue_weather_dashboard", "description": "A Vue.js project", "version": "1.0.0", "author": "FusionCharts", "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" }, "dependencies": { "axios": "^0.18.0", "babel": "^6.23.0", "babel-cli": "^6.26.0", "babel-polyfill": "^6.26.0", "fusioncharts": "^3.13.3", "moment": "^2.22.2", "moment-timezone": "^0.5.21", "vue": "^2.5.11", "vue-fusioncharts": "^2.0.4" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "devDependencies": { "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.0", "babel-preset-stage-3": "^6.24.1", "cross-env": "^5.0.5", "css-loader": "^0.28.7", "file-loader": "^1.1.4", "vue-loader": "^13.0.5", "vue-template-compiler": "^2.4.4", "webpack": "^3.6.0", "webpack-dev-server": "^2.9.1" } }

نحن نشجعك على تصفح package.json الجديدة ومعرفة وظائف الكائنات المختلفة في json. قد تفضل تغيير قيمة مفتاح " author " إلى اسمك. أيضًا ، ستكشف الحزم المذكورة في التبعيات عن نفسها في الوقت المناسب في الكود. في الوقت الحالي ، يكفي معرفة ما يلي:

  • الحزم المتعلقة ب babel مخصصة للتعامل بشكل صحيح مع كود نمط ES6 بواسطة المتصفح ؛
  • تتعامل axios مع طلبات HTTP المستندة إلى Promise ؛
  • المنطقة الزمنية moment مخصصة للتلاعب بالتاريخ / الوقت ؛
  • المخططات الانصهار والمخططات vue-fusioncharts fusioncharts المسؤولة عن عرض المخططات:
  • vue لأسباب واضحة.

webpack.config.js

كما هو الحال مع package.json ، نقترح عليك الاحتفاظ بملف webpack.config.js يتوافق مع الملف الذي استخدمناه لبناء المشروع. ومع ذلك ، قبل إجراء أي تغييرات ، نوصيك بمقارنة الشفرة الافتراضية بعناية في webpack.config.js ، والشفرة التي قدمناها أدناه. ستلاحظ عددًا قليلاً من الاختلافات - ابحث عنها في google وستكون لديك فكرة أساسية عما تعنيه. نظرًا لأن شرح تكوينات webpack بالتفصيل خارج نطاق هذه المقالة ، فأنت وحدك في هذا الصدد.

ملف webpack.config.js المخصص هو كما يلي:

 var path = require('path') var webpack = require('webpack') module.exports = { entry: ['babel-polyfill', './src/main.js'], output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: 'build.js' }, module: { rules: [ { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' ], }, { test: /\.vue$/, loader: 'vue-loader', options: { loaders: { } // other vue-loader options go here } }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file-loader', options: { name: '[name].[ext]?[hash]' } } ] }, resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' }, extensions: ['*', '.js', '.vue', '.json'] }, devServer: { historyApiFallback: true, noInfo: true, overlay: true, host: '0.0.0.0', port: 8080 }, performance: { hints: false }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // https://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }

مع التغييرات التي تم إجراؤها على webpack.config.js الخاص بالمشروع ، من الضروري إيقاف خادم التطوير قيد التشغيل ( Ctrl + C ) ، وإعادة تشغيله باستخدام الأمر التالي المنفذ من دليل المشروع بعد تثبيت جميع الحزم المذكورة في package.json ملف package.json :

 npm install npm run dev

مع هذا ، تنتهي محنة تعديل التكوينات والتأكد من أن الحزم الصحيحة في مكانها الصحيح. ومع ذلك ، فإن هذا يمثل أيضًا رحلة تعديل التعليمات البرمجية وكتابتها ، وهي رحلة طويلة بعض الشيء ولكنها أيضًا مجزية للغاية!

src / main.js

هذا الملف هو مفتاح تنسيق المستوى الأعلى للمشروع - هنا نحدد:

  • ما هي تبعيات المستوى الأعلى (أين يمكن الحصول على أهم حزم npm الضرورية) ؛
  • كيفية حل التبعيات ، جنبًا إلى جنب مع التعليمات إلى Vue حول استخدام الإضافات / الأغلفة ، إن وجدت ؛
  • مثيل Vue يدير المكون الأعلى في المشروع: src/App.vue (ملف .vue العقدي).

تماشياً مع أهدافنا لملف src/main.js ، يجب أن يكون الكود:

 // Import the dependencies and necessary modules import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; // Resolve the dependencies Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); // Globally register the components for project-wide use Vue.use(VueFusionCharts, FusionCharts); // Instantiate the Vue instance that controls the application new Vue({ el: '#app', render: h => h(App) })

src / App.vue

هذا هو أحد أهم الملفات في المشروع بأكمله ، ويمثل المكون الأعلى في التسلسل الهرمي - التطبيق بأكمله نفسه ، ككل. بالنسبة لمشروعنا ، سيقوم هذا المكون بجميع عمليات الرفع الثقيل ، والتي سنستكشفها لاحقًا. في الوقت الحالي ، نريد التخلص من النموذج المعياري الافتراضي ، ووضع شيء خاص بنا.

إذا كنت جديدًا على طريقة Vue لتنظيم الكود ، فمن الأفضل أن تحصل على فكرة عن الهيكل العام داخل ملفات .vue . تتكون ملفات .vue من ثلاثة أقسام:

  • قالب
    هذا هو المكان الذي يتم فيه تحديد قالب HTML للصفحة. بصرف النظر عن HTML الثابت ، يحتوي هذا القسم أيضًا على طريقة Vue لتضمين المحتوى الديناميكي ، باستخدام الأقواس المزدوجة المتعرجة {{ }} .
  • النصي
    يحكم JavaScript هذا القسم ، وهو مسؤول عن إنشاء محتوى ديناميكي ينتقل ويجلس داخل قالب HTML في الأماكن المناسبة. هذا القسم هو في الأساس عنصر يتم تصديره ، ويتكون من:
    • بيانات
      هذه دالة بحد ذاتها ، وعادة ما تُرجع بعض البيانات المرغوبة مغلفة ضمن بنية بيانات لطيفة.
    • طرق
      كائن يتكون من واحدة أو أكثر من الوظائف / الطرق ، كل منها عادة ما تعالج البيانات بطريقة أو بأخرى ، وتتحكم أيضًا في المحتوى الديناميكي لقالب HTML.
    • محسوب
      يشبه إلى حد كبير كائن الأسلوب الذي تمت مناقشته أعلاه مع تمييز واحد مهم - بينما يتم تنفيذ جميع الوظائف داخل كائن الأسلوب كلما تم استدعاء أي منها ، فإن الوظائف داخل الكائن المحسوب تتصرف بشكل أكثر منطقية ، ويتم تنفيذها إذا وفقط إذا كان كذلك مسمى.
  • أسلوب
    هذا القسم مخصص لتصميم CSS الذي ينطبق على HTML للصفحة (مكتوب داخل القالب) - ضع CSS القديم الجيد هنا لجعل صفحاتك جميلة!

مع وضع النموذج أعلاه في الاعتبار ، دعنا نقوم بتخصيص الكود إلى الحد الأدنى في App.vue :

 <template> <div> <p>This component's code is in {{ filename }}</p> </div> </template> <script> export default { data() { return { filename: 'App.vue' } }, methods: { }, computed: { }, } </script> <style> </style>

تذكر أن مقتطف الشفرة أعلاه مخصص ببساطة لاختبار أن App.vue يعمل مع الكود الخاص بنا فيه. سيجري لاحقًا الكثير من التغييرات ، ولكن احفظ الملف أولاً وقم بتحديث الصفحة على المتصفح.

لقطة شاشة للمتصفح مع الرسالة "رمز هذا المكون موجود في App.vue"
(معاينة كبيرة)

في هذه المرحلة ، من المحتمل أن تحصل على بعض المساعدة في الأدوات. تحقق من Vue devtools لمتصفح Chrome ، وإذا لم يكن لديك الكثير من المشاكل في استخدام Google Chrome كمتصفحك الافتراضي للتطوير ، فثبّت الأداة والعب بها قليلاً. سيكون مفيدًا للغاية لمزيد من التطوير والتصحيح ، عندما تصبح الأمور أكثر تعقيدًا.

الدلائل والملفات الإضافية

ستكون الخطوة التالية هي إضافة ملفات إضافية ، بحيث يكتمل هيكل مشروعنا. نضيف الدلائل والملفات التالية:

  • src/css/ - style.css
  • src/assets/ - calendar.svg - vlocation.svg - windspeed.svg - search.svg - winddirection.svg
  • src/components/ - Content.vue - Highlights.vue - TempVarChart.vue - UVIndex.vue - Visibility.vue - WindStatus.vue

ملاحظة : احفظ ملفات .svg ذات الارتباطات التشعبية في مشروعك.

قم بإنشاء الدلائل والملفات المذكورة أعلاه. يجب أن تبدو بنية المشروع النهائية (تذكر حذف المجلدات والملفات من البنية الافتراضية التي أصبحت غير ضرورية الآن):

 vue_weather_dashboard/ |--- README.md |--- node_modules/ | |--- ... | |--- ... | |--- [many npm packages we installed] | |--- ... | |--- ... |--- package.json |--- package-lock.json |--- webpack.config.js |--- index.html |--- src/ | |--- App.vue | |--- css/ | | |--- style.css | |--- assets/ | | |--- calendar.svg | | |--- location.svg | | |--- location.svg | | |--- winddirection.svg | | |--- windspeed.svg | |--- main.js | |--- components/ | | |--- Content.vue | | |--- Highlights.vue | | |--- TempVarChart.vue | | |--- UVIndex.vue | | |--- Visibility.vue | | |--- WindStatus.vue

قد تكون هناك بعض الملفات الأخرى ، مثل .babelrc و .gitignore و .editorconfig وما إلى ذلك في المجلد الجذر للمشروع. يمكنك تجاهلها بأمان الآن.

في القسم التالي ، سنضيف الحد الأدنى من المحتوى إلى الملفات المضافة حديثًا ، ونختبر ما إذا كانت تعمل بشكل صحيح.

src / css / style.css

على الرغم من أنه لن يكون ذا فائدة كبيرة على الفور ، انسخ الكود التالي إلى الملف:

 @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500"); :root { font-size: 62.5%; } body { font-family: Roboto; font-weight: 400; width: 100%; margin: 0; font-size: 1.6rem; } #sidebar { position: relative; display: flex; flex-direction: column; background-image: linear-gradient(-180deg, #80b6db 0%, #7da7e2 100%); } #search { text-align: center; height: 20vh; position: relative; } #location-input { height: 42px; width: 100%; opacity: 1; border: 0; border-radius: 2px; background-color: rgba(255, 255, 255, 0.2); margin-top: 16px; padding-left: 16px; color: #ffffff; font-size: 1.8rem; line-height: 21px; } #location-input:focus { outline: none; } ::placeholder { color: #FFFFFF; opacity: 0.6; } #current-weather { color: #ffffff; font-size: 8rem; line-height: 106px; position: relative; } #current-weather>span { color: #ffffff; font-size: 3.6rem; line-height: 42px; vertical-align: super; opacity: 0.8; top: 15px; position: absolute; } #weather-desc { font-size: 2.0rem; color: #ffffff; font-weight: 500; line-height: 24px; } #possibility { color: #ffffff; font-size: 16px; font-weight: 500; line-height: 19px; } #max-detail, #min-detail { color: #ffffff; font-size: 2.0rem; font-weight: 500; line-height: 24px; } #max-detail>i, #min-detail>i { font-style: normal; height: 13.27px; width: 16.5px; opacity: 0.4; } #max-detail>span, #min-detail>span { color: #ffffff; font-family: Roboto; font-size: 1.2rem; line-height: 10px; vertical-align: super; } #max-summary, #min-summary { opacity: 0.9; color: #ffffff; font-size: 1.4rem; line-height: 16px; margin-top: 2px; opacity: 0.7; } #search-btn { position: absolute; right: 0; top: 16px; padding: 2px; z-index: 999; height: 42px; width: 45px; background-color: rgba(255, 255, 255, 0.2); border: none; } #dashboard-content { text-align: center; height: 100vh; } #date-desc, #location-desc { color: #ffffff; font-size: 1.6rem; font-weight: 500; line-height: 19px; margin-bottom: 15px; } #date-desc>img { top: -3px; position: relative; margin-right: 10px; } #location-desc>img { top: -3px; position: relative; margin-left: 5px; margin-right: 15px; } #location-detail { opacity: 0.7; color: #ffffff; font-size: 1.4rem; line-height: 20px; margin-left: 35px; } .centered { position: fixed; top: 45%; left: 50%; transform: translate(-50%, -50%); } .max-desc { width: 80px; float: left; margin-right: 28px; } .temp-max-min { margin-top: 40px } #dashboard-content { background-color: #F7F7F7; } .custom-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 20px !important; } .custom-content-card { background-color: #FFFFFF !important; border: 0 !important; margin-top: 16px !important; margin-bottom: 0px !important; } .header-card { height: 50vh; } .content-card { height: 43vh; } .card-divider { margin-top: 0; } .content-header { color: #8786A4; font-size: 1.4rem; line-height: 16px; font-weight: 500; padding: 15px 10px 5px 15px; } .highlights-item { min-height: 37vh; max-height: 38vh; background-color: #FFFFFF; } .card-heading { color: rgb(33, 34, 68); font-size: 1.8rem; font-weight: 500; line-height: 21px; text-align: center; } .card-sub-heading { color: #73748C; font-size: 1.6rem; line-height: 19px; } .card-value { color: #000000; font-size: 1.8rem; line-height: 21px; } span text { font-weight: 500 !important; } hr { padding-top: 1.5px; padding-bottom: 1px; margin-bottom: 0; margin-top: 0; line-height: 0.5px; } @media only screen and (min-width: 768px) { #sidebar { height: 100vh; } #info { position: fixed; bottom: 50px; width: 100%; padding-left: 15px; } .wrapper-right { margin-top: 80px; } } @media only screen and (min-width:1440px) { #sidebar { width: 350px; max-width: 350px; flex: auto; } #dashboard-content { width: calc(100% — 350px); max-width: calc(100% — 350px); flex: auto; } }

src / الأصول /

في هذا الدليل ، قم بتنزيل وحفظ ملفات .svg المذكورة أدناه:

  • calendar.svg
  • location.svg
  • search.svg
  • winddirection.svg
  • windspeed.svg

src / المكونات / Content.vue

هذا ما نسميه "المكون الغبي" (أي عنصر نائب) موجود فقط للحفاظ على التسلسل الهرمي ، ويمرر البيانات بشكل أساسي إلى مكوناته الفرعية.

تذكر أنه لا يوجد شريط تقني لكتابة كل التعليمات البرمجية الخاصة بنا في ملف App.vue ، لكننا نتبع نهج تقسيم الكود عن طريق دمج المكونات لسببين:

  • لكتابة رمز نظيف ، مما يساعد على سهولة القراءة والصيانة ؛
  • لتكرار نفس الهيكل الذي سنراه على الشاشة ، أي التسلسل الهرمي.

قبل أن ندمج المكون المحدد في Content.vue داخل App.vue المكون الجذر ، دعنا نكتب بعض التعليمات البرمجية للعبة (لكن التعليمية) لـ Content.vue :

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> </div> </template> <script> export default { data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> </style>

في الكود ، لاحظ وفهم ما يلي بعناية:

  • ضمن علامة <script> (حيث من الواضح أننا نكتب بعض كود JavaScript) ، نحدد الكائن الذي يتم تصديره (إتاحته لملفات أخرى) بشكل افتراضي. يحتوي هذا الكائن على دالة data() ، التي تُرجع كائن مصفوفة يسمى childComponents ، مع كون عناصره أسماء ملفات المكون التي يجب أن يتم تداخلها بشكل أكبر.
  • ضمن علامة <template> (حيث نكتب بعض قوالب HTML) ، الشيء المهم هو <ul> .
    • ضمن القائمة غير المرتبة ، يجب أن يكون كل عنصر قائمة أسماء المكونات الفرعية المقصودة ، كما هو محدد في عنصر المصفوفة childComponents . علاوة على ذلك ، يجب أن تمتد القائمة تلقائيًا حتى آخر عنصر في المصفوفة. يبدو أننا يجب أن نكتب حلقة for ، أليس كذلك؟ نقوم بذلك باستخدام التوجيه v-for المقدم من Vue.js. التوجيه v-for :
      • تعمل كسمة للعلامة <li> ، تتكرر عبر المصفوفة ، تعرض أسماء المكونات الفرعية حيث تم ذكر المكرر داخل قوسين {{ }} (حيث نكتب النص لعناصر القائمة).

يشكل الكود والشرح أعلاه أساس فهمك اللاحق لكيفية ارتباط النص والقالب ببعضهما البعض ، وكيف يمكننا استخدام التوجيهات المقدمة من Vue.js.

لقد تعلمنا الكثير ، ولكن حتى بعد كل هذا ، لدينا شيء واحد لنتعلمه حول ربط المكونات بسلاسة في التسلسل الهرمي - تمرير البيانات من المكون الرئيسي إلى أبنائه. في الوقت الحالي ، نحتاج إلى معرفة كيفية تمرير بعض البيانات من src/App.vue إلى src/components/Content.vue ، حتى نتمكن من استخدام نفس الأساليب لبقية المكونات المتداخلة في هذا المشروع.

قد يبدو انتقال البيانات من الوالد إلى المكونات الفرعية أمرًا بسيطًا ، لكن العيب يكمن في التفاصيل! كما هو موضح بإيجاز أدناه ، هناك عدة خطوات متضمنة في إنجاحها:

  • التعريف والبيانات
    في الوقت الحالي ، نريد أن نلعب بعض البيانات الثابتة - سيكون كائنًا يحتوي على قيم مشفرة حول جوانب مختلفة من الطقس جيدًا! نقوم بإنشاء كائن يسمى weather_data وإعادته من وظيفة data() في App.vue . يتم توفير كائن weather_data في المقتطف أدناه:
 weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, },
  • تمرير البيانات من ولي الأمر
    لتمرير البيانات ، نحتاج إلى وجهة نريد إرسال البيانات إليها! في هذه الحالة ، الوجهة هي مكون Content.vue ، وطريقة تنفيذه هي:
    • قم بتعيين كائن weather_data إلى سمة مخصصة لعلامة <Content>
    • اربط السمة بالبيانات باستخدام v-bind : التوجيه المقدم من Vue.js ، والذي يجعل قيمة السمة ديناميكية (تستجيب للتغييرات التي تم إجراؤها في البيانات الأصلية).
       <Content v-bind:weather_data=“weather_data”></Content>

يتم التعامل مع تحديد البيانات وتمريرها من جانب المصدر من المصافحة ، وهو في حالتنا ملف App.vue .

فيما يلي رمز ملف App.vue الحالية:

 <template> <div> <p>This component's code is in {{ filename }}</p> <Content v-bind:weather_data="weather_data"></Content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'Content': Content }, data () { return { filename: 'App.vue', weather_data: { location: "California", temperature: { current: "35 C", }, highlights: { uvindex: "3", windstatus: { speed: "20 km/h", direction: "NE", }, visibility: "12 km", }, }, } }, methods: { }, computed: { }, } </script> <style> </style> 
لقطة شاشة للمتصفح مع الرسالة "رمز هذا المكون موجود في App.vue. المكونات الفرعية هذه لـ Content.vue هي: TempVarChart.vue ، Highlights.vue "
(معاينة كبيرة)

مع تحديد البيانات وتمريرها من المصدر (المكون الرئيسي) ، أصبح من مسؤولية الطفل الآن تلقي البيانات وتقديمها بشكل مناسب ، كما هو موضح في الخطوتين التاليتين.

  • استلام البيانات من قبل الطفل
    يجب أن يتلقى المكون الفرعي ، Content.vue ، كائن weather_data الذي يتم إرساله إليه بواسطة المكون الرئيسي App.vue . يوفر Vue.js آلية للقيام بذلك - كل ما تحتاجه هو كائن مصفوفة يسمى props ، محدد في الكائن الافتراضي الذي تم تصديره بواسطة Content.vue . كل عنصر من عناصر props هو اسم لكائنات البيانات التي تريد تلقيها من أصلها. في الوقت الحالي ، كائن البيانات الوحيد الذي من المفترض أن يستقبله هو weather_data من App.vue. وبالتالي ، تبدو مصفوفة props كما يلي:
 <template> // HTML template code here </template> <script> export default { props: ["weather_data"], data () { return { // data here } }, } </script> <style> // component specific CSS here </style>
  • تقديم البيانات في الصفحة
    الآن بعد أن تأكدنا من تلقي البيانات ، فإن المهمة الأخيرة التي نحتاج إلى إكمالها هي تقديم البيانات. في هذا المثال ، سنقوم بتفريغ البيانات المستلمة مباشرة على صفحة الويب ، فقط لتوضيح التقنية. ومع ذلك ، في التطبيقات الحقيقية (مثل التطبيق الذي نحن على وشك إنشائه) ، تمر البيانات عادةً بالكثير من المعالجة ، ويتم عرض الأجزاء ذات الصلة منها فقط بالطرق التي تناسب الغرض. على سبيل المثال ، في هذا المشروع ، سنحصل في النهاية على بيانات أولية من واجهة برمجة تطبيقات الطقس ، ونقوم بتنظيفها وتنسيقها ، وتغذية البيانات إلى هياكل البيانات اللازمة للمخططات ، ثم تصورها. على أي حال ، لعرض تفريغ البيانات الأولية ، سنستخدم فقط {{ }} الأقواس التي يفهمها Vue ، كما هو موضح في المقتطف أدناه:
 <template> <div> // other template code here {{ weather_data }} </div> </template>

حان الوقت الآن لاستيعاب كل الأجزاء والقطع. رمز Content.vue - في حالته الحالية - موضح أدناه:

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} </div> </template> <script> export default { props: ["weather_data"], data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'] } }, methods: { }, computed: { }, } </script> <style> #pagecontent { border: 1px solid black; padding: 2px; } </style> 
لقطة شاشة للمتصفح مع نتيجة الشفرة المقدمة
(معاينة كبيرة)

بعد إجراء التغييرات التي تمت مناقشتها أعلاه ، قم بتحديث صفحة الويب على المتصفح وشاهد كيف تبدو. توقف لحظة لتقدير التعقيد الذي يتعامل معه Vue - إذا قمت بتعديل كائن weather_data في App.vue ، فسيتم نقله بصمت إلى Content.vue ، وفي النهاية إلى المتصفح الذي يعرض صفحة الويب! حاول عن طريق تغيير قيمة الموقع الرئيسي.

على الرغم من أننا تعلمنا عن الدعائم وربط البيانات باستخدام البيانات الثابتة ، فإننا سنستخدم البيانات الديناميكية التي تم جمعها باستخدام واجهات برمجة تطبيقات الويب في التطبيق ، وسوف نغير الكود وفقًا لذلك .

ملخص

قبل أن ننتقل إلى بقية ملفات .vue ، دعنا نلخص ما تعلمناه أثناء كتابة كود App.vue components/Content.vue :

  • ملف App.vue هو ما نسميه المكون الجذر - الملف الموجود في الجزء العلوي من التسلسل الهرمي للمكونات. تمثل بقية ملفات .vue المكونات التي هي تابعة مباشرة لها ، وحفيدها ، وما إلى ذلك.
  • يعد ملف Content.vue مكونًا وهميًا - تتمثل مسؤوليته في نقل البيانات إلى المستويات أدناه والحفاظ على التسلسل الهرمي الهيكلي ، بحيث تظل التعليمات البرمجية الخاصة بنا متوافقة مع فلسفة "* ما نراه هو ما نطبقه *".
  • لا تحدث علاقة المكون بين الوالدين والطفل من فراغ - يجب عليك تسجيل مكون (إما عالميًا أو محليًا ، اعتمادًا على الاستخدام المقصود للمكون) ، ثم تداخله باستخدام علامات HTML المخصصة (التي تكون تهجئتها دقيقة مثل تلك الخاصة بالأسماء التي تم تسجيل المكونات بها).
  • بمجرد التسجيل والتداخل ، يتم تمرير البيانات من المكونات الأصلية إلى المكونات الفرعية ، ولا يتم عكس التدفق أبدًا (ستحدث أشياء سيئة إذا سمحت بنية المشروع بالتدفق العكسي). المكون الرئيسي هو المصدر النسبي للبيانات ، ويمرر البيانات ذات الصلة إلى العناصر الفرعية باستخدام التوجيه v-bind لسمات عناصر HTML المخصصة. يتلقى الطفل البيانات المخصصة له باستخدام الدعائم ، ثم يقرر بنفسه ما يجب فعله بالبيانات.

بالنسبة لبقية المكونات ، لن ننغمس في شرح مفصل - سنقوم فقط بكتابة الكود بناءً على الدروس المستفادة من الملخص أعلاه. سيكون الرمز بديهيًا ، وإذا كنت مرتبكًا بشأن التسلسل الهرمي ، فراجع الرسم التخطيطي أدناه:

رسم تخطيطي يوضح التسلسل الهرمي للكود
(معاينة كبيرة)

يوضح الرسم التخطيطي أن TempVarChart.vue و Highlights.vue هما الفرع المباشر لـ Content.vue . وبالتالي ، قد يكون من الجيد إعداد Content.vue لإرسال البيانات إلى تلك المكونات ، وهو ما نقوم به باستخدام الكود أدناه:

 <template> <div> <p>This child components of Content.vue are:</p> <ul> <li v-for="child in childComponents">{{ child }}</li> </ul> {{ weather_data }} <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue' import Highlights from './Highlights.vue' export default { props: ["weather_data"], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, data () { return { childComponents: ['TempVarChart.vue', 'Highlights.vue'], tempVar: this.weather_data.temperature, highlights: this.weather_data.highlights, } }, methods: { }, computed: { }, } </script> <style> </style>

بمجرد حفظ هذا الرمز ، ستحصل على أخطاء - لا تقلق ، هذا متوقع. سيتم إصلاحه بمجرد أن يكون لديك باقي ملفات المكونات جاهزة. إذا كان عدم القدرة على رؤية الإخراج يزعجك ، فقم بالتعليق على الأسطر التي تحتوي على علامات العناصر المخصصة <temp-var-chart> و <today-highlights> .

بالنسبة لهذا القسم ، هذا هو الكود النهائي لـ Content.vue . بالنسبة لبقية هذا القسم ، سوف نشير إلى هذا الرمز ، وليس الرموز السابقة التي كتبناها للتعلم.

src / المكونات / TempVarChart.vue

من خلال المكون الرئيسي Content.vue الذي يقوم بتمرير البيانات ، يجب إعداد TempVarChart.vue لتلقي البيانات وعرضها ، كما هو موضح في الكود أدناه:

 <template> <div> <p>Temperature Information:</p> {{ tempVar }} </div> </template> <script> export default { props: ["tempVar"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

src / المكونات / Highlights.vue

سيتلقى هذا المكون أيضًا بيانات من App.vue - المكون الأصلي الخاص به. بعد ذلك ، يجب ربطها بمكوناتها الفرعية ، ويجب نقل البيانات ذات الصلة إليها.

دعنا نرى أولاً رمز تلقي البيانات من الوالد:

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} </div> </template> <script> export default { props: ["highlights"], data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

في هذه المرحلة ، تبدو صفحة الويب مثل الصورة أدناه:

نتيجة الكود المعروض في المتصفح
(معاينة كبيرة)

نحتاج الآن إلى تعديل كود Highlights.vue لتسجيل مكوناته الفرعية وتداخلها ، متبوعًا بتمرير البيانات إلى الأطفال. الكود الخاص بها كما يلي:

 <template> <div> <p>Weather Highlights:</p> {{ highlights }} <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

بمجرد حفظ الشفرة ورؤية صفحة الويب ، من المتوقع أن ترى أخطاء في أداة Developer Console التي يوفرها المتصفح ؛ تظهر لأنه على الرغم من أن Highlights.vue يرسل البيانات ، فلا أحد يستقبلها. لم نكن بعد في كتابة الكود لأطفال Highlights.vue .

لاحظ أننا لم نقم بالكثير من معالجة البيانات ، أي أننا لم نستخرج العوامل الفردية لبيانات الطقس التي تندرج تحت قسم "النقاط البارزة" في لوحة القيادة. كان بإمكاننا فعل ذلك في وظيفة data() ، لكننا فضلنا الاحتفاظ بـ Highlights.vue مكونًا غبيًا يقوم فقط بتمرير ملف تفريغ البيانات بالكامل الذي يتلقاها إلى كل من الأطفال ، الذين يمتلكون بعد ذلك مقتطفاتهم الخاصة ما هو ضروري لهم . ومع ذلك ، نشجعك على تجربة استخراج البيانات في Highlights.vue ، وإرسال البيانات ذات الصلة إلى كل مكون فرعي - إنها ممارسة جيدة رغم ذلك!

src / المكونات / UVIndex.vue

يتلقى رمز هذا المكون تفريغ البيانات من Highlights.vue ، ويستخرج بيانات مؤشر الأشعة فوق البنفسجية ، ويعرضها على الصفحة.

 <template> <div> <p>UV Index: {{ uvindex }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { uvindex: this.highlights.uvindex } }, methods: { }, computed: { }, } </script> <style> </style>

src / المكونات / Visibility.vue

يتلقى رمز هذا المكون تفريغ البيانات من Highlights.vue ، ويستخرج البيانات من أجل الرؤية ، ويعرضها على الصفحة.

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility, } }, methods: { }, computed: { }, } </script> <style> </style>

src / المكونات / WindStatus.vue

يتلقى رمز هذا المكون تفريغ البيانات من Highlights.vue ، ويستخرج البيانات الخاصة بحالة الرياح (السرعة والاتجاه) ، ويعرضها على الصفحة.

 <template> <div> <p>Wind Status:</p> <p>Speed — {{ speed }}; Direction — {{ direction }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { speed: this.highlights.windstatus.speed, direction: this.highlights.windstatus.direction } }, methods: { }, computed: { }, } </script> <style> </style>

بعد إضافة الكود لجميع المكونات ، ألق نظرة على صفحة الويب على المتصفح.

نتيجة الكود المعروض في المتصفح
(معاينة كبيرة)

لا يثبط العزيمة ، ولكن كل هذه الكدح كانت فقط لربط المكونات في التسلسل الهرمي ، واختبار ما إذا كان تدفق البيانات يحدث بينهم أم لا! في القسم التالي ، سوف نتخلص من معظم الكود الذي كتبناه حتى الآن ، ونضيف الكثير فيما يتعلق بالمشروع الفعلي. ومع ذلك ، فإننا بالتأكيد سنحتفظ بهيكل المكونات وتداخلها ؛ ستسمح لنا الدروس المستفادة من هذا القسم ببناء لوحة تحكم مناسبة باستخدام Vue.js.

4. الحصول على البيانات ومعالجتها

هل تتذكر عنصر weather_data في App.vue ؟ كان يحتوي على بعض البيانات المشفرة التي استخدمناها لاختبار ما إذا كانت جميع المكونات تعمل بشكل صحيح ، وكذلك لمساعدتك على تعلم بعض الجوانب الأساسية لتطبيق Vue دون التورط في تفاصيل بيانات العالم الحقيقي. ومع ذلك ، فقد حان الوقت الآن للتخلص من غلافنا ، والخروج إلى العالم الحقيقي ، حيث ستهيمن البيانات من واجهة برمجة التطبيقات على معظم التعليمات البرمجية الخاصة بنا.

تجهيز المكونات الفرعية لتلقي ومعالجة البيانات الحقيقية

في هذا القسم ، ستحصل على تفريغ التعليمات البرمجية لجميع المكونات باستثناء App.vue . سيتعامل الكود مع تلقي البيانات الحقيقية من App.vue (على عكس الكود الذي كتبناه في القسم السابق لتلقي البيانات الوهمية وعرضها).

نحن نشجع بشدة على قراءة الكود الخاص بكل مكون بعناية ، بحيث يمكنك تكوين فكرة عن البيانات التي يتوقعها كل مكون من هذه المكونات ، والتي ستستخدمها في النهاية في التصور.

ستكون بعض التعليمات البرمجية والهيكل العام مشابهًا لتلك التي رأيتها في الهيكل السابق - لذلك لن تواجه شيئًا مختلفًا تمامًا. لكن الشيطان يكمن في التفاصيل! لذا افحص الكود بعناية ، وعندما تفهمها جيدًا بشكل معقول ، انسخ الكود إلى ملفات المكونات المعنية في مشروعك.

ملاحظة : جميع المكونات في هذا القسم موجودة في المجلد src/components/ . لذلك في كل مرة ، لن يتم ذكر المسار - سيتم ذكر اسم ملف .vue فقط لتحديد المكون.

المحتوى

 <template> <div> <temp-var-chart :tempVar="tempVar"></temp-var-chart> <today-highlights :highlights="highlights"></today-highlights> </div> </template> <script> import TempVarChart from './TempVarChart.vue'; import Highlights from './Highlights.vue'; export default { props: ['highlights', 'tempVar'], components: { 'temp-var-chart': TempVarChart, 'today-highlights': Highlights }, } </script>

تم إجراء التغييرات التالية من الكود السابق:

  • في <template> ، تمت إزالة النص والبيانات الموجودة داخل {{ }} ، نظرًا لأننا نتلقى الآن البيانات فقط ونمرر إلى العناصر الفرعية ، مع عدم تقديم هذا المكون المحدد.
  • في export default {} :
    • تم تغيير App.vue props سبب تغيير الخاصيات هو أن App.vue نفسه سيعرض بعض البيانات التي يحصل عليها من واجهة برمجة تطبيقات الطقس والموارد الأخرى عبر الإنترنت ، بناءً على استعلام بحث المستخدم ، ويمرر بقية البيانات. في الكود الوهمي الذي كتبناه سابقًا ، كان App.vue يمرر ملف تفريغ البيانات الوهمي بالكامل ، دون أي تمييز ، وتم إنشاء الدعائم الخاصة بـ Content.vue وفقًا لذلك.
    • لا تُرجع الدالة data () الآن شيئًا ، لأننا لا نقوم بأي معالجة للبيانات في هذا المكون.

TempVarChart.vue

من المفترض أن يتلقى هذا المكون توقعات مفصلة لدرجة الحرارة لبقية اليوم الحالي ، ثم يعرضها في النهاية باستخدام FusionCharts. لكن في الوقت الحالي ، سنعرضها فقط كنص على صفحة الويب.

 <template> <div> {{ tempVar.tempToday }} </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { }; }, methods: { }, }; </script> <style> </style>

يبرز

 <template> <div> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </template> <script> import UVIndex from './UVIndex.vue'; import Visibility from './Visibility.vue'; import WindStatus from './WindStatus.vue'; export default { props: ["highlights"], components: { 'uv-index': UVIndex, 'visibility': Visibility, 'wind-status': WindStatus, }, data () { return { } }, methods: { }, computed: { }, } </script> <style> </style>

التغييرات التي تم إجراؤها من الكود السابق هي:

  • في <template> ، تمت إزالة النص والبيانات الموجودة داخل {{ }} ، لأن هذا مكون غبي ، تمامًا مثل Content.vue ، وظيفته الوحيدة هي نقل البيانات إلى الأطفال مع الحفاظ على التسلسل الهرمي الهيكلي. تذكر أن المكونات الغبية مثل Highlights.vue و Content.vue موجودة للحفاظ على التكافؤ بين الهيكل المرئي للوحة القيادة والكود الذي نكتبه.

UVIndex.vue

التغييرات التي تم إجراؤها على الكود السابق هي كما يلي:

  • في <template> و <style> ، تم تغيير div id إلى uvIndex ، وهو أكثر قابلية للقراءة.
  • في القيمة export default {} ، تُرجع الدالة data() الآن سلسلة كائن uvIndex ، التي يتم استخراج قيمتها من كائن الإبرازات الذي يتلقاها المكون باستخدام props . يتم الآن استخدام uvIndex هذا مؤقتًا لعرض القيمة كنص داخل <template> . لاحقًا ، سنقوم بتوصيل هذه القيمة بهيكل البيانات المناسب لعرض مخطط.

الرؤية

 <template> <div> <p>Visibility: {{ visibility }}</p> </div> </template> <script> export default { props: ["highlights"], data () { return { visibility: this.highlights.visibility.toString() } }, methods: { }, computed: { }, } </script> <style> </style>

التغيير الوحيد الذي تم إجراؤه في هذا الملف (فيما يتعلق بالكود السابق) هو أن تعريف كائن visibility الذي تم إرجاعه بواسطة وظيفة data() يحتوي الآن على toString() في نهايته ، لأن القيمة المستلمة من الأصل ستكون عائمة رقم النقطة ، والتي يجب تحويلها إلى سلسلة.

WindStatus.vue

 <template> <div> <p>Wind Speed — {{ windSpeed }}</p> <p>Wind Direction — {{ derivedWindDirection }}, or {{ windDirection }} degree clockwise with respect to true N as 0 degree.</p> </div> </template> <script> export default { props: ["highlights"], data () { return { windSpeed: this.highlights.windStatus.windSpeed, derivedWindDirection: this.highlights.windStatus.derivedWindDirection, windDirection: this.highlights.windStatus.windDirection } }, methods: { }, computed: { }, } </script> <style> </style>

التغييرات التي تم إجراؤها على الكود السابق هي كما يلي:

  • خلال الملف ، تمت إعادة تسمية windStatus windstatus لتعزيز إمكانية القراءة وأيضًا لتكون متزامنة مع العنصر المميز الذي App.vue مع البيانات الفعلية.
  • تم إجراء تغييرات تسمية مماثلة للسرعة والاتجاه - التغييرات الجديدة هي windSpeed ​​و windDirection .
  • بدأ تشغيل كائن جديد مشتق من WindowsDirection (يتم App.vue derivedWindDirection حزمة الإبرازات).

في الوقت الحالي ، يتم تقديم البيانات المستلمة كنص ؛ لاحقًا ، سيتم توصيله بهيكل البيانات الضروري للتصور.

الاختبار بالبيانات الوهمية

قد يكون اللجوء إلى البيانات الوهمية بشكل متكرر محبطًا بعض الشيء بالنسبة لك ، ولكن هناك بعض الأسباب الوجيهة وراء ذلك:

  • لقد أجرينا الكثير من التغييرات على رمز كل مكون ، ومن الجيد اختبار ما إذا كانت هذه التغييرات تكسر الشفرة. بمعنى آخر ، يجب أن نتحقق مما إذا كان تدفق البيانات سليمًا أم لا ، والآن بعد أن أصبحنا على وشك الانتقال إلى أجزاء أكثر تعقيدًا من المشروع.
  • ستحتاج البيانات الحقيقية من واجهة برمجة تطبيقات الطقس عبر الإنترنت إلى الكثير من التدليك ، وقد يكون من الصعب عليك التوفيق بين رمز الحصول على البيانات ومعالجتها ، ورمز التدفق السلس للبيانات أسفل المكونات. الفكرة هي إبقاء مقدار التعقيد تحت السيطرة ، حتى يكون لدينا فهم أفضل للأخطاء التي قد نواجهها.

في هذا القسم ، ما نقوم به هو في الأساس ترميز صلب لبعض بيانات json في App.vue ، والتي من الواضح أنه سيتم استبدالها بالبيانات الحية في المستقبل القريب. هناك الكثير من التشابه بين بنية json الوهمية وبنية json التي سنستخدمها للبيانات الفعلية. لذلك فإنه يوفر لك أيضًا فكرة تقريبية عما يمكن توقعه من البيانات الحقيقية ، بمجرد أن نواجهها.

ومع ذلك ، فإننا نعترف بأن هذا بعيد كل البعد عن النهج المثالي الذي قد يتبناه المرء أثناء بناء مثل هذا المشروع من الصفر. في العالم الحقيقي ، ستبدأ غالبًا بمصدر البيانات الحقيقي ، وستتلاعب به قليلاً لفهم ما يمكن وما يجب فعله لترويضه ، ثم تفكر في بنية بيانات json المناسبة لالتقاط المعلومات ذات الصلة. لقد قمنا بحمايتك عن قصد من كل تلك الأعمال القذرة ، نظرًا لأن الأمر يأخذك بعيدًا عن الهدف - تعلم كيفية استخدام Vue.js و FusionCharts لبناء لوحة القيادة.

دعنا الآن ننتقل إلى الكود الجديد لـ App.vue:

 <template> <div> <dashboard-content :highlights="highlights" :tempVar="tempVar"></dashboard-content> </div> </template> <script> import Content from './components/Content.vue' export default { name: 'app', components: { 'dashboard-content': Content }, data () { return { tempVar: { tempToday: [ {hour: '11.00 AM', temp: '35'}, {hour: '12.00 PM', temp: '36'}, {hour: '1.00 PM', temp: '37'}, {hour: '2.00 PM', temp: '38'}, {hour: '3.00 PM', temp: '36'}, {hour: '4.00 PM', temp: '35'}, ], }, highlights: { uvIndex: 4, visibility: 10, windStatus: { windSpeed: '30 km/h', windDirection: '30', derivedWindDirection: 'NNE', }, }, } }, methods: { }, computed: { }, } </script> <style> </style>

التغييرات التي تم إجراؤها على الكود فيما يتعلق بإصداره السابق هي كما يلي:

  • تم تغيير اسم المكون الفرعي إلى محتوى لوحة المعلومات ، وبالتالي تمت مراجعة عنصر HTML المخصص في <template> . لاحظ أنه لدينا الآن سمتان - highlights و tempVar - بدلاً من سمة واحدة استخدمناها سابقًا مع العنصر المخصص. وفقًا لذلك ، تغيرت البيانات المرتبطة بهذه السمات أيضًا. المثير هنا هو أنه يمكننا استخدام التوجيه v-bind: أو اختصاره : (كما فعلنا هنا) ، مع سمات متعددة لعنصر HTML مخصص!
  • تقوم وظيفة data() الآن بإرجاع كائن filename (الذي كان موجودًا في وقت سابق) ، جنبًا إلى جنب مع كائنين جديدين (بدلاً من weather_data القديمة): tempVar highlights بنية Json مناسبة للكود الذي كتبناه في المكونات الفرعية ، حتى يتمكنوا من استخراج أجزاء البيانات التي يحتاجونها من التفريغات. الهياكل تشرح نفسها بنفسها تمامًا ، ويمكنك أن تتوقع أن تكون متشابهة تمامًا عندما نتعامل مع البيانات الحية. ومع ذلك ، فإن التغيير المهم الذي ستواجهه هو عدم وجود تشفير ثابت (واضح ، أليس كذلك) - سنترك القيم فارغة كحالة افتراضية ، ونكتب رمزًا لتحديثها ديناميكيًا بناءً على القيم التي سنحصل عليها من API الطقس.

لقد كتبت الكثير من التعليمات البرمجية في هذا القسم ، دون رؤية الناتج الفعلي. قبل المضي قدمًا ، ألق نظرة على المتصفح (أعد تشغيل الخادم باستخدام npm run dev ، إذا لزم الأمر) ، واستمتع بمجد إنجازك. تبدو صفحة الويب التي يجب أن تراها في هذه المرحلة مثل الصورة أدناه:

نتيجة الكود المعروض في المتصفح
(معاينة كبيرة)

كود للحصول على البيانات ومعالجتها

سيكون هذا القسم هو جوهر المشروع ، مع كتابة جميع التعليمات البرمجية في App.vue لما يلي:

  • إدخال الموقع من المستخدم - يكفي وجود مربع إدخال وزر الحث على اتخاذ إجراء ؛
  • وظائف المنفعة لمختلف المهام ؛ سيتم استدعاء هذه الوظائف لاحقًا في أجزاء مختلفة من رمز المكون ؛
  • الحصول على بيانات مفصلة للموقع الجغرافي من خرائط جوجل API لجافا سكريبت ؛
  • الحصول على بيانات مفصلة عن الطقس من Dark Sky API ؛
  • تنسيق ومعالجة بيانات الموقع الجغرافي والطقس ، والتي سيتم تمريرها إلى المكونات الفرعية.

توضح الأقسام الفرعية التالية كيف يمكننا تنفيذ المهام الموضحة لنا في النقاط أعلاه. مع بعض الاستثناءات ، سيتبع معظمهم التسلسل.

المدخلات من المستخدم

من الواضح تمامًا أن الإجراء يبدأ عندما يقدم المستخدم اسم المكان الذي يجب عرض بيانات الطقس الخاصة به. ولكي يحدث هذا ، نحتاج إلى تنفيذ ما يلي:

  • مربع إدخال لدخول الموقع ؛
  • زر إرسال يخبر تطبيقنا أن المستخدم قد دخل إلى الموقع وحان الوقت للقيام بالباقي. سنقوم أيضًا بتنفيذ السلوك عند بدء المعالجة عند الضغط على Enter .

سيقتصر الرمز الذي نعرضه أدناه على جزء قالب HTML من App.vue . سنذكر فقط اسم الطريقة المرتبطة بأحداث النقر ، ونعرّفها لاحقًا في كائن الأساليب في <script> في App.vue.

 <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div>

إن وضع المقتطف أعلاه في المكان المناسب أمر تافه - نتركه لك. ومع ذلك ، فإن الأجزاء المثيرة للاهتمام من المقتطف هي:

  • @keyup.enter="organizeAllDetails"
  • @click="organizeAllDetails"

كما تعلم من الأقسام السابقة ، @ هو اختصار Vue للتوجيه v-on : المرتبط ببعض الأحداث. الشيء الجديد هو " organizeAllDetails " - ليست سوى الطريقة التي سيتم إطلاقها بمجرد حدوث الأحداث (الضغط على Enter أو النقر فوق الزر). لم نحدد الطريقة بعد ، وسيكتمل اللغز بنهاية هذا القسم.

عرض معلومات النص يتحكم فيه App.vue

بمجرد أن يبدأ إدخال المستخدم الإجراء ويتم الحصول على الكثير من البيانات من واجهات برمجة التطبيقات ، فإننا نواجه السؤال الحتمي - "ماذا نفعل بكل هذه البيانات؟". من الواضح أن بعض بيانات التدليك مطلوبة ، لكن هذا لا يجيب على سؤالنا بالكامل! نحتاج إلى تحديد الاستخدام النهائي للبيانات ، أو بشكل أكثر مباشرة ، ما هي الكيانات التي تتلقى أجزاء مختلفة من البيانات المكتسبة والمعالجة؟

المكونات الفرعية لـ App.vue ، بناءً على التسلسل الهرمي والغرض ، هي المتنافسين في الخطوط الأمامية على الجزء الأكبر من البيانات. ومع ذلك ، سيكون لدينا أيضًا بعض البيانات التي لا تنتمي إلى أي من تلك المكونات الفرعية ، ولكنها غنية بالمعلومات وتجعل لوحة القيادة مكتملة. يمكننا الاستفادة منها بشكل جيد إذا عرضناها كمعلومات نصية يتحكم فيها App.vue مباشرةً ، بينما يتم تمرير باقي البيانات إلى الطفل لعرضها كمخططات جميلة في النهاية.

مع وضع هذا السياق في الاعتبار ، دعنا نركز على الكود لتحديد مرحلة استخدام البيانات النصية. إنه نموذج HTML بسيط في هذه المرحلة ، حيث ستأتي البيانات وتجلس في النهاية.

 <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div>

في المقتطف أعلاه ، يجب أن تفهم ما يلي:

  • العناصر الموجودة بداخل {{ }} - هي طريقة Vue لإدخال البيانات الديناميكية في قالب HTML ، قبل عرضها في المتصفح. لقد صادفتهم من قبل ، ولا يوجد شيء جديد أو مفاجئ. فقط ضع في اعتبارك أن كائنات البيانات هذه تنبع من طريقة data() في كائن export default() في App.vue . لديهم قيم افتراضية سنقوم بتعيينها وفقًا لمتطلباتنا ، ثم نكتب طرقًا معينة لملء الكائنات ببيانات واجهة برمجة التطبيقات الحقيقية.

لا تقلق لعدم رؤية التغييرات على المتصفح - لم يتم تحديد البيانات بعد ، ومن الطبيعي أن لا تعرض Vue الأشياء التي لا تعرفها. ومع ذلك ، بمجرد تعيين البيانات (وفي الوقت الحالي ، يمكنك التحقق من ذلك عن طريق الترميز الثابت للبيانات) ، سيتم التحكم في البيانات النصية بواسطة App.vue .

طريقة data()

طريقة data() هي بناء خاص في ملفات .vue - فهي تحتوي على كائنات بيانات مهمة جدًا للتطبيق وتقوم بإرجاعها. تذكر البنية العامة للجزء <script> في أي ملف .vue - فهي تحتوي تقريبًا على ما يلي:

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. // the data objects will have certain default values chosen by us. // The methods that we define below will manipulate the data. // Since the data is bounded to various attributes and directives, they // will update as and when the values of the data objects change. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. }, computed: { // computed properties here }, // other objects, as necessary } </script>

حتى الآن ، صادفت أسماء بعض كائنات البيانات ، لكن هناك الكثير. معظمها ذو صلة بالمكونات الفرعية ، كل منها يعالج جانبًا مختلفًا من تفريغ معلومات الطقس. فيما يلي طريقة data() الكاملة التي سنحتاجها لهذا المشروع - سيكون لديك فكرة عادلة حول البيانات التي نتوقعها من واجهات برمجة التطبيقات ، وكيف ننشر البيانات ، بناءً على تسمية الكائنات.

 data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; },

كما ترى ، في معظم الحالات ، تكون القيمة الافتراضية فارغة ، لأن ذلك سيكون كافياً في هذه المرحلة. ستتم كتابة طرق لمعالجة البيانات وتعبئتها بالقيم المناسبة ، قبل تقديمها أو تمريرها إلى المكونات الفرعية.

الطرق في App.vue

بالنسبة لملفات .vue ، تتم كتابة الطرق بشكل عام كقيم للمفاتيح المتداخلة في كائن methods { } . يتمثل دورهم الأساسي في معالجة كائنات البيانات الخاصة بالمكون. سنكتب الأساليب في App.vue مع مراعاة نفس الفلسفة. ومع ذلك ، بناءً على الغرض منها ، يمكننا تصنيف طرق App.vue إلى ما يلي:

  • طرق المنفعة
  • طرق العمل / الأحداث الموجهة
  • طرق الحصول على البيانات
  • طرق معالجة البيانات
  • طرق الغراء عالية المستوى

من المهم أن تفهم هذا - نحن نقدم لك الأساليب على طبق لأننا اكتشفنا بالفعل كيفية عمل واجهات برمجة التطبيقات ، وما هي البيانات التي تقدمها ، وكيف ينبغي لنا استخدام البيانات في مشروعنا. ليس الأمر أننا سحبنا الأساليب من فراغ ، وكتبنا بعض الرموز الغامضة للتعامل مع البيانات. لغرض التعلم ، من الجيد قراءة وفهم رمز الأساليب والبيانات بجد. ومع ذلك ، عندما تواجه مشروعًا جديدًا يتعين عليك بناؤه من البداية ، يجب عليك القيام بكل الأعمال القذرة بنفسك ، وهذا يعني تجربة الكثير مع واجهات برمجة التطبيقات - الوصول البرمجي وهيكل بياناتها ، قبل لصقها بسلاسة بالبيانات الهيكل الذي يتطلبه مشروعك. لن يكون لديك أي قبضة ، وستكون هناك لحظات محبطة ، لكن هذا كله جزء من النضج كمطور.

في الأقسام الفرعية التالية ، سنشرح كل نوع من أنواع الطرق ، ونعرض أيضًا تنفيذ الأساليب التي تنتمي إلى تلك الفئة. أسماء الطرق تشرح نفسها بنفسها تمامًا فيما يتعلق بالغرض منها ، وكذلك تنفيذها ، والذي نعتقد أنك ستجده سهلًا بما يكفي لاتباعه. ومع ذلك ، قبل ذلك ، تذكر المخطط العام لطرق الكتابة في ملفات .vue :

 <script> // import statements here export default { // name, components, props, etc. data() { return { // the data that is so crucial for the application is defined here. } }, methods: { // methods (objects whose values are functions) here. // bulk of dynamic stuff (the black magic part) is controlled from here. method_1: function(arg_1) { }, method_2: function(arg_1, arg_2) { }, method_3: function(arg_1) { }, ……. }, computed: { // computed properties here }, // other objects, as necessary } </script>

طرق المنفعة

طرق الأداة المساعدة ، كما يوحي الاسم ، هي طرق مكتوبة أساسًا لغرض توحيد التعليمات البرمجية المتكررة المستخدمة في المهام الهامشية. يتم استدعاؤهم بطرق أخرى عند الضرورة. فيما يلي طرق الأداة المساعدة لـ App.vue :

 convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To format the “possibility” (of weather) string obtained from the weather API formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); },
 // To convert Unix timestamps according to our convenience unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; },
 // To convert temperature from fahrenheit to celcius fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; },
 // To convert the air pressure reading from millibar to kilopascal milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); },
 // To convert distance readings from miles to kilometers mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); },
 // To format the wind direction based on the angle deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; },

على الرغم من أننا لم ننفذها ، يمكنك إخراج طرق الأداة المساعدة من ملف .vue ، ووضعها في ملف JavaScript منفصل. كل ما عليك فعله هو استيراد ملف .js في بداية جزء البرنامج النصي في ملف .vue ، ويجب أن تكون على ما يرام. يعمل هذا النهج جيدًا ويحافظ على نظافة الكود ، خاصة في التطبيقات الكبيرة حيث يمكنك استخدام الكثير من الطرق التي يتم تجميعها بشكل أفضل معًا بناءً على الغرض منها. يمكنك تطبيق هذا الأسلوب على كل مجموعات الطريقة المدرجة في هذه المقالة ، ومشاهدة التأثير نفسه. ومع ذلك ، نقترح عليك القيام بهذا التمرين بمجرد اتباع الدورة التدريبية المقدمة هنا ، بحيث يكون لديك فهم للصورة الكبيرة لجميع الأجزاء التي تعمل في مزامنة كاملة ، ولديك أيضًا برنامج عمل يمكنك الرجوع إليه ، مرة واحدة. فواصل أثناء التجريب.

طرق العمل / الأحداث الموجهة

يتم تنفيذ هذه الطرق بشكل عام عندما نحتاج إلى اتخاذ إجراء يتوافق مع حدث ما. اعتمادًا على الحالة ، قد يتم تشغيل الحدث من تفاعل المستخدم ، أو برمجيًا. في ملف App.vue ، توجد هذه الطرق أسفل طرق الأداة المساعدة.

 makeInputEmpty: function() { this.$refs.input.value = ''; },
 makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; },
 detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); },
 locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); },

أحد الأشياء المثيرة للاهتمام في بعض مقتطفات التعليمات البرمجية أعلاه هو استخدام $ref . بعبارات بسيطة ، إنها طريقة Vue لربط بيان الكود الذي يحتوي عليه ، ببناء HTML الذي من المفترض أن يؤثر عليه (لمزيد من المعلومات ، اقرأ الدليل الرسمي). على سبيل المثال ، الطريقتان makeInputEmpty() و detectEnterKeyPress() يؤثران على مربع الإدخال ، لأنه في HTML لمربع الإدخال ، ذكرنا قيمة السمة ref input .

طرق الحصول على البيانات

نحن نستخدم واجهات برمجة التطبيقات التالية في مشروعنا:

  • واجهة برمجة تطبيقات المكود الجغرافي لخرائط Google
    واجهة برمجة التطبيقات هذه مخصصة للحصول على إحداثيات الموقع الذي يبحث عنه المستخدم. ستحتاج إلى مفتاح API لنفسك ، والذي يمكنك الحصول عليه باتباع الوثائق الموجودة في الرابط المحدد. في الوقت الحالي ، يمكنك استخدام مفتاح API المستخدم بواسطة FusionCharts ، لكننا نطلب منك عدم إساءة استخدامه والحصول على مفتاح خاص بك. نشير إلى JavaScript API من index.html لهذا المشروع ، وسنستخدم المنشئات التي يوفرها لكودنا في ملف App.vue .
  • واجهة برمجة تطبيقات Dark Sky Weather
    هذا API هو للحصول على بيانات الطقس المقابلة للإحداثيات. ومع ذلك ، لن نستخدمه بشكل مباشر ؛ سنقوم بلفه داخل عنوان URL يعيد التوجيه من خلال أحد خوادم FusionCharts. والسبب هو أنك إذا أرسلت طلب GET إلى واجهة برمجة التطبيقات من تطبيق خاص بالعميل بالكامل مثل تطبيقنا ، فسيؤدي ذلك إلى حدوث خطأ CORS المحبط (مزيد من المعلومات هنا وهنا).

ملاحظة مهمة : نظرًا لأننا استخدمنا خرائط Google وواجهات برمجة تطبيقات Dark Sky ، فإن كلا من واجهات برمجة التطبيقات هذه لها مفاتيح API الخاصة بها والتي شاركناها معك في هذه المقالة. سيساعدك هذا على التركيز على التطورات من جانب العميل بدلاً من مشكلة تنفيذ الواجهة الخلفية. ومع ذلك ، نوصيك بإنشاء مفاتيح خاصة بك ، لأن مفاتيح واجهات برمجة التطبيقات الخاصة بنا ستأتي بحدود وإذا تجاوزت هذه الحدود فلن تتمكن من تجربة التطبيق بنفسك.

بالنسبة إلى خرائط Google ، انتقل إلى هذه المقالة للحصول على مفتاح API الخاص بك. بالنسبة إلى Dark Sky API ، قم بزيارة https://darksky.net/dev لإنشاء مفتاح API ونقاط النهاية ذات الصلة.

مع وضع السياق في الاعتبار ، دعنا نرى تنفيذ طرق الحصول على البيانات لمشروعنا.

 getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); },
 /* The coordinates that Google Maps Geocoder API returns are way too accurate for our requirements. We need to bring it into shape before passing the coordinates on to the weather API. Although this is a data processing method in its own right, we can't help mentioning it right now, because the data acquisition method for the weather API has dependency on the output of this method. */ setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } },
 /* This method dynamically creates the the correct weather API query URL, based on the formatted latitude and longitude. The complete URL is then fed to the method querying for weather data. Notice that the base URL used in this method (without the coordinates) points towards a FusionCharts server — we must redirect our GET request to the weather API through a server to avoid the CORS error. */ fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; },
 fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } },

Through these methods, we have introduced the concept of async-await in our code. If you have been a JavaScript developer for some time now, you must be familiar with the callback hell, which is a direct consequence of the asynchronous way JavaScript is written. ES6 allows us to bypass the cumbersome nested callbacks, and our code becomes much cleaner if we write JavaScript in a synchronous way, using the async-await technique. However, there is a downside. It takes away the speed that asynchronous code gives us, especially for the portions of the code that deals with data being exchanged over the internet. Since this is not a mission-critical application with low latency requirements, and our primary aim is to learn stuff, the clean code is much more preferable over the slightly fast code.

Data Processing Methods

Now that we have the methods that will bring the data to us, we need to prepare the ground for properly receiving and processing the data. Safety nets must be cast, and there should be no spills — data is the new gold (OK, that might be an exaggeration in our context)! Enough with the fuss, let's get to the point.

Technically, the methods we implement in this section are aimed at getting the data out of the acquisition methods and the data objects in App.vue , and sometimes setting the data objects to certain values that suits the purpose.

getTimezone: function() { return this.rawWeatherData.timezone; },
 getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; },
 getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; },
 getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; },
 getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); },
 getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; },
 getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; },
 getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; },
 getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } },
 getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; },
 getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); },
 getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); },

طرق الغراء عالية المستوى

مع وجود طرق المنفعة والاقتناء والمعالجة بعيدًا عن طريقنا ، نترك الآن مهمة تنسيق كل شيء. نقوم بذلك عن طريق إنشاء طرق لصق عالية المستوى ، والتي تستدعي بشكل أساسي الطرق المكتوبة أعلاه في تسلسل معين ، بحيث يتم تنفيذ العملية بأكملها بسلاسة.

 // Top level for info section // Data in this.currentWeather organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); },
 // Top level for highlights organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); },
 // Top level organization and rendering organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); },

المركبة

يوفر Vue أدوات ربط لدورة حياة المثيل - الخصائص التي هي في الأساس طرق ، ويتم تشغيلها عندما تصل دورة حياة المثيل إلى تلك المرحلة. على سبيل المثال ، تم إنشاؤه وتثبيته قبل التحديث وما إلى ذلك ، كلها أدوات ربط مفيدة جدًا لدورة الحياة تتيح للمبرمج التحكم في المثيل بمستوى أكثر دقة مما كان يمكن أن يكون ممكنًا بخلاف ذلك.

في كود مكون Vue ، يتم تنفيذ خطافات دورة الحياة هذه تمامًا كما تفعل مع أي prop أخرى. علي سبيل المثال:

 <template> </template> <script> // import statements export default { data() { return { // data objects here } }, methods: { // methods here }, mounted: function(){ // function body here }, } </script> <style> </style>

مسلحًا بهذا الفهم الجديد ، ألق نظرة على الكود أدناه للدعامة mounted لـ App.vue :

 mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); }

أكمل كود التطبيق

لقد غطينا الكثير من الأرضية في هذا القسم ، وقد قدمت لك الأقسام القليلة الأخيرة أشياءً في أجزاء وأجزاء. ومع ذلك ، من المهم أن يكون لديك الكود الكامل والمجمع لـ App.vue (يخضع لمزيد من التعديلات في الأقسام اللاحقة). من هنا تبدأ:

 <template> <div> <div class="container-fluid"> <div class="row"> <div class="col-md-3 col-sm-4 col-xs-12 sidebar"> <div> <input type="text" ref="input" placeholder="Location?" @keyup.enter="organizeAllDetails" > <button @click="organizeAllDetails"> <img src="./assets/Search.svg" width="24" height="24"> </button> </div> <div> <div class="wrapper-left"> <div> {{ currentWeather.temp }} <span>°C</span> </div> <div>{{ currentWeather.summary }}</div> <div class="temp-max-min"> <div class="max-desc"> <div> <i>▲</i> {{ currentWeather.todayHighLow.todayTempHigh }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempHighTime }}</div> </div> <div class="min-desc"> <div> <i>▼</i> {{ currentWeather.todayHighLow.todayTempLow }} <span>°C</span> </div> <div>at {{ currentWeather.todayHighLow.todayTempLowTime }}</div> </div> </div> </div> <div class="wrapper-right"> <div class="date-time-info"> <div> <img src="./assets/calendar.svg" width="20" height="20"> {{ currentWeather.time }} </div> </div> <div class="location-info"> <div> <img src="./assets/location.svg" width="10.83" height="15.83" > {{ currentWeather.full_location }} <div class="mt-1"> Lat: {{ currentWeather.formatted_lat }} <br> Long: {{ currentWeather.formatted_long }} </div> </div> </div> </div> </div> </div> <dashboard-content class="col-md-9 col-sm-8 col-xs-12 content" :highlights="highlights" :tempVar="tempVar" ></dashboard-content> </div> </div> </div> </template> <script> import Content from './components/Content.vue'; export default { name: 'app', props: [], components: { 'dashboard-content': Content }, data() { return { weatherDetails: false, location: '', // raw location from input lat: '', // raw latitude from google maps api response long: '', // raw longitude from google maps api response completeWeatherApi: '', // weather api string with lat and long rawWeatherData: '', // raw response from weather api currentWeather: { full_location: '', // for full address formatted_lat: '', // for N/S formatted_long: '', // for E/W time: '', temp: '', todayHighLow: { todayTempHigh: '', todayTempHighTime: '', todayTempLow: '', todayTempLowTime: '' }, summary: '', possibility: '' }, tempVar: { tempToday: [ // gets added dynamically by this.getSetHourlyTempInfoToday() ], }, highlights: { uvIndex: '', visibility: '', windStatus: { windSpeed: '', windDirection: '', derivedWindDirection: '' }, } }; }, methods: { // Some utility functions convertToTitleCase: function(str) { str = str.toLowerCase().split(' '); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, formatPossibility: function(str) { str = str.toLowerCase().split('-'); for (var i = 0; i < str.length; i++) { str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1); } return str.join(' '); }, unixToHuman: function(timezone, timestamp) { /* READ THIS BEFORE JUDGING & DEBUGGING For any location beyond the arctic circle and the antarctic circle, the goddamn weather api does not return certain keys/values in each of this.rawWeatherData.daily.data[some_array_index]. Due to this, console throws up an error. The code is correct, the problem is with the API. May be later on I will add some padding to tackle missing values. */ var moment = require('moment-timezone'); // for handling date & time var decipher = new Date(timestamp * 1000); var human = moment(decipher) .tz(timezone) .format('llll'); var timeArray = human.split(' '); var timeNumeral = timeArray[4]; var timeSuffix = timeArray[5]; var justTime = timeNumeral + ' ' + timeSuffix; var monthDateArray = human.split(','); var monthDate = monthDateArray[1].trim(); return { fullTime: human, onlyTime: justTime, onlyMonthDate: monthDate }; }, fahToCel: function(tempInFahrenheit) { var tempInCelcius = Math.round((5 / 9) * (tempInFahrenheit — 32)); return tempInCelcius; }, milibarToKiloPascal: function(pressureInMilibar) { var pressureInKPA = pressureInMilibar * 0.1; return Math.round(pressureInKPA); }, mileToKilometer: function(miles) { var kilometer = miles * 1.60934; return Math.round(kilometer); }, deriveWindDir: function(windDir) { var wind_directions_array = [ { minVal: 0, maxVal: 30, direction: 'N' }, { minVal: 31, maxVal: 45, direction: 'NNE' }, { minVal: 46, maxVal: 75, direction: 'NE' }, { minVal: 76, maxVal: 90, direction: 'ENE' }, { minVal: 91, maxVal: 120, direction: 'E' }, { minVal: 121, maxVal: 135, direction: 'ESE' }, { minVal: 136, maxVal: 165, direction: 'SE' }, { minVal: 166, maxVal: 180, direction: 'SSE' }, { minVal: 181, maxVal: 210, direction: 'S' }, { minVal: 211, maxVal: 225, direction: 'SSW' }, { minVal: 226, maxVal: 255, direction: 'SW' }, { minVal: 256, maxVal: 270, direction: 'WSW' }, { minVal: 271, maxVal: 300, direction: 'W' }, { minVal: 301, maxVal: 315, direction: 'WNW' }, { minVal: 316, maxVal: 345, direction: 'NW' }, { minVal: 346, maxVal: 360, direction: 'NNW' } ]; var wind_direction = ''; for (var i = 0; i < wind_directions_array.length; i++) { if ( windDir >= wind_directions_array[i].minVal && windDir <= wind_directions_array[i].maxVal ) { wind_direction = wind_directions_array[i].direction; } } return wind_direction; }, // Some basic action oriented functions makeInputEmpty: function() { this.$refs.input.value = ''; }, makeTempVarTodayEmpty: function() { this.tempVar.tempToday = []; }, detectEnterKeyPress: function() { var input = this.$refs.input; input.addEventListener('keyup', function(event) { event.preventDefault(); var enterKeyCode = 13; if (event.keyCode === enterKeyCode) { this.setHitEnterKeyTrue(); } }); }, locationEntered: function() { var input = this.$refs.input; if (input.value === '') { this.location = "New York"; } else { this.location = this.convertToTitleCase(input.value); } this.makeInputEmpty(); this.makeTempVarTodayEmpty(); }, getCoordinates: function() { this.locationEntered(); var loc = this.location; var coords; var geocoder = new google.maps.Geocoder(); return new Promise(function(resolve, reject) { geocoder.geocode({ address: loc }, function(results, status) { if (status == google.maps.GeocoderStatus.OK) { this.lat = results[0].geometry.location.lat(); this.long = results[0].geometry.location.lng(); this.full_location = results[0].formatted_address; coords = { lat: this.lat, long: this.long, full_location: this.full_location }; resolve(coords); } else { alert("Oops! Couldn't get data for the location"); } }); }); }, // Some basic asynchronous functions setFormatCoordinates: async function() { var coordinates = await this.getCoordinates(); this.lat = coordinates.lat; this.long = coordinates.long; this.currentWeather.full_location = coordinates.full_location; // Remember to beautify lat for N/S if (coordinates.lat > 0) { this.currentWeather.formatted_lat = (Math.round(coordinates.lat * 10000) / 10000).toString() + '°N'; } else if (coordinates.lat < 0) { this.currentWeather.formatted_lat = (-1 * (Math.round(coordinates.lat * 10000) / 10000)).toString() + '°S'; } else { this.currentWeather.formatted_lat = ( Math.round(coordinates.lat * 10000) / 10000 ).toString(); } // Remember to beautify long for N/S if (coordinates.long > 0) { this.currentWeather.formatted_long = (Math.round(coordinates.long * 10000) / 10000).toString() + '°E'; } else if (coordinates.long < 0) { this.currentWeather.formatted_long = (-1 * (Math.round(coordinates.long * 10000) / 10000)).toString() + '°W'; } else { this.currentWeather.formatted_long = ( Math.round(coordinates.long * 10000) / 10000 ).toString(); } }, fixWeatherApi: async function() { await this.setFormatCoordinates(); var weatherApi = 'https://csm.fusioncharts.com/files/assets/wb/wb-data.php?src=darksky&lat=' + this.lat + '&long=' + this.long; this.completeWeatherApi = weatherApi; }, fetchWeatherData: async function() { await this.fixWeatherApi(); var axios = require('axios'); // for handling weather api promise var weatherApiResponse = await axios.get(this.completeWeatherApi); if (weatherApiResponse.status === 200) { this.rawWeatherData = weatherApiResponse.data; } else { alert('Hmm... Seems like our weather experts are busy!'); } }, // Get and set functions; often combined, because they are short // For basic info — left panel/sidebar getTimezone: function() { return this.rawWeatherData.timezone; }, getSetCurrentTime: function() { var currentTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); this.currentWeather.time = this.unixToHuman( timezone, currentTime ).fullTime; }, getSetSummary: function() { var currentSummary = this.convertToTitleCase( this.rawWeatherData.currently.summary ); if (currentSummary.includes(' And')) { currentSummary = currentSummary.replace(' And', ','); } this.currentWeather.summary = currentSummary; }, getSetPossibility: function() { var possible = this.formatPossibility(this.rawWeatherData.daily.icon); if (possible.includes(' And')) { possible = possible.replace(' And', ','); } this.currentWeather.possibility = possible; }, getSetCurrentTemp: function() { var currentTemp = this.rawWeatherData.currently.temperature; this.currentWeather.temp = this.fahToCel(currentTemp); }, getTodayDetails: function() { return this.rawWeatherData.daily.data[0]; }, getSetTodayTempHighLowWithTime: function() { var timezone = this.getTimezone(); var todayDetails = this.getTodayDetails(); this.currentWeather.todayHighLow.todayTempHigh = this.fahToCel( todayDetails.temperatureMax ); this.currentWeather.todayHighLow.todayTempHighTime = this.unixToHuman( timezone, todayDetails.temperatureMaxTime ).onlyTime; this.currentWeather.todayHighLow.todayTempLow = this.fahToCel( todayDetails.temperatureMin ); this.currentWeather.todayHighLow.todayTempLowTime = this.unixToHuman( timezone, todayDetails.temperatureMinTime ).onlyTime; }, getHourlyInfoToday: function() { return this.rawWeatherData.hourly.data; }, getSetHourlyTempInfoToday: function() { var unixTime = this.rawWeatherData.currently.time; var timezone = this.getTimezone(); var todayMonthDate = this.unixToHuman(timezone, unixTime).onlyMonthDate; var hourlyData = this.getHourlyInfoToday(); for (var i = 0; i < hourlyData.length; i++) { var hourlyTimeAllTypes = this.unixToHuman(timezone, hourlyData[i].time); var hourlyOnlyTime = hourlyTimeAllTypes.onlyTime; var hourlyMonthDate = hourlyTimeAllTypes.onlyMonthDate; if (todayMonthDate === hourlyMonthDate) { var hourlyObject = { hour: '', temp: '' }; hourlyObject.hour = hourlyOnlyTime; hourlyObject.temp = this.fahToCel(hourlyData[i].temperature).toString(); this.tempVar.tempToday.push(hourlyObject); /* Since we are using array.push(), we are just adding elements at the end of the array. Thus, the array is not getting emptied first when a new location is entered. to solve this problem, a method this.makeTempVarTodayEmpty() has been created, and called from this.locationEntered(). */ } } /* To cover the edge case where the local time is between 10 — 12 PM, and therefore there are only two elements in the array this.tempVar.tempToday. We need to add the points for minimum temperature and maximum temperature so that the chart gets generated with atleast four points. */ if (this.tempVar.tempToday.length <= 2) { var minTempObject = { hour: this.currentWeather.todayHighLow.todayTempHighTime, temp: this.currentWeather.todayHighLow.todayTempHigh }; var maxTempObject = { hour: this.currentWeather.todayHighLow.todayTempLowTime, temp: this.currentWeather.todayHighLow.todayTempLow }; /* Typically, lowest temp are at dawn, highest temp is around mid day. Thus we can safely arrange like min, max, temp after 10 PM. */ // array.unshift() adds stuff at the beginning of the array. // the order will be: min, max, 10 PM, 11 PM. this.tempVar.tempToday.unshift(maxTempObject, minTempObject); } }, // For Today Highlights getSetUVIndex: function() { var uvIndex = this.rawWeatherData.currently.uvIndex; this.highlights.uvIndex = uvIndex; }, getSetVisibility: function() { var visibilityInMiles = this.rawWeatherData.currently.visibility; this.highlights.visibility = this.mileToKilometer(visibilityInMiles); }, getSetWindStatus: function() { var windSpeedInMiles = this.rawWeatherData.currently.windSpeed; this.highlights.windStatus.windSpeed = this.mileToKilometer( windSpeedInMiles ); var absoluteWindDir = this.rawWeatherData.currently.windBearing; this.highlights.windStatus.windDirection = absoluteWindDir; this.highlights.windStatus.derivedWindDirection = this.deriveWindDir( absoluteWindDir ); }, // top level for info section organizeCurrentWeatherInfo: function() { // data in this.currentWeather /* Coordinates and location is covered (get & set) in: — this.getCoordinates() — this.setFormatCoordinates() There are lots of async-await involved there. So it's better to keep them there. */ this.getSetCurrentTime(); this.getSetCurrentTemp(); this.getSetTodayTempHighLowWithTime(); this.getSetSummary(); this.getSetPossibility(); }, organizeTodayHighlights: function() { // top level for highlights this.getSetUVIndex(); this.getSetVisibility(); this.getSetWindStatus(); }, // topmost level orchestration organizeAllDetails: async function() { // top level organization await this.fetchWeatherData(); this.organizeCurrentWeatherInfo(); this.organizeTodayHighlights(); this.getSetHourlyTempInfoToday(); }, }, mounted: async function() { this.location = "New York"; await this.organizeAllDetails(); } }; </script>

وأخيرًا ، بعد الكثير من الصبر والعمل الجاد ، يمكنك رؤية تدفق البيانات بقوتها الخام! قم بزيارة التطبيق على المتصفح ، وقم بتحديث الصفحة ، وابحث عن موقع في مربع البحث الخاص بالتطبيق ، واضغط على Enter !

التطبيق كما هو موضح في المتصفح
(معاينة كبيرة)

الآن بعد أن انتهينا من كل الأحمال الثقيلة ، خذ قسطًا من الراحة. تركز الأقسام اللاحقة على استخدام البيانات لإنشاء مخططات جميلة وغنية بالمعلومات ، متبوعة بإعطاء تطبيقنا القبيح المظهر جلسة عناية تستحقها كثيرًا باستخدام CSS.

5. تصور البيانات مع FusionCharts

الاعتبارات الأساسية للرسوم البيانية

بالنسبة للمستخدم النهائي ، فإن جوهر لوحة القيادة هو في الأساس ما يلي: مجموعة من المعلومات المصفاة والمنسقة بعناية حول موضوع معين ، والتي يتم نقلها من خلال الأدوات المرئية / الرسومية للاستيعاب السريع. إنهم لا يهتمون بالتفاصيل الدقيقة لهندسة خطوط أنابيب البيانات الخاصة بك ، أو مدى جمالية الكود الخاص بك - كل ما يريدونه هو عرض عالي المستوى في 3 ثوانٍ. لذلك ، فإن تطبيقنا الخام الذي يعرض بيانات نصية لا يعني شيئًا لهم ، وقد حان الوقت لأن ننفذ آليات لف البيانات بالمخططات.

ومع ذلك ، قبل أن نتعمق في تنفيذ الرسوم البيانية ، دعونا نفكر في بعض الأسئلة ذات الصلة والإجابات المحتملة من وجهة نظرنا:

  • ما نوع الرسوم البيانية المناسبة لنوع البيانات التي نتعامل معها؟
    حسنًا ، للإجابة جانبان - السياق والغرض. حسب السياق ، فإننا نعني نوع البيانات ، وهي مناسبة بشكل عام لمخطط الأشياء الأكبر ، التي يحدها نطاق المشروع وجمهوره. وبالقصد ، فإننا نعني أساسًا "ما نريد التأكيد عليه؟". على سبيل المثال ، يمكننا تمثيل درجة حرارة اليوم في أوقات مختلفة من اليوم باستخدام مخطط عمودي (أعمدة رأسية متساوية العرض ، بارتفاع يتناسب مع القيمة التي يمثلها العمود). ومع ذلك ، نادرًا ما نهتم بالقيم الفردية ، بل بالتباين العام والاتجاه في جميع أنحاء البيانات. لتناسب الغرض ، من مصلحتنا استخدام مخطط خطي ، وسنفعل ذلك قريبًا.
  • ما الذي يجب مراعاته قبل اختيار مكتبة الرسوم البيانية؟
    نظرًا لأننا نقوم بمشروع في الغالب باستخدام تقنيات تستند إلى JavaScript ، فمن غير المنطقي أن تكون أي مكتبة رسوم بيانية نختارها لمشروعنا من مواطني عالم JavaScript. مع وضع هذه الفرضية الأساسية في الاعتبار ، يجب أن نأخذ في الاعتبار ما يلي قبل التركيز على أي مكتبة معينة:
    • دعم الأطر التي نختارها ، والتي في هذه الحالة ، هي Vue.js. يمكن تطوير مشروع في أطر عمل JavaScript أخرى شائعة مثل React أو Angular - تحقق من دعم مكتبة الرسوم البيانية لإطار العمل المفضل لديك. أيضًا ، يجب مراعاة دعم لغات البرمجة الشائعة الأخرى مثل Python و Java و C ++ و .Net (AS و VB) ، خاصةً عندما يتضمن المشروع بعض العناصر الخلفية الجادة.
    • توافر أنواع الرسوم البيانية والميزات ، حيث يكاد يكون من المستحيل معرفة الشكل النهائي والغرض من البيانات في المشروع (خاصة إذا كانت المتطلبات ينظمها عملاؤك في بيئة مهنية). في هذه الحالة ، يجب أن تعرض شبكتك على نطاق واسع ، وأن تختار مكتبة مخططات تحتوي على أكبر مجموعة من المخططات. الأهم من ذلك ، لتمييز مشروعك عن الآخرين ، يجب أن تحتوي المكتبة على ميزات كافية في شكل سمات مخطط قابلة للتكوين ، بحيث يمكنك ضبط وتخصيص معظم جوانب المخططات والمستوى الصحيح من التفاصيل. أيضًا ، يجب أن تكون تكوينات الرسم البياني الافتراضية معقولة ، ويجب أن تكون وثائق المكتبة من الدرجة الأولى ، لأسباب واضحة للمطورين المحترفين.
    • يجب أيضًا مراعاة منحنى التعلم ودعم المجتمع والتوازن ، خاصةً عندما تكون جديدًا في تصور البيانات. في أحد طرفي الطيف ، لديك أدوات خاصة مثل Tableau و Qlickview التي تكلف قنبلة ، ولها منحنى تعليمي سلس ، ولكنها تأتي أيضًا مع العديد من القيود من حيث التخصيص والتكامل والنشر. من ناحية أخرى ، هناك d3.js - واسع ، مجاني (مفتوح المصدر) ، وقابل للتخصيص حتى جوهره ، ولكن عليك أن تدفع ثمن منحنى تعليمي شديد الانحدار لتتمكن من فعل أي شيء مثمر مع المكتبة.

ما تحتاجه هو المكان المناسب - التوازن الصحيح بين الإنتاجية والتغطية والتخصيص ومنحنى التعلم والتكلفة خارج المسار. نحثك على إلقاء نظرة على FusionCharts - مكتبة رسوم JavaScript الأكثر شمولاً وجاهزة للمؤسسات في العالم للويب والجوال ، والتي سنستخدمها في هذا المشروع لإنشاء الرسوم البيانية.

مقدمة إلى FusionCharts

يتم استخدام FusionCharts في جميع أنحاء العالم كمكتبة go-to JavaScript للرسوم البيانية بواسطة ملايين المطورين المنتشرين في مئات البلدان حول العالم. من الناحية الفنية ، يتم تحميلها وقابليتها للتكوين قدر الإمكان ، مع دعم دمجها مع أي مكدس تقني شائع تقريبًا يستخدم للمشاريع القائمة على الويب. يتطلب استخدام FusionCharts تجاريًا ترخيصًا ، وعليك أن تدفع مقابل الترخيص اعتمادًا على حالة الاستخدام الخاصة بك (يرجى الاتصال بالمبيعات إذا كنت مهتمًا). ومع ذلك ، فإننا نستخدم FusionCharts في هذه المشاريع فقط لتجربة بعض الأشياء ، وبالتالي الإصدار غير المرخص (يأتي مع علامة مائية صغيرة في مخططاتك ، وبعض القيود الأخرى). يعد استخدام الإصدار غير المرخص جيدًا تمامًا عند تجربة المخططات واستخدامها في مشاريعك غير التجارية أو الشخصية. إذا كانت لديك خطط لنشر التطبيق تجاريًا ، فيرجى التأكد من حصولك على ترخيص من FusionCharts.

نظرًا لأن هذا مشروع يتضمن Vue.js ، فسنحتاج إلى وحدتين يجب تثبيتهما ، إذا لم يتم القيام بهما في وقت سابق:

  • وحدة fusioncharts ، لأنها تحتوي على كل ما تحتاجه لإنشاء الرسوم البيانية
  • الوحدة النمطية vue-fusioncharts ، والتي تعد في الأساس غلافًا للمخططات الانصهار ، بحيث يمكن استخدامها في مشروع Vue.js

إذا لم تكن قد قمت بتثبيتها مسبقًا (كما هو موضح في القسم الثالث) ، فقم بتثبيتها عن طريق تنفيذ الأمر التالي من الدليل الجذر للمشروع:

 npm install fusioncharts vue-fusioncharts --save

بعد ذلك ، تأكد من أن ملف src/main.js الخاص بالمشروع يحتوي على الكود التالي (مذكور أيضًا في القسم 3):

 import Vue from 'vue'; import App from './App.vue'; import FusionCharts from 'fusioncharts'; import Charts from 'fusioncharts/fusioncharts.charts'; import Widgets from 'fusioncharts/fusioncharts.widgets'; import PowerCharts from 'fusioncharts/fusioncharts.powercharts'; import FusionTheme from 'fusioncharts/themes/fusioncharts.theme.fusion'; import VueFusionCharts from 'vue-fusioncharts'; Charts(FusionCharts); PowerCharts(FusionCharts); Widgets(FusionCharts); FusionTheme(FusionCharts); Vue.use(VueFusionCharts, FusionCharts); new Vue({ el: '#app', render: h => h(App) })

ربما يكون السطر الأكثر أهمية في المقتطف أعلاه هو التالي:

 Vue.use(VueFusionCharts, FusionCharts)

يوجه Vue لاستخدام الوحدة النمطية vue-fusioncharts لفهم العديد من الأشياء في المشروع التي يبدو أنها لم يتم تحديدها صراحة من قبلنا ، ولكن تم تحديدها في الوحدة نفسها. أيضًا ، يتضمن هذا النوع من العبارات إعلانًا عالميًا ، والذي نعني به أنه في أي مكان يواجه Vue أي شيء غريب في كود مشروعنا (الأشياء التي لم نقم بتعريفها صراحة حول استخدام FusionCharts) ، سيظهر مرة واحدة على الأقل في مخططات vue-fusioncharts ووحدات عقدة المخططات الانصهار لتعريفها ، قبل إلقاء الأخطاء. إذا كنا قد استخدمنا FusionCharts في جزء منعزل من مشروعنا (لا نستخدمه في جميع ملفات المكونات تقريبًا) ، فربما يكون الإعلان المحلي أكثر منطقية.

مع ذلك ، تكون جاهزًا لاستخدام FusionCharts في المشروع. سنستخدم عددًا غير قليل من المخططات المتنوعة ، ويعتمد الاختيار على جانب بيانات الطقس التي نريد تصورها. أيضًا ، سنرى التفاعل بين ربط البيانات والمكونات المخصصة والمراقبين أثناء العمل.

مخطط عام لاستخدام Fusioncharts في ملفات .vue

في هذا القسم ، سنشرح الفكرة العامة لاستخدام FusionCharts لإنشاء مخططات مختلفة في ملفات .vue . لكن أولاً ، دعنا نرى الرمز الكاذب الذي يوضح بشكل تخطيطي الفكرة الأساسية.

 <template> <div> <fusioncharts :attribute_1="data_object_1" :attribute_2="data_object_2" … … ... > </fusioncharts> </div> </template> <script> export default { props: ["data_prop_received_by_the_component"], components: {}, data() { return { data_object_1: "value_1", data_object_2: "value_2", … … }; }, methods: {}, computed: {}, watch: { data_prop_received_by_the_component: { handler: function() { // some code/logic, mainly data manipulation based }, deep: true } } }; </script> <style> // component specific special CSS code here </style>

دعونا نفهم الأجزاء المختلفة من الكود الكاذب أعلاه:

  • في <template> ، ضمن المستوى الأعلى <div> (هذا إلزامي إلى حد كبير لقالب كود HTML لكل مكون) ، لدينا المكون المخصص <fusioncharts> . لدينا تعريف المكون الوارد في وحدة العقدة vue-fusioncharts التي قمنا بتثبيتها لهذا المشروع. داخليًا ، تعتمد vue-fusioncharts fusioncharts وحدة المخططات الانصهار ، التي تم تثبيتها أيضًا. قمنا باستيراد الوحدات النمطية الضرورية وحلّلنا تبعياتها ، وأصدرنا تعليمات إلى Vue لاستخدام الغلاف عالميًا (طوال المشروع) في ملف src/main.js ، وبالتالي لا يوجد نقص في تعريف مكون <fusioncharts> المخصص الذي استخدمناه هنا. أيضًا ، يحتوي المكون المخصص على سمات مخصصة ، وكل سمة مخصصة مرتبطة بكائن بيانات (وبالتالي قيمها) ، بواسطة التوجيه v-bind ، والذي يكون الاختصار هو رمز النقطتين ( : . سنتعرف على السمات وكائنات البيانات المرتبطة بها بمزيد من التفصيل ، عندما نناقش بعض المخططات المحددة المستخدمة في هذا المشروع.
  • في <script> ، تقوم أولاً بتعريف الخاصيات التي من المفترض أن يتلقاها المكون ، ثم تستمر في تحديد كائنات البيانات <fusioncharts> . القيم المعينة لكائنات البيانات هي القيم التي تسحبها سمات <fusioncharts> ، ويتم إنشاء المخططات على أساس القيم المسحوبة. بصرف النظر عن هؤلاء ، فإن الجزء الأكثر إثارة للاهتمام في الشفرة هو عنصر watch { } . هذا كائن خاص جدًا في مخطط Vue للأشياء - فهو يوجه Vue بشكل أساسي لمراقبة أي تغييرات تحدث لبيانات معينة ، ثم اتخاذ الإجراءات بناءً على كيفية تعريف وظيفة handler لتلك البيانات. على سبيل المثال ، نريد من Vue أن يراقب العنصر المستلم ، أي prop في الكود data_prop_received_by_the_component . تصبح prop مفتاحًا في كائن watch { } ، وقيمة المفتاح هي كائن آخر - طريقة معالج تصف ما يجب القيام به كلما تغيرت prop . مع هذه الآليات الأنيقة للتعامل مع التغييرات ، يحافظ التطبيق على تفاعله. يمثل deep: true علمًا منطقيًا يمكنك ربطه بالمراقبين ، بحيث يتم مراقبة الكائن الذي تتم مشاهدته بعمق إلى حد ما ، أي أنه يتم تعقب التغييرات التي تم إجراؤها في المستويات المتداخلة للكائن.
    ( لمزيد من المعلومات حول المراقبين ، راجع الوثائق الرسمية ).

الآن بعد أن أصبحت مجهزًا بفهم المخطط العام للأشياء ، دعنا نتعمق في التطبيقات المحددة للمخططات في ملفات مكون .vue . سيكون الرمز واضحًا بذاته ، ويجب أن تحاول فهم كيفية ملاءمة التفاصيل في المخطط العام للأشياء الموضحة أعلاه.

تنفيذ الرسوم البيانية في ملفات .vue

بينما تختلف تفاصيل التنفيذ من مخطط إلى آخر ، فإن الشرح التالي ينطبق عليها جميعًا:

  • <template>
    كما أوضحنا سابقًا ، يحتوي المكون المخصص <fusioncharts> على عدة سمات ، كل منها مرتبط بكائن البيانات المقابل المحدد في وظيفة data() باستخدام v-bind : التوجيه. أسماء السمات تشرح نفسها بنفسها تمامًا لما تعنيه ، كما أن اكتشاف كائنات البيانات المقابلة أمر تافه.
  • <script>
    في وظيفة data() ، تكون كائنات البيانات وقيمها هي ما يجعل المخططات تعمل ، بسبب الربط الذي تقوم به توجيهات v-bind ( : المستخدمة في سمات <fusioncharts> . قبل التعمق في كائنات البيانات الفردية ، تجدر الإشارة إلى بعض الخصائص العامة:
    • كائنات البيانات التي تكون قيمها إما 0 أو 1 هي منطقية بطبيعتها ، حيث يمثل 0 شيئًا غير متاح / متوقف ، ويمثل 1 حالة التوفر / قيد التشغيل. ومع ذلك ، كن حذرًا من أن كائنات البيانات غير المنطقية يمكن أن تحتوي أيضًا على 0 أو 1 كقيم لها ، إلى جانب القيم الأخرى المحتملة - فهذا يعتمد على السياق. على سبيل المثال ، سعة containerbackgroundopacity الخلفية بقيمتها الافتراضية هي 0 هي قيمة منطقية ، في حين أن lowerLimit الافتراضية هي 0 يعني ببساطة أن الرقم صفر هو قيمته الحرفية.
    • تتعامل بعض كائنات البيانات مع خصائص CSS مثل الهامش ، والحشو ، وحجم الخط ، وما إلى ذلك - تحتوي القيمة على وحدة ضمنية من "بكسل" أو بكسل. وبالمثل ، يمكن أن تحتوي كائنات البيانات الأخرى على وحدات ضمنية مرتبطة بقيمها. للحصول على معلومات مفصلة ، يرجى الرجوع إلى صفحة سمات الرسم البياني ذات الصلة في FusionCharts Dev Center.
  • في دالة data() ، ربما يكون الكائن الأكثر إثارة للاهتمام وغير الواضح هو مصدر البيانات. يحتوي هذا الكائن على ثلاثة كائنات رئيسية متداخلة بداخله:
    • الرسم البياني : يحتوي هذا الكائن على الكثير من سمات المخطط المتعلقة بتكوين المخطط ومستحضرات التجميل. يكاد يكون بناءًا إلزاميًا ستجده في جميع المخططات التي ستنشئها لهذا المشروع.
    • colorrange : هذا الكائن خاص إلى حد ما بالرسم البياني قيد النظر ، وهو موجود بشكل أساسي في الرسوم البيانية التي تتعامل مع ألوان / ظلال متعددة لتحديد النطاقات الفرعية المختلفة للمقياس المستخدم في الرسم البياني.
    • القيمة: هذا الكائن ، مرة أخرى ، موجود في الرسوم البيانية التي لها قيمة محددة يجب إبرازها في نطاق المقياس.
  • ربما يكون عنصر watch { } هو الشيء الأكثر أهمية الذي يجعل هذا المخطط والمخططات الأخرى المستخدمة في هذا المشروع تنبض بالحياة. يتم التحكم في تفاعل المخططات ، أي تحديث المخططات نفسها بناءً على القيم الجديدة الناتجة عن استعلام مستخدم جديد بواسطة المراقبين المحددين في هذا الكائن. على سبيل المثال ، لقد حددنا مراقبًا highlights العناصر التي يتلقاها المكون ، ثم حددنا وظيفة معالج لإرشاد Vue حول الإجراءات الضرورية التي يجب أن يتخذها ، عندما يتغير أي شيء حول الكائن الذي تتم مشاهدته في المشروع بأكمله. هذا يعني أنه عندما ينتج عن App.vue قيمة جديدة لأي كائن داخل highlights ، تتدفق المعلومات لأسفل وصولاً إلى هذا المكون ، ويتم تحديث القيمة الجديدة في كائنات البيانات لهذا المكون. يتم أيضًا تحديث المخطط المرتبط بالقيم نتيجة لهذه الآلية.

التفسيرات المذكورة أعلاه هي حدود واسعة جدًا لمساعدتنا على تطوير فهم حدسي للصورة الأكبر. بمجرد أن تفهم المفاهيم بشكل حدسي ، يمكنك دائمًا الرجوع إلى وثائق Vue.js و FusionCharts ، عندما لا يكون هناك شيء واضح لك من الكود نفسه. نترك التمرين لك ، ومن القسم الفرعي التالي فصاعدًا ، لن نشرح الأشياء التي غطيناها في هذا القسم الفرعي.

src / المكونات / TempVarChart.vue

رسم تخطيطي يوضح درجة الحرارة كل ساعة
(معاينة كبيرة)
 <template> <div class="custom-card header-card card"> <div class="card-body pt-0"> <fusioncharts type="spline" width="100%" height="100%" dataformat="json" dataEmptyMessage="i-https://i.postimg.cc/R0QCk9vV/Rolling-0-9s-99px.gif" dataEmptyMessageImageScale=39 :datasource="tempChartData" > </fusioncharts> </div> </div> </template> <script> export default { props: ["tempVar"], components: {}, data() { return { tempChartData: { chart: { caption: "Hourly Temperature", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", baseFont: "Roboto", chartTopMargin: "30", showHoverEffect: "1", theme: "fusion", showaxislines: "1", numberSuffix: "°C", anchorBgColor: "#6297d9", paletteColors: "#6297d9", drawCrossLine: "1", plotToolText: "$label<br><hr><b>$dataValue</b>", showAxisLines: "0", showYAxisValues: "0", anchorRadius: "4", divLineAlpha: "0", labelFontSize: "13", labelAlpha: "65", labelFontBold: "0", rotateLabels: "1", slantLabels: "1", canvasPadding: "20" }, data: [], }, }; }, methods: { setChartData: function() { var data = []; for (var i = 0; i < this.tempVar.tempToday.length; i++) { var dataObject = { label: this.tempVar.tempToday[i].hour, value: this.tempVar.tempToday[i].temp }; data.push(dataObject); } this.tempChartData.data = data; }, }, mounted: function() { this.setChartData(); }, watch: { tempVar: { handler: function() { this.setChartData(); }, deep: true }, }, }; </script> <style> </style>

src / المكونات / UVIndex.vue

يحتوي هذا المكون على رسم بياني مفيد للغاية - المقياس الزاوي.

مؤشر الأشعة فوق البنفسجية
(معاينة كبيرة)

يتم إعطاء رمز المكون أدناه. للحصول على معلومات تفصيلية حول سمات الرسم البياني للمقياس الزاوي ، ارجع إلى صفحة مركز تطوير FusionCharts للمقياس الزاوي.

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" ></fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return { type: "angulargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", datasource: { chart: { caption: "UV Index", captionFontBold: "0", captionFontColor: "#000000", captionPadding: "30", lowerLimit: "0", upperLimit: "15", lowerLimitDisplay: "1", upperLimitDisplay: "1", showValue: "0", theme: "fusion", baseFont: "Roboto", bgAlpha: "0", canvasbgAlpha: "0", gaugeInnerRadius: "75", gaugeOuterRadius: "110", pivotRadius: "0", pivotFillAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", tickValueDistance: "3", autoAlignTickValues: "1", majorTMAlpha: "20", chartTopMargin: "30", chartBottomMargin: "40" }, colorrange: { color: [ { minvalue: "0", maxvalue: this.highlights.uvIndex.toString(), code: "#7DA9E0" }, { minvalue: this.highlights.uvIndex.toString(), maxvalue: "15", code: "#D8EDFF" } ] }, annotations: { groups: [ { items: [ { id: "val-label", type: "text", text: this.highlights.uvIndex.toString(), fontSize: "20", font: "Source Sans Pro", fontBold: "1", fillcolor: "#212529", x: "$gaugeCenterX", y: "$gaugeCenterY" } ] } ] }, dials: { dial: [ { value: this.highlights.uvIndex.toString(), baseWidth: "0", radius: "0", borderThickness: "0", baseRadius: "0" } ] } } }; }, methods: {}, computed: {}, watch: { highlights: { handler: function() { this.datasource.colorrange.color[0].maxvalue = this.highlights.uvIndex.toString(); this.datasource.colorrange.color[1].minvalue = this.highlights.uvIndex.toString(); this.datasource.annotations.groups[0].items[0].text = this.highlights.uvIndex.toString(); }, deep: true } } }; </script>

src / المكونات / Visibility.vue

في هذا المكون ، نستخدم مقياس خطي أفقي لتمثيل الرؤية ، كما هو موضح في الصورة أدناه:

لقطة شاشة للمقياس الخطي الأفقي الذي يمثل رؤية الهواء (16 كم)
(معاينة كبيرة)

يتم إعطاء رمز المكون أدناه. للحصول على فهم متعمق للسمات المختلفة لهذا النوع من المخططات ، يرجى الرجوع إلى صفحة FusionCharts Dev Center للحصول على مقياس خطي أفقي.

 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-left border-right border-top"> <div> <fusioncharts :type="type" :width="width" :height="height" :containerbackgroundopacity="containerbackgroundopacity" :dataformat="dataformat" :datasource="datasource" > </fusioncharts> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, methods: {}, computed: {}, data() { return { type: "hlineargauge", width: "100%", height: "100%", containerbackgroundopacity: 0, dataformat: "json", creditLabel: false, datasource: { chart: { caption: "Air Visibility", captionFontBold: "0", captionFontColor: "#000000", baseFont: "Roboto", numberSuffix: " km", lowerLimit: "0", upperLimit: "40", showPointerShadow: "1", animation: "1", transposeAnimation: "1", theme: "fusion", bgAlpha: "0", canvasBgAlpha: "0", valueFontSize: "20", valueFontColor: "#000000", valueFontBold: "1", pointerBorderAlpha: "0", chartBottomMargin: "40", captionPadding: "30", chartTopMargin: "30" }, colorRange: { color: [ { minValue: "0", maxValue: "4", label: "Fog", code: "#6297d9" }, { minValue: "4", maxValue: "10", label: "Haze", code: "#7DA9E0" }, { minValue: "10", maxValue: "40", label: "Clear", code: "#D8EDFF" } ] }, pointers: { pointer: [ { value: this.highlights.visibility.toString() } ] } } }; }, watch: { highlights: { handler: function() { this.datasource.pointers.pointer[0].value = this.highlights.visibility.toString(); }, deep: true } } }; </script>

src / المكونات / WindStatus.vue

يعرض هذا المكون سرعة الرياح واتجاهها (سرعة الرياح ، إذا كنت خبيرًا في الفيزياء) ، ومن الصعب جدًا تمثيل متجه باستخدام مخطط. في مثل هذه الحالات ، نقترح تمثيلها بمساعدة بعض الصور الرائعة والقيم النصية. نظرًا لأن التمثيل الذي فكرنا فيه يعتمد كليًا على CSS ، فسنقوم بتنفيذه في القسم التالي الذي يتعامل مع CSS. ومع ذلك ، ألق نظرة على ما نهدف إلى إنشائه:

حالة الرياح: اتجاه الرياح (يسار) وسرعة الرياح (يمين)
(معاينة كبيرة)
 <template> <div class="highlights-item col-md-4 col-sm-6 col-xs-12 border-top"> <div> <div class="card-heading pt-5">Wind Status</div> <div class="row pt-4 mt-4"> <div class="col-sm-6 col-md-6 mt-2 text-center align-middle"> <p class="card-sub-heading mt-3">Wind Direction</p> <p class="mt-4"><img src="../assets/winddirection.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.derivedWindDirection }}</p> </div> <div class="col-sm-6 col-md-6 mt-2"> <p class="card-sub-heading mt-3">Wind Speed</p> <p class="mt-4"><img src="../assets/windspeed.svg" height="40" width="40"></p> <p class="card-value mt-4">{{ highlights.windStatus.windSpeed }} km/h</p> </div> </div> </div> </div> </template> <script> export default { props: ["highlights"], components: {}, data() { return {}; }, methods: {}, computed: {} }; </script>

اختتام Highlights.vue

تذكر أننا قمنا بالفعل بتنفيذ التعليمات البرمجية باستخدام CSS لجميع المكونات - باستثناء Content.vue و Highlights.vue . نظرًا لأن Content.vue هو مكون غبي يقوم فقط بترحيل البيانات ، فقد تمت تغطية الحد الأدنى من التصميم الذي يحتاجه بالفعل. أيضًا ، قمنا بالفعل بكتابة الكود المناسب لتصميم الشريط الجانبي والبطاقات التي تحتوي على الرسوم البيانية. لذلك ، كل ما يتبقى لنا هو إضافة بعض البتات الأسلوبية إلى Highlights.vue ، والتي تتضمن بشكل أساسي استخدام فئات CSS:

 <template> <div class="custom-content-card content-card card"> <div class="card-body pb-0"> <div class="content-header h4 text-center pt-2 pb-3">Highlights</div> <div class="row"> <uv-index :highlights="highlights"></uv-index> <visibility :highlights="highlights"></visibility> <wind-status :highlights="highlights"></wind-status> </div> </div> </div> </template> <script> import UVIndex from "./UVIndex.vue"; import Visibility from "./Visibility.vue"; import WindStatus from "./WindStatus.vue"; export default { props: ["highlights"], components: { "uv-index": UVIndex, "visibility": Visibility, "wind-status": WindStatus, }, }; </script>

النشر ورمز المصدر

مع الرسوم البيانية والأسلوب بالترتيب ، لقد انتهينا! توقف لحظة لتقدير جمال إبداعك.

النتائج
(معاينة كبيرة)

حان الوقت الآن لنشر تطبيقك ومشاركته مع زملائك. إذا لم يكن لديك الكثير من الأفكار حول النشر وتتوقع منا مساعدتك ، فابحث هنا عن أفكارنا حول النشر. تحتوي المقالة المرتبطة أيضًا على اقتراحات حول كيفية إزالة العلامة المائية FusionCharts في الجزء السفلي الأيسر من كل مخطط.

إذا أخطأت في مكان ما وتريد نقطة مرجعية ، فإن الكود المصدري متاح على Github.