Apa itu Vuetify?
Vuetify merupakan sebuah framework desain komponen material untuk UI/UX yang berasosiasi langsung dengan Vue.js. Mirip seperti BootstrapVue, kira-kira begitulah.
Persiapan
vue create vue-vuetify

Setelah selesai kita masuk direktori vue-vuetify, lalu tambahkan vuetify pada proyek kita.
cd vue-vuetify vue add vuetify
Lalu jalankan service…
npm run serve

Halaman utamanya akan berubah seperti gambar di atas.
Karena nantinya kita akan membutuhkan icon :
npm install --save material-design-icons-iconfont
Edit src/plugins/vuetify.js
import 'material-design-icons-iconfont/dist/material-design-icons.css' import Vue from 'vue'; import Vuetify from 'vuetify/lib'; Vue.use(Vuetify); export default new Vuetify({ icons: { iconfont: 'mdi', }, });
Planing saya akan membuat 5 Page sbb:
- Login integration to Laravel Login JWT API
- Home
- About
- ToDos Crud integration to Laravel ToDos API
- Profile
Karena kita akan mengakses API, kita memerlukan axios
npm install --save axios
Router
Kita akan menggunakan Auth akses permission, sehingga router.js menjadi seperti berikut :
import Vue from "vue"; import Router from "vue-router"; import Home from "./views/Home.vue"; import Login from "./components/Login.vue"; import Register from "./components/Register.vue"; import Profile from "./components/Profile.vue"; import Todos from "@/components/Todos"; import store from "./store.js"; Vue.use(Router); let router = new Router({ mode: "history", base: process.env.BASE_URL, routes: [ { path: "/home", name: "Home", component: Home, meta: { auth: true } }, { path: '/about', name: 'about', component: () => import('./views/About.vue'), meta: { auth: true } }, { path: "/", name: "Login", component: Login, meta: { guest: true } }, { path: "/register", name: "Register", component: Register, meta: { guest: true } }, { path: "/profile", name: "Profile", component: Profile, meta: { auth: true } }, { path: '/todo', name: 'Todos', component: Todos, meta: { auth: true } } ] }); router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.auth)) { if (store.state.isLogin) { next(); } else { next({ path: "/", }); } } else if (to.matched.some(record => record.meta.guest)) { if (store.state.isLogin) { next({ path: "/profile", }); } else { next(); } } else { next(); } }); export default router;
Membuat Store
store.js
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { isLogin: false, token:null, user:null, }, mutations: { resetState (state) { state.isLogin=false; state.token=null; state.user=null; } }, actions: { clearState ({ commit }) { commit('resetState'); }, } });
Membuat Component NavBar
src/component/NavBar.vue
<template> <section> <v-navigation-drawer v-model="drawer" app> <v-list-item> <v-list-item-content> <v-list-item-title class="title"> Application Name </v-list-item-title> <v-list-item-subtitle> subtext </v-list-item-subtitle> </v-list-item-content> </v-list-item> <v-divider></v-divider> <v-list dense nav> <v-list-item to="/Home"> <v-list-item-action> <v-icon>mdi-home</v-icon> </v-list-item-action> <v-list-item-content> <v-list-item-title>Home</v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item to="/About"> <v-list-item-action> <v-icon>mdi-help-circle</v-icon> </v-list-item-action> <v-list-item-content> <v-list-item-title>About</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> <v-divider></v-divider> <v-list dense nav> <v-list-item to="/Messages"> <v-list-item-action> <v-badge> <template v-slot:badge>0</template> <v-icon>mdi-email</v-icon> </v-badge> </v-list-item-action> <v-list-item-content> <v-list-item-title>Messages</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> <template v-slot:append> <div class="pa-2"> <v-btn @click="signoutButtonPressed" color="red darken-4 white--text" block>Logout</v-btn> </div> </template> </v-navigation-drawer> <v-app-bar app color="blue darken-4" dark> <v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon> <v-toolbar-title>Application</v-toolbar-title> <div class="flex-grow-1"></div> <v-btn icon> <v-icon>mdi-heart</v-icon> </v-btn> <v-menu left bottom> <template v-slot:activator="{ on }"> <v-btn icon v-on="on"> <v-icon>mdi-dots-vertical</v-icon> </v-btn> </template> <v-list> <v-list-item to="/Profile"> <v-list-item-action> <v-icon>mdi-account-circle</v-icon> </v-list-item-action> <v-list-item-content> <v-list-item-title>Profile</v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item to="/Setting"> <v-list-item-action> <v-icon>mdi-settings</v-icon> </v-list-item-action> <v-list-item-content> <v-list-item-title>Setting</v-list-item-title> </v-list-item-content> </v-list-item> </v-list> </v-menu> </v-app-bar> </section> </template> <script> export default { props: { source: String, }, data: () => ({ drawer: null, }), methods: { signoutButtonPressed() { this.$store.dispatch('clearState'); this.$router.push({ name: "Login" }); } } }; </script>
Membuat Layout
src/App.vue
<template> <v-app teal> <NavBar v-show="getStatusLogin"/> <v-content> <router-view /> </v-content> <v-footer color="blue darken-4" app> <span class="white--text">© 2019</span> </v-footer> </v-app> </template> <script> import NavBar from "./components/NavBar"; export default { name: "App", components: { NavBar }, data: () => ({ // }), computed: { getStatusLogin: function () { return this.$store.state.isLogin; } } }; </script>
Membuat Halaman Login
src/component/Login.vue
<template> <section> <v-container class="fill-height" fluid> <v-row align="center" justify="center"> <v-col cols="12" sm="8" md="4"> <v-card class="mx-auto"> <v-toolbar color="primary" dark flat> <v-toolbar-title>Login form</v-toolbar-title> </v-toolbar> <v-card-text> <v-form ref="form" v-model="valid"> <v-text-field label="Email" v-model="email" prepend-icon="mail" type="text" :rules="emailRules" required ></v-text-field> <v-text-field label="Password" v-model="password" prepend-icon="lock" type="password" :rules="passwordRules" required ></v-text-field> </v-form> </v-card-text> <v-card-actions> <div class="flex-grow-1"></div> <v-btn color="primary" :disabled="!valid" @click="doLogin">Login</v-btn> <v-btn color="primary" link to="/Register">Register</v-btn> </v-card-actions> </v-card> <v-snackbar v-model="snackbar" bottom> {{error_message}} </v-snackbar> </v-col> </v-row> </v-container> </section> </template> <script> export default { props: { source: String }, data: () => ({ drawer: null, snackbar:false, valid: true, email: "", emailRules: [ v => !!v || "E-mail is required", v => /.+@.+\..+/.test(v) || "E-mail must be valid" ], password: "", passwordRules: [ v => !!v || "Password is required", v => (v && v.length >= 6) || "Password must be larger than 6 characters" ], result:null, error_message:"" }), methods: { reset() { this.$refs.form.reset(); }, doLogin() { const axios = require('axios'); axios.post("http://localhost:8000/api/login", { email:this.email, password:this.password, }) .then(response => { this.result = response.data; if(this.result.token){ axios.get("http://localhost:8000/api/user", { headers: { Authorization: 'Bearer ' + this.result.token, } }) .then(response => { this.result = response.data; if(this.result.user){ this.$store.state.user=this.result.user; this.$store.state.isLogin=true; this.$store.state.jwt_token=this.result.token; this.$router.push({ name: "Profile" }); }else{ this.error_message=response.data.status; this.snackbar=true; } }) } }) .catch(e => { if (e.response && e.response.status === 400) { this.error_message=e.response.data.error; this.snackbar=true; } }); } }, }; </script>
src/components/Profile.vue
<template> <section> <v-container class="pa-2" fluid> <v-row> <v-col> <v-alert v-if="errors" type="error" class="mt-4"> {{errors}} </v-alert> <v-card color="#385F73" dark> <v-card-text class="white--text"> <div class="headline mb-2" v-if="user">{{user.name}}</div> Listen to your favorite artists and albums whenever and wherever, online and offline. </v-card-text> <v-card-actions> <v-btn text>Listen Now</v-btn> </v-card-actions> </v-card> </v-col> </v-row> </v-container> </section> </template> <script> export default { data: () => ({ user:null, result:null, errors:null }), mounted: function () { this.user=this.$store.state.user; } } </script>
Todo Crud
Membuat Module
src/modules/todo.js
const todo = { namespaced: true, state: { listData: [ { id:1, user_id:1, title: 'Example', }, ] }, getters: { getRowById: (state) => (id) => { return state.listData.find((obj) => { return obj.id===id }) } }, mutations: { ADD_ROW: (state, payload) => { state.listData.push(payload.row); }, EDIT_ROW(state, payload){ var listData = state.listData listData.forEach((element, index) => { if(element.id === payload.id) { listData[index] = payload; } }); state.listData = listData; }, REMOVE_ROW(state, payload){ state.listData.splice(state.listData.indexOf(payload.row), 1); }, }, actions: { save_row({ commit, rootState }, payload) { rootState.isLoading = true; setTimeout(() => { commit('ADD_ROW', payload) rootState.isLoading = false }, 1000) }, edit_row({commit}, payload){ commit('EDIT_ROW', payload) }, remove_row({commit}, payload){ commit('REMOVE_ROW', payload) }, } } export default todo;
Setelah itu tambahkan modules todo pada store.js
import Vue from "vue"; import Vuex from "vuex"; import todo from './modules/todo'; Vue.use(Vuex); export default new Vuex.Store({ state: { isLoading:false, isLogin: false, token:null, user:null, }, mutations: { resetState (state) { state.isLoading=false; state.isLogin=false; state.token=null; state.user=null; } }, actions: { clearState ({ commit }) { commit('resetState'); }, }, modules: { todo, } });
Membuat Page
src/components/Todos.vue
<template> <section> <v-form v-model="valid"> <v-container> <v-row> <v-col cols="8" md="8"> <v-text-field v-model="title" :counter="10" label="Enter what todo" required ></v-text-field> </v-col> <v-col cols="4" md="4"> <v-btn class="ma-2 red white--text" @click="addItem"> Add <v-icon right dark>mdi-cloud-upload</v-icon> </v-btn> </v-col> </v-row> <v-row> <v-col cols="12" md="12"> <v-list dense> <v-list-item-group color="primary"> <v-list-item v-for="todo in todos" :key="todo.id"> <v-list-item-content> <v-list-item-subtitle> <v-row> <v-col cols="2" md="2"> <v-btn @click="removeItem(todo)">X</v-btn> </v-col> <v-col cols="8" md="8"> <v-chip color="red" text-color="white"> {{todo.title}} </v-chip> </v-col> <v-col cols="2" md="2"> <v-switch value :input-value="todo.isCompleted==1?true:false" @change="updateItem(todo,$event)"></v-switch> </v-col> </v-row> </v-list-item-subtitle> </v-list-item-content> </v-list-item> </v-list-item-group> </v-list> </v-col> </v-row> </v-container> </v-form> </section> </template> <script> import axios from 'axios'; export default { data: () => ({ drawer: null, snackbar:false, valid: true, title: "", message: "", checkboxes:[] }), created: function () { axios.get("http://localhost:8000/api/todo", { headers: { Authorization: 'Bearer ' + this.$store.state.token } }) .then(response => { this.$store.state.todo.listData=response.data; }); }, computed: { todos(){ return this.$store.state.todo.listData; } }, methods: { addItem() { axios.post("http://localhost:8000/api/todo", { title:this.title, user_id:this.$store.state.user.id }, { headers: { Authorization: 'Bearer ' + this.$store.state.token } }) .then(response => { this.message = response.data.status; this.$store.dispatch('todo/save_row', { row:response.data.row, }); }); }, updateItem(row, value) { axios.put("http://localhost:8000/api/todo/"+row.id, { isCompleted:value?1:0, }, { headers: { Authorization: 'Bearer ' + this.$store.state.token } }) .then(response => { this.message = response.data.message; this.$store.dispatch('todo/edit_row', { row: response.data.row }); }); }, removeItem(row) { axios.delete("http://localhost:8000/api/todo/"+row.id, { headers: { Authorization: 'Bearer ' + this.$store.state.token } }) .then(response => { this.message = response.data.message; this.$store.dispatch('todo/remove_row', { row: row, }); }); }, } } </script>
Sumber