Node.js、Vue.js、およびMongoDBに基づいたWebソリューションを開発するためのガイドの第5部の翻訳は次のとおりです。 
第1部 、 
第2部 、 
第3部、および
第4部では、Budget Managerアプリケーションのクライアント部分とサーバー部分の段階的な作成について説明しました。 この資料の著者が最終的に何になったかを実際に見るのが待ちきれない人は、 
こちらをご覧ください さらに、 
ここに GitHubプロジェクトリポジトリがあります。 あなたが強い型付けを好む人の一人であるなら、 
ここと
ここがBudget ManagerをTypeScriptに移植した結果です。

本日、このトレーニングプロジェクトの作業は完了します。 つまり、この記事では、新しい顧客の記録と財務書類をシステムに追加するページの開発と、このデータを編集するメカニズムの作成について説明します。 ここでは、APIの改善点をいくつか見て、Budget Managerを稼働状態にします。
APIの改訂
まず、 
modelsフォルダーに移動し、 
budget.jsファイルを開きます。 モデルの
descriptionフィールドを追加します。
 description: {   type: String,   required: true }, 
app/apiフォルダーに移動し、その中にある
budget.jsファイルを開きます。 ここでは、新しいドキュメントが正しく処理されるようにデータストレージ関数
storeを編集し、ドキュメントを編集できる
edit機能を追加し、ドキュメントを削除するために必要な
remove関数を追加し、ドキュメントをフィルター処理できる
getByState関数を追加します。 完全なファイルコードを次に示します。 表示するには、対応するブロックを展開します。 将来、同じ方法で大きなコードフラグメントが発行されます。
ソースコード const mongoose = require('mongoose'); const api = {}; api.store = (User, Budget, Client, Token) => (req, res) => { if (Token) {   Client.findOne({ _id: req.body.client }, (error, client) => {     if (error) res.status(400).json(error);     if (client) {       const budget = new Budget({         client_id: req.body.client,         user_id: req.query.user_id,         client: client.name,         state: req.body.state,         description: req.body.description,         title: req.body.title,         total_price: req.body.total_price,         items: req.body.items       });       budget.save(error => {         if (error) return res.status(400).json(error)         res.status(200).json({ success: true, message: "Budget registered successfully" })       })     } else {       res.status(400).json({ success: false, message: "Invalid client" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.getAll = (User, Budget, Token) => (req, res) => { if (Token) {   Budget.find({ user_id: req.query.user_id }, (error, budget) => {     if (error) return res.status(400).json(error);     res.status(200).json(budget);     return true;   }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.getAllFromClient = (User, Budget, Token) => (req, res) => { if (Token) {   Budget.find({ client_id: req.query.client_id }, (error, budget) => {     if (error) return res.status(400).json(error);     res.status(200).json(budget);     return true;   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.index = (User, Budget, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Budget.findOne({ _id: req.query._id }, (error, budget) => {         if (error) res.status(400).json(error);         res.status(200).json(budget);       })     } else {       res.status(400).json({ success: false, message: "Invalid budget" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.edit = (User, Budget, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Budget.findOneAndUpdate({ _id: req.body._id }, req.body, (error, budget) => {         if (error) res.status(400).json(error);         res.status(200).json(budget);       })     } else {       res.status(400).json({ success: false, message: "Invalid budget" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.getByState = (User, Budget, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Budget.find({ state: req.query.state }, (error, budget) => {         console.log(budget)         if (error) res.status(400).json(error);         res.status(200).json(budget);       })     } else {       res.status(400).json({ success: false, message: "Invalid budget" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.remove = (User, Budget, Client, Token) => (req, res) => { if (Token) {   Budget.remove({ _id: req.query._id }, (error, removed) => {     if (error) res.status(400).json(error);     res.status(200).json({ success: true, message: 'Removed successfully' });   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } module.exports = api; 
 apiフォルダーからclient.jsファイルに同様の変更を加えます。
ソースコード const mongoose = require('mongoose'); const api = {}; api.store = (User, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       const client = new Client({         user_id: req.query.user_id,         name: req.body.name,         email: req.body.email,         phone: req.body.phone,       });       client.save(error => {         if (error) return res.status(400).json(error);         res.status(200).json({ success: true, message: "Client registration successful" });       })     } else {       res.status(400).json({ success: false, message: "Invalid client" })     }   }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.getAll = (User, Client, Token) => (req, res) => { if (Token) {   Client.find({ user_id: req.query.user_id }, (error, client) => {     if (error) return res.status(400).json(error);     res.status(200).json(client);     return true;   }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.index = (User, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Client.findOne({ _id: req.query._id }, (error, client) => {         if (error) res.status(400).json(error);         res.status(200).json(client);       })     } else {       res.status(400).json({ success: false, message: "Invalid client" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.edit = (User, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Client.findOneAndUpdate({ _id: req.body._id }, req.body, (error, client) => {         if (error) res.status(400).json(error);         res.status(200).json(client);       })     } else {       res.status(400).json({ success: false, message: "Invalid client" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.remove = (User, Client, Token) => (req, res) => { if (Token) {   User.findOne({ _id: req.query.user_id }, (error, user) => {     if (error) res.status(400).json(error);     if (user) {       Client.remove({ _id: req.query._id }, (error, removed) => {         if (error) res.status(400).json(error);         res.status(200).json({ success: true, message: 'Removed successfully' });       })     } else {       res.status(400).json({ success: false, message: "Invalid client" })     }   }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } module.exports = api; 
 最後に、システムに新しいルートを追加します。 これを行うには、 
routesフォルダーに移動して
budget.jsファイルを開きます。
ソースコード const passport = require('passport'),     config = require('@config'),     models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.budget; app.route('/api/v1/budget')    .post(passport.authenticate('jwt', config.session), api.store(models.User, models.Budget, models.Client, app.get('budgetsecret')))    .get(passport.authenticate('jwt', config.session), api.getAll(models.User, models.Budget, app.get('budgetsecret')))    .get(passport.authenticate('jwt', config.session), api.getAllFromClient(models.User, models.Budget, app.get('budgetsecret')))    .delete(passport.authenticate('jwt', config.session), api.remove(models.User, models.Budget, models.Client, app.get('budgetsecret'))) app.route('/api/v1/budget/single')    .get(passport.authenticate('jwt', config.session), api.index(models.User, models.Budget, models.Client, app.get('budgetsecret')))    .put(passport.authenticate('jwt', config.session), api.edit(models.User, models.Budget, models.Client, app.get('budgetsecret'))) app.route('/api/v1/budget/state')    .get(passport.authenticate('jwt', config.session), api.getByState(models.User, models.Budget, models.Client, app.get('budgetsecret'))) } 
 同じフォルダーにある
client.jsファイルに同様の変更を加えます。
ソースコード const passport = require('passport'),     config = require('@config'),     models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.client; app.route('/api/v1/client')    .post(passport.authenticate('jwt', config.session), api.store(models.User, models.Client, app.get('budgetsecret')))    .get(passport.authenticate('jwt', config.session), api.getAll(models.User, models.Client, app.get('budgetsecret')))    .delete(passport.authenticate('jwt', config.session), api.remove(models.User, models.Client, app.get('budgetsecret'))) app.route('/api/v1/client/single')   .get(passport.authenticate('jwt', config.session), api.index(models.User, models.Client, app.get('budgetsecret')))   .put(passport.authenticate('jwt', config.session), api.edit(models.User, models.Client, app.get('budgetsecret'))) } 
 APIに対して行う必要があるすべての変更です。
ルーターの改良
次に、ルートに新しいコンポーネントを追加します。 これを行うには、 
routerフォルダー内にある
index.jsファイルを開きます。
ソースコード ... // Global components import Header from '@/components/Header' import List from '@/components/List/List' import Create from '@/components/pages/Create' // Register components Vue.component('app-header', Header) Vue.component('list', List) Vue.component('create', Create) Vue.use(Router) const router = new Router({ routes: [   {     path: '/',     name: 'Home',     components: {       default: Home,       header: Header,       list: List,       create: Create     }   },   {     path: '/login',     name: 'Authentication',     component: Authentication   } ] }) … 
 ここで、 
Createコンポーネントをインポートして定義し、 
Homeルートコンポーネントに割り当てました(以下でコンポーネントを作成します)。
新しいコンポーネントの作成
componentコンポーネントの作成
Createコンポーネントから始めましょう。 
components/pagesフォルダーに移動し、 
Create.vue新しい
Create.vueファイルを作成します。
ソースコード <template> <div class="l-create-page">   <budget-creation v-if="budgetCreation && !editPage" slot="budget-creation" :clients="clients" :saveBudget="saveBudget"></budget-creation>   <client-creation v-if="!budgetCreation && !editPage" slot="client-creation" :saveClient="saveClient"></client-creation>   <budget-edit v-else-if="budgetEdit && editPage"     slot="budget-creation"     :clients="clients"     :selectedBudget="budget"     :fixClientNameAndUpdate="fixClientNameAndUpdate">   </budget-edit>   <client-edit v-else-if="!budgetEdit && editPage"     slot="client-creation"     :selectedClient="client"     :updateClient="updateClient">   </client-edit> </div> </template> <script> import BudgetCreation from './../Creation/BudgetCreation' import ClientCreation from './../Creation/ClientCreation' import BudgetEdit from './../Creation/BudgetEdit' import ClientEdit from './../Creation/ClientEdit' export default {   props: [     'budgetCreation', 'clients', 'saveBudget',     'saveClient', 'budget', 'client', 'updateClient',     'fixClientNameAndUpdate', 'editPage', 'budgetEdit'   ],   components: {     'budget-creation': BudgetCreation,     'client-creation': ClientCreation,     'budget-edit': BudgetEdit,     'client-edit': ClientEdit   } } </script> 
 最初の名前付きスロットは
budget-creationです。 これは、新しい財務ドキュメントを作成するために使用するコンポーネントを表します。 
budgetCreationプロパティ
budgetCreation true設定され、 
editPageが
falseに設定されている場合にのみ表示され
false 。すべてのクライアントと
saveBudgetメソッドを
saveBudgetます。
2番目の名前付きスロットは
client-creationです。 これは、新しい顧客を作成するために使用されるコンポーネントです。 
budgetCreationプロパティ
budgetCreation falseに設定され、 
editPageも
false場合にのみ表示され
false 。 ここで、saveClientメソッドを渡します。
3番目の名前付きスロットは
budget-editです。 これは、選択したドキュメントの編集に使用されるコンポーネントです。 
editPageと
editPage true設定されて
editPage場合にのみ
budgetEditされ
true 。 ここでは、すべてのクライアント、選択した財務ドキュメント、および
fixClientNameAndUpdateメソッドを転送します。
そして最後に、顧客情報の編集に使用される最後の名前付きスロットがあります。 
budgetEditプロパティ
budgetEdit falseに設定され、 
editPageが
true設定されている
trueされ
true 。 選択したクライアントと
updateClientメソッドを渡します。
予算BudgetCreationコンポーネント
新しい財務ドキュメントの作成に使用されるコンポーネントを開発します。 
componentsフォルダーに移動して、その中に新しいフォルダーを作成し、それに
Creationという名前を付けます。 このフォルダーで、 
BudgetCreation.vueコンポーネント
BudgetCreation.vue作成し
BudgetCreation.vue 。
コンポーネントは非常に大きいため、テンプレートから始めて段階的に分析します。
BudgetCreationコンポーネントテンプレートコンポーネントテンプレートコードは次のとおりです <template> <div class="l-budget-creation">   <v-layout row wrap>     <span class="md-budget-state-hint uppercased white--text">status</span>     <v-flex xs12 md2>       <v-select         label="Status"         :items="states"         v-model="budget.state"       >       </v-select>     </v-flex>     <v-flex xs12 md9 offset-md1>       <v-select         label="Client"         :items="clients"         v-model="budget.client"         item-text="name"         item-value="_id"       >       </v-select>     </v-flex>     <v-flex xs12 md12>       <v-text-field label="Title"                     v-model="budget.title"                     required                     color="light-blue lighten-1">       </v-text-field>       <v-text-field label="Description"                     v-model="budget.description"                     textarea                     required                     color="light-blue lighten-1">       </v-text-field>     </v-flex>     <v-layout row wrap v-for="item in budget.items" class="l-budget-item" :key="item.id">       <v-flex xs12 md1>         <v-btn block dark color="red lighten-1" @click.native="removeItem(item)">Remove</v-btn>       </v-flex>       <v-flex xs12 md3 offset-md1>         <v-text-field label="Title"                       box dark                       v-model="item.title"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md1 offset-md1>         <v-text-field label="Price"                       box dark                       prefix="$"                       v-model="item.price"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md2 offset-md1>         <v-text-field label="Quantity"                       box dark                       min="0"                       v-model="item.quantity"                       type="number"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md2>         <span class="md-budget-item-subtotal white--text">ITEM PRICE $ {{ item.subtotal }}</span>       </v-flex>     </v-layout>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn light-blue lighten-1" @click.native="addItem()">Add item</v-btn>     </v-flex>     <v-flex xs12 md2 offset-md10>       <span class="md-budget-item-total white--text">TOTAL $ {{ budget.total_price }}</span>     </v-flex>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn green lighten-1" @click.native="saveBudget(budget)">Save</v-btn>     </v-flex>   </v-layout> </div> </template> 
 ここでは、最初に
v-select要素をテンプレートに追加してドキュメントの状態を設定し、次に
v-selectを使用して必要なクライアントを選択します。 次に、ドキュメントのタイトルを入力する
v-text-fieldと説明を表示する
v-text-fieldがあります。
次に、 
budget.items要素を
budget.itemsします。これにより、ドキュメントに要素を追加して削除する機会が与えられます。 また、 
removeItem関数を呼び出して、削除する要素を渡すことができる赤いボタンもあります。
さらに、製品名、単価、数量をそれぞれ対象とする3つの
v-text-fieldsがあります。
シリーズの最後には単純な
span要素があり、商品の数量と価格の積である
subtotalという行に
subtotalを表示します。
製品リストの下にはさらに3つのアイテムがあります。 これは、 
addItem関数、ドキュメント内のすべての商品の合計コストを表示する
span要素(すべての要素の
subtotal合計)を呼び出して新しい要素を追加するために使用される青いボタン、およびドキュメントをデータベースに保存するために使用される緑のボタン
saveBudget関数を呼び出して、保存するドキュメントをパラメーターとして渡します。
BudgetCreationコンポーネントスクリプトBudgetCreationコンポーネントを強化するコードは次のとおりです。 <script> export default {   props: ['clients', 'saveBudget'],   data () {     return {       budget: {         title: null,         description: null,         state: 'writing',         client: null,         get total_price () {           let value = 0           this.items.forEach(({ subtotal }) => {             value += parseInt(subtotal)           })           return value         },         items: [           {             title: null,             quantity: 0,             price: 0,             get subtotal () {               return this.quantity * this.price             }           }         ]       },       states: [         'writing', 'editing', 'pending', 'approved', 'denied', 'waiting'       ]     }   },   methods: {     addItem () {       const items = this.budget.items       const item = {         title: '',         quantity: 0,         price: 0,         get subtotal () {           return this.quantity * this.price         }       }       items.push(item)     },     removeItem (selected) {       const items = this.budget.items       items.forEach((item, index) => {         if (item === selected) {           items.splice(index, 1)         }       })     }   } } </script> 
 このコードでは、 
clientsと
saveBudget 2つのプロパティを最初に取得し
clients 。 これらのプロパティのソースは、 
Homeコンポーネントです。
次に、データの役割を果たすオブジェクトと配列を定義します。 オブジェクトの名前は
budgetです。 ドキュメントの作成に使用され、値を追加してデータベースに保存できます。 このオブジェクトには、プロパティ
title (title)、 
description (description)、 
state (デフォルトのstateを
writing設定)、 
client (client)、 
total_price (ドキュメントの総コスト)、items 
items配列があり
items 。 商品には、 
title (名前)、 
quantity (数量)、 
price (価格)、および
subtotal (小計)のプロパティがあります。
ここでは、ドキュメントの
statesの配列
statesが定義されています。 その値は、ドキュメントの状態を設定するために使用されます。 これらの状態は、 
writing 、 
editing pending 、 
pending 、 
approved 、 
denied 、 
waitingです。
以下に、データ構造の説明の後に、 
addItem (製品を追加するための)と
removeItem (それらを削除するための)の2つのメソッドがあり
addItem 。
青いボタンをクリックするたびに、 
addItemメソッドが
addItemメソッドは、要素を
budgetオブジェクト内の
items配列に追加します。
removeItemメソッドは反対のアクションを実行します。 つまり、赤いボタンをクリックすると、指定されたアイテムが
items配列から削除され
items 。
BudgetCreationコンポーネントスタイル問題のコンポーネントのスタイルは次のとおりです。 <style lang="scss"> @import "./../../assets/styles"; .uppercased {   text-transform: uppercase; } .l-budget-creation {   label, input, .icon, .input-group__selections__comma, textarea {     color: #29b6f6!important;   }   .input-group__details {     &:before {       background-color: $border-color-input !important;     }   }   .input-group__input {     border-color: $border-color-input !important;     .input-group--text-field__prefix {       margin-bottom: 3px !important;     }   }   .input-group--focused {     .input-group__input {       border-color: #29b6f6!important;     }   } } .md-budget-state-hint {   margin: 10px 0;   display: block;   width: 100%; } .md-budget-state {   background-color: rgba(41, 182, 246, .6);   display: flex;   height: 35px;   width: 100%;   font-size: 14px;   align-items: center;   justify-content: center;   border-radius: 2px;   margin: 10px 0 15px; } .l-budget-item {   align-items: center; } .md-budget-item-subtotal {   font-size: 16px;   text-align: center;   display: block; } .md-budget-item-total {   font-size: 22px;   text-align: center;   display: block;   width: 100%;   margin: 30px 0 10px; } .md-add-item-btn {   margin-top: 30px !important;   display: block; } .list__tile__title, .input-group__selections {   text-transform: uppercase !important; } </style> 
 次に、次のコンポーネントを検討します。
▍ClientCreationコンポーネント
実際、このコンポーネントは、レビューしたばかりの
BudgetCreationコンポーネントの簡易バージョンです。 上記で行ったように、我々はそれを部分的に検討します。 
BudgetCreationコンポーネントを理解すれば、 
BudgetCreationを簡単に理解できます。
ClientCreationコンポーネントテンプレート <template> <div class="l-client-creation">   <v-layout row wrap>     <v-flex xs12 md4>       <v-text-field label="Name"                     v-model="client.name"                     required                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md3 offset-md1>       <v-text-field label="Email"                     v-model="client.email"                     required                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md3 offset-md1>       <v-text-field label="Phone"                     v-model="client.phone"                     required                     mask="phone"                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn green lighten-1" @click.native="saveClient(client)">Save</v-btn>     </v-flex>   </v-layout> </div> </template> 
 ClientCreationコンポーネントスクリプト <script> export default {   props: ['saveClient'],   data () {     return {       client: {         name: null,         email: null,         phone: null       }     }   } } </script> 
 ClientCreationコンポーネントスタイル <style lang="scss"> @import "./../../assets/styles"; .uppercased {   text-transform: uppercase; } .l-client-creation {   label, input, .icon, .input-group__selections__comma, textarea {     color: #66bb6a!important;   }   .input-group__details {     &:before {       background-color: $border-color-input !important;     }   }   .input-group__input {     border-color: $border-color-input !important;     .input-group 
 次に、 
BudgetEditコンポーネントの
BudgetEditです。
▍BudgetEditコンポーネント
実際、このコンポーネントは、すでに考慮されている
BudgetCreationコンポーネントの修正バージョンです。 そのコンポーネントを検討してください。
BudgetEditコンポーネントテンプレート <template> <div class="l-budget-creation">   <v-layout row wrap>     <span class="md-budget-state-hint uppercased white--text">status</span>     <v-flex xs12 md2>       <v-select         label="Status"         :items="states"         v-model="budget.state"       >       </v-select>     </v-flex>     <v-flex xs12 md9 offset-md1>       <v-select         label="Client"         :items="clients"         v-model="budget.client_id"         item-text="name"         item-value="_id"       >       </v-select>     </v-flex>     <v-flex xs12 md12>       <v-text-field label="Title"                     v-model="budget.title"                     required                     color="light-blue lighten-1">       </v-text-field>       <v-text-field label="Description"                     v-model="budget.description"                     textarea                     required                     color="light-blue lighten-1">       </v-text-field>     </v-flex>     <v-layout row wrap v-for="item in budget.items" class="l-budget-item" :key="item.id">       <v-flex xs12 md1>         <v-btn block dark color="red lighten-1" @click.native="removeItem(item)">Remove</v-btn>       </v-flex>       <v-flex xs12 md3 offset-md1>         <v-text-field label="Title"                       box dark                       v-model="item.title"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md1 offset-md1>         <v-text-field label="Price"                       box dark                       prefix="$"                       v-model="item.price"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md2 offset-md1>         <v-text-field label="Quantity"                       box dark                       min="0"                       v-model="item.quantity"                       type="number"                       required                       color="light-blue lighten-1">         </v-text-field>       </v-flex>       <v-flex xs12 md2>         <span class="md-budget-item-subtotal white--text">ITEM PRICE $ {{ item.subtotal }}</span>       </v-flex>     </v-layout>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn light-blue lighten-1" @click.native="addItem()">Add item</v-btn>     </v-flex>     <v-flex xs12 md2 offset-md10>       <span class="md-budget-item-total white--text">TOTAL $ {{ budget.total_price }}</span>     </v-flex>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn green lighten-1" @click.native="fixClientNameAndUpdate(budget)">Update</v-btn>     </v-flex>   </v-layout> </div> </template> 
 BudgetEditと
BudgetCreationコンポーネントテンプレートの唯一の違いは、保存ボタンとその関連ロジックです。 つまり、 
BudgetCreationでは
Saveと表示され、 
saveBudgetメソッドが呼び出され
Save 。 
BudgetEditこのボタンに碑文の
Updateが表示され、 
fixClientNameAndUpdateメソッドが
fixClientNameAndUpdateます。
BudgetCreationコンポーネントスクリプト <script> export default {   props: ['clients', 'fixClientNameAndUpdate', 'selectedBudget'],   data () {     return {       budget: {         title: null,         description: null,         state: 'pending',         client: null,         get total_price () {           let value = 0           this.items.forEach(({ subtotal }) => {             value += parseInt(subtotal)           })           return value         },         items: [           {             title: null,             quantity: 0,             price: null,             get subtotal () {               return this.quantity * this.price             }           }         ]       },       states: [         'writing', 'editing', 'pending', 'approved', 'denied', 'waiting'       ]     }   },   mounted () {     this.parseBudget()   },   methods: {     addItem () {       const items = this.budget.items       const item = {         title: '',         quantity: 0,         price: 0,         get subtotal () {           return this.quantity * this.price         }       }       items.push(item)     },     removeItem (selected) {       const items = this.budget.items       items.forEach((item, index) => {         if (item === selected) {           items.splice(index, 1)         }       })     },     parseBudget () {       for (let key in this.selectedBudget) {         if (key !== 'total' && key !== 'items') {           this.budget[key] = this.selectedBudget[key]         }         if (key === 'items') {           const items = this.selectedBudget.items           const buildItems = item => ({             title: item.title,             quantity: item.quantity,             price: item.price,             get subtotal () {               return this.quantity * this.price             }           })           const parseItems = items => items.map(buildItems)           this.budget.items = parseItems(items)         }       }     }   } } </script> 
 すべて3つのプロパティで始まります。 これらは、 
clients 、 
fixClientNameAndUpdate 、および
selectedBudgetです。 このデータは、 
BudgetCreationコンポーネントのデータと同じです。 つまり、 
Budgetオブジェクトと
states配列があり
states 。
さらに、ここでは、 
mountedコンポーネントライフサイクルイベントハンドラーを確認できます。このハンドラーでは、 
parseBudgetメソッドを呼び出します。 最後に、 
BudgetCreationコンポーネントでおなじみの
addItemと
removeItemメソッド、および新しい
parseBudgetメソッドを含む
methodsオブジェクトがあり
addItem 。 このメソッドは、 
budgetオブジェクトの値を
selectedBudgetプロパティで渡される値に設定するために使用
selectedBudgetますが、ドキュメントの商品の小計とドキュメントの合計金額の計算にも使用されます。
BudgetCreationコンポーネントスタイル <style lang="scss"> @import "./../../assets/styles"; .uppercased {   text-transform: uppercase; } .l-budget-creation {   label, input, .icon, .input-group__selections__comma, textarea {     color: #29b6f6!important;   }   .input-group__details {     &:before {       background-color: $border-color-input !important;     }   }   .input-group__input {     border-color: $border-color-input !important;     .input-group--text-field__prefix {       margin-bottom: 3px !important;     }   }   .input-group--focused {     .input-group__input {       border-color: #29b6f6!important;     }   } } .md-budget-state-hint {   margin: 10px 0;   display: block;   width: 100%; } .md-budget-state {   background-color: rgba(41, 182, 246, .6);   display: flex;   height: 35px;   width: 100%;   font-size: 14px;   align-items: center;   justify-content: center;   border-radius: 2px;   margin: 10px 0 15px; } .l-budget-item {   align-items: center; } .md-budget-item-subtotal {   font-size: 16px;   text-align: center;   display: block; } .md-budget-item-total {   font-size: 22px;   text-align: center;   display: block;   width: 100%;   margin: 30px 0 10px; } .md-add-item-btn {   margin-top: 30px !important;   display: block; } .list__tile__title, .input-group__selections {   text-transform: uppercase !important; } </style> 
 ▍ClientEditコンポーネント
検討したばかりのこのコンポーネントは、クライアントの作成に使用する対応するコンポーネント
ClientCreation似ています。 主な違いは、 
saveClientメソッドの代わりに
updateClientメソッドが
updateClientです。 デバイスコンポーネント
ClientEdit検討してください。
ClientEditコンポーネントテンプレート <template> <div class="l-client-creation">   <v-layout row wrap>     <v-flex xs12 md4>       <v-text-field label="Name"                     v-model="client.name"                     required                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md3 offset-md1>       <v-text-field label="Email"                     v-model="client.email"                     required                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md3 offset-md1>       <v-text-field label="Phone"                     v-model="client.phone"                     required                     mask="phone"                     color="green lighten-1">       </v-text-field>     </v-flex>     <v-flex xs12 md2 offset-md10>       <v-btn block color="md-add-item-btn green lighten-1" @click.native="updateClient(client)">Update</v-btn>     </v-flex>   </v-layout> </div> </template> 
 ClientEditコンポーネントスクリプト <script> export default {   props: ['updateClient', 'selectedClient'],   data () {     return {       client: {         name: null,         email: null,         phone: null       }     }   },   mounted () {     this.client = this.selectedClient   } } </script> 
 ClientEditコンポーネントスタイル <style lang="scss"> @import "./../../assets/styles"; .uppercased {   text-transform: uppercase; } .l-client-creation {   label, input, .icon, .input-group__selections__comma, textarea {     color: #66bb6a!important;   }   .input-group__details {     &:before {       background-color: $border-color-input !important;     }   }   .input-group__input {     border-color: $border-color-input !important;     .input-group 
 これにより、新しいコンポーネントの作成が完了し、システムに既に存在していたコンポーネントの処理に進みます。
既存のコンポーネントの改良
これで、既存のコンポーネントにいくつかの変更を加えるだけで、アプリケーションを使用する準備が整います。
ListBodyコンポーネントから始めましょう。
▍ListBodyコンポーネント
ListBodyコンポーネントテンプレートこのコンポーネントのコードは
ListBody.vueファイルに保存されていることを思い出してください
ソースコード <template> <section class="l-list-body">   <div class="md-list-item"        v-if="data != null && parsedBudgets === null"        v-for="item in data">     <div :class="budgetsVisible ? 'md-budget-info white--text' : 'md-client-info white--text'"           v-for="info in item"           v-if="info != item._id && info != item.client_id">       {{ info }}     </div>     <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'">       <v-btn small flat color="yellow accent-1" @click.native="getItemAndEdit(item)">         <v-icon>mode_edit</v-icon>       </v-btn>       <v-btn small flat color="red lighten-1" @click.native="deleteItem(item, data, budgetsVisible)">         <v-icon>delete_forever</v-icon>       </v-btn>     </div>   </div>   <div class="md-list-item"        v-if="parsedBudgets !== null"        v-for="item in parsedBudgets">     <div :class="budgetsVisible ? 'md-budget-info white--text' : 'md-client-info white--text'"           v-for="info in item"           v-if="info != item._id && info != item.client_id">       {{ info }}     </div>     <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'">       <v-btn small flat color="yellow accent-1" @click.native="getItemAndEdit(item)">         <v-icon>mode_edit</v-icon>       </v-btn>       <v-btn small flat color="red lighten-1" @click.native="deleteItem(item, data, budgetsVisible)">         <v-icon>delete_forever</v-icon>       </v-btn>     </div>   </div> </section> </template> 
 このコンポーネントでは、いくつかの変更と追加を実行するだけで済みます。 そのため、最初に
md-list-itemブロックの
v-ifコンストラクトに新しい条件を追加します。
 parsedBudgets === null 
さらに、編集ボタンをクリックしてドキュメントを表示できるため、ドキュメントを表示するために使用した最初のボタンを削除します。
ここでは、 
getItemAndEditメソッドを新しい最初のボタンに、 
deleteItemメソッドを最後のボタンに追加して、この要素、データ、および
budgetsVisible変数をパラメーターとして
budgetsVisibleます。
この下には
md-item-listブロックがあり、検索後にフィルター処理されたドキュメントのリストを表示するために使用します。
ListBodyコンポーネントスクリプト <script> export default {   props: ['data', 'budgetsVisible', 'deleteItem', 'getBudget', 'getClient', 'parsedBudgets'],   methods: {     getItemAndEdit (item) {       !item.phone ? this.getBudget(item) : this.getClient(item)     }   } } </script> 
 このコンポーネントでは、多くのプロパティを取得します。 それらについて説明します。
data :これはドキュメントのリストまたはクライアントのリストのいずれかですが、両方ではありません。
budgetsVisible :ドキュメントまたはクライアントのリストを表示しているかどうかを確認するために使用されますtrueまたはfalse場合がありfalse 。
deleteItem :パラメーターとして要素を取る要素を削除する関数。
getBudget :編集する予定の単一のドキュメントをロードするために使用する関数。
getClient :さらに編集するために個々のクライアントのカードをロードするために使用される関数。
parsedBudgets :検索の実行後にフィルター処理されたドキュメント。
コンポーネントには
getItemAndEdit 1つだけあります。 彼は、パラメータとして要素を受け取り、電話番号を含む要素のプロパティの分析に基づいて、要素が顧客のカードであるか財務書類であるかを決定します。
ListBodyコンポーネントスタイル <style lang="scss"> @import "./../../assets/styles"; .l-list-body {   display: flex;   flex-direction: column;   .md-list-item {     width: 100%;     display: flex;     flex-direction: column;     margin: 15px 0;     @media (min-width: 960px) {       flex-direction: row;       margin: 0;     }     .md-budget-info {       flex-basis: 25%;       width: 100%;       background-color: rgba(0, 175, 255, 0.45);       border: 1px solid $border-color-input;       padding: 0 15px;       display: flex;       height: 35px;       align-items: center;       justify-content: center;       &:first-of-type, &:nth-of-type(2) {         text-transform: capitalize;       }       &:nth-of-type(3) {         text-transform: uppercase;       }       @media (min-width: 601px) {         justify-content: flex-start;       }     }     .md-client-info {       @extend .md-budget-info;       background-color: rgba(102, 187, 106, 0.45)!important;       &:nth-of-type(2) {         text-transform: none;       }     }     .l-budget-actions {       flex-basis: 25%;       display: flex;       background-color: rgba(0, 175, 255, 0.45);       border: 1px solid $border-color-input;       align-items: center;       justify-content: center;       .btn {         min-width: 45px !important;         margin: 0 5px !important;       }     }     .l-client-actions {       @extend .l-budget-actions;       background-color: rgba(102, 187, 106, 0.45)!important;     }   } } </style> 
 ListBody , 
Header .
▍ Header
Header <template> <header class="l-header-container">   <v-layout row wrap :class="budgetsVisible ? 'l-budgets-header' : 'l-clients-header'">     <v-flex xs12 md5>       <v-text-field v-model="searchValue"                     label="Search"                     append-icon="search"                     :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'">       </v-text-field>     </v-flex>     <v-flex xs12 offset-md1 md1>       <v-btn block              :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'"              @click.native="$emit('toggleVisibleData')">              {{ budgetsVisible ? "Clients" : "Budgets" }}       </v-btn>     </v-flex>     <v-flex xs12 offset-md1 md2>       <v-select label="Status"                 :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'"                 v-model="status"                 :items="statusItems"                 single-line                 @change="selectState">       </v-select>     </v-flex>     <v-flex xs12 offset-md1 md1>       <v-btn block color="red lighten-1 white--text" @click.native="submitSignout()">Sign out</v-btn>     </v-flex>   </v-layout> </header> </template> 
 , , 
v-model searchValue .
, 
v-select , 
change selectState .
Header <script> import Authentication from '@/components/pages/Authentication' export default {   props: ['budgetsVisible', 'selectState', 'search'],   data () {     return {       searchValue: '',       status: '',       statusItems: [         'all', 'approved', 'denied', 'waiting', 'writing', 'editing'       ]     }   },   watch: {     'searchValue': function () {       this.$emit('input', this.searchValue)     }   },   created () {     this.searchValue = this.search   },   methods: {     submitSignout () {       Authentication.signout(this, '/login')     }   } } </script> 
 — 
selectState , , 
search , . 
search searchValue statusItems .
Header <script> import Authentication from '@/components/pages/Authentication' export default {   props: ['budgetsVisible', 'selectState', 'search'],   data () {     return {       searchValue: '',       status: '',       statusItems: [         'all', 'approved', 'denied', 'waiting', 'writing', 'editing'       ]     }   },   watch: {     'searchValue': function () {       this.$emit('input', this.searchValue)     }   },   created () {     this.searchValue = this.search   },   methods: {     submitSignout () {       Authentication.signout(this, '/login')     }   } } </script> 
 Header , 
Home .
▍ Home
Home <template> <main class="l-home-page">   <app-header :budgetsVisible="budgetsVisible"     @toggleVisibleData="budgetsVisible = !budgetsVisible; budgetCreation = !budgetCreation"     :selectState="selectState"     :search="search"     v-model="search">   </app-header>   <div class="l-home">     <h4 class="white--text text-xs-center my-0">       Focus Budget Manager     </h4>     <list v-if="listPage">       <list-header slot="list-header" :headers="budgetsVisible ? budgetHeaders : clientHeaders"></list-header>       <list-body slot="list-body"                  :budgetsVisible="budgetsVisible"                  :data="budgetsVisible ? budgets : clients"                  :search="search"                  :deleteItem="deleteItem"                  :getBudget="getBudget"                  :getClient="getClient"                  :parsedBudgets="parsedBudgets">       </list-body>     </list>     <create v-else-if="createPage"       :budgetCreation="budgetCreation"       :budgetEdit="budgetEdit"       :editPage="editPage"       :clients="clients"       :budget="budget"       :client="client"       :saveBudget="saveBudget"       :saveClient="saveClient"       :fixClientNameAndUpdate="fixClientNameAndUpdate"       :updateClient="updateClient">     </create>   </div>   <v-snackbar :timeout="timeout"               bottom="bottom"               :color="snackColor"               v-model="snackbar">     {{ message }}   </v-snackbar>   <v-fab-transition>     <v-speed-dial v-model="fab"                   bottom                   right                   fixed                   direction="top"                   transition="scale-transition">         <v-btn slot="activator"                color="red lighten-1"                dark                fab                v-model="fab">               <v-icon>add</v-icon>               <v-icon>close</v-icon>         </v-btn>         <v-tooltip left>           <v-btn color="light-blue lighten-1"                  dark                  small                  fab                  slot="activator"                  @click.native="budgetCreation = true; listPage = false; editPage = false; createPage = true">                 <v-icon>assignment</v-icon>           </v-btn>           <span>Add new Budget</span>         </v-tooltip>         <v-tooltip left>           <v-btn color="green lighten-1"                  dark                  small                  fab                  slot="activator"                  @click.native="budgetCreation = false; listPage = false; editPage = false; createPage = true">                 <v-icon>account_circle</v-icon>           </v-btn>           <span>Add new Client</span>         </v-tooltip>         <v-tooltip left>           <v-btn color="purple lighten-2"                  dark                  small                  fab                  slot="activator"                  @click.native="budgetCreation = false; listPage = true; budgetsVisible = true">                 <v-icon>assessment</v-icon>           </v-btn>           <span>List Budgets</span>         </v-tooltip>         <v-tooltip left>           <v-btn color="deep-orange lighten-2"                  dark                  small                  fab                  slot="activator"                  @click.native="budgetCreation = false; listPage = true; budgetsVisible = false;">                 <v-icon>supervisor_account</v-icon>           </v-btn>           <span>List Clients</span>         </v-tooltip>     </v-speed-dial>   </v-fab-transition> </main> </template> 
 , . 
budgetsVisible , 
selectState , 
search toggleVisibleData , , 
toggleVisibleData , 
v-model search .
list v-if , , . , 
list-body .
create , 
list , , . , .
v-fab-transition , , .
Home <script> import Axios from 'axios' import Authentication from '@/components/pages/Authentication' import ListHeader from './../List/ListHeader' import ListBody from './../List/ListBody' const BudgetManagerAPI = `http://${window.location.hostname}:3001` export default {   components: {     'list-header': ListHeader,     'list-body': ListBody   },   data () {     return {       parsedBudgets: null,       budget: null,       client: null,       state: null,       search: null,       budgets: [],       clients: [],       budgetHeaders: ['Client', 'Title', 'Status', 'Actions'],       clientHeaders: ['Client', 'Email', 'Phone', 'Actions'],       budgetsVisible: true,       snackbar: false,       timeout: 6000,       message: '',       fab: false,       listPage: true,       createPage: true,       editPage: false,       budgetCreation: true,       budgetEdit: true,       snackColor: 'red lighten-1'     }   },   mounted () {     this.getAllBudgets()     this.getAllClients()     this.hidden = false   },   watch: {     'search': function () {       if (this.search !== null || this.search !== '') {         const searchTerm = this.search         const regex = new RegExp(`^(${searchTerm})`, 'g')         const results = this.budgets.filter(budget => budget.client.match(regex))         this.parsedBudgets = results       } else {         this.parsedBudgets = null       }     }   },   methods: {     getAllBudgets () {       Axios.get(`${BudgetManagerAPI}/api/v1/budget`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       }).then(({data}) => {         this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state', 'client_id')       }).catch(error => {         this.errorHandler(error)       })     },     getAllClients () {       Axios.get(`${BudgetManagerAPI}/api/v1/client`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       }).then(({data}) => {         this.clients = this.dataParser(data, 'name', 'email', '_id', 'phone')       }).catch(error => {         this.errorHandler(error)       })     },     getBudget (budget) {       Axios.get(`${BudgetManagerAPI}/api/v1/budget/single`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: {           user_id: this.$cookie.get('user_id'),           _id: budget._id         }       }).then(({data}) => {         this.budget = data         this.enableEdit('budget')       }).catch(error => {         this.errorHandler(error)       })     },     getClient (client) {       Axios.get(`${BudgetManagerAPI}/api/v1/client/single`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: {           user_id: this.$cookie.get('user_id'),           _id: client._id         }       }).then(({data}) => {         this.client = data         this.enableEdit('client')       }).catch(error => {         this.errorHandler(error)       })     },     enableEdit (type) {       if (type === 'budget') {         this.listPage = false         this.budgetEdit = true         this.budgetCreation = false         this.editPage = true       } else if (type === 'client') {         this.listPage = false         this.budgetEdit = false         this.budgetCreation = false         this.editPage = true       }     },     saveBudget (budget) {       Axios.post(`${BudgetManagerAPI}/api/v1/budget`, budget, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       })       .then(res => {         this.resetFields(budget)         this.snackbar = true         this.message = res.data.message         this.snackColor = 'green lighten-1'         this.getAllBudgets()       })       .catch(error => {         this.errorHandler(error)       })     },     fixClientNameAndUpdate (budget) {       this.clients.find(client => {         if (client._id === budget.client_id) {           budget.client = client.name         }       })       this.updateBudget(budget)     },     updateBudget (budget) {       Axios.put(`${BudgetManagerAPI}/api/v1/budget/single`, budget, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       })       .then(() => {         this.snackbar = true         this.message = 'Budget updated'         this.snackColor = 'green lighten-1'         this.listPage = true         this.budgetCreation = false         this.budgetsVisible = true         this.getAllBudgets()       })       .catch(error => {         this.errorHandler(error)       })     },     updateClient (client) {       Axios.put(`${BudgetManagerAPI}/api/v1/client/single`, client, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       })       .then(() => {         this.snackbar = true         this.message = 'Client updated'         this.snackColor = 'green lighten-1'         this.listPage = true         this.budgetCreation = false         this.budgetsVisible = false         this.getAllClients()       })       .catch(error => {         this.errorHandler(error)       })     },     saveClient (client) {       Axios.post(`${BudgetManagerAPI}/api/v1/client`, client, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id') }       })       .then(res => {         this.resetFields(client)         this.snackbar = true         this.message = res.data.message         this.snackColor = 'green lighten-1'         this.getAllClients()       })       .catch(error => {         this.errorHandler(error)       })     },     deleteItem (selected, items, api) {       let targetApi = ''       api ? targetApi = 'budget' : targetApi = 'client'       Axios.delete(`${BudgetManagerAPI}/api/v1/${targetApi}`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: {           user_id: this.$cookie.get('user_id'),           _id: selected._id         }       })       .then(() => {         this.removeItem(selected, items)       })       .then(() => {         api ? this.getAllBudgets() : this.getAllClients()       })       .catch(error => {         this.errorHandler(error)       })     },     errorHandler (error) {       const status = error.response.status       this.snackbar = true       this.snackColor = 'red lighten-1'       if (status === 404) {         this.message = 'Invalid request'       } else if (status === 401 || status === 403) {         this.message = 'Unauthorized'       } else if (status === 400) {         this.message = 'Invalid or missing information'       } else {         this.message = error.message       }     },     removeItem (selected, items) {       items.forEach((item, index) => {         if (item === selected) {           items.splice(index, 1)         }       })     },     dataParser (targetedArray, ...options) {       let parsedData = []       targetedArray.forEach(item => {         let parsedItem = {}         options.forEach(option => (parsedItem[option] = item[option]))         parsedData.push(parsedItem)       })       return parsedData     },     resetFields (item) {       for (let key in item) {         item[key] = null         if (key === 'quantity' || key === 'price') {           item[key] = 0         }         item['items'] = []       }     },     selectState (state) {       this.state = state       state === 'all' ? this.getAllBudgets() : this.getBudgetsByState(state)     },     getBudgetsByState (state) {       Axios.get(`${BudgetManagerAPI}/api/v1/budget/state`, {         headers: { 'Authorization': Authentication.getAuthenticationHeader(this) },         params: { user_id: this.$cookie.get('user_id'), state }       }).then(({data}) => {         this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state', 'client_id')       }).catch(error => {         this.errorHandler(error)       })     }   } } </script> 
 . .
parsedBudgets : , .
budget : , .
client : , .
state : , , .
search : , .
budgets : , API.
clients : , API.
budgetHeaders : , .
clientHeaders : , , .
budgetsVisible : , .
snackbar : .
timeout : - .
message : , .
fab : , false .
listPage : , , true .
createPage : , , false .
editPage : , , false .
budgetCreation : , , true .
budgetEdit : , , true .
snackColor : .
, , , 
mounted , .
, , 
watch . 
@mrmonkeytech , ( ).
.
getAllBudgets dataParser , errorHandler catch . getAllClients .
getBudget getClient , API.
enableEdit , , , .
saveBudget saveClient , , .
fixClientNameAndUpdate , ID , updateBudget .
updateBudget .
updateClient .
deleteItem . , selected , items ( ), api .
errorHandler .
removeItem deleteItem , .
dataParser , , .
resetFields . , , .
selectState v-select Header .
getBudgetsByState selectState , .
Home <style lang="scss"> @import "./../../assets/styles"; .l-home {   background-color: $background-color;   margin: 25px auto;   padding: 15px;   min-width: 272px; } .snack__content {   justify-content: center !important; } </style> 
 まとめ
- Budget Manager . .
, 
, — 
.
, , , , .
親愛なる読者! , ?
