iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
2
Modern Web

初探Vue.js 30天系列 第 27

[Day 27] Vue 商品&信用卡

用Vue實做信用卡&商品操作

先前準備安裝套件與資料

在開始前請先安裝sweetalert套件

npm install --save-dev sweetalert@2.1.2

引用bootstrap

resources/sass,在這支檔案引用bootstrap

@import "node_modules/bootstrap/scss/bootstrap";

新增config

config資料夾,建立card.php信用卡資料.txt

新增Controller

在專案的目錄下,輸入指令新增CardController.php

php artisan make:controller CardController

使用card.php

CardsController使用Configcard.php,拿到信用卡資料

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Config;

class CardController extends Controller
{
    public function getCardData()
    {
        $card = Config::get('card');
        
        return [
            'items' => $card['items'],
            'cardItems' => $card['cardItems'],
        ];
    }
}

新增JS

resources/js路徑底下,新增card.js檔案,並且引用List.vueMessage.vue

import cardList from "./components/card/List.vue"
import cardMessage from "./components/card/Message.vue"

let app = new Vue({
    el: "#app",
    components: {
        "card-list": cardList,
        "card-message": cardMessage
    },
    data: {
        items: [],
        cardItems: [],
        showErrorMessage: false,
        errorMessageText: "",
        url: '/card'
    },
    mounted() {
        axios.post(this.url).then((response) => {
            this.items = response.data.items
            this.cardItems = response.data.cardItems
        }).catch((error) => {
            if (error.response) {
                console.log(error.response.data);
                console.log(error.response.status);
                console.log(error.response.headers);
            } else {
                console.log('Error', error.message);
            }
        })
    },
    computed: {
        cardData() {
            const data = []
            const cardItems = this.cardItems
            const items = this.items

            _.forEach(cardItems, (value, key) => {
                let isUse = false
                _.mapKeys(value, (card, cardkey) => {
                    _.forEach(items, (obj) => {
                        if (cardkey === obj.card && obj.status === "1") {
                            isUse = true
                        }
                    })

                    const obj = {
                        cardName: card.cardName,
                        cardNumber: card.full,
                        cardId: card.cardName + key,
                        cardValue: card.last,
                        isUseCard: isUse,
                    }
                    data.push(obj)
                })
            })
            return data
        },
    },
    methods: {
        saveCardData(cardObj, index) {
            const length = this.cardItems.length
            const id = Math.random().toString(36).substr(6)
            this.cardItems.push(id)
            const itenObj = {}
            itenObj[id] = cardObj
            this.cardItems[length] = itenObj
            this.items[index].card = id
        },
        deleteCardData(index) {
            try {
                this.cardData.splice(index, 1)
                this.cardItems.splice(index, 1)
            } catch (e) {
                this.showErrorMessage = true
                this.errorMessageText = "刪除失敗!"
            }
        },
    },
})

畫面載入時會先執行mounted(),用axiosurl變數的Route,從CardsController取得信用卡資料,這時cardData()會組信用卡列表的資料,saveCardData()是儲存商品綁定哪張信用卡資料,deleteCardData()則是移除信用卡資料。

新增component

新增四個component檔案,AddData.vueList.vueMessage.vueSelect.vue

<!-- AddData.vue -->
<script>
export default {
	data() {
		return {
			cardName: '',
			card1: '',
			card2: '',
			card3: '',
			card4: '',
		}
	},
	computed: {
		cardData() {
			const obj = {}
			obj.cardName = this.cardName
			obj.full = `${this.card1}-${this.card2}-${this.card3}-${this.card4}`
			obj.last = this.card4
			obj.first = this.card1
			return obj
		},
	},
	methods: {
		checkCardData() {
			let message = ''
			const data = this.cardData
			if (data.cardName === '') {
				message = '信用卡名稱不能為空'
			} else if (data.full.length < 19) {
				message = '卡號請輸入16碼數字'
			} else if (/[A-Za-z\u4e00-\u9fa5]/.test(data.full)) {
				message = '卡號不能輸入中英文'
			}

			if (message === '') {
				const cardNum = _.split(data.full, '-')
				_.forEach(cardNum, (value) => {
					if (value.length !== 4) {
						message = '每個卡號區間請輸入4碼數字'
						return
					}
				})
			}

			this.$emit('send-card', message, data)
		},
	},
}
</script>

AddData.vue - 新增信用卡資料,將信用卡資料傳到card.js,並更新信用卡列表,cardData()是重新組信用卡卡號資料,將資料傳到文字框裡,checkCardData()檢查輸入的信用卡資料是否正確。

<!-- List.vue -->
<script>
import CardSelect from './Select.vue'

export default {
	components: {
		'card-select': CardSelect
	},
	props: {
		item: {
			type:Object
		},

		cardData: {
			type:Array
		},

		productIndex: {
			type:Number
		},
	},
	data() {
		return {
			selectItem: this.item,
			btnSuccess: 'btn btn-success',
			btnDanger: 'btn btn-danger',
			btnEdit: 'btn btn-primary',
			successText: '啟用',
			dangerText: '停止',
			editText: '編輯',
			isShow: false,
			isStatus: this.item.status == '1',
			messageText: '',
			isDisabled: false,
		}
	},
	computed: {
		itemData() {
			const select = this.selectItem
			const item = _.find(this.cardData, select.card)
			this.isDisabled = false
			return item
		},

		cardName() {
			const itemData = this.itemData
			const item = this.item
			const key = _.findKey(itemData, (e, key) => {
				return key == item.card
			})

			let name = (key === undefined || key === null) ? '' : itemData[key].cardName

			if (name === '') {
				name = '未綁卡'
				this.isDisabled = true
			}

			return name
		},
		
		cardSelected() {
			const itemData = this.itemData
			const item = this.item
			const key = _.findKey(itemData, (e, key) => {
				return key == item.card 
			})
			
			const selected = (key === undefined || key === null) ? '' : itemData[key].last

			return selected
		},
	},
	methods: {
		changeStatus(status) {
			if (status === '0') {
				this.item.status = '1'
				this.isStatus = true
			} else {
				this.item.status = '0'
				this.isStatus = false
			}
		},
		changeCard() {
			if (this.isShow) {
				this.isShow = false
			} else {
				this.isShow = true
			}
		},
		saveCard(isAdd, CardObj) {
			this.isShow = false
			if (!isAdd) {
				let key = ''
				_.mapKeys(CardObj, (card, cardkey) => {
					key = cardkey
				})

				this.selectItem.card = key
			} else {
				this.$emit('save-new-card', CardObj, this.index)
			}
		},
	},
}
</script>

List.vue是顯示商品使用信用卡現況,這裡有引用到Select.vue,是切換信用卡資料用的。

props傳進來的item是所有商品資料,cardDataindex是將資料到Select.vue的信用卡資料。computeditemData()是將每個信用卡資料組成物件,cardName()itemData()拿信用卡名稱,cardSelected()則是信用卡卡號的末四碼。

methodschangeStatus()是切換目前商品的信用卡狀態,changeCard()是商品要切換信用卡資料,saveCard()是將信用卡資料,使用emit傳遞,function名稱為save-new-card,將信用卡物件索引值資料傳到card.jssaveCardData()

<!-- Message.vue -->
<template>
    <transition name="modal">
        <div class="modal-mask">
          <div class="modal-wrapper">
            <div class="modal-container">
                <div class="modal-header">
                    <slot name="header">
                        <h3>{{ message }}</h3>
                    </slot>
                </div>
                <div class="modal-footer">
                    <slot name="footer">
                        <div class="form-check form-check-inline">
                            <input type="button" class="btn btn-primary" id="save" name="save" value="確認" @click="$emit('close')">
                        </div>
                    </slot>
                </div>
            </div>
          </div>
        </div>
      </transition>
</template>

<script>
export default {
     props:{
         message:{
             type:String
         }
     }
}
</script>>

Message.vue是顯示提示訊息用的,將新增、更新、刪除信用卡資料的訊息傳到這裡。

<!-- Select.vue -->
<script>
import CardAddData from './AddData.vue'
import swal from 'sweetalert'

export default {
    components:{
        'card-add-data' : CardAddData
    },
    props:{
        cardData:{
            type:Array
        },

        cardSelected:{
            type:String
        },

        isShow:{
            type:Boolean
        },

        productIndex:{
            type:Number
        },
    },
    data:function(){
        return {
             'cardLastData' : '',
             'selectedData' : '',
             'sendId' : 'send' + this.productIndex,
             'selected' : this.cardSelected,
             'btnSuccess' : 'btn btn-success',
             'btnDanger' : 'btn btn-danger',
             'showModal': false,
             'showCardList' : this.isShow,
             'isError' : false,
             'isRepeat' : false
        }
    },
    computed: {
        cardAllData(){
            let cardArray = []
            let cardData = this.cardData
            _.forEach(cardData, function (value, key) {
                _.mapKeys(value, function(card, cardkey){
                    cardArray.push(card)
                })
            })

            return cardArray
        },
        
        cardIndexData(){
            let cardIdArray = []
            let cardName = 'cardname' + this.productIndex + '_'
            let length = this.cardData.length
            this.cardLastData = cardName + length
            for (let i = 0; i < length; i++) {
                let id = cardName + i
                cardIdArray.push(id)
            }
            return  cardIdArray
        },

				selectedData(){
            let obj = 'add'
            let selected = this.selected
             _.forEach(this.cardData, function (value, key) {
                _.mapKeys(value, function(card, cardkey){
                    let cardLast = value[cardkey].last
                    if(selected == cardLast){
                        obj = value
                        return
                    }
                })
            })
            return obj
        }
    },
    watch:{
        isShow(newVal, oldVal){
            if(newVal == true){
                this.selected = this.cardSelected
            }
            this.showCardList = newVal
        }
    },
    methods: {
        changeCard(){
            if(this.selectedData == 'add'){
                this.showModal = true
            }else{
                this.$emit('save-card', false, this.selectedData)
            }
        },
        sendCard(message, CardObj){
            let isError = false

            if(message != ''){
                isError = true
                this.messageText = message
            }else{
                let cardData = this.cardData
                _.forEach(cardData, function (value, key) {
                    _.mapKeys(value, function(card, cardkey){
                        if(CardObj.full == card.full){
                            isError = true
                            return
                        }
                    })
                })

                if(!isError){
                    this.messageText = '新增成功!'
                    this.showModal = false
                    this.$emit('save-card', true, CardObj)
                }else{
                    this.messageText = '卡號有重複!'
                }
            }

            if(this.messageText != ''){
                swal({
                    title: this.messageText,
                    confirmButtonColor: '#e6b930',
                    icon: !isError ? 'success':'error',
                    showCloseButton: true
                })
            }
                
            this.isError = isError
            
        },
        closeMessage(){
            if(!this.isError){
                this.showCardList = false
            }
        }
    },
}
</script>

Select.vue是顯示選擇信用卡列表,這裡有引用到AddData.vue是在新增信用卡資料用的,sweetalert是美化訊息視窗的套件。

props傳進來的cardData是目前商品資料,cardSelected是目前選到的信用卡資料,isShow是決定顯示全部信用卡資料視窗,productIndex是目前商品的索引值。

computedcardAllData()是重新組好信用卡的物件,cardIndexData()是重新組信用卡資料的索引值,避免與其他商品重複,selectedData()是重新組選擇信用卡的資料。

watchisShow()是顯示全部信用卡資料視窗時,將選擇到信用卡初始化。

methodschangeCard()是商品切換或新增信用卡,對應顯示信用卡視窗或傳值,sendCard()是儲存信用卡資料,會先檢查到信用卡資料後,會顯示提示訊息是否有成功。

新增商品頁面

<!-- card.blade.php -->
<html>

<head>
    <meta name="csrf-token" content="{{ csrf_token() }}">
</head>
<style>
    .modal-mask {
        position: fixed;
        z-index: 9998;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        display: table;
        transition: opacity 0.3s ease;
    }

    .modal-wrapper {
        display: table-cell;
        vertical-align: middle;
    }

    .modal-container {
        width: 300px;
        margin: 0px auto;
        padding: 20px 30px;
        background-color: #fff;
        border-radius: 2px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
        transition: all 0.3s ease;
        font-family: Helvetica, Arial, sans-serif;
    }

    .modal-header h3 {
        margin-top: 0;
        color: #42b983;
    }

    .modal-body {
        margin: 20px 0;
    }

    .modal-default-button {
        float: right;
    }

    /*
 * The following styles are auto-applied to elements with
 * transition="modal" when their visibility is toggled
 * by Vue.js.
 *
 * You can easily play with the modal transition by editing
 * these styles.
 */

    .modal-enter {
        opacity: 0;
    }

    .modal-leave-active {
        opacity: 0;
    }

    .modal-enter .modal-container,
    .modal-leave-active .modal-container {
        -webkit-transform: scale(1.1);
        transform: scale(1.1);
    }
</style>

<body>
    <div class="container">
        <div id="app" class="justify-content-center align-items-center">
            <div class="row" style="margin-bottom: 60px;">
                <div class="col">
                    <div class="card">
                        <div class="card-body">
                            <h5 class="card-title">Card List</h5>
                            <div class="form-inline" v-for="(card, index) in cardData" :key="index"
                                style="height: 50px;">
                                <div class="form-group">
                                    <label>@{{ card.cardName }}:@{{ card.cardNumber }}</label>
                                    <div class="col">
                                        <small v-if="card.isUseCard" class="text-danger">卡片正使用中!</small>
                                        <input type="button" v-else class="btn btn-primary" id="delete" name="delete"
                                            value="刪除" v-on:click="deleteCardData(index)">
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <card-list v-for="(item, index) in items" :item="item" :index="index" :key="item.id"
                :card-data="cardItems" @save-new-card="saveCardData"></card-list>
            <card-message v-if="showErrorMessage" @close="showErrorMessage = false" :message="errorMessageText"></card-message>
        </div>
    </div>
    <script src="{{mix('js/app.js')}}"></script>
    <script src="{{mix('js/card.js')}}"></script>
    <link rel="stylesheet" type="text/css" href="{{mix('/css/app.css')}}">
</body>

</html>

設定Routes

routes/web.php新增網址,指定要顯示card.blade.php

Route::get('/card', function () {
    return view('card');
});

Route::post('/card', 'CardController@getCardData');

編譯檔案

上面步驟完成之後,我們要進行編譯,請在webpack.mix.js檔案,新增card.jsapp.sass檔案

mix.js('resources/js/app.js', 'public/js')
   .js('resources/js/card.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css');

做完之後,執行編譯指令

npm run watch

啟動本機站台

$ php artisan serve
Starting Laravel development server: http://127.0.0.1:8000

啟動之後,輸入網址為http://127.0.0.1:8000/card

操作流程

在頁面上方顯示信用卡是否正在使用中,沒有正在使的可將信用卡資料做刪除,有在使用的顯示卡片正使用中!
每個商品都會綁定一張信用卡,若沒有會顯示信用卡名稱:未綁卡啟用按鈕會註解起來,需要編輯信用卡資料才可以啟用新增更換信用卡時,系統會偵測卡號是否與其他信用卡卡號相同,卡號資料要16碼數字,不能輸入中英文,每個卡號區間要輸入4碼數字,當系統有偵測到輸入的資料有問題時,會提示使用者。


上一篇
[Day 26] Vue - axios&vSelect 模糊查詢
下一篇
[Day 28] Laravel+MongoDB+Vue CRUD - part 1
系列文
初探Vue.js 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言