lummy / laravel-vue-api-crud-generator
为Laravel与Vue.js单文件组件创建一个CRUD应用的骨架。
README
概述
一个Laravel包,允许你为Vue.js/Laravel应用生成样板代码。只需输入数据库表的名称,它就会根据这个名称创建
- Laravel模型
- Laravel控制器(包括获取、列表、创建、更新、删除,以及基于所选数据库表的验证)
- Laravel路由(获取、列表、创建、更新、删除)
- 2个Vue.js单文件组件用于创建、更新、列表、删除和显示(使用Vform & axios)
此包旨在加快后端(Laravel)与前端(Vue.js)之间的通信过程。
安装
composer require lummy/laravel-vue-api-crud-generator
使用方法
首先,你应该以通常的方式创建一个新的迁移。例如,如果创建一个posts表,使用以下命令:
php artisan make:migration create_posts_table
然后在迁移文件中添加你的字段,就像通常那样
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title',200);
$table->text('content')->nullable();
$table->timestamps();
});
然后运行迁移命令创建posts表
php artisan migrate
一旦完成,只需运行一个vueapi
命令。将你的表名添加到命令的末尾,在这个例子中是posts。
php artisan vueapi:generate posts
这将生成上面提到的所有文件。
运行此命令后,使用上述的posts
示例,将创建以下样板文件
路由
根据posts
数据库表,将生成以下路由
Route::get('posts', 'PostsController@list');
Route::get('posts/{id}', 'PostsController@get');
Route::post('posts', 'PostsController@create');
Route::put('posts/{id}', 'PostsController@update');
Route::delete('posts/{id}', 'PostsController@delete');
控制器
根据posts
数据库表,将生成此控制器
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Posts;
class PostsController extends Controller
{
public function get(Request $request, $id){
return Posts::findOrFail($id);
}
public function list(Request $request){
return Posts::get();
}
public function create(Request $request){
$validatedData = $request->validate([
'title' => 'required |max:200 ',
'content' => 'required ',
'meta_description' => 'required |max:160 ',
],[
'title.required' => 'title is a required field.',
'title.max' => 'title can only be 200 characters.',
'content.required' => 'content is a required field.',
'meta_description.required' => 'meta_description is a required field.',
'meta_description.max' => 'meta_description can only be 160 characters.',
]);
$posts = Posts::create($request->all());
return $posts;
}
public function update(Request $request, $id){
$validatedData = $request->validate([
'title' => 'required |max:200 ',
'content' => 'required ',
'meta_description' => 'required |max:160 ',
],[
'title.required' => 'title is a required field.',
'title.max' => 'title can only be 200 characters.',
'content.required' => 'content is a required field.',
'meta_description.required' => 'meta_description is a required field.',
'meta_description.max' => 'meta_description can only be 160 characters.',
]);
$posts = Posts::findOrFail($id);
$input = $request->all();
$posts->fill($input)->save();
return $posts;
}
public function delete(Request $request, $id){
$posts = Posts::findOrFail($id);
$posts->delete();
}
}
?>
模型
根据posts
数据库表,将生成此模型
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Posts extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $guarded = [
'id'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
''
];
}?>
Vue(列表模板)
根据posts
数据库表,将生成此Vue.js列表单文件组件(Posts-list.vue)
<template lang="html">
<div class="posts">
<div class="half">
<h1>Create post</h1>
<form @submit.prevent="createPost">
<div class="form-group">
<input type="hidden" v-model="form.id"></input>
</div>
<div class="form-group">
<label>title</label>
<input type="text" v-model="form.title" maxlength="200" ></input>
<has-error :form="form" field="title"></has-error>
</div>
<div class="form-group">
<label>content</label>
<textarea v-model="form.content" ></textarea>
<has-error :form="form" field="content"></has-error>
</div>
<div class="form-group">
<label>meta_description</label>
<input type="text" v-model="form.meta_description" maxlength="160" ></input>
<has-error :form="form" field="meta_description"></has-error>
</div>
<div class="form-group">
<input type="hidden" v-model="form.created_at"></input>
</div>
<div class="form-group">
<input type="hidden" v-model="form.updated_at"></input>
</div>
<div class="form-group">
<button class="button" type="submit" :disabled="form.busy" name="button">{{ (form.busy) ? 'Please wait...' : 'Submit'}}</button>
</div>
</form>
</div><!-- End first half -->
<div class="half">
<h1>List posts</h1>
<ul v-if="posts.length > 0">
<li v-for="(post,index) in posts" :key="post.id">
<router-link :to="'/post/'+post.id">
post {{ index }}
<button @click.prevent="deletePost(post,index)" type="button" :disabled="form.busy" name="button">{{ (form.busy) ? 'Please wait...' : 'Delete'}}</button>
</router-link>
</li>
</ul>
<span v-else-if="!posts">Loading...</span>
<span v-else>No posts exist</span>
</div><!-- End 2nd half -->
</div>
</template>
<script>
import { Form, HasError, AlertError } from 'vform'
export default {
name: 'Post',
components: {HasError},
data: function(){
return {
posts : false,
form: new Form({
"id" : "",
"title" : "",
"content" : "",
"meta_description" : "",
"created_at" : "",
"updated_at" : "",
})
}
},
created: function(){
this.listPosts();
},
methods: {
listPosts: function(){
var that = this;
this.form.get('/posts').then(function(response){
that.posts = response.data;
})
},
createPost: function(){
var that = this;
this.form.post('/posts').then(function(response){
that.posts.push(response.data);
})
},
deletePost: function(post, index){
var that = this;
this.form.delete('/posts/'+post.id).then(function(response){
that.posts.splice(index,1);
})
}
}
}
</script>
<style lang="less">
.posts{
margin:0 auto;
width:700px;
display:flex;
.half{
flex:1;
&:first-of-type{
margin-right:20px;
}
}
form{
.form-group{
margin-bottom:20px;
label{
display:block;
margin-bottom:5px;
text-transform: capitalize;
}
input[type="text"],input[type="number"],textarea{
width:100%;
max-width:100%;
min-width:100%;
padding:10px;
border-radius:3px;
border:1px solid silver;
font-size:1rem;
&:focus{
outline:0;
border-color:blue;
}
}
.invalid-feedback{
color:red;
&::first-letter{
text-transform:capitalize;
}
}
}
.button{
appearance: none;
background: #3bdfd9;
font-size: 1rem;
border: 0px;
padding: 10px 20px;
border-radius: 3px;
font-weight: bold;
&:hover{
cursor:pointer;
background: darken(#3bdfd9,10);
}
}
}
}
</style>
Vue(单模板)
根据posts
数据库表,将生成此Vue.js单文件组件(Posts-single.vue)
<template lang="html">
<div class="PostSingle">
<h1>Update Post</h1>
<form @submit.prevent="updatePost" v-if="loaded">
<router-link to="/posts">< Back to posts</router-link>
<div class="form-group">
<input type="hidden" v-model="form.id"></input>
</div>
<div class="form-group">
<label>title</label>
<input type="text" v-model="form.title" maxlength="200" ></input>
<has-error :form="form" field="title"></has-error>
</div>
<div class="form-group">
<label>content</label>
<textarea v-model="form.content" ></textarea>
<has-error :form="form" field="content"></has-error>
</div>
<div class="form-group">
<label>meta_description</label>
<input type="text" v-model="form.meta_description" maxlength="160" ></input>
<has-error :form="form" field="meta_description"></has-error>
</div>
<div class="form-group">
<input type="hidden" v-model="form.created_at"></input>
</div>
<div class="form-group">
<input type="hidden" v-model="form.updated_at"></input>
</div>
<div class="form-group">
<button class="button" type="submit" :disabled="form.busy" name="button">{{ (form.busy) ? 'Please wait...' : 'Update'}}</button>
<button @click.prevent="deletePost">{{ (form.busy) ? 'Please wait...' : 'Delete'}}</button>
</div>
</form>
<span v-else>Loading post...</span>
</div>
</template>
<script>
import { Form, HasError, AlertError } from 'vform'
export default {
name: 'Post',
components: {HasError},
data: function(){
return {
loaded: false,
form: new Form({
"id" : "",
"title" : "",
"content" : "",
"meta_description" : "",
"created_at" : "",
"updated_at" : "",
})
}
},
created: function(){
this.getPost();
},
methods: {
getPost: function(Post){
var that = this;
this.form.get('/posts/'+this.$route.params.id).then(function(response){
that.form.fill(response.data);
that.loaded = true;
}).catch(function(e){
if (e.response && e.response.status == 404) {
that.$router.push('/404');
}
});
},
updatePost: function(){
var that = this;
this.form.put('/posts/'+this.$route.params.id).then(function(response){
that.form.fill(response.data);
})
},
deletePost: function(){
var that = this;
this.form.delete('/posts/'+this.$route.params.id).then(function(response){
that.form.fill(response.data);
that.$router.push('/posts');
})
}
}
}
</script>
<style lang="less">
.PostSingle{
margin:0 auto;
width:700px;
form{
.form-group{
margin-bottom:20px;
label{
display:block;
margin-bottom:5px;
text-transform: capitalize;
}
input[type="text"],input[type="number"],textarea{
width:100%;
max-width:100%;
min-width:100%;
padding:10px;
border-radius:3px;
border:1px solid silver;
font-size:1rem;
&:focus{
outline:0;
border-color:blue;
}
}
.button{
appearance: none;
background: #3bdfd9;
font-size: 1rem;
border: 0px;
padding: 10px 20px;
border-radius: 3px;
font-weight: bold;
&:hover{
cursor:pointer;
background: darken(#3bdfd9,10);
}
}
.invalid-feedback{
color:red;
&::first-letter{
text-transform:capitalize;
}
}
}
}
}
</style>
配置
以下是配置设置及其默认值。
<?php
return [
'model_dir' => base_path('app'),
'controller_dir' => base_path('app/Http/Controllers'),
'vue_files_dir' => base_path('resources/views/vue'),
'routes_dir' => base_path('routes'),
'routes_file' => 'api.php'
];
?>
要将配置文件复制到你的工作Laravel项目中,请输入以下Artisan命令:
php artisan vendor:publish --provider="lummy\vueApi\vueApiServiceProvider" --tag="config"
model_dir
指定生成的模型文件应存储的位置
controller_dir
指定生成的控制器文件应存储的位置
vue_files_dir
指定Vue单文件模板应存储的位置
vue_url_prefix
指定应在视图文件中的URL前添加的前缀。默认为/api
,即/api/posts
routes_dir
指定路由目录的位置
routes_file
指定路由文件的名字
自定义模板
如果你使用其他前端框架,如React,或者你想调整模板的结构,则可以通过将它们发布到你的工作Laravel项目来自定义模板
`php artisan vendor:publish --provider="lummy\vueApi\vueApiServiceProvider" --tag="templates"`
它们将出现在
\resources\views\vendor\vueApi
模板中的变量
每个模板文件都传递一个包含以下字段的数据数组
$data['singular']
数据库表的单一名称,例如Post
$data['plural']
数据库表的复数名称,例如Posts
$data['singular_lower']
数据库表的单一名称(小写),例如post
$data['plural_lower']
数据库表的复数名称,例如(小写)eg posts
$data['fields']
包含在模型中的字段数组。
- name(字段名称)
- type(MySQL varchar, int 等)
- simplified_type(text, textarea, number)
- required(字段是否必需)
- max(最大字符数)
其他注意事项
我只在 Laravel MYSQL 驱动程序上进行了测试,所以不确定它是否在其他数据库上工作。
在 Vue.js 文件中,路由假定如下:以 posts 为例。您可以从生成的模板中轻松配置这些。
/posts(Posts-list.vue) /posts/{id}(Posts-single.vue)
请随时与我联系,提供反馈或建议 https://github.com/aarondo