iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 29
2
Modern Web

初探Vue.js 30天系列 第 29

[Day 29] Laravel+MongoDB+Vue CRUD - part 2

Day28已建立好環境了,接下來開始製作CURD功能囉~/images/emoticon/emoticon08.gif

Controller

php artisan make:controller KeywordController

KeywordController負責進行實際對MongoDB的CRUD操作,再將資料回傳到View用Vue作呈現

<?php

namespace App\Http\Controllers;

use App\Models\Keyword;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Pagination\Paginator;
use Carbon\Carbon;

class KeywordController extends Controller
{
    public function getKeywordView(){
        return view('keyword');
    }

    public function index(Request $request){
        $perPage = 5;
        $page = (int)$request->page;
        $from= ($page * $perPage) - ($perPage - 1);
        $keyword = Keyword::all()->splice($from, $perPage);
        $total = Keyword::count();
        $response = [
            'pagination' => [
                'total' => $total,
                'per_page' => $perPage,
                'current_page' => $page,
                'last_page' => ceil($total / $perPage),
                'from' => $from,
                'to' => $page * $perPage
            ],
            'keyword' => $keyword, 'add' => route('addKeyword'), 'update' => route('updateKeyword'),
            'delete' => route('deleteKeyword')
        ];

        return response()->json($response);
    }

    public function addKeywordData(Request $request)
    {
        $english_name = $request->english_name;
        $chinese_name = $request->chinese_name;
        $datetime = Carbon::now()->toDateTimeString();
        $status = 'success';
        $message = '新增成功!';

        try {
            Keyword::insert([
                'english_name' => $english_name, 
                'chinese_name' => $chinese_name,
                'created_at' => $datetime,
                'updated_at' => $datetime
            ]);
        } catch (Exception $e) {
            $status = 'error';
            $message = '新增失敗!';
        }
        
        return [ 
            'status' => $status,
            'message' => $message 
        ];
    }

    public function updateKeywordData(Request $request){
        $english_name = $request->english_name;
        $chinese_name = $request->chinese_name;
        $datetime = Carbon::now()->toDateTimeString();
        $status = 'success';
        $message = '更新成功!';

        $data = [
            'english_name' => $english_name,
            'chinese_name' => $chinese_name,
            'updated_at' => $datetime
        ];

        try {
            Keyword::where('_id', $request->id)->update($data);
        } catch (Exception $e) {
            $status = 'error';
            $message = ' 更新失敗!';
        }
        
        return [
            'status' => $status,
            'message' => $message 
        ];
    }

    public function deleteKeywordData(Request $request){
        $datetime = Carbon::now()->toDateTimeString();
        $status = 'success';
        $message = '刪除成功!';

        try {
            Keyword::where('_id', $request->id)->delete();
        } catch (Exception $e) {
            $status = 'error';
            $message = '刪除失敗!';
        }
        
        return [
            'status' => $status,
            'message' => $message
        ];
    }

}

JS code

keyword.js裡引用Edit.vueList.vue、NavbarPagination.vue

import keywordList from './components/keyword/List.vue';
import keywordEdit from './components/keyword/Edit.vue';
import NavbarPagination from './components/NavbarPagination.vue'

let app = new Vue({
    el: '#app',
    components: {
        'keyword-list': keywordList,
        'keyword-edit': keywordEdit,
        'navbar-pagination': NavbarPagination
    },
    data: {
        editTitle: '',
        showEdit: false,
        isAdd: false,
        keyword: [],
        urlAdd: '',
        urlUpdate: '',
        urlDelete: '',
        pagination: {},
        getPage:1,
        params: {
            'id': '',
            'enName': '',
            'chName': ''
        },
        keyWordDataIndex: null
    },
    mounted() {
        this.getKeywordData(1)
    },
    methods: {
        getPagination(getPage) {
            this.getKeywordData(getPage)
        },
        getKeywordData(page){
            axios.get('/getKeywordData?page=' + page).then(response => {
                this.keyword = response.data.keyword,
                this.urlAdd = response.data.add,
                this.urlUpdate = response.data.update,
                this.urlDelete = response.data.delete,
                this.pagination = response.data.pagination
            }).catch((error) => {
                //顯示請求資料失敗的錯誤訊息
                if (error.response){
                    //在log顯示response錯誤的資料、狀態、表頭
                    console.log(error.response.data);
                    console.log(error.response.status);
                    console.log(error.response.headers);
                }else{
                    //在log顯示r錯誤訊息
                    console.log('Error',error.message);
                }
                
            })
        },
        getAddKeyword() {
            this.editTitle = '新增關鍵字'
            this.showEdit = true
            this.isAdd = true
            this.params = {
                'id': '',
                'enName': '',
                'chName': ''
            }
        },
        updateKeyword(id, enName, chName, index) {
            this.editTitle = '更新關鍵字'
            this.showEdit = true
            this.params = {
                'id': id,
                'enName': enName,
                'chName': chName
            }
            this.keyWordDataIndex = index
        },
        updateKeywordData(params) {
            if (this.keyWordDataIndex != null) {
                this.keyword[this.keyWordDataIndex].chinese_name = params.chinese_name
                this.keyword[this.keyWordDataIndex].english_name = params.english_name
            }
        },
        deleteKeywordData(index) {
            this.keyWordDataIndex = index
            if (this.keyWordDataIndex != null) {
                this.keyword.splice(this.keyWordDataIndex, 1)
            }
        },
        isShowMessage(isSuccess, message){
            let isAdd =  this.isAdd
            swal({
                title: message,
                confirmButtonColor: "#e6b930",
                icon: isSuccess ? 'success':'error',
                showCloseButton: true
            }).then(function() {
                if (isSuccess && isAdd){
                    location.reload()
                }
            })

            if (isSuccess) {
                this.showEdit = false
            }
        }
    },
    
})

mounted
getKeywordData() 取得該分頁的關鍵字資料

method

  • getPagination() 切換分頁時,會更新分頁對應的關鍵字資料
  • getAddKeyword() 顯示新增的視窗
  • updateKeyword() 顯示更新的視窗
  • updateKeywordData() 更新目前的資料
  • deleteKeywordData() 移除目前的資料
  • isShowMessage() 顯示提示訊息

Vue component

  • Edit.vue
  • List.vue
  • NavbarPagination.vue

Edit.vue

<!-- Edit.vue -->
<script>
import swal from "sweetalert"

export default {
	props: {
		title: {
			type:String
		},
		params: {
			type:Object
		},
		urlAdd: {
			type:String
		},
		urlUpdate: {
			type:String
		},
		isAdd: {
			type:Boolean
		}
	},
	data() {
		return {
			enName: this.params.enName,
			chName: this.params.chName,
			messageText: '',
		}
	},
	methods: {
		checkKeyword(enName, chName) {
			let isSuccess = false
			this.messageText = ''
			
			if (enName === '' && chName === '') {
				this.messageText = "英文或中文名稱不能為空"
			} else if (enName === '') {
				this.messageText = "英文名稱不能為空"
			} else if(/[\u4e00-\u9fa5]/.test(enName)) {
				this.messageText = "英文名稱不能輸入中文"
			} else if (chName === '') {
				this.messageText = "中文名稱不能為空"
			} else if(/[A-Za-z]/.test(chName)) {
				this.messageText = "中文名稱不能輸入英文"
			} 

			if (this.messageText == '') {
				isSuccess = true
			} else {
				this.$emit('is-show-message', isSuccess, this.messageText)
			}
			return isSuccess
		},
		saveKeyword(){
			let enName = this.enName
			let chName = this.chName
			let url = this.isAdd ? this.urlAdd : this.urlUpdate
			let isSuccess = this.checkKeyword(enName, chName)

			if (isSuccess) {
				let data = {
					'english_name' : enName,
					'chinese_name' : chName,
				}

				if (!this.isAdd) {
					data['id'] = this.params.id
				}

				axios.post(url, data).then((response) => {
					let isSuccess = response.data.status == 'success' ? true : false
					if (isSuccess && !this.isAdd) {
						this.$emit('update-keyword-data', data)
					}
					this.$emit('is-show-message', isSuccess, response.data.message)
				}).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);
					}
					this.$emit('is-show-message', false, '發生意外錯誤!')
				})
			}
			
		}
	},
}
</script>

Edit.vue是編輯關鍵字資料的視窗,sweetalert是能強大、美觀又容易使用的dialog library。

props

  • title 傳視窗的標題
  • params 關鍵字的資料
  • urlAdd 新增資料的Route
  • urlUpdate 新增資料的Route
  • isAdd 要新增資料的布林值

methods

  • checkKeyword()是檢查關鍵字資料是否問題,有問題就使用emit傳訊息文字
  • saveKeyword()是儲存關鍵字資料用的

List.vue

<!-- List.vue -->
<script>
export default {
	props: {
		keyWordData: {
			type:Array
		},
		urlDelete: {
			type:String
		}
	},
	data() {
		return {
			
		}
	},
	methods: {
		deleteKeyword(id, index) {
			let url = this.urlDelete
			let params = {
                id: id
			}
			
			axios.post(url, params).then((response) => {
				let isSuccess = response.data.status == 'success' ? true : false
				if (isSuccess) {
					this.$emit('delete-keyword-data', index)
				}
                this.isShowMessage(isSuccess, response.data.message)
            }).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);
                }
                this.$emit('is-show-message', false, '發生意外錯誤!')
            })
		}
	}
}
</script>

List.vue是顯示關鍵字資料的表

props

  • keyWordData 傳全部關鍵字資料
  • urlDelete 移除資料Route

methods
deleteKeyword() 移出關鍵字資料

NavbarPagination.vue

<!-- NavbarPagination.vue -->
<script>
export default {
    props:{
        paginationData:{
            type:Object
        }
    },
    data:function(){
        return {
            offset: 4
        }
    },
    computed: {
        isActived: function () {
            return this.paginationData.current_page;
        },
        pagesNumber: function () {
            if (!this.paginationData.to) {
                return [];
            }
            let from = this.paginationData.current_page - this.offset;
            if (from < 1) {
                from = 1;
            }
            let to = from + (this.offset * 2);
            if (to >= this.paginationData.last_page) {
                to = this.paginationData.last_page;
            }
            let pagesArray = [];
            while (from <= to) {
                pagesArray.push(from);
                from++;
            }
            return pagesArray;
        }
    },
    methods: {
        changePage: function (page) {
            this.$emit('change-pagination', page)
        }
    }
}
</script>

NavbarPagination.vue是分頁的導覽列

props

  • paginationData 分頁資料的物件

computed

  • isActived() 計算目前第幾頁
  • pagesNumber() 重新排列分頁資料

methods

  • changePage() 切換分頁時,將目前頁數使用emit傳遞

查詢頁面

<!-- keyword.blade.php-->
<html>
    <head>
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <meta http-equiv="Access-Control-Allow-Origin" content="*" />
    </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 v-cloak id="app" class="content">
                <form action="" method="POST">
                    {{ csrf_field() }}
                    <h2 
                        id="title" 
                        class="text-center text-black font-weight-bold" 
                        style="margin-bottom:20px;">
                    關鍵字查詢
                    </h2>
                    <div style="text-align:right">
                        <input type="button" id="btn_insert" class="btn btn-primary" @click="getAddKeyword()" value="新增" />
                    </div><br/>
                    <keyword-list
                        :key-word-data="keyword" 
                        :url-delete="urlDelete"
                        @update-keyword="updateKeyword"
                        @delete-keyword-data="deleteKeywordData"
                    >
                    </keyword-list>
                    <keyword-edit 
                        v-if="showEdit" 
                        @close="showEdit = false" 
                        :title="editTitle"
                        :is-add="isAdd"
                        :url-add="urlAdd"
                        :url-update="urlUpdate"
                        :params="params"
                        @update-keyword-data="updateKeywordData"
                        @is-show-message="isShowMessage"
                        
                    >
                    </keyword-edit>
                    <navbar-pagination 
                        @change-pagination="getPagination" 
                        :pagination-data="pagination"
                    >
                    </navbar-pagination>
                </form>
            </div>
        </div>
        <script src="{{mix('js/app.js')}}"></script>
        <script src="{{mix('js/keyword.js')}}"></script>
        <link rel="stylesheet" type="text/css" href="{{mix('/css/app.css')}}">
    </body>
</html>

設定Route

Route::get('/keyword', 'KeywordController@getKeywordView');

Route::get('/getKeywordData','KeywordController@index');

Route::get('/updateKeyword', 'KeywordController@updateKeywordData');

Route::post('/addKeyword', 'KeywordController@addKeywordData')
    ->name('addKeyword');

Route::post('/updateKeyword', 'KeywordController@updateKeywordData')
    ->name('updateKeyword');

Route::post('/deleteKeyword', 'KeywordController@deleteKeywordData')
    ->name('deleteKeyword');

編譯檔案

webpack.mix.js新增keyword.jsapp.sass檔案進行邊ㄧ

mix.js('resources/js/app.js', 'public/js')
   .js('resources/js/keyword.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/keyword

Resource
xampp-gmongodb
laravel-mongodb


上一篇
[Day 28] Laravel+MongoDB+Vue CRUD - part 1
下一篇
[Day 30] 完賽,回顧留下的足跡
系列文
初探Vue.js 30天30

尚未有邦友留言

立即登入留言