lummy/laravel-vue-api-crud-generator

为Laravel与Vue.js单文件组件创建一个CRUD应用的骨架。

1.1.6 2020-03-29 15:13 UTC

This package is auto-updated.

Last update: 2024-09-29 05:29:05 UTC


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