Membuat Aplikasi Desktop Dengan Vue JS Dan Electron - CRUDPRO

Membuat Aplikasi Desktop Dengan Vue JS Dan Electron

Membuat Aplikasi Desktop Dengan Vue JS Dan Electron

Untuk pengembang web, membuat aplikasi Windows membutuhkan kurva belajar yang signifikan. Namun, sekarang ada solusi untuk mengonversi aplikasi web JavaScript ke aplikasi Windows dengan Electron tanpa terlalu banyak pekerjaan. React dapat dengan mudah dikombinasikan dengan Electron untuk membangun aplikasi Windows.

Electron adalah runtime berbasis browser Chromium yang memungkinkan kita menjalankan aplikasi web seperti aplikasi desktop asli. Ini membungkus Chromium di sekitar aplikasi web kami sehingga terlihat seperti aplikasi desktop. Kami dapat melakukan beberapa panggilan ke API asli seperti memanipulasi file, memodifikasi menu, dan berinteraksi dengan beberapa perangkat keras.

Membangun aplikasi Electron Vue.js mudah jika kita menggunakan add-on vue-cli-plugin-electron-builder untuk Vue CLI 3. Ini dari https://github.com/nklayman/vue-cli-plugin-electron -pembangun.

Pada artikel ini, kami akan membangun aplikasi Vue Electron yang berjalan di Windows. Ini adalah aplikasi buku alamat yang memungkinkan kita untuk menambahkan kontak dan menyimpannya dengan bagian belakang yang menyajikan file JSON.

Untuk mulai membangun aplikasi, kita mulai dengan menginstal Vue CLI dengan menjalankan:

npm i -g @vue/cli

Selanjutnya, kami membuat proyek Vue.js kami dengan menjalankan vue create address-book-app. Pastikan untuk memilih 'Pilih fitur secara manual', dan setelah itu pilih untuk menyertakan Babel, Vuex, dan Vue Router. Ini akan membuat file awal untuk aplikasi kita. Selanjutnya, kami menambahkan Electron ke aplikasi kami dengan menjalankan:

vue add electron-builder

Perintah ini menambahkan file dan skrip yang diperlukan bagi kami untuk membangun aplikasi kami menjadi aplikasi Electron, dan juga melihat pratinjau aplikasi Vue.js kami yang berjalan di jendela Electron, memungkinkan kami untuk men-debug di dalamnya alih-alih browser biasa.

Setelah kita menambahkan itu, kita perlu menambahkan perpustakaan kita sendiri. Kita membutuhkan Axios untuk membuat permintaan HTTP, Bootstrap-Vue untuk styling, dan Vee-Validate untuk validasi form. Kami menginstal ini dengan menjalankan:

npm i axios bootstrap-vue vee-validate

dalam folder proyek.

Sekarang setelah kami menginstal perpustakaan kami, kami dapat mulai membangun aplikasi buku alamat kami. Kita mulai dengan membuat formulir kontak untuk menambahkan dan mengedit kontak kita. Kami menambahkan file ContactFome.vue ke folder komponen dan menambahkan:

<template>
  <ValidationObserver ref="observer" v-slot="{ invalid }">
    <b-form @submit.prevent="onSubmit" novalidate>
      <b-form-group label="First Name">
        <ValidationProvider name="firstName" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.firstName"
            required
            placeholder="First Name"
            name="firstName"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">First name is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Last Name">
        <ValidationProvider name="lastName" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.lastName"
            required
            placeholder="Last Name"
            name="lastName"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Last name is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Address">
        <ValidationProvider name="addressLineOne" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.addressLineOne"
            required
            placeholder="Address"
            name="addressLineOne"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Address is required.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="City">
        <ValidationProvider name="city" rules="required" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.city"
            required
            placeholder="City"
            name="city"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">City is required.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Postal Code">
        <ValidationProvider
          name="postalCode"
          rules="required|postal_code:country"
          v-slot="{ errors }"
        >
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.postalCode"
            required
            placeholder="Postal Code"
            name="postalCode"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">Postal code is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Country">
        <ValidationProvider name="country" rules="required" v-slot="{ errors }">
          <b-form-select
            :options="countries"
            :state="errors.length == 0"
            v-model="form.country"
            required
            placeholder="Country"
            name="country"
          ></b-form-select>
          <b-form-invalid-feedback :state="errors.length == 0">Country is requied.</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Email">
        <ValidationProvider name="email" rules="required|email" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.email"
            required
            placeholder="Email"
            name="email"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Phone">
        <ValidationProvider name="phone" rules="required|phone:country" v-slot="{ errors }">
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.phone"
            required
            placeholder="Phone"
            name="phone"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
      <b-form-group label="Age">
        <ValidationProvider
          name="age"
          rules="required|min_value:0|max_value:200"
          v-slot="{ errors }"
        >
          <b-form-input
            type="text"
            :state="errors.length == 0"
            v-model="form.age"
            required
            placeholder="Age"
            name="age"
          ></b-form-input>
          <b-form-invalid-feedback :state="errors.length == 0">{{errors.join('. ')}}</b-form-invalid-feedback>
        </ValidationProvider>
      </b-form-group>
<b-button type="submit" variant="primary">Submit</b-button>
      <b-button type="reset" variant="danger" @click="cancel()">Cancel</b-button>
    </b-form>
  </ValidationObserver>
</template>
<script>
import { COUNTRIES } from "@/helpers/exports";
import { requestsMixin } from "@/mixins/requestsMixin";
export default {
  name: "ContactForm",
  mixins: [requestsMixin],
  props: {
    edit: Boolean,
    contact: Object
  },
  methods: {
    async onSubmit() {
      const isValid = await this.$refs.observer.validate();
      if (!isValid) {
        return;
      }
if (this.edit) {
        await this.editContact(this.form);
      } else {
        await this.addContact(this.form);
      }
      const response = await this.getContacts();
      this.$store.commit("setContacts", response.data);
      this.$emit("saved");
    },
    cancel() {
      this.$emit("cancelled");
    }
  },
  data() {
    return {
      form: {},
      countries: COUNTRIES.map(c => ({ value: c.name, text: c.name }))
    };
  },
  watch: {
    contact: {
      handler(c) {
        this.form = c || {};
      },
      deep: true,
      immediate: true
    }
  }
};
</script>

Dalam formulir, kami membungkus setiap input dengan ValidationProvider sehingga kami mendapatkan validasi formulir untuk setiap bidang, bersama dengan kesalahan validasi formulir. Kami menambahkan :state=”errors.length == 0" di setiap input b-form sehingga kami mendapatkan pesan validasi yang tepat ditampilkan dan ditata dengan benar untuk setiap input. Objek error memiliki bentuk pesan kesalahan validasi untuk setiap input. Kita juga perlu menentukan prop nama di ValidationProvider dan b-form-input sehingga aturan validasi formulir diterapkan ke input di dalam ValidationProvider.

Kami menggunakan ValidationObserver untuk melihat kesalahan validasi dalam formulir kami yang dibungkus di dalamnya. Kami memiliki prop ref="observer" di ValidationObserver sehingga kami dapat memanggil waiting this.$refs.observer.validate(); untuk memvalidasi formulir kami. pengamat adalah referensi kami untuk komponen ValidationObserver. Kami menempatkan formulir di dalam komponen ValidationObserver di sini untuk memungkinkan kami memvalidasi seluruh formulir. Dengan Vee-Validate, kita mendapatkan fungsi this.$refs.observer.validate() saat kita menggunakan ValidationObserver seperti yang kita lakukan pada kode di atas. Ini mengembalikan janji yang memutuskan untuk benar jika formulir itu valid dan salah sebaliknya. Jadi jika dinyatakan salah, kami tidak menjalankan sisa kode fungsi.

Dalam formulir ini, ada validasi lintas bidang. Bidang negara dicentang sebelum memeriksa format nomor telepon dan kode pos. Kami akan menambahkan aturan validasi tersebut ke main.js nanti.

Untuk menampilkan pesan kesalahan validasi formulir, kami hanya memiliki objek kesalahan yang tersedia di template. Slot tercakup yang dibangun ke dalam komponen Vee-Validate menyediakan objek kesalahan, yang memiliki pesan validasi.

Di prop aturan setiap bidang, kami melewati nama aturan yang dipisahkan oleh pipa. Aturan telepon dan kode pos adalah aturan lintas bidang. Negara setelah titik dua adalah nama dari field country, yaitu country.

Komponen form dan input semuanya disediakan oleh BootstrapVue.

Ketika tombol kirim formulir diklik, kami memanggil tombol onSubmit. Fungsi onSubmit diteruskan ke prop submit.prevent untuk mencegah tindakan submit default sehingga kita dapat menggunakan Ajax untuk mengirimkan formulir.

Dalam fungsi ini, kita menggunakan this.$refs.observer.validate(); untuk memvalidasi formulir. Kemudian setelah itu, kita memanggil editContact atau addContact tergantung apakah edit prop benar atau tidak. Kami meneruskannya dari file HomePage.vue yang akan kami tambahkan. 2 fungsi ini untuk membuat permintaan HTTP ke server kami untuk mengirimkan data kami.

Setelah salah satu fungsi dipanggil, kami mendapatkan data terbaru dan memasukkannya ke toko Vuex kami dengan:

this.$store.commit("setContacts", response.data);

this.$store disediakan oleh Vuex.

Kemudian kami memancarkan acara yang disimpan ke HomePage.vue untuk menutup modals.

Negara diimpor dari file lain dan prop kontak diteruskan dari HomePage.vue ketika pengguna memilih entri untuk diedit.

Selanjutnya buat folder pembantu di folder src dan tambahkan file export.js. Di sana, tambahkan:

export const COUNTRIES = [
  { name: "Afghanistan", code: "AF" },
  { name: "Aland Islands", code: "AX" },
  { name: "Albania", code: "AL" },
  { name: "Algeria", code: "DZ" },
  { name: "American Samoa", code: "AS" },
  { name: "AndorrA", code: "AD" },
  { name: "Angola", code: "AO" },
  { name: "Anguilla", code: "AI" },
  { name: "Antarctica", code: "AQ" },
  { name: "Antigua and Barbuda", code: "AG" },
  { name: "Argentina", code: "AR" },
  { name: "Armenia", code: "AM" },
  { name: "Aruba", code: "AW" },
  { name: "Australia", code: "AU" },
  { name: "Austria", code: "AT" },
  { name: "Azerbaijan", code: "AZ" },
  { name: "Bahamas", code: "BS" },
  { name: "Bahrain", code: "BH" },
  { name: "Bangladesh", code: "BD" },
  { name: "Barbados", code: "BB" },
  { name: "Belarus", code: "BY" },
  { name: "Belgium", code: "BE" },
  { name: "Belize", code: "BZ" },
  { name: "Benin", code: "BJ" },
  { name: "Bermuda", code: "BM" },
  { name: "Bhutan", code: "BT" },
  { name: "Bolivia", code: "BO" },
  { name: "Bosnia and Herzegovina", code: "BA" },
  { name: "Botswana", code: "BW" },
  { name: "Bouvet Island", code: "BV" },
  { name: "Brazil", code: "BR" },
  { name: "British Indian Ocean Territory", code: "IO" },
  { name: "Brunei Darussalam", code: "BN" },
  { name: "Bulgaria", code: "BG" },
  { name: "Burkina Faso", code: "BF" },
  { name: "Burundi", code: "BI" },
  { name: "Cambodia", code: "KH" },
  { name: "Cameroon", code: "CM" },
  { name: "Canada", code: "CA" },
  { name: "Cape Verde", code: "CV" },
  { name: "Cayman Islands", code: "KY" },
  { name: "Central African Republic", code: "CF" },
  { name: "Chad", code: "TD" },
  { name: "Chile", code: "CL" },
  { name: "China", code: "CN" },
  { name: "Christmas Island", code: "CX" },
  { name: "Cocos (Keeling) Islands", code: "CC" },
  { name: "Colombia", code: "CO" },
  { name: "Comoros", code: "KM" },
  { name: "Congo", code: "CG" },
  { name: "Congo, The Democratic Republic of the", code: "CD" },
  { name: "Cook Islands", code: "CK" },
  { name: "Costa Rica", code: "CR" },
  {
    name: 'Cote D"Ivoire',
    code: "CI"
  },
  { name: "Croatia", code: "HR" },
  { name: "Cuba", code: "CU" },
  { name: "Cyprus", code: "CY" },
  { name: "Czech Republic", code: "CZ" },
  { name: "Denmark", code: "DK" },
  { name: "Djibouti", code: "DJ" },
  { name: "Dominica", code: "DM" },
  { name: "Dominican Republic", code: "DO" },
  { name: "Ecuador", code: "EC" },
  { name: "Egypt", code: "EG" },
  { name: "El Salvador", code: "SV" },
  { name: "Equatorial Guinea", code: "GQ" },
  { name: "Eritrea", code: "ER" },
  { name: "Estonia", code: "EE" },
  { name: "Ethiopia", code: "ET" },
  { name: "Falkland Islands (Malvinas)", code: "FK" },
  { name: "Faroe Islands", code: "FO" },
  { name: "Fiji", code: "FJ" },
  { name: "Finland", code: "FI" },
  { name: "France", code: "FR" },
  { name: "French Guiana", code: "GF" },
  { name: "French Polynesia", code: "PF" },
  { name: "French Southern Territories", code: "TF" },
  { name: "Gabon", code: "GA" },
  { name: "Gambia", code: "GM" },
  { name: "Georgia", code: "GE" },
  { name: "Germany", code: "DE" },
  { name: "Ghana", code: "GH" },
  { name: "Gibraltar", code: "GI" },
  { name: "Greece", code: "GR" },
  { name: "Greenland", code: "GL" },
  { name: "Grenada", code: "GD" },
  { name: "Guadeloupe", code: "GP" },
  { name: "Guam", code: "GU" },
  { name: "Guatemala", code: "GT" },
  { name: "Guernsey", code: "GG" },
  { name: "Guinea", code: "GN" },
  { name: "Guinea-Bissau", code: "GW" },
  { name: "Guyana", code: "GY" },
  { name: "Haiti", code: "HT" },
  { name: "Heard Island and Mcdonald Islands", code: "HM" },
  { name: "Holy See (Vatican City State)", code: "VA" },
  { name: "Honduras", code: "HN" },
  { name: "Hong Kong", code: "HK" },
  { name: "Hungary", code: "HU" },
  { name: "Iceland", code: "IS" },
  { name: "India", code: "IN" },
  { name: "Indonesia", code: "ID" },
  { name: "Iran, Islamic Republic Of", code: "IR" },
  { name: "Iraq", code: "IQ" },
  { name: "Ireland", code: "IE" },
  { name: "Isle of Man", code: "IM" },
  { name: "Israel", code: "IL" },
  { name: "Italy", code: "IT" },
  { name: "Jamaica", code: "JM" },
  { name: "Japan", code: "JP" },
  { name: "Jersey", code: "JE" },
  { name: "Jordan", code: "JO" },
  { name: "Kazakhstan", code: "KZ" },
  { name: "Kenya", code: "KE" },
  { name: "Kiribati", code: "KI" },
  {
    name: 'Korea, Democratic People"S Republic of',
    code: "KP"
  },
  { name: "Korea, Republic of", code: "KR" },
  { name: "Kuwait", code: "KW" },
  { name: "Kyrgyzstan", code: "KG" },
  {
    name: 'Lao People"S Democratic Republic',
    code: "LA"
  },
  { name: "Latvia", code: "LV" },
  { name: "Lebanon", code: "LB" },
  { name: "Lesotho", code: "LS" },
  { name: "Liberia", code: "LR" },
  { name: "Libyan Arab Jamahiriya", code: "LY" },
  { name: "Liechtenstein", code: "LI" },
  { name: "Lithuania", code: "LT" },
  { name: "Luxembourg", code: "LU" },
  { name: "Macao", code: "MO" },
  { name: "Macedonia, The Former Yugoslav Republic of", code: "MK" },
  { name: "Madagascar", code: "MG" },
  { name: "Malawi", code: "MW" },
  { name: "Malaysia", code: "MY" },
  { name: "Maldives", code: "MV" },
  { name: "Mali", code: "ML" },
  { name: "Malta", code: "MT" },
  { name: "Marshall Islands", code: "MH" },
  { name: "Martinique", code: "MQ" },
  { name: "Mauritania", code: "MR" },
  { name: "Mauritius", code: "MU" },
  { name: "Mayotte", code: "YT" },
  { name: "Mexico", code: "MX" },
  { name: "Micronesia, Federated States of", code: "FM" },
  { name: "Moldova, Republic of", code: "MD" },
  { name: "Monaco", code: "MC" },
  { name: "Mongolia", code: "MN" },
  { name: "Montenegro", code: "ME" },
  { name: "Montserrat", code: "MS" },
  { name: "Morocco", code: "MA" },
  { name: "Mozambique", code: "MZ" },
  { name: "Myanmar", code: "MM" },
  { name: "Namibia", code: "NA" },
  { name: "Nauru", code: "NR" },
  { name: "Nepal", code: "NP" },
  { name: "Netherlands", code: "NL" },
  { name: "Netherlands Antilles", code: "AN" },
  { name: "New Caledonia", code: "NC" },
  { name: "New Zealand", code: "NZ" },
  { name: "Nicaragua", code: "NI" },
  { name: "Niger", code: "NE" },
  { name: "Nigeria", code: "NG" },
  { name: "Niue", code: "NU" },
  { name: "Norfolk Island", code: "NF" },
  { name: "Northern Mariana Islands", code: "MP" },
  { name: "Norway", code: "NO" },
  { name: "Oman", code: "OM" },
  { name: "Pakistan", code: "PK" },
  { name: "Palau", code: "PW" },
  { name: "Palestinian Territory, Occupied", code: "PS" },
  { name: "Panama", code: "PA" },
  { name: "Papua New Guinea", code: "PG" },
  { name: "Paraguay", code: "PY" },
  { name: "Peru", code: "PE" },
  { name: "Philippines", code: "PH" },
  { name: "Pitcairn", code: "PN" },
  { name: "Poland", code: "PL" },
  { name: "Portugal", code: "PT" },
  { name: "Puerto Rico", code: "PR" },
  { name: "Qatar", code: "QA" },
  { name: "Reunion", code: "RE" },
  { name: "Romania", code: "RO" },
  { name: "Russian Federation", code: "RU" },
  { name: "RWANDA", code: "RW" },
  { name: "Saint Helena", code: "SH" },
  { name: "Saint Kitts and Nevis", code: "KN" },
  { name: "Saint Lucia", code: "LC" },
  { name: "Saint Pierre and Miquelon", code: "PM" },
  { name: "Saint Vincent and the Grenadines", code: "VC" },
  { name: "Samoa", code: "WS" },
  { name: "San Marino", code: "SM" },
  { name: "Sao Tome and Principe", code: "ST" },
  { name: "Saudi Arabia", code: "SA" },
  { name: "Senegal", code: "SN" },
  { name: "Serbia", code: "RS" },
  { name: "Seychelles", code: "SC" },
  { name: "Sierra Leone", code: "SL" },
  { name: "Singapore", code: "SG" },
  { name: "Slovakia", code: "SK" },
  { name: "Slovenia", code: "SI" },
  { name: "Solomon Islands", code: "SB" },
  { name: "Somalia", code: "SO" },
  { name: "South Africa", code: "ZA" },
  { name: "South Georgia and the South Sandwich Islands", code: "GS" },
  { name: "Spain", code: "ES" },
  { name: "Sri Lanka", code: "LK" },
  { name: "Sudan", code: "SD" },
  { name: "Suriname", code: "SR" },
  { name: "Svalbard and Jan Mayen", code: "SJ" },
  { name: "Swaziland", code: "SZ" },
  { name: "Sweden", code: "SE" },
  { name: "Switzerland", code: "CH" },
  { name: "Syrian Arab Republic", code: "SY" },
  { name: "Taiwan, Province of China", code: "TW" },
  { name: "Tajikistan", code: "TJ" },
  { name: "Tanzania, United Republic of", code: "TZ" },
  { name: "Thailand", code: "TH" },
  { name: "Timor-Leste", code: "TL" },
  { name: "Togo", code: "TG" },
  { name: "Tokelau", code: "TK" },
  { name: "Tonga", code: "TO" },
  { name: "Trinidad and Tobago", code: "TT" },
  { name: "Tunisia", code: "TN" },
  { name: "Turkey", code: "TR" },
  { name: "Turkmenistan", code: "TM" },
  { name: "Turks and Caicos Islands", code: "TC" },
  { name: "Tuvalu", code: "TV" },
  { name: "Uganda", code: "UG" },
  { name: "Ukraine", code: "UA" },
  { name: "United Arab Emirates", code: "AE" },
  { name: "United Kingdom", code: "GB" },
  { name: "United States", code: "US" },
  { name: "United States Minor Outlying Islands", code: "UM" },
  { name: "Uruguay", code: "UY" },
  { name: "Uzbekistan", code: "UZ" },
  { name: "Vanuatu", code: "VU" },
  { name: "Venezuela", code: "VE" },
  { name: "Viet Nam", code: "VN" },
  { name: "Virgin Islands, British", code: "VG" },
  { name: "Virgin Islands, U.S.", code: "VI" },
  { name: "Wallis and Futuna", code: "WF" },
  { name: "Western Sahara", code: "EH" },
  { name: "Yemen", code: "YE" },
  { name: "Zambia", code: "ZM" },
  { name: "Zimbabwe", code: "ZW" }
];

sehingga kita dapat memiliki daftar negara di bidang drop-down Negara di ContactForm.vue.

Selanjutnya kita tambahkan mixin yang kita referensikan di ContactForm.vue. Buat folder mixin di folder src dan tambahkan file requestMixin.js. Di sana tambahkan:

const APIURL = "http://localhost:3000";
const axios = require("axios");
export const requestsMixin = {
  methods: {
    getContacts() {
      return axios.get(`${APIURL}/contacts`);
    },
    addContact(data) {
      return axios.post(`${APIURL}/contacts`, data);
    },
    editContact(data) {
      return axios.put(`${APIURL}/contacts/${data.id}`, data);
    },
    deleteContact(id) {
      return axios.delete(`${APIURL}/contacts/${id}`);
    }
  }
};

Ini adalah fungsi untuk mengembalikan janji untuk permintaan yang kami buat ke bagian belakang kami.

Selanjutnya di Home.vue, ganti kode yang ada dengan yang berikut ini:

<template>
  <div class="page">
    <h1 class="text-center">Address Book</h1>
    <b-button-toolbar>
      <b-button @click="openAddModal()">Add Contact</b-button>
      <b-button @click="getAllContacts()">Refresh</b-button>
    </b-button-toolbar>
    <br />
    <b-table-simple responsive>
      <b-thead>
        <b-tr>
          <b-th>First Name</b-th>
          <b-th>Last Name</b-th>
          <b-th>Address</b-th>
          <b-th>Phone</b-th>
          <b-th>Email</b-th>
          <b-th>Age</b-th>
          <b-th></b-th>
          <b-th></b-th>
        </b-tr>
      </b-thead>
      <b-tbody>
        <b-tr v-for="c in contacts" :key="c.id">
          <b-td>{{c.firstName}}</b-td>
          <b-td>{{c.lastName}}</b-td>
          <b-td>{{c.addressLineOne}}, {{c.city}}, {{c.region}}, {{c.country}}, {{c.postalCode}}</b-td>
          <b-td>{{c.phone}}</b-td>
          <b-td>{{c.email}}</b-td>
          <b-td>{{c.age}}</b-td>
          <b-td>
            <b-button @click="openEditModal(c)">Edit</b-button>
          </b-td>
          <b-td>
            <b-button @click="deleteOneContact(c.id)">Delete</b-button>
          </b-td>
        </b-tr>
      </b-tbody>
    </b-table-simple>
<b-modal id="add-modal" title="Add Contact" hide-footer>
      <ContactForm @saved="closeModal()" @cancelled="closeModal()" :edit="false"></ContactForm>
    </b-modal>
<b-modal id="edit-modal" title="Edit Contact" hide-footer>
      <ContactForm
        @saved="closeModal()"
        @cancelled="closeModal()"
        :edit="true"
        :contact="selectedContact"
      ></ContactForm>
    </b-modal>
  </div>
</template>
<script>
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue/dist/bootstrap-vue.css";
import { requestsMixin } from "@/mixins/requestsMixin";
import ContactForm from "@/components/ContactForm";
export default {
  name: "home",
  mixins: [requestsMixin],
  components: {
    ContactForm
  },
  computed: {
    contacts() {
      return this.$store.state.contacts;
    }
  },
  beforeMount() {
    this.getAllContacts();
  },
  data() {
    return {
      selectedContact: {}
    };
  },
  methods: {
    openAddModal() {
      this.$bvModal.show("add-modal");
    },
    openEditModal(contact) {
      this.$bvModal.show("edit-modal");
      this.selectedContact = contact;
    },
    closeModal() {
      this.$bvModal.hide("add-modal");
      this.$bvModal.hide("edit-modal");
      this.selectedContact = {};
    },
    async deleteOneContact(id) {
      await this.deleteContact(id);
      this.getAllContacts();
    },
    async getAllContacts() {
      const response = await this.getContacts();
      this.$store.commit("setContacts", response.data);
    }
  }
};
</script>
<style scoped>
#add-button {
  margin-bottom: 20px;
}
</style>

Kami memiliki tabel untuk menampilkan daftar kontak dari toko. Komponen ini mengawasi pembaruan toko Vuex kami dengan mendapatkannya dari properti kontak di bidang yang dihitung. Data penyimpanan Vuex terbaru selalu dikembalikan ke sana.

Data dimuat saat halaman pertama kali dimuat dengan panggilan fungsi getAllContacts di hook beforeMount. getAllContacts mengatur kontak di toko setelah diambil dari back end.

Kami memiliki tombol untuk membuka dan menutup modals yang berisi formulir kontak kami. Perhatikan bahwa kita harus menyetel formulir untuk diteruskan ke prop kontak di ContactForm di modal edit dengan menyetel this.selectedContact di openEditModal dengan argumen kontak yang diteruskan. openEditModal digunakan oleh tombol Edit di setiap baris tabel.

Di setiap baris tabel, juga ada tombol Hapus, kami memasukkan ID kontak di sana sehingga kami dapat menghapusnya dengan ID.

Selanjutnya di App.vue, kita ganti kode yang ada dengan:

<template>
  <div id="app">
    <b-navbar toggleable="lg" type="dark" variant="info">
      <b-navbar-brand href="#">Address Book</b-navbar-brand>
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
<b-collapse id="nav-collapse" is-nav>
        <b-navbar-nav>
          <b-nav-item to="/" :active="path  == '/'">Home</b-nav-item>
        </b-navbar-nav>
      </b-collapse>
    </b-navbar>
    <router-view />
  </div>
</template>
<script>
export default {
  data() {
    return {
      path: this.$route && this.$route.path
    };
  },
  watch: {
    $route(route) {
      this.path = route.path;
    }
  }
};
</script>
<style lang="scss">
.page {
  padding: 20px;
}
button {
  margin-right: 10px;
}
</style>

Dalam file ini, kami menambahkan komponen navbar BootstrapVue dan menyorot tautan dengan memeriksa jalur yang kami dapatkan di blok watch. Prop active adalah tempat penyorotan diatur. Jika aktif benar maka tautan akan disorot. Kami memilih untuk menyorot tautan jika jalurnya sama dengan rute yang dilalui pengguna.

Di blok gaya, kami menambahkan beberapa padding ke halaman kami dan margin ke tombol kami.

Di main.js ganti kode yang ada dengan:

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import BootstrapVue from "bootstrap-vue";
import { ValidationProvider, extend, ValidationObserver } from "vee-validate";
import { required, email, min_value, max_value } from "vee-validate/dist/rules";
extend("required", required);
extend("email", email);
extend("min_value", min_value);
extend("max_value", max_value);
extend("phone", {
  validate: (value, { country }) => {
    if (["United States", "Canada"].includes(country)) {
      return /^(\(\d{3}\)|\d{3})-?\d{3}-?\d{4}$/.test(value);
    }
    return true;
  },
  message: "Phone number is invalid.",
  params: [{ name: "country", isTarget: true }]
});
extend("postal_code", {
  validate: (value, { country }) => {
    if ("United States" == country) {
      return /^[0-9]{5}(?:-[0-9]{4})?$/.test(value);
    } else if ("Canada" == country) {
      return /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$/.test(value);
    }
    return true;
  },
  message: "Phone number is invalid.",
  params: [{ name: "country", isTarget: true }]
});
Vue.config.productionTip = false;
Vue.use(BootstrapVue);
Vue.component("ValidationProvider", ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
new Vue({
  router,
  store,
  render: h => h(App),
  mounted() {
    this.$router.push("/");
  }
}).$mount("#app");

Kami memiliki aturan validasi Vee-Validate yang ditambahkan di sini dan mendaftarkan komponen BootstrapVue kami, dan komponen validasi Vee-Validate di sini sehingga kami dapat menggunakannya di template kami.

Perhatikan bahwa kami memiliki:

mounted() {
  this.$router.push("/");
}

di objek yang diteruskan ke konstruktor Vue sehingga aplikasi Windows yang kita buat tidak akan menampilkan halaman kosong.

Di router.js kita ganti kode yang ada dengan:

import Vue from "vue";
import Router from "vue-router";
import Home from "./views/Home.vue";
Vue.use(Router);
export default new Router({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home
    }
  ]
});

untuk membiarkan kami pergi ke Home.vue.

Di store.js , ganti kode yang ada dengan:

import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    contacts: []
  },
  mutations: {
    setContacts(state, payload) {
      state.contacts = payload;
    }
  },
  actions: {}
});

sehingga kita dapat menyimpan kontak di toko agar mudah diakses oleh semua komponen.

Sekarang kita dapat menjalankan npm run electron:serve untuk menjalankan aplikasi.

Untuk memulai back end, pertama-tama kita menginstal paket json-server dengan menjalankan npm i json-server. Kemudian, buka folder proyek kami dan jalankan:

json-server --watch db.json

Di db.json, ubah teks menjadi:

{
  "contacts": [
  ]
}

Jadi kami memiliki titik akhir kontak yang ditentukan dalam request.is available.

Pada akhirnya, kami memiliki yang berikut:

Membuat Aplikasi Desktop Dengan Vue JS Dan Electron
Membuat Aplikasi Desktop Dengan Vue JS Dan Electron
Membuat Aplikasi Desktop Dengan Vue JS Dan Electron