QuickAdminPanel: Vue.js+Laravel Version
In August 2020, we released a new separate QuickAdminPanel version that generates Vue.js + Laravel API code.
Notice: this version is available only for the Yearly Plan customers.
Compared to the "classic" generator version with jQuery Datatables, this Vue+Laravel code is totally different.
It's a SPA with a front-end-first approach, where most of the logic is inside Vue, using Vue Components, Vue Router, Vuex. Laravel serves only as an API layer, powered by Laravel Sanctum authentication.
Here are a few screenshots of a simple adminpanel, fully generated without writing a single line of code.
Login form page
Simple Datatable Example
Simple Form Example
For the design, we're using a Material Dashboard theme by Creative Tim, based on Bootstrap 4.

Structure of Generated Vue.js Code

As mentioned above, most of the logic of generated panel is inside of Vue.js SPA application.
That said, the public non-auth part of the website is simple Laravel + Blade, without Vue.js at all, the SPA behavior starts only when you log into the panel.
So, inside of the main Blade file resources/views/layouts/admin/app.blade.php you will find this line:
1
<div id="app"></div>
Copied!
And from there, everything happens with Vue, in the folder resources/adminapp/js
Here's the code of the main resources/adminapp/js/app.js:
1
/**
2
* First we will load all of this project's JavaScript dependencies which
3
* includes Vue and other libraries. It is a great starting point when
4
* building robust, powerful web applications using Vue and Laravel.
5
*/
6
​
7
require('./bootstrap')
8
​
9
window.Vue = require('vue')
10
window.moment.updateLocale('en', { week: { dow: 1 } })
11
​
12
Vue.config.productionTip = false
13
Vue.prototype.$jquery = $
14
​
15
import App from './App.vue'
16
​
17
// Core
18
import router from './routes/routes'
19
import store from './store/store'
20
import i18n from './i18n'
21
​
22
// Plugins
23
​
24
import GlobalComponents from './globalComponents'
25
import GlobalDirectives from './globalDirectives'
26
import GlobalMixins from './mixins/global'
27
import { mapGetters, mapActions } from 'vuex'
28
​
29
Vue.use(GlobalComponents)
30
Vue.use(GlobalDirectives)
31
Vue.use(GlobalMixins)
32
​
33
/**
34
* Next, we will create a fresh Vue application instance and attach it to
35
* the page. Then, you may begin adding components to this application
36
* or customize the JavaScript scaffolding to fit your unique needs.
37
*/
38
​
39
const app = new Vue({
40
el: '#app',
41
render: h => h(App),
42
router,
43
store,
44
i18n,
45
created() {
46
this.fetchLanguages()
47
},
48
methods: {
49
...mapActions('I18NStore', ['fetchLanguages'])
50
}
51
})
Copied!
Then, all the generated CRUDs are registered as Routes, in resources/adminapp/js/routes/routes.js:
1
import Vue from 'vue'
2
import VueRouter from 'vue-router'
3
​
4
Vue.use(VueRouter)
5
​
6
const View = { template: '<router-view></router-view>' }
7
​
8
const routes = [
9
{
10
path: '/',
11
component: () => import('@pages/Layout/DashboardLayout.vue'),
12
redirect: 'dashboard',
13
children: [
14
{
15
path: 'dashboard',
16
name: 'dashboard',
17
component: () => import('@pages/Dashboard.vue'),
18
meta: { title: 'global.dashboard' }
19
},
20
{
21
path: 'user-management',
22
name: 'user_management',
23
component: View,
24
redirect: { name: 'permissions.index' },
25
children: [
26
{
27
path: 'permissions',
28
name: 'permissions.index',
29
component: () => import('@cruds/Permissions/Index.vue'),
30
meta: { title: 'cruds.permission.title' }
31
},
32
{
33
path: 'permissions/create',
34
name: 'permissions.create',
35
component: () => import('@cruds/Permissions/Create.vue'),
36
meta: { title: 'cruds.permission.title' }
37
},
38
{
39
path: 'permissions/:id',
40
name: 'permissions.show',
41
component: () => import('@cruds/Permissions/Show.vue'),
42
meta: { title: 'cruds.permission.title' }
43
},
44
{
45
path: 'permissions/:id/edit',
46
name: 'permissions.edit',
47
component: () => import('@cruds/Permissions/Edit.vue'),
48
meta: { title: 'cruds.permission.title' }
49
},
50
]
51
},
52
53
// ... More routes
54
]
55
}
56
]
57
​
58
export default new VueRouter({
59
mode: 'history',
60
base: '/admin',
61
routes
62
})
Copied!
For every CRUD, we generate a set of Vue.js components, in the folder resources/adminapp/js/components/[CRUD Folder].
Here's an example of the list page of Transactions CRUD, in resources/adminapp/js/components/Transactions/Index.vue:
1
<template>
2
<div class="container-fluid">
3
<div class="row">
4
<div class="col-md-12">
5
<div class="card">
6
<div class="card-header card-header-primary card-header-icon">
7
<div class="card-icon">
8
<i class="material-icons">assignment</i>
9
</div>
10
<h4 class="card-title">
11
{{ $t('global.table') }}
12
<strong>{{ $t('cruds.transaction.title') }}</strong>
13
</h4>
14
</div>
15
<div class="card-body">
16
<router-link
17
class="btn btn-primary"
18
v-if="$can(xprops.permission_prefix + 'create')"
19
:to="{ name: xprops.route + '.create' }"
20
>
21
<i class="material-icons">
22
add
23
</i>
24
{{ $t('global.add') }}
25
</router-link>
26
<button
27
type="button"
28
class="btn btn-default"
29
@click="fetchIndexData"
30
:disabled="loading"
31
:class="{ disabled: loading }"
32
>
33
<i class="material-icons" :class="{ 'fa-spin': loading }">
34
refresh
35
</i>
36
{{ $t('global.refresh') }}
37
</button>
38
</div>
39
<div class="card-body">
40
<div class="row">
41
<div class="col-md-12">
42
<div class="table-overlay" v-show="loading">
43
<div class="table-overlay-container">
44
<material-spinner></material-spinner>
45
<span>Loading...</span>
46
</div>
47
</div>
48
<datatable
49
:columns="columns"
50
:data="data"
51
:total="total"
52
:query="query"
53
:xprops="xprops"
54
:HeaderSettings="false"
55
:pageSizeOptions="[10, 25, 50, 100]"
56
>
57
<global-search :query="query" class="pull-left" />
58
<header-settings :columns="columns" class="pull-right" />
59
</datatable>
60
</div>
61
</div>
62
</div>
63
</div>
64
</div>
65
</div>
66
</div>
67
</template>
68
​
69
<script>
70
import { mapGetters, mapActions } from 'vuex'
71
import DatatableActions from '@components/Datatables/DatatableActions'
72
import TranslatedHeader from '@components/Datatables/TranslatedHeader'
73
import HeaderSettings from '@components/Datatables/HeaderSettings'
74
import GlobalSearch from '@components/Datatables/GlobalSearch'
75
​
76
export default {
77
components: {
78
GlobalSearch,
79
HeaderSettings
80
},
81
data() {
82
return {
83
columns: [
84
{
85
title: 'cruds.transaction.fields.id',
86
field: 'id',
87
thComp: TranslatedHeader,
88
sortable: true,
89
colStyle: 'width: 100px;'
90
},
91
{
92
title: 'cruds.transaction.fields.amount',
93
field: 'amount',
94
thComp: TranslatedHeader,
95
sortable: true
96
},
97
{
98
title: 'cruds.transaction.fields.transaction_date',
99
field: 'transaction_date',
100
thComp: TranslatedHeader,
101
sortable: true
102
},
103
{
104
title: 'global.actions',
105
thComp: TranslatedHeader,
106
tdComp: DatatableActions,
107
visible: true,
108
thClass: 'text-right',
109
tdClass: 'text-right td-actions',
110
colStyle: 'width: 150px;'
111
}
112
],
113
query: { sort: 'id', order: 'desc', limit: 100, s: '' },
114
xprops: {
115
module: 'TransactionsIndex',
116
route: 'transactions',
117
permission_prefix: 'transaction_'
118
}
119
}
120
},
121
beforeDestroy() {
122
this.resetState()
123
},
124
computed: {
125
...mapGetters('TransactionsIndex', ['data', 'total', 'loading'])
126
},
127
watch: {
128
query: {
129
handler(query) {
130
this.setQuery(query)
131
this.fetchIndexData()
132
},
133
deep: true
134
}
135
},
136
methods: {
137
...mapActions('TransactionsIndex', [
138
'fetchIndexData',
139
'setQuery',
140
'resetState'
141
])
142
}
143
}
144
</script>
Copied!
For the Datatables, we're using the Vue2-Datatable package, which we forked under our own LaravelDaily name, to be able to have more control or fixes if needed.
You can see the contents of all other Vue files by checking out the demo repository.

Laravel API Structure

On the back-end, in Laravel, we generate the API routes and Controllers.
Here's the example routes/api.php:
1
<?php
2
​
3
Route::group(['prefix' => 'v1', 'as' => 'api.', 'namespace' => 'Api\V1\Admin', 'middleware' => ['auth:sanctum']], function () {
4
// Abilities
5
Route::apiResource('abilities', 'AbilitiesController', ['only' => ['index']]);
6
​
7
// Locales
8
Route::get('locales/languages', '[email protected]')->name('locales.languages');
9
Route::get('locales/messages', '[email protected]')->name('locales.messages');
10
​
11
// Permissions
12
Route::resource('permissions', 'PermissionsApiController');
13
​
14
// Roles
15
Route::resource('roles', 'RolesApiController');
16
​
17
// Users
18
Route::resource('users', 'UsersApiController');
19
​
20
// Contact Company
21
Route::resource('contact-companies', 'ContactCompanyApiController');
22
​
23
// Contact Contacts
24
Route::resource('contact-contacts', 'ContactContactsApiController');
25
​
26
// Transactions
27
Route::resource('transactions', 'TransactionsApiController');
28
});
Copied!
And here's an example API Controller, in app/Http/Controllers/Api/V1/Admin/TransactionsApiController.php:
1
<?php
2
​
3
namespace App\Http\Controllers\Api\V1\Admin;
4
​
5
use App\Http\Controllers\Controller;
6
use App\Http\Requests\StoreTransactionRequest;
7
use App\Http\Requests\UpdateTransactionRequest;
8
use App\Http\Resources\Admin\TransactionResource;
9
use App\Models\Transaction;
10
use Gate;
11
use Illuminate\Http\Request;
12
use Illuminate\Http\Response;
13
​
14
class TransactionsApiController extends Controller
15
{
16
public function index()
17
{
18
abort_if(Gate::denies('transaction_access'), Response::HTTP_FORBIDDEN, '403 Forbidden');
19
​
20
return new TransactionResource(Transaction::advancedFilter());
21
}
22
​
23
public function store(StoreTransactionRequest $request)
24
{
25
$transaction = Transaction::create($request->validated());
26
​
27
return (new TransactionResource($transaction))
28
->response()
29
->setStatusCode(Response::HTTP_CREATED);
30
}
31
​
32
public function create(Transaction $transaction)
33
{
34
abort_if(Gate::denies('transaction_create'), Response::HTTP_FORBIDDEN, '403 Forbidden');
35
​
36
return response([
37
'meta' => [],
38
]);
39
}
40
​
41
public function show(Transaction $transaction)
42
{
43
abort_if(Gate::denies('transaction_show'), Response::HTTP_FORBIDDEN, '403 Forbidden');
44
​
45
return new TransactionResource($transaction);
46
}
47
​
48
public function update(UpdateTransactionRequest $request, Transaction $transaction)
49
{
50
$transaction->update($request->validated());
51
​
52
return (new TransactionResource($transaction))
53
->response()
54
->setStatusCode(Response::HTTP_ACCEPTED);
55
}
56
​
57
public function edit(Transaction $transaction)
58
{
59
abort_if(Gate::denies('transaction_edit'), Response::HTTP_FORBIDDEN, '403 Forbidden');
60
​
61
return response([
62
'data' => new TransactionResource($transaction),
63
'meta' => [],
64
]);
65
}
66
​
67
public function destroy(Transaction $transaction)
68
{
69
abort_if(Gate::denies('transaction_delete'), Response::HTTP_FORBIDDEN, '403 Forbidden');
70
​
71
$transaction->delete();
72
​
73
return response(null, Response::HTTP_NO_CONTENT);
74
}
75
}
Copied!
We also generate Laravel API Resources, so it would be easier to customize in the future, but they contain mostly default Laravel code. Example from app/Http/Resources/Admin/TransactionResource.php:
1
<?php
2
​
3
namespace App\Http\Resources\Admin;
4
​
5
use Illuminate\Http\Resources\Json\JsonResource;
6
​
7
class TransactionResource extends JsonResource
8
{
9
public function toArray($request)
10
{
11
return parent::toArray($request);
12
}
13
}
Copied!
As mentioned above, Authorization is powered by Laravel Sanctum, and for Roles and Permissions on the front-end, we use CASL Vue package, see video demo below:
You can look at the full code of this example adminpanel in this public repository.
Last modified 7mo ago