laravel-admin 与 vue 结合使用

由于 Laravel-admin 采用的是 pjax 的方式刷新页面,意味着很多页面刷新的操作,并不是刷新整个 document,而是从服务器拿到部分 document,再通过类似 $(“#pjax-container”).html(newPart) 的方式更新的。

这就造成一个问题,每次 pjax 刷新,都会破坏 vue 的 dom 映射。

所以理论上有2种方法解决:

  1. 重新绑定一下 vue 的映射关系
  2. 在某些页面禁止 pjax

1 太难搞,而且没啥资料,放弃。2 的话比较可行。

部分禁止 pjax

打开 public/vendor/laravel-admin/laravel-admin/laravel-admin.js
添加代码:

// 不使用 pjax 刷新的页面$(document).on('pjax:beforeReplace', function (e, options) {  // console.log(arguments)  var freshPaths = [    /\/admin.*\/products/,  ]  for (let path of freshPaths) {    if (path.test) {      if (path.test(e.state.url)) {        location.reload()        return false      }    }    else if (options.url.search(path) !== -1) {      location.reload()      return false    }  }})

使用自定义 view

很多时候我们并不需要大动干戈地建立一个全部的 view,只需要在内置 view 中稍作修改。
这时候,我们需要先自定义一个 Content 类:

use Encore\Admin\Layout\Content;class MyContent extends Content {    public function render() {        $items = [            'header'      => $this->header,            'description' => $this->description,            'breadcrumb'  => $this->breadcrumb,            'content'     => $this->build(),        ];        return view('admin.content', $items)->render();    }}

然后引用它:

    public function index(MyContent $content) {        return $content            ->header('product')            ->description($this->brand)            ->body($this->grid());    }

这样一来,每次进入到 index 页面,都会渲染 admin.content 这个 view 。

view 的内容直接 copy 自 vendor/encore/laravel-admin/resources/views/content.blade.php

在 view 里插入 vue 组件

添加2部分代码即可。
第一部分是初始化 vue app:

    <script data-exec-on-popstate>    // boot up the demo    $(function () {      // vapp      window.vapp = new Vue({        el: '#app',        data () {          return {            status: {              showGalleryEditor: false,            },            store: {              images: [],              el: '',            },          }        },        components: {},        methods: {          startGalleryEditing (event) {            this.status.showGalleryEditor = true            this.store.pk = $(event.target).parent().find('ul').data('pk')            this.store.images = $(event.target).parent().find('img').toArray().map((e) => e.getAttribute('src'))            window.p = $(event.target).parent().find('ul')          },        },      })    })    </script>

第2部分是插入组件:

        <gallery-editor :status="status" :images="store.images" :pk="store.pk"></gallery-editor>

vue 组件单独一个 js 文件

位置如下:public/vendor/components/gallery-editor.js
定义如下:

Vue.component('gallery-editor', {  props: {    status: {      showGalleryEditor: false,    },    images: [],    pk: 0,    moveTo: [],  },  data () {    return {}  },  watch: {    images (newVal, oldVal) {      this.moveTo = []      for (let src of newVal) {        this.moveTo.push({          src: src,          productId: this.pk,          deleted: 0,        })      }    },  },  methods: {    close () {      this.status.showGalleryEditor = false    },    save () {      let args = {_token: LA.token}      args.id = this.pk      args.images = []      args.move_to = []      // console.log(JSON.stringify(this.moveTo))      for (let imgObj of this.moveTo) {        if (imgObj.deleted) {          continue        }        if (imgObj.productId === this.pk) {          args.images.push(imgObj.src)        } else {          args.move_to.push({src: imgObj.src, product_id: imgObj.productId})        }      }      // console.log(JSON.stringify(args))      $.post('/admin/products/move-images', args).done(() => {        toastr.success('success')        this.status.showGalleryEditor = false      }).fail((response) => {        toastr.error(response.responseText)      })    },  },  template: `            <div class="modal" tabindex="-1" role="dialog" :class="{show: status.showGalleryEditor, fade: !status.showGalleryEditor}">              <div class="modal-dialog" role="document">                <div class="modal-content">                  <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-label="Close" @click="close"><span aria-hidden="true">&times;</span></button>                    <h4 class="modal-title">Editing images</h4>                  </div>                  <div class="modal-body">                  <ul style="list-style-type: none;">                      <li v-for="(imageObj, key) in moveTo" :key="key" style="margin-bottom: 8px">                          <img :src="imageObj.src" alt="" style="width:40px;height:40px">                          <label>Move to product: <input type="text" v-model="imageObj.productId"></label>                          <label>Delete:<input type="checkbox" v-model="imageObj.deleted"></label>                    </li>                    </ul>                  </div>                  <div class="modal-footer">                    <button type="button" class="btn btn-default" data-dismiss="modal" @click="close">Close</button>                    <button type="button" class="btn btn-primary" @click="save">Save changes</button>                  </div>                </div>              </div>            </div>`,})

这是一个弹出式编辑框,具体作用就不解释了,只是个示例。

然后还需要在 Admin/bootstrap.php 中引用这个 js 文件:

Admin::js('/vendor/components/gallery-editor.js');

为什么不把组件代码直接写进 view 中呢?
因为 pjax 的后端逻辑似乎有 bug,template 的内容无法全部渲染到前端,导致页面出错。