NodeおよびVueのWebアプリケーション、パート5:プロジェクトの完成

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設定され、 editPagefalseに設定されている場合にのみ表示されfalse 。すべてのクライアントとsaveBudgetメソッドをsaveBudgetます。

2番目の名前付きスロットはclient-creationです。 これは、新しい顧客を作成するために使用されるコンポーネントです。 budgetCreationプロパティbudgetCreation falseに設定され、 editPagefalse場合にのみ表示されfalse 。 ここで、saveClientメソッドを渡します。

3番目の名前付きスロットはbudget-editです。 これは、選択したドキュメントの編集に使用されるコンポーネントです。 editPageeditPage true設定されてeditPage場合にのみbudgetEditされtrue 。 ここでは、すべてのクライアント、選択した財務ドキュメント、およびfixClientNameAndUpdateメソッドを転送します。

そして最後に、顧客情報の編集に使用される最後の名前付きスロットがあります。 budgetEditプロパティbudgetEdit falseに設定され、 editPagetrue設定されている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> 

このコードでは、 clientssaveBudget 2つのプロパティを最初に取得しclients 。 これらのプロパティのソースは、 Homeコンポーネントです。

次に、データの役割を果たすオブジェクトと配列を定義します。 オブジェクトの名前はbudgetです。 ドキュメントの作成に使用され、値を追加してデータベースに保存できます。 このオブジェクトには、プロパティtitle (title)、 description (description)、 state (デフォルトのstateをwriting設定)、 client (client)、 total_price (ドキュメントの総コスト)、items items配列がありitems 。 商品には、 title (名前)、 quantity (数量)、 price (価格)、およびsubtotal (小計)のプロパティがあります。

ここでは、ドキュメントのstatesの配列statesが定義されています。 その値は、ドキュメントの状態を設定するために使用されます。 これらの状態は、 writingediting pendingpendingapproveddeniedwaitingです。

以下に、データ構造の説明の後に、 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--text-field__prefix {       margin-bottom: 3px !important;     }   }   .input-group--focused {     .input-group__input {       border-color: #66bb6a!important;     }   } } </style> 

次に、 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> 

BudgetEditBudgetCreationコンポーネントテンプレートの唯一の違いは、保存ボタンとその関連ロジックです。 つまり、 BudgetCreationではSaveと表示され、 saveBudgetメソッドが呼び出されSaveBudgetEditこのボタンに碑文の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つのプロパティで始まります。 これらは、 clientsfixClientNameAndUpdate 、およびselectedBudgetです。 このデータは、 BudgetCreationコンポーネントのデータと同じです。 つまり、 Budgetオブジェクトとstates配列がありstates

さらに、ここでは、 mountedコンポーネントライフサイクルイベントハンドラーを確認できます。このハンドラーでは、 parseBudgetメソッドを呼び出します。 最後に、 BudgetCreationコンポーネントでおなじみのaddItemremoveItemメソッド、および新しい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--text-field__prefix {       margin-bottom: 3px !important;     }   }   .input-group--focused {     .input-group__input {       border-color: #66bb6a!important;     }   } } </style> 

これにより、新しいコンポーネントの作成が完了し、システムに既に存在していたコンポーネントの処理に進みます。

既存のコンポーネントの改良


これで、既存のコンポーネントにいくつかの変更を加えるだけで、アプリケーションを使用する準備が整います。

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> 

このコンポーネントでは、多くのプロパティを取得します。 それらについて説明します。


コンポーネントには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> 

. .


, , , mounted , .

, , watch . @mrmonkeytech , ( ).

.


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 . .


, , — .
, , , , .

親愛なる読者! , ?

Source: https://habr.com/ru/post/J346784/


All Articles