Laravel + Vue.js 管理面板生成器
最后更新于 作者: PovilasKorop
Laravel 和 Vue.js 通常一起使用。随着这些技术上更多工具的发布,这里有一个新的工具 - 向您介绍 Vue+Laravel 管理面板生成器。
免责声明:我是该工具的创始人兼开发人员之一,也是 Laravel 专用生成器 QuickAdminPanel 的开发人员,但本文的目标不仅是向您展示产品,而是解释它生成的內容以及 Vue + Laravel 如何协同工作。此外,您将在 Github 上找到一个具有源代码的示例项目。
生成器如何工作?
对于喜欢视频的人,这里有一个快速演示
现在,让我们更详细地看看它。
步骤 1. 您无需编写代码,只需添加菜单项和字段即可创建面板。
步骤 2. 在任何时候,您都可以查看生成的代码,逐个文件查看。
步骤 3. 然后,您下载代码并安装它 - 在本地或远程服务器上,使用以下命令
composer installphp artisan key:generatephp artisan migrate --seedphp artisan passport:install
当然,您的 .env 文件应该在此时配置好。
然后,在前端
npm installnpm run dev
步骤 4. 就这样;您拥有了自己的面板。
步骤 5. 最重要的一点:您可以随意更改代码,它纯粹是 Laravel+Vue,没有我们的生成器包作为依赖项。这是它与像 Voyager 或 Laravel Backpack (顺便说一句,它们都很棒!) 这样的包的主要区别。
我们正在生成什么 - 项目结构
下载项目后,您会看到类似这样的内容
生成的代码:后端 Laravel
首先,让我们分析充当 API 的后端 Laravel 部分
以下是 routes/api.php 文件
Route::group(['prefix' => '/v1', 'middleware' => ['auth:api'], 'namespace' => 'Api\V1', 'as' => 'api.'], function () { Route::post('change-password', 'ChangePasswordController@changePassword')->name('auth.change_password'); Route::apiResource('roles', 'RolesController'); Route::apiResource('users', 'UsersController'); Route::apiResource('companies', 'CompaniesController'); Route::apiResource('employees', 'EmployeesController');});
您可以看到每个 CRUD 的 apiResource,以及一个单独的 POST 用于更改密码。
控制器在 Api/V1 下命名空间,所以这里是我们的 app/Http/Controllers/Api/V1/CompaniesController.php
namespace App\Http\Controllers\Api\V1; use App\Company;use App\Http\Controllers\Controller;use App\Http\Resources\Company as CompanyResource;use App\Http\Requests\Admin\StoreCompaniesRequest;use App\Http\Requests\Admin\UpdateCompaniesRequest;use Illuminate\Http\Request; class CompaniesController extends Controller{ public function index() { return new CompanyResource(Company::with([])->get()); } public function show($id) { $company = Company::with([])->findOrFail($id); return new CompanyResource($company); } public function store(StoreCompaniesRequest $request) { $company = Company::create($request->all()); return (new CompanyResource($company)) ->response() ->setStatusCode(201); } public function update(UpdateCompaniesRequest $request, $id) { $company = Company::findOrFail($id); $company->update($request->all()); return (new CompanyResource($company)) ->response() ->setStatusCode(202); } public function destroy($id) { $company = Company::findOrFail($id); $company->delete(); return response(null, 204); }}
我们有一个典型的资源控制器,只有一个例外 - 资源类,自 Laravel 5.5 以来一直可用。
在我们的案例中,每个资源都是一个简单的数组转换,这里是一个文件 app/Http/Resources/Company.php
namespace App\Http\Resources; use Illuminate\Http\Resources\Json\JsonResource; class Company extends JsonResource{ /** * Transform the resource into an array. * * @param \Illuminate\Http\Request $request * @return array */ public function toArray($request) { return parent::toArray($request); }}
但您可以扩展它,在上面添加您的逻辑 - 在这里查看更多示例 这里 和 这里。
最后,Laravel Passport 保护所有路由 - 安装项目时,您需要运行以下命令
php artisan passport:install
作为后端结果,每个控制器都负责从 Vue.js 前端调用 API 的特定 CRUD 操作。
生成的代码:前端 Vue.js
现在,让我们看看前端部分。主要文件是 resources/client/assets/js/app.js,我们在这里初始化 Vue 和一些库
// ...window.Vue = require('vue')Vue.prototype.$eventHub = new Vue() import router from './routes'import store from './store'import Datatable from 'vue2-datatable-component'import VueAWN from 'vue-awesome-notifications'import vSelect from 'vue-select'import datePicker from 'vue-bootstrap-datetimepicker'import VueSweetalert2 from 'vue-sweetalert2'import 'eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.css' Vue.use(Datatable)Vue.use(VueAWN, { position: 'top-right'})Vue.use(datePicker)Vue.use(VueSweetalert2) Vue.component('back-buttton', require('./components/BackButton.vue'))Vue.component('bootstrap-alert', require('./components/Alert.vue'))Vue.component('event-hub', require('./components/EventHub.vue'))Vue.component('vue-button-spinner', require('./components/VueButtonSpinner.vue'))Vue.component('v-select', vSelect) moment.updateLocale(window.app_locale, { week: { dow: 1 }}) const app = new Vue({ data: { relationships: {}, dpconfigDate: { format: window.date_format_moment }, dpconfigTime: { format: window.time_format_moment }, dpconfigDatetime: { format: window.datetime_format_moment, sideBySide: true } }, router, store}).$mount('#app')
接下来,每个 CRUD 都有自己的一组组件
为了显示数据表,我们使用的是 vue2-datatable-component - 以下是 resources/clients/assets/components/cruds/Companies/Index.vue 的完整代码
<template> <section class="content-wrapper" style="min-height: 960px;"> <section class="content-header"> <h1>Companies</h1> </section> <section class="content"> <div class="row"> <div class="col-xs-12"> <div class="box"> <div class="box-header with-border"> <h3 class="box-title">List</h3> </div> <div class="box-body"> <div class="btn-group"> <router-link :to="{ name: xprops.route + '.create' }" class="btn btn-success btn-sm"> <i class="fa fa-plus"></i> Add new </router-link> <button type="button" class="btn btn-default btn-sm" @click="fetchData"> <i class="fa fa-refresh" :class="{'fa-spin': loading}"></i> Refresh </button> </div> </div> <div class="box-body"> <div class="row" v-if="loading"> <div class="col-xs-4 col-xs-offset-4"> <div class="alert text-center"> <i class="fa fa-spin fa-refresh"></i> Loading </div> </div> </div> <datatable v-if="!loading" :columns="columns" :data="data" :total="total" :query="query" :xprops="xprops" /> </div> </div> </div> </div> </section> </section></template> <script>import { mapGetters, mapActions } from 'vuex'import DatatableActions from '../../dtmodules/DatatableActions'import DatatableSingle from '../../dtmodules/DatatableSingle'import DatatableList from '../../dtmodules/DatatableList'import DatatableCheckbox from '../../dtmodules/DatatableCheckbox' export default { data() { return { columns: [ { title: '#', field: 'id', sortable: true, colStyle: 'width: 50px;' }, { title: 'Name', field: 'name', sortable: true }, { title: 'Description', field: 'description', sortable: true }, { title: 'Actions', tdComp: DatatableActions, visible: true, thClass: 'text-right', tdClass: 'text-right', colStyle: 'width: 130px;' } ], query: { sort: 'id', order: 'desc' }, xprops: { module: 'CompaniesIndex', route: 'companies' } } }, created() { this.$root.relationships = this.relationships this.fetchData() }, destroyed() { this.resetState() }, computed: { ...mapGetters('CompaniesIndex', ['data', 'total', 'loading', 'relationships']), }, watch: { query: { handler(query) { this.setQuery(query) }, deep: true } }, methods: { ...mapActions('CompaniesIndex', ['fetchData', 'setQuery', 'resetState']), }}</script> <style scoped> </style>
相当多的代码,不是吗?当然,它可以更简单,但我们试图遵循官方文档和最佳实践,为可能扩展到更大项目的案例生成代码。
接下来,我们可以看看 Create.vue
<template> <section class="content-wrapper" style="min-height: 960px;"> <section class="content-header"> <h1>Companies</h1> </section> <section class="content"> <div class="row"> <div class="col-xs-12"> <form @submit.prevent="submitForm"> <div class="box"> <div class="box-header with-border"> <h3 class="box-title">Create</h3> </div> <div class="box-body"> <back-buttton></back-buttton> </div> <bootstrap-alert /> <div class="box-body"> <div class="form-group"> <label for="name">Name</label> <input type="text" class="form-control" name="name" placeholder="Enter Name" :value="item.name" @input="updateName" > </div> <div class="form-group"> <label for="description">Description</label> <textarea rows="3" class="form-control" name="description" placeholder="Enter Description" :value="item.description" @input="updateDescription" > </textarea> </div> </div> <div class="box-footer"> <vue-button-spinner class="btn btn-primary btn-sm" :isLoading="loading" :disabled="loading" > Save </vue-button-spinner> </div> </div> </form> </div> </div> </section> </section></template> <script>import { mapGetters, mapActions } from 'vuex' export default { data() { return { // Code... } }, computed: { ...mapGetters('CompaniesSingle', ['item', 'loading']) }, created() { // Code ... }, destroyed() { this.resetState() }, methods: { ...mapActions('CompaniesSingle', ['storeData', 'resetState', 'setName', 'setDescription']), updateName(e) { this.setName(e.target.value) }, updateDescription(e) { this.setDescription(e.target.value) }, submitForm() { this.storeData() .then(() => { this.$router.push({ name: 'companies.index' }) this.$eventHub.$emit('create-success') }) .catch((error) => { console.error(error) }) } }}</script> <style scoped> </style>
Edit 和 Show CRUD 的组件非常相似,所以这里就不讨论了。
除了 Vue 代码之外,还有许多小的细节和辅助工具,如 Sweet Alert、Notifications、Datepickers 以及设置/获取表单的关系数据。我想我会留给您去分析。
注意:Vue.js 库的选择非常主观,也是项目中最具挑战性的部分 - 选择值得信赖的 Vue 库。生态系统仍然缺乏标准,或 100% 可信赖的开源 - 市场上有很大的变动,一些库的支持比其他库更好。所以总是很难预测,最好的库可能会随着时间的推移而改变,或者会出现新的库。
这就是对 Vue+Laravel QuickAdminPanel 的快速概述的结尾,请在这里尝试它:https://vue.quickadminpanel.com
最后,这里是 包含两个 CRUD 的演示项目的源代码:公司和客户。
我希望我们的生成器不仅能节省您编写代码的时间,还能向您展示 Vue 如何与 Laravel 协同工作。我们对代码进行结构化的方法不是唯一的方法,您可以以不同的方式对代码进行结构化,但我们尽力坚持标准。