laravel5.7如何使用 Vue来实现省市区三级联动下拉菜单

在电商系统开发过程中用户收货地址设置是必要的关联数据之一,目前市面上比较方便使用的收货地址应用组件就是Vue提供的省市级三级联动菜单给众多开发人员提供了很大的便利。因为不同的开发环境Vue添加应用库的方式不同,因为我用的是Liunx系统,所以一切应用环境以liunx开发环境为基准,来添加应用Vue应用组件开发。

接下来进行用户地址功能开发几个步骤:

1.首先数据库-用户地址表设计


整理user_addresses 表的字段名称和类型:

字段名称描述类型加索引缘由
id自增长IDunsigned int
user_id该地址所属的用户unsigned int
provincevarchar
cityvarchar
districtvarchar
address具体地址varchar
zip邮编unsigned int
contact_name联系人姓名varchar
contact_phone联系人电话varchar
last_used_at最后一次使用时间datetime null

2.创建地址模型

2.1 用过 make:model 命令来创建用户地址模型这里给模型取名为UserAddress

$ php artisan make:model Models/UserAddress -fm

这里 -mf 意思是分别创建用户模型与工厂文件,接下来就不对工厂文件讲解了。
现在编辑用户地址文件 app/Models/UserAddress.php代码如下:

    protected $fillable = [
        'province',
        'city',
        'district',
        'address',
        'zip',
        'contact_name',
        'contact_phone',
        'last_used_at',
    ];
    protected $dates = ['last_used_at'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function getFullAddressAttribute()
    {
        return "{$this->province}{$this->city}{$this->district}{$this->address}";
    }

代码分析:
protected $dates = ['last_used_at'];表示last_used_at是一个时间类型
public function user() 是对用户模型进行的一对多的关联,也就是说,一个用户会有一个或者多个地址,反之也能表现出反向关联关系。
public function getFullAddressAttribute() 这是一个访问器,根据用户ID通过关联关系获取用户所有的地址,在代码中我们将用$address->full_address来获取。

2.2 需要在用户模型中创建与用户地址表的关联关系

app/Models/User.php

    public function addresses()
    {
        return $this->hasMany(UserAddress::class);
    }

3. 创建用户地址控制器

通过命令 make:controller 创建UserAddressesController 控制器

$ php artisan make:controller UserAddressesController

控制器中 index() 方法如下:

use App\Models\UserAddress;

public function index(Request $request)
{
     return view('user_addresses/index',[
            'addresses' => $request->user()->addresses,
     ]);
}
public function create()
    {
        return view('user_addresses.create_and_edit', ['address' => new UserAddress()]);
    }

代码解析:
use App\Models\UserAddress; 控制器中引入用户地址模型,方便数据关联关系的调用。
$request->user()->addresses上面在创建控制器模型的时候已经讲过了,模型关联关系函数的问题。这里就不细说了,这段代码主要就是 获取用户所有的收货地址,并将数据注入到前端模板中进行数据的渲染。
create()为创建用户地址方法。

4. 创建模板

更具上面收货地址控制器中的代码 按照路径在相应的目录中创建一个文件夹并在文件夹中创建收货地址模板文件

$ mkdir -p resources/views/user_addresses
$ touch resources/views/user_addresses/index.blade.php
$ touch resources/views/user_addresses/create_and_edit.blade.php

resources/views/user_addresses/index.blade.php

@extends('layouts.app')
@section('title', '收货地址列表')

@section('content')
  <div class="row">
    <div class="col-md-10 offset-md-1">
      <div class="card panel-default">
        <div class="card-header">收货地址列表</div>
        <div class="card-body">
          <table class="table table-bordered table-striped">
            <thead>
            <tr>
              <th>收货人</th>
              <th>地址</th>
              <th>邮编</th>
              <th>电话</th>
              <th>操作</th>
            </tr>
            </thead>
            <tbody>
            @foreach($addresses as $address)
              <tr>
                <td>{{ $address->contact_name }}</td>
                <td>{{ $address->full_address }}</td>
                <td>{{ $address->zip }}</td>
                <td>{{ $address->contact_phone }}</td>
                <td>
                  <button class="btn btn-primary">修改</button>
                  <button class="btn btn-danger">删除</button>
                </td>
              </tr>
            @endforeach
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
@endsection

5.创建路由

routes/web.php

Route::get('user_addresses', 'UserAddressesController@index')->name('user_addresses.index');
Route::get('user_addresses/create', 'UserAddressesController@create')->name('user_addresses.create');

根据路由地址访问:http://xxxx.xx/user_addresses 将能够看到地址列表,如果您没有进行数据填充看到的将是空白列表

Vue城市三级联动菜单

接下来将是本文的重点Vue联动菜单组件的应用

6.Vue省市区联动组件,添加一个中国省市区的库

$ yarn add china-area-data

之后创建Vue组件

$ touch resources/js/components/SelectDistrict.js

resources/js/components/SelectDistrict.js

// 从刚刚安装的库中加载数据
const addressData = require('china-area-data/v3/data');
// 引入 lodash,lodash 是一个实用工具库,提供了很多常用的方法
import _ from 'lodash';

// 注册一个名为 select-district 的 Vue 组件
Vue.component('select-district', {
  // 定义组件的属性
  props: {
    // 用来初始化省市区的值,在编辑时会用到
    initValue: {
      type: Array, // 格式是数组
      default: () => ([]), // 默认是个空数组
    }
  },
  // 定义了这个组件内的数据
  data() {
    return {
      provinces: addressData['86'], // 省列表
      cities: {}, // 城市列表
      districts: {}, // 地区列表
      provinceId: '', // 当前选中的省
      cityId: '', // 当前选中的市
      districtId: '', // 当前选中的区
    };
  },
  // 定义观察器,对应属性变更时会触发对应的观察器函数
  watch: {
    // 当选择的省发生改变时触发
    provinceId(newVal) {
      if (!newVal) {
        this.cities = {};
        this.cityId = '';
        return;
      }
      // 将城市列表设为当前省下的城市
      this.cities = addressData[newVal];
      // 如果当前选中的城市不在当前省下,则将选中城市清空
      if (!this.cities[this.cityId]) {
        this.cityId = '';
      }
    },
    // 当选择的市发生改变时触发
    cityId(newVal) {
      if (!newVal) {
        this.districts = {};
        this.districtId = '';
        return;
      }
      // 将地区列表设为当前城市下的地区
      this.districts = addressData[newVal];
      // 如果当前选中的地区不在当前城市下,则将选中地区清空
      if (!this.districts[this.districtId]) {
        this.districtId = '';
      }
    },
    // 当选择的区发生改变时触发
    districtId() {
      // 触发一个名为 change 的 Vue 事件,事件的值就是当前选中的省市区名称,格式为数组
      this.$emit('change', [this.provinces[this.provinceId], this.cities[this.cityId], this.districts[this.districtId]]);
    },
  },
  // 组件初始化时会调用这个方法
  created() {
    this.setFromValue(this.initValue);
  },
  methods: {
    // 
    setFromValue(value) {
      // 过滤掉空值
      value = _.filter(value);
      // 如果数组长度为0,则将省清空(由于我们定义了观察器,会联动触发将城市和地区清空)
      if (value.length === 0) {
        this.provinceId = '';
        return;
      }
      // 从当前省列表中找到与数组第一个元素同名的项的索引
      const provinceId = _.findKey(this.provinces, o => o === value[0]);
      // 没找到,清空省的值
      if (!provinceId) {
        this.provinceId = '';
        return;
      }
      // 找到了,将当前省设置成对应的ID
      this.provinceId = provinceId;
      // 由于观察器的作用,这个时候城市列表已经变成了对应省的城市列表
      // 从当前城市列表找到与数组第二个元素同名的项的索引
      const cityId = _.findKey(addressData[provinceId], o => o === value[1]);
      // 没找到,清空城市的值
      if (!cityId) {
        this.cityId = '';
        return;
      }
      // 找到了,将当前城市设置成对应的ID
      this.cityId = cityId;
      // 由于观察器的作用,这个时候地区列表已经变成了对应城市的地区列表
      // 从当前地区列表找到与数组第三个元素同名的项的索引
      const districtId = _.findKey(addressData[cityId], o => o === value[2]);
      // 没找到,清空地区的值
      if (!districtId) {
        this.districtId = '';
        return;
      }
      // 找到了,将当前地区设置成对应的ID
      this.districtId = districtId;
    }
  }
});

做完这些操作后还需要在esources/js/app.js 引入我们创建的组件SelectDistrict.js

// 此处需在引入 Vue 之后引入
require('./components/SelectDistrict');

const app = new Vue({
    el: '#app'
});

接下来我们按照Vue语法渲染添加用户收货地址页面的代码
resources/views/user_addresses/create_and_edit.blade.php

@extends('layouts.app')
@section('title', '新增收货地址')

@section('content')
  <div class="row">
    <div class="col-md-10 offset-lg-1">
      <div class="card">
        <div class="card-header">
          <h2 class="text-center">
            新增收货地址
          </h2>
        </div>
        <div class="card-body">
          <form class="form-horizontal" role="form">
            <!-- inline-template 代表通过内联方式引入组件 -->
            <select-district inline-template>
              <div class="form-row">
                <label class="col-form-label col-sm-2 text-md-right">省市区</label>
                <div class="col-sm-3">
                  <select class="form-control" v-model="provinceId">
                    <option value="">选择省</option>
                    <option v-for="(name, id) in provinces" :value="id">@{{ name }}</option>
                  </select>
                </div>
                <div class="col-sm-3">
                  <select class="form-control" v-model="cityId">
                    <option value="">选择市</option>
                    <option v-for="(name, id) in cities" :value="id">@{{ name }}</option>
                  </select>
                </div>
                <div class="col-sm-3">
                  <select class="form-control" v-model="districtId">
                    <option value="">选择区</option>
                    <option v-for="(name, id) in districts" :value="id">@{{ name }}</option>
                  </select>
                </div>
              </div>
            </select-district>
          </form>
        </div>
      </div>
    </div>
  </div>
@endsection

这样用户收货地址的三级联动菜单功能就完成了。测试从菜单之前必须将$ npm run watch-poll保持运行,否则将看不到菜单效果
Vue城市三级联动菜单

阅读 725

Comments