通常一个字段如果有多个选择,我们会使用下拉框来表现数据,如支付类型(支付宝,微信,银行卡);但是有时候普通的下拉框满足不了业务需求,如我想选择多种支付方式,甚至如果支付方式又延伸为线上和线下两大类,线上为支付宝等等,线下为当面交易等等,这样页面的数据展示效果和页面与后台存值取值交互就变得更麻烦。但是vue已经为我们安排好了一切~

本篇博客就以上述场景作为例子来写一个demo。

效果图

payInfo


建表以及初始数据SQL

-- 创建用户信息表
CREATE TABLE `user_info`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(255) NULL DEFAULT NULL COMMENT '用户名称',
  `remark` text NULL COMMENT '备注',
  `create_datetime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) COMMENT = '用户信息表';

-- 创建支付方式表
CREATE TABLE `pay_info`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `pay_name` varchar(255) NULL DEFAULT NULL COMMENT '支付方式名称',
  `parent_id` bigint(20) NULL DEFAULT NULL COMMENT '父级标签id',
  `remark` text NULL COMMENT '备注',
  `create_datetime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) COMMENT = '支付方式表';

-- 创建用户支付方式关联表
CREATE TABLE `user_pay_rel`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
  `pay_info_id` bigint(20) NULL DEFAULT NULL COMMENT '支付方式id',
	`remark` text NULL COMMENT '备注',
  `create_datetime` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) COMMENT = '用户支付方式关联表';


-- 插入初始数据
insert into user_info (user_name,remark) value ('斗苦故事','梦想成为全栈。');
insert into pay_info (pay_name,parent_id,remark) value ('线上',0,'线上支付方式(父)'),('支付宝',1,'线上支付方式(子)'),('微信',1,'线上支付方式(子)'),('网银支付',1,'线上支付方式(子)');
insert into pay_info (pay_name,parent_id,remark) value ('线下',0,'线下支付方式(父)'),('银行转账',5,'线下支付方式(子)'),('当面交易',5,'线下支付方式(子)');
insert into user_pay_rel (user_id,pay_info_id,remark) value (1,2,'使用支付宝支付'),(1,3,'使用微信支付'),(1,6,'到银行转账支付');

实体类 JavaBean

除了三张表各对应一个基础实体类外,还要额外创建三个扩展实体类

注:为了代码规范以及未来可扩展性,一般页面接收和返回的数据都会封装成一个扩展实体类

先分析一下下拉框多分组需要哪样的数据格式(对应上面的支付方式表的数据)

object : [

{

         parentLabelName:'线上',     ->     payName

         parentLabelValue:'1',           ->      id

         child: [       childLabelName -> payName ; childLabelValue -> id

                   {childLabelName:'支付宝',childLabelValue:'2'},

                   {childLabelName:'微信',childLabelValue:'3'},

                   {childLabelName:'网银支付',childLabelValue:'4'}

         ]

},

{

         parentLabelName:'线下',     ->     payName

         parentLabelValue:'5',           ->      id

         child: [       childLabelName -> payName ; childLabelValue -> id

                   {childLabelName:'银行转账',childLabelValue:'6'},

                   {childLabelName:'当面交易',childLabelValue:'7'}

         ]

},

]

  • object:第一个扩展对象,里面封装着父级对象
  • parentLabel:第二个扩展对象,里面封装着父级属性和子级对象
  • child:第三个扩展对象,里面封装着子级属性
  • 一共三层,最外面一层 object 就是返回到页面的主要对象,里面包含着父级集合(线上对象和线下对象),每一个父级对象中又包含一个子级集合(线上->支付宝/微信/网银支付   线下->银行转账/当面交易)

实体类 Java 代码

  • 三个基础实体类
    // UserInfo.java
    private static final long serialVersionUID = 1L;

    // 主键
    private Long id;
    // 用户名称
    private String userName;
    // 备注
    private String remark;
    // 创建时间
    private Date createDatetime;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public String getUserName() {return userName;}
    public void setUserName(String userName) {this.userName = userName;}
    public String getRemark() {return remark;}
    public void setRemark(String remark) {this.remark = remark;}
    public Date getCreateDatetime() {return createDatetime;}
    public void setCreateDatetime(Date createDatetime) {this.createDatetime = createDatetime;}
    // PayInfo.java
    private static final long serialVersionUID = 1L;

    // 主键
    private Long id;
    // 支付方式名称
    private String payName;
    // 父级标签id
    private Long parentId;
    // 备注
    private String remark;
    // 创建时间
    private Date createDatetime;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public String getPayName() {return payName;}
    public void setPayName(String payName) {this.payName = payName;}
    public Long getParentId() {return parentId;}
    public void setParentId(Long parentId) {this.parentId = parentId;}
    public String getRemark() {return remark;}
    public void setRemark(String remark) {this.remark = remark;}
    public Date getCreateDatetime() {return createDatetime;}
    public void setCreateDatetime(Date createDatetime) {this.createDatetime = createDatetime;}
    // UserPayRel.java
    private static final long serialVersionUID = 1L;

    // 主键
    private Long id;
    // 用户id
    private Long userId;
    // 支付方式id
    private Long payInfoId;
    // 备注
    private String remark;
    // 创建时间
    private Date createDatetime;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public Long getUserId() {return userId;}
    public void setUserId(Long userId) {this.userId = userId;}
    public Long getPayInfoId() {return payInfoId;}
    public void setPayInfoId(Long payInfoId) {this.payInfoId = payInfoId;}
    public String getRemark() {return remark;}
    public void setRemark(String remark) {this.remark = remark;}
    public Date getCreateDatetime() {return createDatetime;}
    public void setCreateDatetime(Date createDatetime) {this.createDatetime = createDatetime;}
  • 第三个扩展对象,里面封装着子级属性
    // PayChildDTO.java
    private static final long serialVersionUID = 1L;

    // 主键
    private Long id;
    // 支付方式名称
    private String payName;
    // 父级标签id
    private Long parentId;
    // 备注
    private String remark;
    // 创建时间
    private Date createDatetime;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public String getPayName() {return payName;}
    public void setPayName(String payName) {this.payName = payName;}
    public Long getParentId() {return parentId;}
    public void setParentId(Long parentId) {this.parentId = parentId;}
    public String getRemark() {return remark;}
    public void setRemark(String remark) {this.remark = remark;}
    public Date getCreateDatetime() {return createDatetime;}
    public void setCreateDatetime(Date createDatetime) {this.createDatetime = createDatetime;}
  • 第二个扩展对象,里面封装着父级属性和子级集合对象
    // PayParentDTO.java
    private static final long serialVersionUID = 1L;

    // 主键
    private Long id;
    // 支付方式名称
    private String payName;
    // 父级标签id
    private Long parentId;
    // 备注
    private String remark;
    // 创建时间
    private Date createDatetime;
    // 子级支付方式集合
    private List<PayChildDTO> options;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public String getPayName() {return payName;}
    public void setPayName(String payName) {this.payName = payName;}
    public Long getParentId() {return parentId;}
    public void setParentId(Long parentId) {this.parentId = parentId;}
    public String getRemark() {return remark;}
    public void setRemark(String remark) {this.remark = remark;}
    public Date getCreateDatetime() {return createDatetime;}
    public void setCreateDatetime(Date createDatetime) {this.createDatetime = createDatetime;}
    public List<PayChildDTO> getOptions() {return options;}
    public void setOptions(List<PayChildDTO> options) {this.options = options;}
  • 第一个扩展对象,里面封装着父级集合对象
    // PayResponseDTO.java
    private static final long serialVersionUID = 1L;

    // 父级支付方式集合
    private List<PayParentDTO> options;
    public List<PayParentDTO> getOptions() {return options;}
    public void setOptions(List<PayParentDTO> options) {this.options = options;}

页面 vue 代码

payInfo.vue

  1. <template>

<template>
    <el-select @change="changeFun" multiple filterable v-model="relList" placeholder="请选择">
        <el-option-group v-for="(group,index) in allPayInfo" :key="group.id" :label="group.payName">
            <el-option v-for="(item,index) in group.options" :key="item.id" :label="item.payName" :value="item.id"></el-option>
        </el-option-group>
    </el-select>
</template>
  • <el-select>:当选项过多时,使用下拉菜单展示并选择内容
  • <el-select> -> @change:选中值发生变化时触发
  • <el-select> -> multiple:是否能够多选
  • <el-select> -> filterable:是否可搜索
  • <el-select> -> v-model:下拉框绑定的值,值的格式为 String 类型的集合,取 <el-option> 中 :value 的值
  • <el-select> -> placeholder:占位符
  • <el-option-group>:下拉框分组
  • <el-option-group> -> v-for:遍历父级集合
  • <el-option-group> -> :key:分组唯一 key
  • <el-option-group> -> :label:分组的组名
  • <el-option>:下拉框的选择项
  • <el-option> -> v-for:遍历父级对象中的子级集合
  • <el-option> -> :key:下拉选择项的唯一 key
  • <el-option> -> :label:选项的标签,若不设置则默认与 value 相同
  • <el-option> -> :value:选项的值。每选择一次值后, <el-select>  -> v-model 绑定的集合中就会添加所选择的值

  2. <script>

<script>
import { getAllPayInfoApi , getUserPayRelByUserIdApi } from "@/api/payInfo/payInfo";
export default {
    name: "payInfo",
    data () {
        return {
            allPayInfo:[],
            relList:[]
        }
    },
    created () {
        // 获取所有支付方式
        this.getAllPayInfo();
        // 根据用户id查询用户支付方式关联信息
        this.getUserPayRelByUserId();
    },
    mounted () {},  
    methods : {
        // 获取所有支付方式
        getAllPayInfo(){
            getAllPayInfoApi().then(res => {
                this.allPayInfo = res.data.data.options;
                console.log(this.allPayInfo);
            });
        },
        // 根据用户id查询用户支付方式关联信息
        getUserPayRelByUserId(){
            getUserPayRelByUserIdApi(1).then(res => {
                this.relList = res.data.data;
                console.log(this.relList);
            });
        },
        // 选中值发生变化时触发
        changeFun() {
            console.log(this.relList);
        },
    }
}
</script>
  • import:导入两个查询请求接口
  • name:全局ID
  • data -> allPayInfo / relList:页面上绑定的两个集合(所有支付方式 / 用户支付方式关联信息)
  • created:页面创建后触发的方法
  • methods:自定义的方法

 payInfo.js 

// 获取所有支付方式
export function getAllPayInfoApi() {
    return request({
        url: `127.0.0.1:8080/payInfo/getAllPayInfo`,
        method: 'post'
    })
}

// 根据用户id查询用户支付方式关联信息
export function getUserPayRelByUserIdApi(id) {
    return request({
        url: `127.0.0.1:8080/userPayRel/getUserPayRelByUserId?userId=` + id,
        method: 'post'
    })
}
  • 这两个接口就是调用的 Java 后台 controller,请求方式都是 post
  • 第二个方法入参在 vue 页面的方法调用处,给的值是固定值 1,当然这里只是因为测试所以这样写,运用到项目中的时候可以动态传参进去

前端完整代码如下

// payInfo.vue
<template>
    <div class="app-container">
        <el-form>
            <el-form-item label="支付方式:">
                <template>
                    <el-select @change="changeFun" multiple filterable v-model="relList" placeholder="请选择">
                        <el-option-group v-for="(group,index) in allPayInfo" :key="group.id" :label="group.payName">
                            <el-option v-for="(item,index) in group.options" :key="item.id" :label="item.payName" :value="item.id"></el-option>
                        </el-option-group>
                    </el-select>
                </template>
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
import { getAllPayInfoApi , getUserPayRelByUserIdApi } from "@/api/payInfo/payInfo";
export default {
    name: "payInfo",
    data () {
        return {
            allPayInfo:[],
            relList:[]
        }
    },
    created () {
        // 获取所有支付方式
        this.getAllPayInfo();
        // 根据用户id查询用户支付方式关联信息
        this.getUserPayRelByUserId();
    },
    mounted () {},  
    methods : {
        // 获取所有支付方式
        getAllPayInfo(){
            getAllPayInfoApi().then(res => {
                this.allPayInfo = res.data.data.options;
                console.log(this.allPayInfo);
            });
        },
        // 根据用户id查询用户支付方式关联信息
        getUserPayRelByUserId(){
            getUserPayRelByUserIdApi(1).then(res => {
                this.relList = res.data.data;
                console.log(this.relList);
            });
        },
        // 选中值发生变化时触发
        changeFun() {
            console.log(this.relList);
        },
    }
}
</script>

<style lang="scss">

</style>


// payInfo.js
import request from '@/这里写你项目中具体路径'

// 获取所有支付方式
export function getAllPayInfoApi() {
    return request({
        url: `127.0.0.1:8080/payInfo/getAllPayInfo`,
        method: 'post'
    })
}

// 根据用户id查询用户支付方式关联信息
export function getUserPayRelByUserIdApi(id) {
    return request({
        url: `127.0.0.1:8080/userPayRel/getUserPayRelByUserId?userId=` + id,
        method: 'post'
    })
}


后台 Java 逻辑代码

两个接口按照 url 请求到 Java 后台 controller,以及查询所有支付方式和根据 id 查询关联表数据 这两个单表查询操作就不详细写了,这里只把关键代码粘贴出来说明一下

  1. 获取所有支付方式

  • 方法返回的数据类型是 PayResponseDTO
  • 业务逻辑代码如下
    /*
     * @ClassName PayInfoService
     * @Desc TODO   获取所有支付方式
     * @Date 2019/1/9 11:35
     * @Version 1.0
     */
    public PayResponseDTO getAllPayInfo(){
        // 返回结果对象
        PayResponseDTO result = new PayResponseDTO();

        // 1. 查询出所有支付方式集合
        // 注:当前 demo 数据量不大,如果正式环境中数据量比较大,建议此步操作从 redis 中取
        List<PayInfo> allPayInfo = payInfoMapper.getAllPayInfo();

        // 如果存在支付方式
        if(allPayInfo.size()>0){
            // 2. 放入父级
            List<PayParentDTO> payParentDTOS = new ArrayList<>();
            allPayInfo.forEach(
                payInfo -> {
                    if(payInfo.getParentId()==0){
                        // 复制父级属性值
                        PayParentDTO payParentDTO = new PayParentDTO();
                        BeanUtils.copyProperties(payInfo,payParentDTO);
                        // 存入父级中
                        payParentDTOS.add(payParentDTO);
                    }
                }
            );

            // 如果存在父级集合
            if(payParentDTOS.size()>0){
                // 3. 遍历父级,把父级下的所有子级放入当前遍历父级的子级集合中
                payParentDTOS.forEach(
                    parentForEach -> {
                        // 每个父级 new 一个子级集合
                        List<PayChildDTO> payChildDTOS = new ArrayList<>();
                        // 遍历所有支付方式
                        allPayInfo.forEach(
                            allPayForEach -> {
                                if(parentForEach.getId()==allPayForEach.getParentId()){
                                    // 复制子级属性值
                                    PayChildDTO payChildDTO = new PayChildDTO();
                                    BeanUtils.copyProperties(allPayForEach,payChildDTO);
                                    payChildDTOS.add(payChildDTO);
                                }
                            }
                        );
                        // 每一个父级遍历结束后都存入子级
                        parentForEach.setOptions(payChildDTOS);
                    }
                );

                // 4. 父级遍历结束,父级集合放入响应对象中
                result.setOptions(payParentDTOS);
            }
        }

        // 5. 返回结果
        return result;
    }
  • payInfoMapper.getAllPayInfo() 就是一个查询 pay_info 全部数据的操作(select 全部字段 from pay_info)

  2. 根据用户 id 查询用户支付方式关联信息

  • 方法返回的数据类型是 List<String>
  • 业务逻辑代码如下
    /*
     * @ClassName UserPayRelService
     * @Desc TODO   根据用户id查询用户支付方式关联信息
     * @Date 2019/1/9 13:45
     * @Version 1.0
     */
    public List<String> getUserPayRelByUserId(Long userId){
        // 返回集合对象
        List<String> result = new ArrayList<>();
        // 根据用户id查询用户支付方式关联信息
        List<UserPayRel> userPayRelByUserId = userPayRelMapper.getUserPayRelByUserId(userId);
        // 非空判断
        if(userPayRelByUserId.size()>0){
            // 遍历所有关联关系,把支付方式id存入返回集合对象中
            userPayRelByUserId.forEach(userPayRel -> {
                result.add(userPayRel.getPayInfoId().toString());
            });
        }
        return result;
    }
  • userPayRelMapper.getUserPayRelByUserId(userId) 方法是根据 user_id 到 user_pay_rel 表中查询出关联数据操作(select 全部字段 from user_pay_rel where user_id = #{userId} )

到此此篇博客已经结束了

有不对的地方或有疑问的地方欢迎留言与我交流,探讨

 

欢迎来访我的vue专栏总篇博客 

 

希望能够帮助到你

over

 

 

 

Logo

前往低代码交流专区

更多推荐