Для веб-разработчиков создание приложения для Windows требует значительного обучения. Однако теперь есть решение для преобразования веб-приложения JavaScript в приложение для Windows с помощью Electron без особых усилий. React можно легко объединить с Electron для создания приложения для Windows.
Electron - это среда выполнения на основе браузера Chromium, которая позволяет нам запускать веб-приложения как собственные настольные приложения. Он обертывает Chromium вокруг нашего веб-приложения, так что оно выглядит как настольное приложение. Мы можем делать некоторые вызовы собственных API-интерфейсов, например, манипулировать файлами, изменять меню и взаимодействовать с некоторым оборудованием.
Создать приложение Electron Vue.js легко, если мы будем использовать надстройку vue-cli-plugin-electronic-builder для Vue CLI 3. Он взят из https://github.com/nklayman/vue-cli- плагин-электрон-строитель .
В этой статье мы создадим приложение Vue Electron, работающее в Windows. Это приложение адресной книги, которое позволяет нам добавлять контакты и сохранять их с помощью серверной части, обслуживающей файл JSON.
Чтобы начать сборку приложения, мы начнем с установки Vue CLI, запустив:
npm i -g @vue/cli
Затем мы создаем наш проект Vue.js, запустив vue create address-book-app
. Обязательно выберите «Выбрать функции вручную», а затем выберите включение Babel, Vuex и Vue Router. Это создаст исходные файлы для нашего приложения. Затем мы добавляем Electron в наше приложение, выполнив:
vue add electron-builder
Эта команда добавляет необходимые файлы и скрипты, чтобы мы могли встроить наше приложение в приложение Electron, а также предварительно просмотреть наше приложение Vue.js, работающее в окне Electron, что позволяет нам выполнять отладку внутри него, а не в обычном браузере.
Как только мы добавим это, нам нужно добавить наши собственные библиотеки. Нам нужны Axios для выполнения HTTP-запросов, Bootstrap-Vue для стилизации и Vee-Validate для проверки формы. Мы устанавливаем их, запустив:
npm i axios bootstrap-vue vee-validate
в папке проекта.
Теперь, когда мы установили наши библиотеки, мы можем приступить к созданию нашего приложения адресной книги. Начнем с создания контактной формы для добавления и редактирования наших контактов. Добавляем ContactFome.vue
файл в папку components
и добавляем:
<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>
В форме мы оборачиваем каждый ввод с помощью ValidationProvider
, чтобы получить подтверждение формы для каждого поля вместе с ошибками проверки формы. Мы добавляем :state=”errors.length == 0"
в каждый b-form-input
, чтобы получить правильное сообщение проверки, отображаемое и оформленное должным образом для каждого ввода. Объект errors
имеет форму сообщений об ошибках проверки для каждого ввода. Нам также необходимо указать опору name
в ValidationProvider
и b-form-input
, чтобы правила проверки формы применялись к входным данным внутри ValidationProvider
.
Мы используем ValidationObserver
для отслеживания ошибок проверки в нашей форме, которая находится внутри. У нас есть свойство ref=”observer”
в ValidationObserver
, чтобы мы могли вызвать await this.$refs.observer.validate();
для проверки нашей формы. observer
- наша ссылка на компонент ValidationObserver
. Мы помещаем форму внутрь компонента ValidationObserver
, чтобы мы могли проверить всю форму. С Vee-Validate мы получаем функцию this.$refs.observer.validate()
, когда используем ValidationObserver
, как мы это делали в приведенном выше коде. Он возвращает обещание, которое принимает значение true, если форма действительна, и false в противном случае. Поэтому, если он принимает значение false, мы не запускаем остальную часть кода функции.
В этой форме есть проверка между полями. Поле страны проверяется перед проверкой формата номера телефона и почтового индекса. Мы добавим эти правила проверки в main.js
позже.
Для отображения сообщений об ошибках проверки формы у нас есть объект errors
, доступный только в шаблоне. Слоты с заданной областью, встроенные в компоненты Vee-Validate, предоставляют объект errors
, который имеет сообщения проверки.
В атрибуте rules
каждого поля мы передаем имена правил, разделенные вертикальной чертой. Правила phone
и postal_code
- это правила перекрестного поля. country
после двоеточия - это свойство имени поля country
, то есть country
.
Компоненты формы и ввода предоставляются BootstrapVue.
Когда нажимается кнопка отправки формы, мы вызываем кнопку onSubmit
. Функция onSubmit
передается в опору submit.prevent
, чтобы предотвратить действие отправки по умолчанию, поэтому мы можем использовать Ajax для отправки формы.
В этой функции мы используем this.$refs.observer.validate();
для проверки формы. Затем после этого мы вызываем editContact
или addContact
в зависимости от того, верно edit
prop или нет. Мы передали их из файла HomePage.vue
, который мы добавим. Эти 2 функции предназначены для выполнения HTTP-запросов к нашему серверу для отправки наших данных.
После вызова любой из функций мы получаем последние данные и помещаем их в наше хранилище Vuex с помощью:
this.$store.commit("setContacts", response.data);
this.$store
предоставляется Vuex.
Затем мы отправляем событие saved
в HomePage.vue
, чтобы закрыть модальные окна.
Страны импортируются из другого файла, а свойство contact
передается из HomePage.vue
, когда пользователь выбирает запись для редактирования.
Затем создайте папку helpers
в папке src
и добавьте файл exports.js
. Там добавьте:
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" } ];
так что у нас может быть список стран в раскрывающемся поле "Страны" в ContactForm.vue
.
Затем мы добавляем миксин, на который мы ссылались в ContactForm.vue
. Создайте папку mixins
в папке src
и добавьте файл requestsMixin.js
. Там добавьте:
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}`); } } };
Это функции для возврата обещаний для запросов, которые мы делаем к нашей серверной части.
Затем в Home.vue
замените существующий код следующим:
<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>
У нас есть таблица для отображения списка контактов из магазина. Этот компонент следит за обновлениями нашего магазина Vuex, получая их из свойства contacts
в поле computed
. Последние данные магазина Vuex всегда возвращаются туда.
Данные загружаются при первой загрузке страницы с помощью вызова функции getAllContacts
в ловушке beforeMount
. getAllContacts
установить контакты в магазине после получения из серверной части.
У нас есть кнопки для открытия и закрытия модальных окон, содержащих нашу контактную форму. Обратите внимание, что мы должны настроить форму для передачи в опору contact
в ContactForm
в модальном окне редактирования, установив this.selectedContact
в openEditModal
с переданным аргументом contact
. openEditModal
используется кнопкой Edit в каждой строке таблицы.
В каждой строке таблицы также есть кнопка «Удалить», мы передаем туда идентификатор контакта, чтобы мы могли удалить его по идентификатору.
Затем в App.vue
мы заменяем существующий код на:
<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>
В этот файл мы добавляем компонент BootstrapVue navbar
и выделяем ссылки, проверяя path
, который мы получаем в блоке watch
. Опора active
- это место, где устанавливается подсветка. Если active
равно true
, ссылка будет выделена. Мы выбираем выделение ссылки, если path
совпадает с маршрутом, по которому находится пользователь.
В блоке style
мы добавляем отступы на нашу страницу и поля для наших кнопок.
В main.js
замените существующий код на:
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");
У нас есть правила проверки Vee-Validate, добавленные здесь, и мы регистрируем наши компоненты BootstrapVue и компоненты проверки Vee-Validate здесь, чтобы мы могли использовать их в наших шаблонах.
Обратите внимание, что у нас есть:
mounted() { this.$router.push("/"); }
в объекте, переданном в конструктор Vue
, чтобы наше созданное приложение для Windows не отображало пустую страницу.
В router.js
мы заменяем существующий код на:
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 } ] });
позволить нам перейти к Home.vue
.
В store.js
замените существующий код на:
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: {} });
так что мы можем хранить контакты в магазине для быстрого доступа всех компонентов.
Теперь мы можем запустить npm run electron:serve
, чтобы запустить приложение.
Чтобы запустить серверную часть, мы сначала устанавливаем пакет json-server
, запустив npm i json-server
. Затем перейдите в папку нашего проекта и запустите:
json-server --watch db.json
В db.json
измените текст на:
{ "contacts": [ ] }
Итак, у нас есть contacts
конечные точки, определенные в requests.js
.
В итоге имеем следующее: