Skip to content

18. کلاینت Vue.js سرور محاسبه مالیات

18.1. Architecture

ما قصد داریم یک برنامهٔ کلاینت/سرور با معماری زیر را پیاده‌سازی کنیم:

Image

سرور محاسبه مالیات نسخه ۱۴ خواهد بود، همانطور که در سند |https://tahe.developpez.com/tutoriels-cours/php7| توسعه یافته است.

18.2. نماهای برنامه

ویوهای برنامه [vuejs-10] همان ویوهای نسخه ۱۳ سند |https://tahe.developpez.com/tutoriels-cours/php7| سرور محاسبه مالیات هستند که در حالت HTML استفاده می‌شوند. با این حال، در این برنامه، این نماها توسط کلاینت جاوااسکریپت تولید می‌شوند، نه توسط سرور PHP.

نمای اول، نمای احراز هویت است:

Image

نمای دوم نمای محاسبه مالیات است:

Image

نما سوم فهرست شبیه‌سازی‌های انجام‌شده توسط کاربر را نمایش می‌دهد:

Image

صفحه بالا نشان می‌دهد که شبیه‌سازی شماره ۱ قابل حذف است. این منجر به نمای زیر می‌شود:

Image

اگر اکنون آخرین شبیه‌سازی را حذف کنیم، نمای جدید زیر به ما نمایش داده می‌شود:

Image

18.3. عناصر پروژه [vuejs-20]

ساختار پروژه برای [vuejs-20] به شرح زیر است:

Image

اجزای پروژه به شرح زیر است:

  • [assets/logo.jpg]: لوگوی پروژه؛
  • [couches]: لایه‌های [métier] و [dao] برنامه؛
  • [plugins]: افزونه‌های برنامه؛
  • [views]: نماهای برنامه؛
  • [config.js]: پیکربندی برنامه را انجام می‌دهد؛
  • [router.js]: مسیریابی برنامه را تعریف می‌کند؛
  • [store.js]: مخزن برای [Vuex];
  • [main.js]: اسکریپت اصلی برنامه؛

18.3.1. لایه‌های [métier] و [dao]

18.3.1.1. لایه [dao]

لایه [dao] توسط کلاس [Dao] در بخش |vuejs-10| پیاده‌سازی شده است

18.3.1.2. لایه [métier]

لایه [métier] توسط کلاس [Métier] در سند |https://tahe.developpez.com/tutoriels-cours/php7| پیاده‌سازی شده است. متد زیر، [setTaxAdminData]، به آن اضافه شده است:


// سازنده
  constructor(taxAdmindata) {
    // this.taxAdminData: داده‌های مرجع مالیاتی
    this.taxAdminData = taxAdmindata;
  }

  // تنظیم‌کننده
  setTaxAdminData(taxAdmindata) {
    // this.taxAdminData: داده‌های سازمان مالیاتی
    this.taxAdminData = taxAdmindata;
}

متد [setTaxAdminData] همان کاری را انجام می‌دهد که سازنده انجام می‌دهد. وجود آن این توالی را ممکن می‌سازد:

  1. وقتی می‌خواهید کلاس را نمونه‌سازی کنید اما هنوز داده‌های [taxAdminData] را ندارید، از عبارت [métier=new Métier()] برای نمونه‌سازی کلاس [Métier] استفاده کنید؛
  2. سپس در مرحله‌ای بعدی با استفاده از عملیاتی [métier.setTaxAdminData(taxAdmindata)]، ویژگی آن را [taxAdminData] مقداردهی کنید؛

18.3.2. فایل پیکربندی [config]

فایل [config.js] به شرح زیر است:


//استفاده از کتابخانه [axios]
const axios = require('axios');
// زمان‌دهی درخواست HTTP
axios.defaults.timeout = 2000;
//پایگاه داده سرور محاسبه مالیات URL
// اسکما [https] در فایرفاکس مشکل ایجاد می‌کند زیرا سرور محاسبه مالیات
// یک گواهی خودامضا ارسال می‌کند. با کروم و اج به خوبی کار می‌کند. روی سافاری آزمایش نشده است.
axios.defaults.baseURL = 'https://localhost/php7/scripts-web/impots/version-14';
// ما از کوکی‌ها استفاده خواهیم کرد
axios.defaults.withCredentials = true;

// صادرات پیکربندی
export default {
  axios: axios
}

این پیکربندی برای کتابخانه [axios] است که لایه [dao] از آن برای ارسال درخواست‌های HTTP خود استفاده می‌کند. توجه کنید که در خط ۸، سرور بر روی پورت امن [https] عمل می‌کند.

18.3.3. پلاگین‌ها

پلاگین‌های [pluginDao, pluginMétier, pluginConfig] برای ایجاد سه ویژگی جدید برای تابع/کلاس [Vue] طراحی شده‌اند:

  • [$dao]: مقدار آن یک نمونه از کلاس [Dao] خواهد بود؛
  • [$métier]: مقدار آن یک نمونه از کلاس [Métier] خواهد بود؛
  • [$config]: به ابجکت صادرشده توسط فایل پیکربندی [config] مقداردهی خواهد شد؛

[pluginDao]


export default {
  install(Vue, dao) {
    //یک ویژگی [$dao] به کلاس Vue اضافه می‌کند
    Object.defineProperty(Vue.prototype, '$dao', {
      //وقتی به Vue.$dao ارجاع داده می‌شود، ما پارامتر دوم [dao] را بازمی‌گردانیم
      get: () => dao,
    })
  }
}

[pluginMétier]

export default {
  install(Vue, métier) {
    //یک ویژگی [$métier] را به کلاس Vue اضافه می‌کند
    Object.defineProperty(Vue.prototype, '$métier', {
      //وقتی Vue.$métier مرجع داده می‌شود، پارامتر دوم به صورت [métier] رندر می‌شود
      get: () => métier,
    })
  }
}

[pluginConfig]


export default {
  install(Vue, config) {
    //یک ویژگی [$config] را به کلاس View اضافه می‌کند
    Object.defineProperty(Vue.prototype, '$config', {
      //وقتی Vue.$config مرجع داده می‌شود، پارامتر دوم به صورت [config] رندر می‌شود
      get: () => config,
    })
  }
}

18.3.4. ذخیره‌سازی [Vuex]

فروشگاه برای [Vuex] توسط فایل زیر پیاده‌سازی شده است، [store]:


// پلاگین Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// انبار Vuex
const store = new Vuex.Store({
  state: {
    // جدول شبیه‌سازی
    simulations: [],
    // شماره آخرین شبیه‌سازی
    idSimulation: 0
  },
  mutations: {
    // شاخص شماره خط حذف
    deleteSimulation(state, index) {
      // eslint-disable-next-line no-console
      console.log("mutation deleteSimulation");
      // شماره خط در حال حذف است [index]
      state.simulations.splice(index, 1);
      // eslint-disable-next-line no-console
      console.log("store simulations", state.simulations);
    },
    // یک شبیه‌سازی اضافه کنید
    addSimulation(state, simulation) {
      // eslint-disable-next-line no-console
      console.log("mutation addSimulation");
      // شماره شبیه‌سازی
      state.idSimulation++;
      simulation.id = state.idSimulation;
      //شبیه‌سازی به جدول شبیه‌سازی اضافه می‌شود
      state.simulations.push(simulation);
    },
    //پاک کردن وضعیت
    clear(state) {
      state.simulations = [];
      state.idSimulation = 1;
    }
  }
});
// صادرات شیء [store]
export default store;

نظرات

  • خطوط ۲–۴: افزونه [Vuex] در چارچوب [Vue] یکپارچه شده است؛
  • خطوط ۸–۱۳: ما عناصر زیر را در فروشگاه [Vuex] قرار می‌دهیم:
    • [simulations]: فهرست شبیه‌سازی‌های انجام‌شده توسط کاربر؛
    • [idSimulation]: شماره آخرین شبیه‌سازی انجام‌شده توسط کاربر؛

به یاد داشته باشید که فروشگاه بین ویوها مشترک خواهد بود و محتوای آن واکنشی است: وقتی تغییر کند، ویوهایی که از آن استفاده می‌کنند به‌طور خودکار به‌روزرسانی می‌شوند. در برنامهٔ ما، تنها عنصر [simulations] باید واکنشی باشد، نه عنصر [idSimulation]. ما این عنصر را برای سهولت در فروشگاه قرار داده‌ایم؛

  • خطوط 14–40: تغییرات مجاز بر روی شیء [state] از خطوط 8–13. توجه داشته باشید که این موارد همیشه شیء [state] از خطوط 8–13 را به عنوان اولین پارامتر خود دریافت می‌کنند؛
    • خط 16: به‌روزرسانی [deleteSimulation] اجازه می‌دهد شبیه‌سازی با شماره [index] حذف شود؛
    • خط ۲۵: جهش [addSimulation] یک شبیه‌سازی جدید به جدول شبیه‌سازی اضافه می‌کند؛
    • خط ۳۵: تغییر [clear] شیء [state] را از خطوط ۸–۱۳ ریست می‌کند؛

18.3.5. فایل مسیریابی [router]

فایل مسیریابی به شرح زیر است:


// واردات‌ها
import Vue from 'vue'
import VueRouter from 'vue-router'
// نماها
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'

// پلاگین مسیریابی
Vue.use(VueRouter)

//مسیرهای برنامه
const routes = [
  // احراز هویت
  {
    path: '/', name: 'authentification', component: Authentification
  },
  // محاسبه مالیات
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // فهرست شبیه‌سازی‌ها
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  //پایان جلسه
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]

// روتر
const router = new VueRouter({
  // مسیرها
  routes,
  // نحوه نمایش مسیرها در مرورگر
  mode: 'history',
})

// خروجی روتر
export default router

توضیحات

  • خط ۱۶: هنگامی که برنامه اجرا می‌شود، نمای [Authentification] نمایش داده می‌شود زیرا URL ریشه [/] است؛
  • خط ۲۰: نمای [CalculImpot] زمانی نمایش داده می‌شود که نمای URL (که فرزند [/calcul-impot] است) درخواست شود؛
  • خط ۲۴: نما [ListeSimulations] زمانی نمایش داده می‌شود که URL یا [/liste-des-simualtions] درخواست شود؛
  • خط ۲۸: نمای [Authentification] هنگامی که URL یا [/fin-session] درخواست می‌شود، نمایش داده می‌شود؛
  • خطوط ۳۳–۳۸: یک شیء [router] با این مسیرها (خط ۳۵) و حالت [history] (خط ۳۷) برای مدیریت URL ایجاد می‌شود؛
  • خط ۴۱: این روتر صادر می‌شود؛

18.3.6. اسکریپت اصلی [main.js]

اسکریپت [main.js] به شرح زیر است:


// واردات
import Vue from 'vue'

// نمای اصلی
import Main from './views/Main.vue'

// افزونه [bootstrap-vue]
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);

//CSS bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

// روتر
import router from './router'

// [config] افزونه
import config from './config';
import pluginConfig from './plugins/pluginConfig'
Vue.use(pluginConfig, config)

// مثال‌سازی لایه [dao]
import Dao from './couches/Dao';
const dao = new Dao(config.axios);

// پلاگین [dao]
import pluginDao from './plugins/pluginDao'
Vue.use(pluginDao, dao)

// [métier] نمونه‌سازی لایه
import Métier from './couches/Métier';
const métier = new Métier();

// [métier] plugin
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)

// استور Vuex
import store from './store'

//راه‌اندازی UI
new Vue({
  el: '#اپلیکیش',
  //روتر
  router: router,
  // فروشگاه Vuex
  store: store,
  // نما اصلی
  render: h => h(Main),
})

لطفاً به نکات زیر توجه کنید:

  • خطوط ۱۸–۲۱: ابجکت صادرشده توسط اسکریپت [./config] در ویژگی [Vue.$config] در دسترس خواهد بود و بنابراین برای همه ویوها در برنامه قابل دسترسی است. این کار در اینجا غیرضروری بود زیرا شیء [config] تنها توسط اسکریپت [main] (خط 25) استفاده می‌شود. با این حال، اغلب پیش می‌آید که این پیکربندی برای چندین نما مورد نیاز است. بنابراین ما می‌خواستیم به اصل در دسترس قرار دادن آن در یک ویژگی نما پایبند باشیم؛
  • خطوط ۲۴–۲۵: نمونه‌سازی لایه [dao]. کلاس [Dao] در خط 24 وارد (import) شده و سپس در خط 25 نمونه سازی (instantiate) می‌شود. سازنده (constructor) آن، شیء [axios]—یک ویژگی پیکربندی—را به عنوان تنها پارامتر خود می‌پذیرد؛
  • خطوط ۲۷–۲۹: لایه [dao] در ویژگی [$dao] تمام ویوها در دسترس قرار می‌گیرد؛
  • خطوط ۳۱–۳۷: همین توالی برای لایه [métier] تکرار می‌شود. سازنده کلاس [Métier]، [taxAdminData] را به‌عنوان پارامتر می‌پذیرد که نمایانگر داده‌های مرجع مالیاتی است. ما هنوز این داده‌ها را نداریم. بنابراین شیء [métier] در خط ۳۳ باید در تاریخ بعدی تکمیل شود؛
  • خط ۴۰: فروشگاه [Vuex] وارد می‌شود؛
  • خطوط ۴۳–۵۱: نمای اصلی [Main] (خطوط ۵ و ۵۰) نمونه‌سازی می‌شود و دو پارامتر به آن ارسال می‌گردد:
    • خط ۴۶: روتر [router] تعریف‌شده در خط ۱۶؛
    • خط ۴۸: فروشگاه [Vuex] [store] که در خط ۴۰ تعریف شده است؛
    • در هر دو مورد، نام ویژگی در سمت چپ و مقدار آن در سمت راست قرار دارد. نام‌های ویژگی [router, store] توسط فریم‌ورک‌های [vue-router] و [vuex] تعریف شده‌اند. مقادیر مربوطه می‌توانند هر چیزی باشند؛

18.4. نماهای برنامه

18.4.1. ویوی اصلی [Main]

کد نمای اصلی [Main] به شرح زیر است:


<!-- HTML تعریف نما -->
<template>
  <div class="container">
    <b-card>
      <!-- جامبوترون -->
      <b-jumbotron>
        <b-row>
          <b-col cols="4">
            <img src="../assets/logo.jpg" alt="Cerisier en fleurs" />
          </b-col>
          <b-col cols="8">
            <h1>Calculez votre impôt</h1>
          </b-col>
        </b-row>
      </b-jumbotron>
      <!-- HTTP خطای درخواست -->
      <b-alert
        show
        variant="danger"
        v-if="showError"
      >L'erreur suivante s'est produite : {{error.message}}</b-alert>
      <!-- نما فعلی -->
      <router-view v-if="showView" @loading="mShowLoading" @error="mShowError" />
      <!--در حال بارگذاری -->
      <b-alert show v-if="showLoading" variant="light">
        <strong>Requête au serveur de calcul d'impôt en cours...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
    </b-card>
  </div>
</template>

<script>
export default {
  // نام
  name: "app",
  // وضعیت داخلی
  data() {
    return {
      //بررسی هشدار در انتظار
      showLoading: false,
      // هشدار خطا را کنترل می‌کند
      showError: false,
      //کنترل نمایش نمای مسیریابی فعلی را بر عهده دارد
      showView: true,
      // یک پیام خطا
      error: ""
    };
  },
  // پردازشگرهای رویداد
  methods: {
    //خطای درخواست ناهمزمان
    mShowError(error) {
      // eslint-disable-next-line
      console.log("Main evt error");
      // نمایش پیام خطا
      this.error = error;
      this.showError = true;
      // پنهان کردن نمای مسیریابی‌شده
      this.showView = false;
      // پنهان کردن پیام در حال بارگذاری
      this.showLoading = false;
    },
    // نمایش یا عدم نمایش آیکون بارگذاری
    mShowLoading(value) {
      //eslint-disable-next-line
      console.log("Main evt showLoading");
      // نمایش یا عدم نمایش هشدار بارگذاری
      this.showLoading = value;
    }
  }
};
</script>

نظرات

  • ویوی [Main] چیدمان ویویی را که در خط ۲۳ مسیریابی و نمایش داده می‌شود، مدیریت می‌کند:

Image

  • خطوط ۵–۱۵ ناحیه ۱ را نمایش می‌دهند؛
  • خط ۲۳ نمای مسیریابی‌شده [2] را نمایش می‌دهد؛
  • خطوط ۱۶–۱۹: هشداری که تنها در صورت خطای ارتباطی با سرور محاسبه مالیات نمایش داده می‌شود؛
  • خطوط ۲۵–۲۸: پیامی برای بارگذاری که برای هر درخواست HTTP به سرور نمایش داده می‌شود؛
  • تمام ویوها با استفاده از این چیدمان نمایش داده می‌شوند، زیرا هر ویوی مسیریابی‌شده از طریق خطوط ۲۰ تا ۲۴ نمایش داده می‌شود. ویوی [Main] برای استخراج عناصری که می‌توانند در ویوهای مختلف مشترک باشند، استفاده می‌شود؛
  • خط ۲۳: هر نمای مسیریابی‌شده می‌تواند سه رویداد را فعال کند:
    • [loading]: یک درخواست HTTP ارسال شده است. پیامی که نشان می‌دهد یک پاسخ در انتظار است باید نمایش داده شود؛
    • [error]: درخواست HTTP با خطا پایان یافت. پیام خطا باید نمایش داده شود و نمای مسیریابی‌شده پنهان گردد؛
  • خطوط ۳۸–۴۹: وضعیت نما:
    • خط ۴۱: [showLoading] نمایش پیامی را که نشان می‌دهد یک درخواست (HTTP، خط ۲۵) در حال انتظار تکمیل است، کنترل می‌کند؛
    • خط ۴۳: [showError] نمایش پیام خطا برای درخواست HTTP (خطوط ۱۷–۲۱) را کنترل می‌کند؛
    • خط ۴۵: [showView] نمایش نمای مسیریابی‌شده (خط ۲۳) را کنترل می‌کند؛
  • خطوط ۵۳–۶۳: متد [mShowError] رویداد [error] را که توسط نمای مسیریابی‌شده (خط ۲۳) منتشر می‌شود، مدیریت می‌کند؛
  • خطوط ۶۵–۷۰: متد [mShowLoading] رویداد [loading] را که توسط نمای مسیریابی‌شده (خط ۲۳) منتشر می‌شود، مدیریت می‌کند؛
  • خط ۲۳: به رویدادهای [error] و [loading] توجه کنید. این رویدادها تنها در صورتی رهگیری می‌شوند که نمای مسیریابی‌شده نمایش داده شود ([showView=true]). به همین دلیل است که نمای مسیریابی‌شده در ابتدا نمایش داده می‌شود (خط ۴۵). این [بلوک] تنها در صورت بروز خطا (خط ۶۰) پنهان می‌شود. برای جلوگیری از این مشکل، می‌توانست به جای [v-if] از دستور [v-show] استفاده شود. تفاوت بین این دو دستور به شرح زیر است:
    • [v-if=’false’] بلوک کنترل‌شده را با حذف آن از کد جهانی HTML پنهان می‌کند. در این صورت دیگر نمی‌توان رویدادهای نمای مسیریابی‌شده را رهگیری کرد؛
    • [v-show=’false’] بلوک کنترل‌شده را با اصلاح CSS آن پنهان می‌کند، اما کد بلوک در HTML کلی باقی می‌ماند و بنابراین می‌تواند رویدادهای نمای مسیریابی‌شده را رهگیری کند؛

18.4.2. نمایه چیدمان [Layout]

کد برای نمای [Layout] به شرح زیر است:


<!--HTML تعریف چیدمان نمای مسیریابی‌شده -->
<template>
  <!-- رشته -->
  <div>
    <b-row>
      <!-- منطقهٔ سه‌ستونی در سمت چپ -->
      <b-col cols="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- منطقهٔ نه‌ستونی در سمت راست -->
      <b-col cols="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
  export default {
    // تنظیمات نما
    props: {
      // ستون سمت چپ را کنترل می‌کند
      left: {
        type: Boolean
      },
      // ستون سمت راست را کنترل می‌کند
      right: {
        type: Boolean
      }
    }
  };
</script>

نظرات

  • نما [Layout] امکان تقسیم نمای مسیریابی‌شده به دو ناحیه را فراهم می‌کند:
    • یک ناحیهٔ سه‌ستونی Bootstrap در سمت چپ (خطوط ۷–۹). این ناحیه شامل منوی ناوبری خواهد بود، در صورتی که منویی وجود داشته باشد؛
    • یک ناحیهٔ ۹ ستونی در سمت راست (خطوط ۱۱–۱۳). این ناحیه حاوی اطلاعاتی خواهد بود که توسط نمای مسیریابی‌شده ارائه می‌شود؛

18.4.3. نما [Authentification]

نمایه احراز هویت به شرح زیر است:

Image

این نما با حذف ستون سمت چپ از [Layout] استخراج شده تا فقط ستون سمت راست نمایش داده شود.

کد آن به شرح زیر است:


<!-- تعریف نما HTML -->
<template>
  <Layout :left="false" :right="true">
    <template slot="right">
      <!--فرم HTML – مقادیر با استفاده از action QZXW2HTMLBW2F1dGhlbnRpZmllci-11dGlsaXNhdGV1cl0ZQX ارسال می‌شوند -->
      <b-form @submit.prevent="login">
        <!-- عنوان -->
        <b-alert show variant="primary">
          <h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
        </b-alert>
        <!-- خط اول -->
        <b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
          <!-- میدان ورودی کاربر -->
          <b-col cols="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- خط دوم -->
        <b-form-group label="Mot de passe" label-for="password" label-cols="3">
          <!-- میدان وارد کردن رمز عبور -->
          <b-col cols="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- خط سوم -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          class="mt-3"
        >L'erreur suivante s'est produite : {{message}}</b-alert>
        <!-- دکمه از نوع [submit] در خط سوم -->
        <b-row>
          <b-col cols="2">
            <b-button variant="primary" type="submit" :disabled="!valid">Valider</b-button>
          </b-col>
        </b-row>
      </b-form>
    </template>
  </Layout>
</template>

<!-- رفتار نما -->
<script>
import Layout from "./Layout";
export default {
  //وضعیت مؤلفه
  data() {
    return {
      // کاربر
      user: "",
      // رمز عبور آنها
      password: "",
      // نمایش پیام خطا را کنترل می‌کند
      showError: false,
      // پیام خطا
      message: "",
      // جلسه آغاز شد
      sessionStarted: false
    };
  },

  // کامپوننت‌های استفاده شده
  components: {
    Layout
  },

  // ویژگی‌های محاسباتی
  computed: {
    // ورودی‌های معتبر
    valid() {
      return this.user && this.password && this.sessionStarted;
    }
  },

  // پردازشگرهای رویداد
  methods: {
    // ----------- احراز هویت
    async login() {
      try {
        // آغاز انتظار
        this.$emit("loading", true);
        // احراز هویت مسدودکننده با سرور
        const response = await this.$dao.authentifierUtilisateur(
          this.user,
          this.password
        );
        //پایان بارگذاری
        this.$emit("loading", false);
        // تحلیل پاسخ
        if (response.état != 200) {
          // خطا نمایش داده شد
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // بدون خطا
        this.showError = false;
        // --------- اکنون در حال درخواست داده‌ها از مقامات مالیاتی
        // انتظار آغاز شد
        this.$emit("loading", true);
        // درخواست به سرور مسدود شده است
        const response2 = await this.$dao.getAdminData();
        // پایان بارگذاری
        this.$emit("loading", false);
        // در حال تجزیه و تحلیل پاسخ
        if (response2.état != 1000) {
          // خطا نمایش داده شد
          this.message = response2.réponse;
          this.showError = true;
          return;
        }
        // بدون خطا
        this.showError = false;
        //ذخیرهٔ داده‌های دریافتی در لایهٔ [métier]
        this.$métier.setTaxAdminData(response2.réponse);
        // به نمای محاسبه مالیات بروید
        this.$router.push({ name: "calculImpot" });
      } catch (error) {
        //خطا به کامپوننت اصلی ارجاع داده می‌شود
        this.$emit("error", error);
      }
    }
  },
  //چرخهٔ عمر: کامپوننت به‌تازگی ایجاد شده است
  created() {
    // eslint-disable-next-line
    console.log("authentification", "created");
    // شروع یک جلسه jSON با سرور
    // انتظار آغاز می‌شود
    this.$emit("loading", true);
    // ابتدای جلسه با سرور – درخواست ناهمزمان
    // استفاده از پرامیس بازگردانده‌شده توسط متدهای لایه [dao]
    this.$dao
      // ابتکار یک جلسه jSON
      .initSession()
      // پاسخ دریافت شده است
      .then(response => {
        //پایان انتظار
        this.$emit("loading", false);
        // تحلیل پاسخ
        if (response.état != 700) {
          // نمایش خطا
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // جلسه آغاز شد
        this.sessionStarted = true;
      })
      //در صورت بروز خطا
      .catch(error => {
        //خطا به مشاهده ارجاع داده می‌شود [Main]
        this.$emit("error", error);
      });
  }
};
</script>

نظرات

  • خط ۳: نمای [Authentification] تنها از ستون سمت راست [Layout] (خطوط ۳ و ۴) استفاده می‌کند؛
  • خطوط ۶–۳۸: فرم Bootstrap که ناحیه ۱ را در اسکرین‌شات بالا تولید می‌کند؛
  • خط ۶: رویداد [@submit] زمانی رخ می‌دهد که کاربر دکمه با نوع [submit] در خط ۳۵ را کلیک می‌کند. ویرایشگر [prevent] تضمین می‌کند که صفحه در طول رویداد [submit] دوباره بارگذاری نشود. به طور جایگزین، می‌توانست نوشت:
    • یک تگ <b-form> بدون رسیدگی به رویداد [submit]؛
    • یک تگ <b-button> با رویداد [@click=’login’] و بدون ویژگی [type=’submit’]؛

این هم کار می‌کند. مزیت راه‌حل انتخاب‌شده این است که فرم نه تنها با کلیک روی دکمه [Valider] ارسال می‌شود، بلکه با فشردن کلید Enter ([Entrée]) در فیلدهای ورودی نیز ارسال می‌گردد. بنابراین، به منظور راحتی کاربر، راه‌حل [<b-form @submit.prevent="login">] در اینجا انتخاب شده است؛

  • خطوط ۳۳–۳۷: هشداری که هنگام رد شدن اعتبارنامه‌های وارد شده توسط کاربر توسط سرور، نمایش داده می‌شود:

Image

  • خط ۳۵: دکمه [Valider] همیشه فعال نیست. وضعیت آن به ویژگی محاسبه‌شده [valid] در خطوط ۷۱–۷۳ بستگی دارد. ویژگی [valid] زمانی true است که:
    • در فیلدهای [user, password] فرم چیزی وجود داشته باشد؛
    • جلسه jSON آغاز شده باشد. در ابتدا این جلسه آغاز نشده است (خط ۵۹) و بنابراین دکمه [Valider] غیرفعال است.
  • خطوط ۴۹–۶۰: وضعیت نما؛
    • [user] نمایانگر ورودی کاربر در فیلد [user] (خطوط ۱۲–۱۷) فرم است. دستور [v-model] در خط ۱۵ یک پیوند دوطرفه بین ورودی کاربر و ویژگی [user] نما برقرار می‌کند؛
    • [password] نمایانگر ورودی کاربر در فیلد [password] (خطوط ۱۹–۲۴) فرم است. دستور [v-model] در خط ۲۲ یک پیوند دوطرفه بین ورودی کاربر و ویژگی [password] نما برقرار می‌کند؛
    • [showError] کنترل (خط ۲۹) نمایش هشدار در خطوط ۲۶–۳۱ را بر عهده دارد؛
    • [message] پیام خطا (خط ۳۱) است که باید در هشدار مربوط به خطوط ۲۶–۳۱ نمایش داده شود؛
    • [sessionStarted] نشان می‌دهد که آیا جلسه jSON با سرور آغاز شده است یا خیر. در ابتدا، این ویژگی مقدار [false] (خط 59) را دارد. جلسه jSON با سرور در رویداد [created] چرخهٔ زندگی نما، خطوط 126–156، آغاز می‌شود. اگر سرور پاسخ مثبت دهد، آنگاه ویژگی [sessionStarted] روی [true] تنظیم می‌شود (خط 149);
  • خطوط ۱۲۶–۱۵۶: تابع [created] به محض ایجاد نمای [Authentification] اجرا می‌شود (هرچند ممکن است هنوز نمایش داده نشده باشد). در پس‌زمینه، یک جلسه jSON با سرور راه‌اندازی می‌شود. ما می‌دانیم که این اولین عملی است که با سرور محاسبه مالیات انجام می‌شود. برای این کار، از لایه [dao] برنامه (خط ۱۳۴) استفاده می‌کنیم. تمام متدهای این لایه غیرهمزمان هستند. در اینجا، از پرامیس (Promise) بازگشتی متد [$dao.initSession] استفاده می‌کنیم که جلسه jSON را با سرور راه‌اندازی می‌کند.
  • خطوط ۱۳۸–۱۵۰: کدی که زمانی اجرا می‌شود که سرور پاسخ خود را بدون هیچ خطایی بازگردانده است؛
  • خط 142: ویژگی [état] پاسخ بررسی می‌شود. برای موفقیت عملیات، این ویژگی باید مقدار [700] را داشته باشد. در غیر این صورت، خطایی رخ داده است که علت آن در خصوصیت [response.réponse] (خط 144) نشان داده شده است. سپس پیام خطا در نما نمایش داده می‌شود (خط 145)؛
  • خط ۱۴۹: توجه کنید که جلسه jSON آغاز شده است؛
  • خطوط ۱۵۲–۱۵۵: کدی که در صورت رخ دادن خطا اجرا می‌شود. این خطا به نمای والد [Main] منتقل می‌شود، که
    • پیغام خطا را نمایش خواهد داد؛
    • پیام در حال انتظار را مخفی می‌کند؛
    • نمایه مسیریابی‌شده، نمایه [Autentification] را مخفی می‌کند؛
  • خطوط ۷۹–۱۲۴: متد [login] کلیک روی دکمه [Valider] را مدیریت می‌کند؛
  • خط ۷۹: متد با کلمه کلیدی [async] پیش‌وند شده است تا امکان استفاده از کلمه کلیدی [await] در خطوط ۸۴ و ۱۰۳ فراهم شود؛
  • خطوط ۸۴–۸۷: یک فراخوانی مسدودکننده به متد [$dao.authentifierUtilisateur(user, password)]. می‌توانستیم از یک promise [Promise] استفاده کنیم، همان‌طور که در تابع [created] انجام شده است. ما می‌خواستیم سبک‌ها را متنوع کنیم. خطری برای مسدود کردن کاربر وجود ندارد، زیرا ما یک [timeout] دو ثانیه‌ای را روی تمام درخواست‌های HTTP تنظیم کرده‌ایم. آنها مجبور نیستند مدت زیادی منتظر بمانند. علاوه بر این، تا زمانی که سرور پاسخ خود را بازنگردانده است، آنها نمی‌توانند کاری انجام دهند، زیرا دکمه [Valider] تا آن زمان غیرفعال باقی می‌ماند؛
  • خط ۹۱: سرور محاسبه مالیات پاسخ‌های jSON را ارسال می‌کند که همگی ساختار [{‘action’:action, ‘état’:val, ‘réponse’:réponse}] را دارند. اگر [état==200] باشد، احراز هویت موفقیت‌آمیز است. در غیر این صورت، یک پیام خطا در خطوط 93–94 نمایش داده می‌شود؛
  • خط ۹۸: هرگونه پیام خطا از عملیات قبلی سرکوب می‌شود؛
  • خطوط 99–116: اکنون از سرور برای دریافت داده‌های مرجع مالیاتی مورد نیاز برای محاسبه مالیات، استعلام گرفته می‌شود. در [this.$métierما یک نمونه از کلاس [Métier] داریم که در حال حاضر، به دلیل نداشتن این داده‌ها، نمی‌تواند هیچ اقدامی انجام دهد؛
  • خط ۱۰۳: داده‌های مرجع مالیاتی از طریق یک عملیات مسدودکننده از سرور درخواست می‌شود؛
  • خطوط ۱۰۷–۱۱۲: پاسخ سرور تحلیل می‌شود. این پاسخ باید دارای مقدار وضعیت ۱۰۰۰ باشد؛ در غیر این صورت، خطایی رخ داده است. در این حالت، یک پیام خطا نمایش داده می‌شود (خطوط ۱۰۹–۱۱۰)؛
  • خطوط ۱۱۳–۱۱۸: اگر عملیات موفقیت‌آمیز باشد، موارد زیر رخ می‌دهد:
    • پیام خطا را پنهان کنید (خط 114);
    • داده‌های مرجع مالیاتی به لایه [métier] منتقل می‌شود (خط 116);
    • نمایش ویوی [CalculImpot]، خط ۱۱۸. به یاد داشته باشید که [this.$router] به روتر برنامه اشاره دارد. متد [push] برای تنظیم ویوی مسیریابی بعدی استفاده می‌شود. در اینجا، با ویژگی [name] آن شناسایی شده است. همچنین می‌توانست با ویژگی [path] شناسایی شود. این اطلاعات در فایل مسیریابی قرار دارد:

//محاسبه مالیات
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  • خطوط ۱۱۹–۱۲۲: [catch] زمانی فعال می‌شود که یکی از دو درخواست HTTP ناموفق بوده باشد (سرور پیدا نشد، زمان‌دهی بیش از حد گذشت و غیره). سپس این خطا به نمای والد [Main] گزارش می‌شود که آن را نمایش داده، پیام بارگذاری را پنهان کرده و نمای [Authentification] را نمایش می‌دهد؛

18.4.4. نما [CalculImpot]

نما [CalculImpot] به شرح زیر است:

Image

  • [1]: یک منوی ناوبری ستون سمت چپ نمای مسیریابی‌شده را اشغال می‌کند؛
  • [2]: فرم محاسبه مالیات ستون سمت راست نمای مسیریابی‌شده را اشغال می‌کند؛

کد مربوط به نما [CalculImpot] به شرح زیر است:


<!--HTML تعریف نما -->
<template>
  <div>
    <Layout :left="true" :right="true">
      <!-- فرم محاسبه مالیات در سمت راست -->
      <FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
      <!-- منوی ناوبری در سمت چپ -->
      <Menu slot="left" :options="options" />
    </Layout>
    <!-- منطقه نمایش نتایج محاسبه مالیات در زیر فرم -->
    <b-row v-if="résultatObtenu" class="mt-3">
      <!-- فضای خالی سه‌ستونی -->
      <b-col cols="3" />
      <!-- منطقهٔ نه‌ستونی -->
      <b-col cols="9">
        <b-alert show variant="success">
          <span v-html="résultat"></span>
        </b-alert>
      </b-col>
    </b-row>
  </div>
</template>

<script>
// واردات
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";

export default {
  // گزارش داخلی
  data() {
    return {
      // گزینه‌های منو
      options: [
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        },
        {
          text: "Fin de session",
          path: "/fin-session"
        }
      ],
      // نتیجه محاسبه مالیات
      résultat: "",
      résultatObtenu: false
    };
  },
  // کامپوننت‌های استفاده شده
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // روش‌های رسیدگی به رویدادها
  methods: {
    // نتیجه محاسبه مالیات
    handleResultatObtenu(résultat) {
      // نتیجه الحاق شده HTML
      const impôt = "Montant de l'impôt : " + résultat.impôt + " euro(s)";
      const décôte = "Décôte : " + résultat.décôte + " euro(s)";
      const réduction = "Réduction : " + résultat.réduction + " euro(s)";
      const surcôte = "Surcôte : " + résultat.surcôte + " euro(s)";
      const taux = "Taux d'imposition : " + résultat.taux;
      this.résultat =
        impôt +
        "<br/>" +
        décôte +
        "<br/>" +
        réduction +
        "<br/>" +
        surcôte +
        "<br/>" +
        taux;
      //نمایش نتیجه
      this.résultatObtenu = true;
      // ---- به‌روزرسانی به فروشگاه [Vuex]
      // یک شبیه‌سازی از +
      this.$store.commit("addSimulation", résultat);
    }
  }
};
</script>

نظرات

  • خط ۴: هر دو ستون [Layout] در اینجا موجود هستند؛
  • خط ۶: فرم محاسبه مالیات ستون سمت راست را اشغال می‌کند. این فرم پس از به دست آمدن نتیجه محاسبه مالیات، رویداد [resultatObtenu] را فعال می‌کند. توجه داشته باشید که نام رویدادها و نام متدهایی که آن‌ها را مدیریت می‌کنند نباید حاوی کاراکترهای دارای نشانه باشند؛
  • خط ۸: منوی ناوبری ستون سمت چپ را اشغال می‌کند؛
  • خطوط ۱۱–۲۰: نتیجه محاسبه مالیات در زیر فرم نمایش داده می‌شود:

Image

  • خط ۱۱: نتیجه تنها در صورتی نمایش داده می‌شود که ویژگی [résultatObtenu] (خط ۴۷) روی [true] تنظیم شده باشد؛
  • خطوط ۳۴–۴۸: وضعیت نما:
    • [options]: فهرست گزینه‌های منوی ناوبری. این جدول به‌عنوان پارامتر به کامپوننت [Menu] در خط ۸ ارسال می‌شود؛
    • [résultat]: نتیجه محاسبه مالیات. این نتیجه یک رشته HTML است. به همین دلیل از دستور [v-html] در خط 17 برای نمایش آن استفاده شد؛
    • [résultatObtenu]: بولی که کنترل می‌کند آیا نتیجه نمایش داده شود یا خیر، خط ۱۱؛
  • خطوط ۵۹–۸۱: متد [handleResultatObtenu] نتیجه محاسبه مالیات را که توسط نمای فرزند [FormCalculImpot] (خط ۶) برای آن ارسال شده است، نمایش می‌دهد. این نتیجه یک شیء با ویژگی‌های [impot, décôte, réduction, surcôte, taux, marié, enfants, salaire] است؛
  • خطوط ۶۱–۷۵: شیء [impot, décôte, réduction, surcôte, taux] در یک فیلد متنی HTML نوشته می‌شود که توسط خط ۱۷ قالب نمایش داده می‌شود؛
  • خط ۷۷: این نتیجه نمایش داده می‌شود؛
  • خط ۸۰: متد [addSimulation] از استور Vuex فراخوانی می‌شود که [résultat] را به شبیه‌سازی‌های موجود در استور اضافه می‌کند؛

18.4.5. منوی ناوبری [Menu]

منوی ناوبری در ستون سمت چپ نماهای مسیریابی‌شده نمایش داده می‌شود:

Image

کد نما [Menu] به شرح زیر است:


<!-- تعریف نما HTML -->
<template>
  <!-- منوی عمودی بوت‌استرپ -->
  <b-nav vertical>
    <!-- گزینه‌های منو -->
    <b-nav-item
      v-for="(option,index) of options"
      :key="index"
      :to="option.path"
      exact
      exact-active-class="active"
    >{{option.text}}</b-nav-item>
  </b-nav>
</template>

<script>
export default {
  // تنظیمات نما
  props: {
    options: {
      type: Array
    }
  }
};
</script>

نظرات

  • گزینه‌های منو توسط پارامتر [options] (خطوط ۷، ۲۰–۲۲) ارائه می‌شوند؛
  • هر عنصر در آرایه [options] دارای یک ویژگی [text] (خط ۱۲) است که متن لینک است، و یک ویژگی [path] (خط ۹) است که مسیر به نمای هدف لینک است؛

18.4.6. نما [FormCalculImpot]

این نما فرم محاسبه مالیات را ارائه می‌دهد:

Image

کد آن به شرح زیر است:


  <!-- تعریف نما HTML -->
  <template>
  <!-- HTML فرم -->
  <b-form @submit.prevent="calculerImpot" class="mb-3">
    <!-- پیام در ۱۲ ستون روی پس‌زمینه آبی -->
    <b-alert show variant="primary">
      <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
    </b-alert>
    <!-- fields of form -->
    <!-- رشته اول -->
    <b-form-group label="Etes-vous marié(e) ou pacsé(e) ?" label-cols="4">
      <!-- دکمه‌های رادیویی در ۵ ستون-->
      <b-col cols="5">
        <b-form-radio v-model="marié" value="oui">Oui</b-form-radio>
        <b-form-radio v-model="marié" value="non">Non</b-form-radio>
      </b-col>
    </b-form-group>
    <!-- ردیف دوم -->
    <b-form-group label="Nombre d'enfants à charge" label-cols="4" label-for="enfants">
      <b-input
        type="text"
        id="enfants"
        placeholder="Indiquez votre nombre d'enfants"
        v-model="enfants"
        :state="enfantsValide"
      />
      <!-- پیام خطای احتمالی -->
      <b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- ردیف سوم -->
    <b-form-group
      label="Salaire annuel"
      label-cols="4"
      label-for="salaire"
      description="Arrondissez à l'euro inférieur"
    >
      <b-input
        type="text"
        id="salaire"
        placeholder="Salaire annuel"
        v-model="salaire"
        :state="salaireValide"
      />
      <!-- پیام خطای احتمالی -->
      <b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- خط چهارم، دکمه [submit] در ۵ ستون -->
    <b-col cols="5">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>

<!-- اسکریپت -->
<script>
export default {
  //وضعیت داخلی
  data() {
    return {
      // متأهل یا مجرد
      marié: "non",
      // تعداد فرزندان
      enfants: "",
      // حقوق سالانه
      salaire: ""
    };
  },
  //وضعیت داخلی محاسبه شده
  computed: {
    //اعتبارسنجی فرم
    formInvalide() {
      return (
        // حقوق نامعتبر
        !this.salaireValide ||
        // یا تعداد فرزندان نامعتبر
        !this.enfantsValide ||
        // یا دریافت نشدن اطلاعات مالیاتی
        !this.$métier.taxAdminData
      );
    },
    //اعتبارسنجی حقوق
    salaireValide() {
      //باید یک عدد ≥ 0 باشد
      return Boolean(this.salaire.match(/^\s*\d+\s*$/));
    },
    //اعتبارسنجی فرزندان
    enfantsValide() {
      //باید یک مقدار عددی ≥ 0 باشد
      return Boolean(this.enfants.match(/^\s*\d+\s*$/));
    }
  },
  // مدیر رویداد
  methods: {
    calculerImpot() {
      // مالیات با استفاده از لایه [métier] محاسبه می‌شود
      const résultat = this.$métier.calculerImpot(
        this.marié,
        this.enfants,
        this.salaire
      );
      // eslint-disable-next-line
      console.log("résultat=", résultat);
      // نتیجه نهایی شد
      résultat.marié = this.marié;
      résultat.enfants = this.enfants;
      résultat.salaire = this.salaire;
      // رویداد [resultatObtenu] فعال شد
      this.$emit("resultatObtenu", résultat);
    }
  }
};
</script>

نظرات

  • خطوط ۴–۵۱: فرم Bootstrap؛
  • خطوط ۱۱–۱۷: گروهی از دکمه‌های رادیویی همراه با برچسب‌هایشان؛
  • خطوط ۱۴–۱۵: تگ <b-form-radio> تضمین می‌کند که یک دکمهٔ رادیویی نمایش داده شود:
    • خط ۱۴: دستور [v-model] تضمین می‌کند که هنگام کلیک روی دکمه، ویژگی [marié] در خط ۶۱ روی مقدار [oui] (ویژگی [value="oui"]) تنظیم شود؛
    • خط 15: دستور [v-model] تضمین می‌کند که با کلیک روی دکمه، ویژگی [marié] در خط ۶۱ روی مقدار [non] (ویژگی [value="non"]) تنظیم شود؛
  • خطوط ۱۹–۲۹: بخش وارد کردن تعداد فرزندان:
    • خط ۲۴: ورودی مربوط به تعداد فرزندان به ویژگی [enfants] در خط ۶۳ متصل می‌شود؛
    • خط ۲۵: اعتبار ورود توسط ویژگی محاسبه‌شده [enfantsValide] در خطوط ۸۷–۸۹ بررسی می‌شود؛
    • خط ۲۸: تضمین می‌کند که اگر ورودی نامعتبر باشد، یک پیام خطا نمایش داده شود؛
  • رده‌های ۳۱–۴۵: بخش ورودی حقوق سالانه:
    • خط ۳۵: یک پیام راهنما درست در زیر فیلد ورودی نمایش می‌دهد؛
    • خط ۴۱: مقدار حقوق واردشده به ویژگی [salaire] در خط ۶۵ متصل است؛
    • خط ۴۲: اعتبار ورود توسط ویژگی محاسباتی [salaireValide] در خطوط ۸۲–۸۵ بررسی می‌شود؛
    • خط ۴۵: تضمین می‌کند که اگر ورودی نامعتبر باشد، یک پیام خطا نمایش داده شود؛
  • خطوط ۴۸–۵۰: یک دکمه از نوع [submit]. هنگامی که این دکمه کلیک می‌شود یا هنگام تأیید یک ورودی با استفاده از کلید [Entrée]، متد [calculerImpot] اجرا می‌شود (خط ۹۴);
    • خط ۴۹: وضعیت فعال/غیرفعال دکمه توسط ویژگی محاسبه‌شده [formInvalide] در خطوط ۷۱–۸۰ کنترل می‌شود؛
  • خطوط ۷۱–۸۰: فرم در صورتی معتبر است که:
    • تعداد فرزندان معتبر است؛
    • حقوق معتبر باشد؛
    • برنامه داده‌های سازمان مالیاتی را از سرور دریافت کرده است، که امکان محاسبه مالیات را فراهم می‌کند. توجه داشته باشید که این داده‌ها در خصوصیت [$métier.taxAdminData] ذخیره شده‌اند. ویوی [FormCalculImpot] ممکن است قبل از دریافت این داده‌ها نمایش داده شود، زیرا هم‌زمان با نمایش ویو، به‌صورت غیرهم‌زمان درخواست می‌شود. در اینجا، اطمینان حاصل می‌کنیم که کاربر نمی‌تواند تا زمانی که داده‌ها بازیابی نشده‌اند، روی دکمه [Valider] کلیک کند؛
  • خطوط ۹۴–۱۰۹: روش محاسبه مالیات:
    • خطوط ۹۶–۱۰۰: لایه [métier] این محاسبه را انجام می‌دهد. این یک محاسبه همگام است. پس از بازیابی داده‌های [taxAdminData]، کلاینت [Vue] دیگر نیازی به ارتباط با سرور ندارد. همه چیز به‌صورت محلی انجام می‌شود. یک شیء [résultat] با ویژگی‌های [impôt, décôte, surcôte, réduction, taux] به‌دست می‌آید؛
    • خطوط ۱۰۴–۱۰۶: ویژگی‌های [marié, enfants, salaire] به نتیجه اضافه می‌شوند؛
    • خط ۱۰۸: نتیجه از طریق رویداد [resultatObtenu] به نمای والد [CalculImpot] ارسال می‌شود. این نما مسئول نمایش نتیجه است؛

18.4.7. نما [ListeSimulations]

ویوی [ListeSimulations] فهرست شبیه‌سازی‌های انجام‌شده توسط کاربر را نمایش می‌دهد:

Image

کد نما به شرح زیر است:


<!-- HTML تعریف نما -->
<template>
  <div>
    <!-- چیدمان -->
    <Layout :left="true" :right="true">
      <!-- شبیه‌سازی‌ها در ستون سمت راست -->
      <template slot="right">
        <template v-if="simulations.length==0">
          <!-- شبیه‌سازی وجود ندارد -->
          <b-alert show variant="primary">
            <h4>Votre liste de simulations est vide</h4>
          </b-alert>
        </template>
        <template v-if="simulations.length!=0">
          <!-- شبیه‌سازی‌ها وجود دارند -->
          <b-alert show variant="primary">
            <h4>Liste de vos simulations</h4>
          </b-alert>
          <!-- جدول شبیه‌سازی‌ها -->
          <b-table striped hover responsive :items="simulations" :fields="fields">
            <template v-slot:cell(action)="data">
              <b-button variant="link" @click="supprimerSimulation(data.index)">Supprimer</b-button>
            </template>
          </b-table>
        </template>
      </template>
      <!-- منوی ناوبری در ستون سمت چپ -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>

<script>
  // واردات‌ها
  import Layout from "./Layout";
  import Menu from "./Menu";
  export default {
    // اجزا
    components: {
      Layout,
      Menu
    },
    //وضعیت داخلی
    data() {
      return {
        // گزینه‌های منوی ناوبری
        options: [
          {
            text: "Calcul de l'impôt",
            path: "/calcul-impot"
          },
          {
            text: "Fin de session",
            path: "/fin-session"
          }
        ],
        //پارامترهای جدول HTML
        fields: [
          { label: "#", key: "id" },
          { label: "Marié", key: "marié" },
          { label: "Nombre d'enfants", key: "enfants" },
          { label: "Salaire", key: "salaire" },
          { label: "Impôt", key: "impôt" },
          { label: "Décôte", key: "décôte" },
          { label: "Réduction", key: "réduction" },
          { label: "Surcôte", key: "surcôte" },
          { label: "", key: "action" }
        ]
      };
    },
    //وضعیت داخلی محاسبه‌شده
    computed: {
      // فهرست شبیه‌سازی‌های برداشته‌شده از فروشگاه Vuex
      simulations() {
        return this.$store.state.simulations;
      }
    },
    // روش‌ها
    methods: {
      supprimerSimulation(index) {
        // eslint-disable-next-line
        console.log("supprimerSimulation", index);
        // حذف شبیه‌سازی شماره [index]
        this.$store.commit("deleteSimulation", index);
      }
    }
  };
</script>

نظرات

  • خط ۵: نما هر دو ستون طرح‌بندی [Layout] برای نماهای مسیریابی‌شده را اشغال می‌کند؛
  • خطوط ۷–۲۶: شبیه‌سازی‌ها در ستون سمت راست ظاهر می‌شوند؛
  • خط ۲۸: منوی ناوبری در ستون سمت چپ ظاهر می‌شود؛
  • خطوط ۸، ۱۴، ۲۰ و ۷۵: شبیه‌سازی‌ها از فروشگاه‌های [Vuex] و [$this.store] آمده‌اند؛
  • خطوط ۸–۱۳: هنگامی که فهرست شبیه‌سازی‌ها خالی باشد، یک هشدار نمایش داده می‌شود؛
  • خطوط ۱۴–۲۵: جدول HTML زمانی نمایش داده می‌شود که لیست شبیه‌سازی‌ها خالی نباشد؛
  • خطوط ۲۰–۲۴: جدول HTML توسط تگ <b-table> تولید می‌شود؛
    • خط ۲۰: جدول شبیه‌سازی توسط ویژگی محاسبه‌شده [simulations] از خطوط ۷۴–۷۶ ارائه می‌شود؛
    • خط ۲۰: پیکربندی جدول HTML توسط ویژگی محاسباتی [fields] در خطوط ۵۸–۶۹ تعیین می‌شود. خط ۶۷: ستون کلیدی [action] آخرین ستون جدول HTML است؛
    • رده‌های ۲۱–۲۳: قالب برای ستون آخر جدول HTML;
    • ردیف ۲۲: یک دکمه پیوندی در اینجا قرار می‌گیرد. هنگام کلیک، متد [supprimerSimulation(data.index)] فراخوانی می‌شود، که در آن [data] نمایانگر سطر فعلی (سطر ۲۱) است. [data.index] نمایانگر شماره این سطر در لیست سطرهای نمایش داده شده است؛
  • خط ۲۸: تولید منوی ناوبری. گزینه‌های آن توسط ویژگی [options] در خطوط ۴۷–۵۶ ارائه می‌شود؛
  • خطوط ۸۰–۸۵: متدی که به کلیک روی یک لینک [Supprimer] در صفحه HTML پاسخ می‌دهد؛
    • خط ۸۴: متد mutate از store با شناسه [Vuex] فراخوانی می‌شود (به بخش |vuejs-15| مراجعه کنید)؛

18.5. اجرای پروژه

Image

شما همچنین باید سرور [Laragon] را راه‌اندازی کنید (به سند |https://tahe.developpez.com/tutoriels-cours/php7| مراجعه کنید) تا سرور محاسبه مالیات آنلاین شود.

18.6. راه‌اندازی برنامه روی سرور محلی

در حال حاضر، مشتری ما [Vue] روی سرور آزمایشی URL [http://localhost:8080] مستقر شده است. ما قصد داریم آن را روی سرور [Laragon] در URL [http://localhost:80] مستقر کنیم. برای این کار باید چندین مرحله را دنبال کرد.

مرحله ۱

ابتدا، اطمینان حاصل می‌کنیم که کلاینت [Vue] روی سرورهای آزمایشی URL و [http://localhost:8080/client-vuejs-impot/] مستقر شده است.

ما یک فایل با نام [vue.config.js] را در ریشه پروژه فعلی [VSCode] خود ایجاد می‌کنیم:

Image

فایل [vue.config.js] [1] دارای محتوای زیر خواهد بود:


//QZXW2HTMLCdnVlLmNvbmZpZy.qcwZQX
module.exports = {
  //سرویس مشتری URL سرور محاسبه مالیات [vuejs]
  publicPath: '/client-vuejs-impot/'
}

ما همچنین باید فایل مسیریابی [router.js] [2] را اصلاح کنیم:


// واردات‌ها
import Vue from 'vue'
import VueRouter from 'vue-router'
// مشاهده‌ها
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'

// پلاگین مسیریابی
Vue.use(VueRouter)

//مسیرهای برنامه
const routes = [
  //احراز هویت
  {
    path: '/', name: 'authentification', component: Authentification
  },
  //محاسبه مالیات
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // فهرست شبیه‌سازی‌ها
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  //پایان جلسه
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]

// روتر
const router = new VueRouter({
  // مسیرها
  routes,
  // حالت نمایش مسیر در مرورگر
  mode: 'history',
  // URL پایهٔ برنامه
  base: '/client-vuejs-impot/'
})

// خروجی روتر
export default router
  • خط ۳۹: ما به روتر دستور می‌دهیم که مسیرهای روت‌های تعریف‌شده در خطوط ۱۳ تا ۳۰، نسبت به مسیر تعریف‌شده در خط ۳۹ باشند. به عنوان مثال، مسیر در خط ۲۰، [/calcul-impotبه [/client-vuejs-impot/calcul-impot] تبدیل خواهد شد؛

سپس می‌توانید پروژه [vuejs-20] را مجدداً آزمایش کنید تا تغییرات مسیرهای برنامه را تأیید کنید:

Image

مرحله ۲

اکنون نسخه تولید پروژه [vuejs-20] را بسازیم:

Image

  • در [1-2]، ما وظیفه [build] [2] را در فایل [package.json] [1] پیکربندی می‌کنیم؛
  • در [3-5]، ما این وظیفه را اجرا می‌کنیم. این وظیفه است که نسخه تولید پروژه [vuejs-20] را ساخت خواهد کرد؛

وظیفه [build] در داخل یک ترمینال از [VSCode] اجرا می‌شود:

Image

Image

  • در [3-6]، هشدارها نشان می‌دهند که کد تولید شده بیش از حد بزرگ است و باید به [8] تقسیم شود. این موضوع به بهینه‌سازی معماری کد مربوط می‌شود که در اینجا به آن نمی‌پردازیم؛
  • در [7]، به ما گفته شده است که پوشه [dist] حاوی نسخه تولیدشدهٔ تولیدشده است:

Image

  • در [3]، فایل [index.html] فایلی است که هنگام درخواست URL یا [https://localhost:80/client-vue-js-impot/] استفاده خواهد شد؛

این یک وب‌سایت ایستا است که می‌تواند روی هر سروری مستقر شود. ما قصد داریم آن را روی سرور محلی Laragon مستقر کنیم (به سند |https://tahe.developpez.com/tutoriels-cours/php7| مراجعه کنید). پوشه [dist] [2] به پوشه [<laragon>/www] [4] کپی می‌شود، که در آن <laragon> پوشه نصب سرور Laragon است. ما نام این پوشه را به [client-vuejs-impot] [5] تغییر می‌دهیم، زیرا نسخه تولید را برای اجرا روی URL [/client-vuejs-impot/] پیکربندی کرده‌ایم.

مرحله ۳

ما فایل زیر را، [.htaccess]، به پوشه [client-vuejs-impot] که به‌تازگی ایجاد شده است اضافه می‌کنیم:


<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /client-vuejs-impot/
  RewriteRule ^index\.html$ - [L]
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteRule . /client-vuejs-impot/index.html [L]
</IfModule>

Image

این فایل یک فایل پیکربندی سرور وب آپاچی است. اگر این فایل را وارد نکنیم و مستقیماً URL و [https://localhost/client-vuejs-impot/calcul-impot] را درخواست کنیم، بدون اینکه ابتدا از طریق URL و [https://localhost/client-vuejs-impot/] برویم، با خطای 404 مواجه می‌شویم. با این فایل، ما در واقع نمای [CalculImpot] را دریافت می‌کنیم.

پس از انجام این کار، اگر قبلاً این کار را انجام نداده‌ایم، سرور لارگون را راه‌اندازی می‌کنیم و URL [https://localhost/client-vuejs-impot/] را درخواست می‌کنیم:

Image

از خوانندگان دعوت می‌شود تا نسخهٔ تولیدی اپلیکیشن ما را آزمایش کنند.

ما می‌توانیم سرور محاسبه مالیات را از یک جنبه تغییر دهیم: سربرگ‌های CORS که به‌طور سیستماتیک برای کلاینت‌های خود ارسال می‌کند. این کار برای نسخه کلاینت که از دامنه [localhost:8080] اجرا می‌شود، ضروری بود. اکنون که هم کلاینت و هم سرور در دامنه [localhost:80] در حال اجرا هستند، هدِرهای CORS دیگر مورد نیاز نیستند.

ما در حال ویرایش فایل [config.json] در نسخه 14 سرور هستیم:

Image

  • به [4]، و مشخص کنیم که درخواست‌ها برای CORS اکنون رد می‌شوند؛

بیایید این تغییر را ذخیره کنیم و درخواست‌های URL و [https://localhost/client-vuejs-impot/] را مجدداً ارسال کنیم. باید همچنان کار کند.

18.7. مدیریت درخواست‌های دستی URL

به جای استفاده ساده از پیوندهای منوی ناوبری، کاربر ممکن است بخواهد آدرس‌های URL برنامه را به صورت دستی در نوار آدرس مرورگر وارد کند. برای مثال، بیایید دسترسی به URL [https://client-vuejs-impot/calcul-impot] را بدون عبور از صفحهٔ احراز هویت امتحان کنیم. یک هکر قطعاً این کار را انجام خواهد داد. نمای زیر را مشاهده می‌کنیم:

Image

ما واقعاً صفحه محاسبه مالیات را می‌بینیم. اکنون بیایید فیلدهای ورودی را پر کرده و ارسال کنیم:

Image

سپس متوجه می‌شویم که دکمه [1] [Valider] حتی با وجود صحیح بودن ورودی‌ها، همچنان غیرفعال باقی می‌ماند. بیایید کد مربوط به نما [FormCalculImpot] را بررسی کنیم:


<b-col cols="5">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>

در خط ۲ می‌بینیم که وضعیت فعال/غیرفعال بودن آن به خصوصیت [formInvalide] بستگی دارد. این خصوصیت محاسباتی زیر است:


formInvalide() {
      return (
        // حقوق نامعتبر
        !this.salaireValide ||
        // یا فرزندان نامعتبر
        !this.enfantsValide ||
        //یا داده‌های مالیاتی بازیابی نشد
        !this.$métier.taxAdminData
      );
},

در خط ۸، می‌بینیم که برای معتبر بودن فرم، داده‌های مالیاتی باید بازیابی شده باشند. با این حال، این داده‌ها در حین اعتبارسنجی نمای [Authentification] بازیابی می‌شوند، نمایی که کاربر آن را «عبور داده» است. بنابراین، آنها قادر به ارسال فرم نخواهند بود. اگر آنها می‌توانستند این کار را انجام دهند، یک پیام خطا از سرور دریافت می‌کردند که بیان می‌کرد آنها احراز هویت نشده‌اند. اعتبارسنجی همیشه باید در سمت سرور انجام شود. اعتبارسنجی در سمت مرورگر همیشه قابل دور زدن است. تنها چیزی که لازم است یک کلاینت از نوع [Postman] است که درخواست‌های خام را به سرور ارسال می‌کند.

اکنون بیایید URL و [https://localhost/client-vuejs-impot/liste-des-simulations] را درخواست کنیم. نمای زیر را مشاهده می‌کنیم:

Image

حالا بیایید URL و [https://localhost/client-vuejs-impot/fin-session] را امتحان کنیم. نمای زیر را دریافت می‌کنیم:

Image

اکنون یک نما که وجود ندارد: [https://localhost/client-vuejs-impot/abcd]:

Image

اپلیکیشن ما با ورودی‌های دستی URL به خوبی کنار می‌آید. هنگامی که این موارد فراخوانی می‌شوند، رواتر اپلیکیشن از آن مطلع می‌شود. بنابراین می‌توان پیش از نمایش نهایی نما، مداخله کرد. ما این موضوع را در پروژه [vuejs-21] بررسی خواهیم کرد.

نکته دیگری که باید در نظر گرفت به شرح زیر است. بیایید تصور کنیم که کاربر چند شبیه‌سازی را مطابق با قوانین انجام داده است:

Image

اکنون بیایید صفحه را با استفاده از یک F5 رفرش کنیم:

Image

ما کاری انجام دادیم که توصیه نمی‌شود: تایپ دستی URL (تایپ F5 نیز همین نتیجه را دارد). بنابراین شبیه‌سازی‌هایمان را از دست دادیم.

پروژه بعدی، [vuejs-21 با هدف معرفی دو بهبود است:

  • اعتبارسنجی URL وارد شده توسط کاربر؛
  • حفظ وضعیت برنامه حتی اگر کاربر URL را وارد کند. در بالا می‌بینیم که فهرست شبیه‌سازی‌ها را از دست داده‌ایم؛