Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(二)项目实现-第五篇-核心功能车票预定开发及nacos集成

news2024/11/27 14:53:36

本文参考自

Springboot3+微服务实战12306高性能售票系统 - 慕课网 (imooc.com)

本文是仿12306项目实战第(二)章——项目实现 的第五篇,本篇讲解该项目的核心功能——余票查询、车票预定功能的基础版开发,以及讲解项目与Nacos的集成

本章目录

  • 一、核心功能介绍
  • 二、增加余票信息表,生成代码
  • 三、生成车次时初始化余票信息
  • 四、生成车次时初始化各种座位的余票数量
  • 五、为余票信息页面增加查询条件
  • 六、为会员端增余票查询功能
  • 七、增加订票页面并且实现车次信息传递
    • 1.增加预订按钮,点击预订时,跳转到下单页面,并使用sessionStorage传递参数
    • 2.为余票查询页面缓存查询参数,方便用户使用;将session key写成常量,方便统一维护,可以避免多个功能使用同一个key
    • 3.美化车次信息的显示
    • 4.订单页面显示座位信息
  • 八、订票页面勾选乘客并显示购票列表
    • 1.订票页面,查询我的所有的乘客(可以在新增乘客的时候,增加一个校验:超过50个乘客,就不能再新增了)
    • 2.订票页面,显示我的乘客复选框
    • 3.订票页面,为勾选的乘客构造购票数据
    • 4.订票页面,优化购票列表的展示
    • 5.订票页面,勾选乘客后提交,显示购票列表确认框
  • 九、分解选座购票功能的前后端逻辑
  • 十、订票页面增加选座效果
    • 1.勾选乘客后,提交时,校验余票是否足够(前端校验不一定准,但前端校验可以减轻后端很多压力)
    • 2.根据购票列表,计算出是否支持选座
    • 3.根据购票列表,展示选座按钮
    • 4.余票小于20张时,不允许选座
    • 5.确认提交时,计算出最终每个乘客所选的座位
    • 6.chooseSeatObj先清空,再初始化,保证两排座位是有序的
  • 十、增加确认订单表并生成前后端代码
  • 十一、后端增加确认下单购票接口
  • 十二、确认下单接口数据初始化
  • 十三、预扣减库存并判断余票是否足够
  • 十四、计算多个选座之间的偏移值

一、核心功能介绍

在这里插入图片描述

二、增加余票信息表,生成代码

  • business.sql

    drop table if exists `daily_train_ticket`;
    create table `daily_train_ticket` (
      `id` bigint not null comment 'id',
      `date` date not null comment '日期',
      `train_code` varchar(20) not null comment '车次编号',
      `start` varchar(20) not null comment '出发站',
      `start_pinyin` varchar(50) not null comment '出发站拼音',
      `start_time` time not null comment '出发时间',
      `start_index` tinyint not null comment '出发站序|本站是整个车次的第几站',
      `end` varchar(20) not null comment '到达站',
      `end_pinyin` varchar(50) not null comment '到达站拼音',
      `end_time` time not null comment '到站时间',
      `end_index` tinyint not null comment '到站站序|本站是整个车次的第几站',
      `ydz` int not null comment '一等座余票',
      `ydz_price` decimal(8, 2) not null comment '一等座票价',
      `edz` int not null comment '二等座余票',
      `edz_price` decimal(8, 2) not null comment '二等座票价',
      `rw` int not null comment '软卧余票',
      `rw_price` decimal(8, 2) not null comment '软卧票价',
      `yw` int not null comment '硬卧余票',
      `yw_price` decimal(8, 2) not null comment '硬卧票价',
      `create_time` datetime(3) comment '新增时间',
      `update_time` datetime(3) comment '修改时间',
      primary key (`id`),
      unique key `date_train_code_start_end_unique` (`date`, `train_code`, `start`, `end`)
    ) engine=innodb default charset=utf8mb4 comment='余票信息';
    

    实际12306用的是商业缓存软件来做的余票信息缓存,本项目用mysql新建临时表来代替缓存演示业务实现

  • 修改generator-config-business.xml,生成持久层、前后端代码

    <table tableName="daily_train_ticket" domainObjectName="DailyTrainTicket"/>
    

    操作同之前

  • 修改admin/package.json,加规则去除ESLint报错

    "rules": {
      "vue/multi-word-component-names": 0,
      "no-undef": 0,
      "vue/no-unused-vars": 0
    }
    
  • 修改路由、侧边栏

    操作同之前

  • 测试

在这里插入图片描述

数据后续由定时任务填充

三、生成车次时初始化余票信息

  • 给批量方法都加上@Transactional

    虽然由于事务的传递性,外层有事务,内层方法也会是事务,但是规范点还是都加上事务注解

  • DailyTrainTicketService.java

    逻辑:比如车站1——车站2——车站3,站站组合情况就有 1、2;1、3;2、3 三种,需要分别生成每种组合的余票信息

    这里第一版 先解决站站组合逻辑,具体余票信息后面完善

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.domain.DailyTrainTicketExample;
    import com.neilxu.train.business.domain.TrainStation;
    import com.neilxu.train.business.mapper.DailyTrainTicketMapper;
    import com.neilxu.train.business.req.DailyTrainTicketQueryReq;
    import com.neilxu.train.business.req.DailyTrainTicketSaveReq;
    import com.neilxu.train.business.resp.DailyTrainTicketQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainTicketService {
    
        private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class);
    
        @Resource
        private DailyTrainTicketMapper dailyTrainTicketMapper;
    
        @Resource
        private TrainStationService trainStationService;
    
        public void save(DailyTrainTicketSaveReq req) {
            DateTime now = DateTime.now();
            DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class);
            if (ObjectUtil.isNull(dailyTrainTicket.getId())) {
                dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                dailyTrainTicket.setCreateTime(now);
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.insert(dailyTrainTicket);
            } else {
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket);
            }
        }
    
        public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) {
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.setOrderByClause("id desc");
            DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
    
            PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class);
    
            PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            dailyTrainTicketMapper.deleteByPrimaryKey(id);
        }
    
        @Transactional
        public void genDaily(Date date, String trainCode) {
            LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode);
    
            // 删除某日某车次的余票信息
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.createCriteria()
                    .andDateEqualTo(date)
                    .andTrainCodeEqualTo(trainCode);
            dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample);
    
            // 查出某车次的所有的车站信息
            List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
            if (CollUtil.isEmpty(stationList)) {
                LOG.info("该车次没有车站基础数据,生成该车次的余票信息结束");
                return;
            }
    
            DateTime now = DateTime.now();
            for (int i = 0; i < stationList.size(); i++) {
                // 得到出发站
                TrainStation trainStationStart = stationList.get(i);
                for (int j = (i + 1); j < stationList.size(); j++) {
                    TrainStation trainStationEnd = stationList.get(j);
    
                    DailyTrainTicket dailyTrainTicket = new DailyTrainTicket();
    
                    dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                    dailyTrainTicket.setDate(date);
                    dailyTrainTicket.setTrainCode(trainCode);
                    dailyTrainTicket.setStart(trainStationStart.getName());
                    dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin());
                    dailyTrainTicket.setStartTime(trainStationStart.getOutTime());
                    dailyTrainTicket.setStartIndex(trainStationStart.getIndex());
                    dailyTrainTicket.setEnd(trainStationEnd.getName());
                    dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin());
                    dailyTrainTicket.setEndTime(trainStationEnd.getInTime());
                    dailyTrainTicket.setEndIndex(trainStationEnd.getIndex());
                    dailyTrainTicket.setYdz(0);
                    dailyTrainTicket.setYdzPrice(BigDecimal.ZERO);
                    dailyTrainTicket.setEdz(0);
                    dailyTrainTicket.setEdzPrice(BigDecimal.ZERO);
                    dailyTrainTicket.setRw(0);
                    dailyTrainTicket.setRwPrice(BigDecimal.ZERO);
                    dailyTrainTicket.setYw(0);
                    dailyTrainTicket.setYwPrice(BigDecimal.ZERO);
                    dailyTrainTicket.setCreateTime(now);
                    dailyTrainTicket.setUpdateTime(now);
                    dailyTrainTicketMapper.insert(dailyTrainTicket);
                }
            }
            LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode);
    
        }
    }
    
  • DailyTrainService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrain;
    import com.neilxu.train.business.domain.DailyTrainExample;
    import com.neilxu.train.business.domain.Train;
    import com.neilxu.train.business.mapper.DailyTrainMapper;
    import com.neilxu.train.business.req.DailyTrainQueryReq;
    import com.neilxu.train.business.req.DailyTrainSaveReq;
    import com.neilxu.train.business.resp.DailyTrainQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainService {
    
        private static final Logger LOG = LoggerFactory.getLogger(DailyTrainService.class);
    
        @Resource
        private DailyTrainMapper dailyTrainMapper;
    
        @Resource
        private TrainService trainService;
    
        @Resource
        private DailyTrainStationService dailyTrainStationService;
    
        @Resource
        private DailyTrainCarriageService dailyTrainCarriageService;
    
        @Resource
        private DailyTrainSeatService dailyTrainSeatService;
    
        @Resource
        private DailyTrainTicketService dailyTrainTicketService;
    
        public void save(DailyTrainSaveReq req) {
            DateTime now = DateTime.now();
            DailyTrain dailyTrain = BeanUtil.copyProperties(req, DailyTrain.class);
            if (ObjectUtil.isNull(dailyTrain.getId())) {
                dailyTrain.setId(SnowUtil.getSnowflakeNextId());
                dailyTrain.setCreateTime(now);
                dailyTrain.setUpdateTime(now);
                dailyTrainMapper.insert(dailyTrain);
            } else {
                dailyTrain.setUpdateTime(now);
                dailyTrainMapper.updateByPrimaryKey(dailyTrain);
            }
        }
    
        public PageResp<DailyTrainQueryResp> queryList(DailyTrainQueryReq req) {
            DailyTrainExample dailyTrainExample = new DailyTrainExample();
            dailyTrainExample.setOrderByClause("date desc, code asc");
            DailyTrainExample.Criteria criteria = dailyTrainExample.createCriteria();
            if (ObjectUtil.isNotNull(req.getDate())) {
                criteria.andDateEqualTo(req.getDate());
            }
            if (ObjectUtil.isNotEmpty(req.getCode())) {
                criteria.andCodeEqualTo(req.getCode());
            }
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<DailyTrain> dailyTrainList = dailyTrainMapper.selectByExample(dailyTrainExample);
    
            PageInfo<DailyTrain> pageInfo = new PageInfo<>(dailyTrainList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<DailyTrainQueryResp> list = BeanUtil.copyToList(dailyTrainList, DailyTrainQueryResp.class);
    
            PageResp<DailyTrainQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            dailyTrainMapper.deleteByPrimaryKey(id);
        }
    
        /**
         * 生成某日所有车次信息,包括车次、车站、车厢、座位
         * @param date
         */
        public void genDaily(Date date) {
            List<Train> trainList = trainService.selectAll();
            if (CollUtil.isEmpty(trainList)) {
                LOG.info("没有车次基础数据,任务结束");
                return;
            }
    
            for (Train train : trainList) {
                genDailyTrain(date, train);
            }
        }
    
        @Transactional
        public void genDailyTrain(Date date, Train train) {
            LOG.info("生成日期【{}】车次【{}】的信息开始", DateUtil.formatDate(date), train.getCode());
            // 删除该车次已有的数据
            DailyTrainExample dailyTrainExample = new DailyTrainExample();
            dailyTrainExample.createCriteria()
                    .andDateEqualTo(date)
                    .andCodeEqualTo(train.getCode());
            dailyTrainMapper.deleteByExample(dailyTrainExample);
    
            // 生成该车次的数据
            DateTime now = DateTime.now();
            DailyTrain dailyTrain = BeanUtil.copyProperties(train, DailyTrain.class);
            dailyTrain.setId(SnowUtil.getSnowflakeNextId());
            dailyTrain.setCreateTime(now);
            dailyTrain.setUpdateTime(now);
            dailyTrain.setDate(date);
            dailyTrainMapper.insert(dailyTrain);
    
            // 生成该车次的车站数据
            dailyTrainStationService.genDaily(date, train.getCode());
    
            // 生成该车次的车厢数据
            dailyTrainCarriageService.genDaily(date, train.getCode());
    
            // 生成该车次的座位数据
            dailyTrainSeatService.genDaily(date, train.getCode());
    
            // 生成该车次的余票数据
            dailyTrainTicketService.genDaily(date, train.getCode());
    
            LOG.info("生成日期【{}】车次【{}】的信息结束", DateUtil.formatDate(date), train.getCode());
        }
    }
    
  • 测试

在这里插入图片描述

四、生成车次时初始化各种座位的余票数量

  • DailyTrainSeatService.java

    public int countSeat(Date date, String trainCode, String seatType) {
        DailyTrainSeatExample example = new DailyTrainSeatExample();
        example.createCriteria()
            .andDateEqualTo(date)
            .andTrainCodeEqualTo(trainCode)
            .andSeatTypeEqualTo(seatType);
        long l = dailyTrainSeatMapper.countByExample(example);
        if (l == 0L) {
            return -1;
        }
        return (int) l;
    }
    
  • DailyTrainService.java

    // 生成该车次的余票数据
    dailyTrainTicketService.genDaily(dailyTrain, date, train.getCode());
    
  • DailyTrainTicketService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.EnumUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrain;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.domain.DailyTrainTicketExample;
    import com.neilxu.train.business.domain.TrainStation;
    import com.neilxu.train.business.enums.SeatTypeEnum;
    import com.neilxu.train.business.enums.TrainTypeEnum;
    import com.neilxu.train.business.mapper.DailyTrainTicketMapper;
    import com.neilxu.train.business.req.DailyTrainTicketQueryReq;
    import com.neilxu.train.business.req.DailyTrainTicketSaveReq;
    import com.neilxu.train.business.resp.DailyTrainTicketQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainTicketService {
    
        private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class);
    
        @Resource
        private DailyTrainTicketMapper dailyTrainTicketMapper;
    
        @Resource
        private TrainStationService trainStationService;
    
        @Resource
        private DailyTrainSeatService dailyTrainSeatService;
    
        public void save(DailyTrainTicketSaveReq req) {
            DateTime now = DateTime.now();
            DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class);
            if (ObjectUtil.isNull(dailyTrainTicket.getId())) {
                dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                dailyTrainTicket.setCreateTime(now);
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.insert(dailyTrainTicket);
            } else {
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket);
            }
        }
    
        public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) {
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.setOrderByClause("id desc");
            DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
    
            PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class);
    
            PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            dailyTrainTicketMapper.deleteByPrimaryKey(id);
        }
    
        @Transactional
        public void genDaily(DailyTrain dailyTrain, Date date, String trainCode) {
            LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode);
    
            // 删除某日某车次的余票信息
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.createCriteria()
                    .andDateEqualTo(date)
                    .andTrainCodeEqualTo(trainCode);
            dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample);
    
            // 查出某车次的所有的车站信息
            List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
            if (CollUtil.isEmpty(stationList)) {
                LOG.info("该车次没有车站基础数据,生成该车次的余票信息结束");
                return;
            }
    
            DateTime now = DateTime.now();
            int ydz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YDZ.getCode());
            int edz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.EDZ.getCode());
            int rw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.RW.getCode());
            int yw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YW.getCode());
            
            for (int i = 0; i < stationList.size(); i++) {
                // 得到出发站
                TrainStation trainStationStart = stationList.get(i);
                BigDecimal sumKM = BigDecimal.ZERO;
                for (int j = (i + 1); j < stationList.size(); j++) {
                    TrainStation trainStationEnd = stationList.get(j);
                    sumKM = sumKM.add(trainStationEnd.getKm());
    
                    DailyTrainTicket dailyTrainTicket = new DailyTrainTicket();
                    dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                    dailyTrainTicket.setDate(date);
                    dailyTrainTicket.setTrainCode(trainCode);
                    dailyTrainTicket.setStart(trainStationStart.getName());
                    dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin());
                    dailyTrainTicket.setStartTime(trainStationStart.getOutTime());
                    dailyTrainTicket.setStartIndex(trainStationStart.getIndex());
                    dailyTrainTicket.setEnd(trainStationEnd.getName());
                    dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin());
                    dailyTrainTicket.setEndTime(trainStationEnd.getInTime());
                    dailyTrainTicket.setEndIndex(trainStationEnd.getIndex());
                    // 票价 = 里程之和 * 座位单价 * 车次类型系数
                    String trainType = dailyTrain.getType();
                    // 计算票价系数:TrainTypeEnum.priceRate
                    BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate, TrainTypeEnum::getCode, trainType);
                    BigDecimal ydzPrice = sumKM.multiply(SeatTypeEnum.YDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal edzPrice = sumKM.multiply(SeatTypeEnum.EDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal rwPrice = sumKM.multiply(SeatTypeEnum.RW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal ywPrice = sumKM.multiply(SeatTypeEnum.YW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    dailyTrainTicket.setYdz(ydz);
                    dailyTrainTicket.setYdzPrice(ydzPrice);
                    dailyTrainTicket.setEdz(edz);
                    dailyTrainTicket.setEdzPrice(edzPrice);
                    dailyTrainTicket.setRw(rw);
                    dailyTrainTicket.setRwPrice(rwPrice);
                    dailyTrainTicket.setYw(yw);
                    dailyTrainTicket.setYwPrice(ywPrice);
                    dailyTrainTicket.setCreateTime(now);
                    dailyTrainTicket.setUpdateTime(now);
                    dailyTrainTicketMapper.insert(dailyTrainTicket);
                }
            }
            LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode);
    
        }
    }
    
  • 测试

    重新生成车次

在这里插入图片描述

五、为余票信息页面增加查询条件

  • DailyTrainTicketQueryReq.java

    package com.neilxu.train.business.req;
    
    import com.neilxu.train.common.req.PageReq;
    import lombok.Data;
    import org.springframework.format.annotation.DateTimeFormat;
    
    import java.util.Date;
    
    @Data
    public class DailyTrainTicketQueryReq extends PageReq {
        /**
         * 日期
         */
        @DateTimeFormat(pattern = "yyyy-MM-dd")
        private Date date;
    
        /**
         * 车次编号
         */
        private String trainCode;
    
        /**
         * 出发站
         */
        private String start;
    
        /**
         * 到达站
         */
        private String end;
    
    }
    
  • DailyTrainTicketService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.EnumUtil;
    import cn.hutool.core.util.ObjUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrain;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.domain.DailyTrainTicketExample;
    import com.neilxu.train.business.domain.TrainStation;
    import com.neilxu.train.business.enums.SeatTypeEnum;
    import com.neilxu.train.business.enums.TrainTypeEnum;
    import com.neilxu.train.business.mapper.DailyTrainTicketMapper;
    import com.neilxu.train.business.req.DailyTrainTicketQueryReq;
    import com.neilxu.train.business.req.DailyTrainTicketSaveReq;
    import com.neilxu.train.business.resp.DailyTrainTicketQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    import java.math.RoundingMode;
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainTicketService {
    
        private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class);
    
        @Resource
        private DailyTrainTicketMapper dailyTrainTicketMapper;
    
        @Resource
        private TrainStationService trainStationService;
    
        @Resource
        private DailyTrainSeatService dailyTrainSeatService;
    
        public void save(DailyTrainTicketSaveReq req) {
            DateTime now = DateTime.now();
            DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class);
            if (ObjectUtil.isNull(dailyTrainTicket.getId())) {
                dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                dailyTrainTicket.setCreateTime(now);
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.insert(dailyTrainTicket);
            } else {
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket);
            }
        }
    
        public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) {
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.setOrderByClause("id desc");
            DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria();
            if (ObjUtil.isNotNull(req.getDate())) {
                criteria.andDateEqualTo(req.getDate());
            }
            if (ObjUtil.isNotEmpty(req.getTrainCode())) {
                criteria.andTrainCodeEqualTo(req.getTrainCode());
            }
            if (ObjUtil.isNotEmpty(req.getStart())) {
                criteria.andStartEqualTo(req.getStart());
            }
            if (ObjUtil.isNotEmpty(req.getEnd())) {
                criteria.andEndEqualTo(req.getEnd());
            }
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
    
            PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class);
    
            PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            dailyTrainTicketMapper.deleteByPrimaryKey(id);
        }
    
        @Transactional
        public void genDaily(DailyTrain dailyTrain, Date date, String trainCode) {
            LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode);
    
            // 删除某日某车次的余票信息
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.createCriteria()
                    .andDateEqualTo(date)
                    .andTrainCodeEqualTo(trainCode);
            dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample);
    
            // 查出某车次的所有的车站信息
            List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
            if (CollUtil.isEmpty(stationList)) {
                LOG.info("该车次没有车站基础数据,生成该车次的余票信息结束");
                return;
            }
    
            DateTime now = DateTime.now();
            for (int i = 0; i < stationList.size(); i++) {
                // 得到出发站
                TrainStation trainStationStart = stationList.get(i);
                BigDecimal sumKM = BigDecimal.ZERO;
                for (int j = (i + 1); j < stationList.size(); j++) {
                    TrainStation trainStationEnd = stationList.get(j);
                    sumKM = sumKM.add(trainStationEnd.getKm());
    
                    DailyTrainTicket dailyTrainTicket = new DailyTrainTicket();
                    dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                    dailyTrainTicket.setDate(date);
                    dailyTrainTicket.setTrainCode(trainCode);
                    dailyTrainTicket.setStart(trainStationStart.getName());
                    dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin());
                    dailyTrainTicket.setStartTime(trainStationStart.getOutTime());
                    dailyTrainTicket.setStartIndex(trainStationStart.getIndex());
                    dailyTrainTicket.setEnd(trainStationEnd.getName());
                    dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin());
                    dailyTrainTicket.setEndTime(trainStationEnd.getInTime());
                    dailyTrainTicket.setEndIndex(trainStationEnd.getIndex());
                    int ydz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YDZ.getCode());
                    int edz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.EDZ.getCode());
                    int rw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.RW.getCode());
                    int yw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YW.getCode());
                    // 票价 = 里程之和 * 座位单价 * 车次类型系数
                    String trainType = dailyTrain.getType();
                    // 计算票价系数:TrainTypeEnum.priceRate
                    BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate, TrainTypeEnum::getCode, trainType);
                    BigDecimal ydzPrice = sumKM.multiply(SeatTypeEnum.YDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal edzPrice = sumKM.multiply(SeatTypeEnum.EDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal rwPrice = sumKM.multiply(SeatTypeEnum.RW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    BigDecimal ywPrice = sumKM.multiply(SeatTypeEnum.YW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                    dailyTrainTicket.setYdz(ydz);
                    dailyTrainTicket.setYdzPrice(ydzPrice);
                    dailyTrainTicket.setEdz(edz);
                    dailyTrainTicket.setEdzPrice(edzPrice);
                    dailyTrainTicket.setRw(rw);
                    dailyTrainTicket.setRwPrice(rwPrice);
                    dailyTrainTicket.setYw(yw);
                    dailyTrainTicket.setYwPrice(ywPrice);
                    dailyTrainTicket.setCreateTime(now);
                    dailyTrainTicket.setUpdateTime(now);
                    dailyTrainTicketMapper.insert(dailyTrainTicket);
                }
            }
            LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode);
    
        }
    }
    
  • daily-train-ticket.vue

    <template>
      <p>
        <a-space>
          <train-select-view v-model="params.trainCode" width="200px"></train-select-view>
          <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
          <station-select-view v-model="params.start" width="200px"></station-select-view>
          <station-select-view v-model="params.end" width="200px"></station-select-view>
          <a-button type="primary" @click="handleQuery()">查找</a-button>
        </a-space>
      </p>
      <a-table :dataSource="dailyTrainTickets"
               :columns="columns"
               :pagination="pagination"
               @change="handleTableChange"
               :loading="loading">
        <template #bodyCell="{ column, record }">
          <template v-if="column.dataIndex === 'operation'">
          </template>
        </template>
      </a-table>
    </template>
    
    <script>
    import { defineComponent, ref, onMounted } from 'vue';
    import {notification} from "ant-design-vue";
    import axios from "axios";
    import TrainSelectView from "@/components/train-select";
    import StationSelectView from "@/components/station-select";
    
    export default defineComponent({
      name: "daily-train-ticket-view",
      components: {StationSelectView, TrainSelectView},
      setup() {
        const visible = ref(false);
        let dailyTrainTicket = ref({
          id: undefined,
          date: undefined,
          trainCode: undefined,
          start: undefined,
          startPinyin: undefined,
          startTime: undefined,
          startIndex: undefined,
          end: undefined,
          endPinyin: undefined,
          endTime: undefined,
          endIndex: undefined,
          ydz: undefined,
          ydzPrice: undefined,
          edz: undefined,
          edzPrice: undefined,
          rw: undefined,
          rwPrice: undefined,
          yw: undefined,
          ywPrice: undefined,
          createTime: undefined,
          updateTime: undefined,
        });
        const dailyTrainTickets = ref([]);
        // 分页的三个属性名是固定的
        const pagination = ref({
          total: 0,
          current: 1,
          pageSize: 10,
        });
        let loading = ref(false);
        const params = ref({});
        const columns = [
          {
            title: '日期',
            dataIndex: 'date',
            key: 'date',
          },
          {
            title: '车次编号',
            dataIndex: 'trainCode',
            key: 'trainCode',
          },
          {
            title: '出发站',
            dataIndex: 'start',
            key: 'start',
          },
          {
            title: '出发站拼音',
            dataIndex: 'startPinyin',
            key: 'startPinyin',
          },
          {
            title: '出发时间',
            dataIndex: 'startTime',
            key: 'startTime',
          },
          {
            title: '出发站序',
            dataIndex: 'startIndex',
            key: 'startIndex',
          },
          {
            title: '到达站',
            dataIndex: 'end',
            key: 'end',
          },
          {
            title: '到达站拼音',
            dataIndex: 'endPinyin',
            key: 'endPinyin',
          },
          {
            title: '到站时间',
            dataIndex: 'endTime',
            key: 'endTime',
          },
          {
            title: '到站站序',
            dataIndex: 'endIndex',
            key: 'endIndex',
          },
          {
            title: '一等座余票',
            dataIndex: 'ydz',
            key: 'ydz',
          },
          {
            title: '一等座票价',
            dataIndex: 'ydzPrice',
            key: 'ydzPrice',
          },
          {
            title: '二等座余票',
            dataIndex: 'edz',
            key: 'edz',
          },
          {
            title: '二等座票价',
            dataIndex: 'edzPrice',
            key: 'edzPrice',
          },
          {
            title: '软卧余票',
            dataIndex: 'rw',
            key: 'rw',
          },
          {
            title: '软卧票价',
            dataIndex: 'rwPrice',
            key: 'rwPrice',
          },
          {
            title: '硬卧余票',
            dataIndex: 'yw',
            key: 'yw',
          },
          {
            title: '硬卧票价',
            dataIndex: 'ywPrice',
            key: 'ywPrice',
          },
        ];
    
    
        const handleQuery = (param) => {
          if (!param) {
            param = {
              page: 1,
              size: pagination.value.pageSize
            };
          }
          loading.value = true;
          axios.get("/business/admin/daily-train-ticket/query-list", {
            params: {
              page: param.page,
              size: param.size,
              trainCode: params.value.trainCode,
              date: params.value.date,
              start: params.value.start,
              end: params.value.end
            }
          }).then((response) => {
            loading.value = false;
            let data = response.data;
            if (data.success) {
              dailyTrainTickets.value = data.content.list;
              // 设置分页控件的值
              pagination.value.current = param.page;
              pagination.value.total = data.content.total;
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const handleTableChange = (page) => {
          // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
          pagination.value.pageSize = page.pageSize;
          handleQuery({
            page: page.current,
            size: page.pageSize
          });
        };
    
        onMounted(() => {
          handleQuery({
            page: 1,
            size: pagination.value.pageSize
          });
        });
    
        return {
          dailyTrainTicket,
          visible,
          dailyTrainTickets,
          pagination,
          columns,
          handleTableChange,
          handleQuery,
          loading,
          params
        };
      },
    });
    </script>
    
  • 测试

在这里插入图片描述

问题:当前页面显示信息太乱,且有多余的列,需要优化

  • 优化余票信息页面

    • daily-train-ticket.vue

      <template>
        <p>
          <a-space>
            <train-select-view v-model="params.trainCode" width="200px"></train-select-view>
            <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
            <station-select-view v-model="params.start" width="200px"></station-select-view>
            <station-select-view v-model="params.end" width="200px"></station-select-view>
            <a-button type="primary" @click="handleQuery()">查找</a-button>
          </a-space>
        </p>
        <a-table :dataSource="dailyTrainTickets"
                 :columns="columns"
                 :pagination="pagination"
                 @change="handleTableChange"
                 :loading="loading">
          <template #bodyCell="{ column, record }">
            <template v-if="column.dataIndex === 'operation'">
            </template>
            <template v-else-if="column.dataIndex === 'station'">
              {{record.start}}<br/>
              {{record.end}}
            </template>
            <template v-else-if="column.dataIndex === 'time'">
              {{record.startTime}}<br/>
              {{record.endTime}}
            </template>
            <template v-else-if="column.dataIndex === 'duration'">
              {{calDuration(record.startTime, record.endTime)}}<br/>
              <div v-if="record.startTime.replaceAll(':', '') >= record.endTime.replaceAll(':', '')">
                次日到达
              </div>
              <div v-else>
                当日到达
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'ydz'">
              <div v-if="record.ydz >= 0">
                {{record.ydz}}<br/>
                {{record.ydzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'edz'">
              <div v-if="record.edz >= 0">
                {{record.edz}}<br/>
                {{record.edzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'rw'">
              <div v-if="record.rw >= 0">
                {{record.rw}}<br/>
                {{record.rwPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'yw'">
              <div v-if="record.yw >= 0">
                {{record.yw}}<br/>
                {{record.ywPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
          </template>
        </a-table>
      </template>
      
      <script>
      import { defineComponent, ref, onMounted } from 'vue';
      import {notification} from "ant-design-vue";
      import axios from "axios";
      import TrainSelectView from "@/components/train-select";
      import StationSelectView from "@/components/station-select";
      import dayjs from "dayjs";
      
      export default defineComponent({
        name: "daily-train-ticket-view",
        components: {StationSelectView, TrainSelectView},
        setup() {
          const visible = ref(false);
          let dailyTrainTicket = ref({
            id: undefined,
            date: undefined,
            trainCode: undefined,
            start: undefined,
            startPinyin: undefined,
            startTime: undefined,
            startIndex: undefined,
            end: undefined,
            endPinyin: undefined,
            endTime: undefined,
            endIndex: undefined,
            ydz: undefined,
            ydzPrice: undefined,
            edz: undefined,
            edzPrice: undefined,
            rw: undefined,
            rwPrice: undefined,
            yw: undefined,
            ywPrice: undefined,
            createTime: undefined,
            updateTime: undefined,
          });
          const dailyTrainTickets = ref([]);
          // 分页的三个属性名是固定的
          const pagination = ref({
            total: 0,
            current: 1,
            pageSize: 10,
          });
          let loading = ref(false);
          const params = ref({});
          const columns = [
            {
              title: '日期',
              dataIndex: 'date',
              key: 'date',
            },
            {
              title: '车次编号',
              dataIndex: 'trainCode',
              key: 'trainCode',
            },
            {
              title: '车站',
              dataIndex: 'station',
            },
            {
              title: '时间',
              dataIndex: 'time',
            },
            {
              title: '历时',
              dataIndex: 'duration',
            },
            // {
            //   title: '出发站',
            //   dataIndex: 'start',
            //   key: 'start',
            // },
            // {
            //   title: '出发站拼音',
            //   dataIndex: 'startPinyin',
            //   key: 'startPinyin',
            // },
            // {
            //   title: '出发时间',
            //   dataIndex: 'startTime',
            //   key: 'startTime',
            // },
            // {
            //   title: '出发站序',
            //   dataIndex: 'startIndex',
            //   key: 'startIndex',
            // },
            // {
            //   title: '到达站',
            //   dataIndex: 'end',
            //   key: 'end',
            // },
            // {
            //   title: '到达站拼音',
            //   dataIndex: 'endPinyin',
            //   key: 'endPinyin',
            // },
            // {
            //   title: '到站时间',
            //   dataIndex: 'endTime',
            //   key: 'endTime',
            // },
            // {
            //   title: '到站站序',
            //   dataIndex: 'endIndex',
            //   key: 'endIndex',
            // },
            {
              title: '一等座',
              dataIndex: 'ydz',
              key: 'ydz',
            },
            // {
            //   title: '一等座票价',
            //   dataIndex: 'ydzPrice',
            //   key: 'ydzPrice',
            // },
            {
              title: '二等座',
              dataIndex: 'edz',
              key: 'edz',
            },
            // {
            //   title: '二等座票价',
            //   dataIndex: 'edzPrice',
            //   key: 'edzPrice',
            // },
            {
              title: '软卧',
              dataIndex: 'rw',
              key: 'rw',
            },
            // {
            //   title: '软卧票价',
            //   dataIndex: 'rwPrice',
            //   key: 'rwPrice',
            // },
            {
              title: '硬卧',
              dataIndex: 'yw',
              key: 'yw',
            },
            // {
            //   title: '硬卧票价',
            //   dataIndex: 'ywPrice',
            //   key: 'ywPrice',
            // },
          ];
      
      
          const handleQuery = (param) => {
            if (!param) {
              param = {
                page: 1,
                size: pagination.value.pageSize
              };
            }
            loading.value = true;
            axios.get("/business/admin/daily-train-ticket/query-list", {
              params: {
                page: param.page,
                size: param.size,
                trainCode: params.value.trainCode,
                date: params.value.date,
                start: params.value.start,
                end: params.value.end
              }
            }).then((response) => {
              loading.value = false;
              let data = response.data;
              if (data.success) {
                dailyTrainTickets.value = data.content.list;
                // 设置分页控件的值
                pagination.value.current = param.page;
                pagination.value.total = data.content.total;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleTableChange = (page) => {
            // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
            pagination.value.pageSize = page.pageSize;
            handleQuery({
              page: page.current,
              size: page.pageSize
            });
          };
      
          const calDuration = (startTime, endTime) => {
            let diff = dayjs(endTime, 'HH:mm:ss').diff(dayjs(startTime, 'HH:mm:ss'), 'seconds');
            return dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss');
          };
      
          onMounted(() => {
            handleQuery({
              page: 1,
              size: pagination.value.pageSize
            });
          });
      
          return {
            dailyTrainTicket,
            visible,
            dailyTrainTickets,
            pagination,
            columns,
            handleTableChange,
            handleQuery,
            loading,
            params,
            calDuration
          };
        },
      });
      </script>
      
    • 测试

在这里插入图片描述

六、为会员端增余票查询功能

  • 修改乘客管理页面,正常显示页面;修改前端模块的启动指令名称

    • passenger.vue

      web/src/views/main/passenger.vue

      <template>
        <p>
          <a-space>
            <a-button type="primary" @click="handleQuery()">刷新</a-button>
            <a-button type="primary" @click="onAdd">新增</a-button>
          </a-space>
        </p>
        <a-table :dataSource="passengers"
                 :columns="columns"
                 :pagination="pagination"
                 @change="handleTableChange"
                 :loading="loading">
          <template #bodyCell="{ column, record }">
            <template v-if="column.dataIndex === 'operation'">
              <a-space>
                <a-popconfirm
                    title="删除后不可恢复,确认删除?"
                    @confirm="onDelete(record)"
                    ok-text="确认" cancel-text="取消">
                  <a style="color: red">删除</a>
                </a-popconfirm>
                <a @click="onEdit(record)">编辑</a>
              </a-space>
            </template>
            <template v-else-if="column.dataIndex === 'type'">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === record.type">
                  {{item.desc}}
                </span>
              </span>
            </template>
          </template>
        </a-table>
        <a-modal v-model:visible="visible" title="乘车人" @ok="handleOk"
                 ok-text="确认" cancel-text="取消">
          <a-form :model="passenger" :label-col="{span: 4}" :wrapper-col="{ span: 20 }">
            <a-form-item label="姓名">
              <a-input v-model:value="passenger.name" />
            </a-form-item>
            <a-form-item label="身份证">
              <a-input v-model:value="passenger.idCard" />
            </a-form-item>
            <a-form-item label="旅客类型">
              <a-select v-model:value="passenger.type">
                <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                  {{item.desc}}
                </a-select-option>
              </a-select>
            </a-form-item>
          </a-form>
        </a-modal>
      </template>
      
      <script>
      import { defineComponent, ref, onMounted } from 'vue';
      import {notification} from "ant-design-vue";
      import axios from "axios";
      
      export default defineComponent({
        name: "passenger-view",
        setup() {
          const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
          const visible = ref(false);
          let passenger = ref({
            id: undefined,
            memberId: undefined,
            name: undefined,
            idCard: undefined,
            type: undefined,
            createTime: undefined,
            updateTime: undefined,
          });
          const passengers = ref([]);
          // 分页的三个属性名是固定的
          const pagination = ref({
            total: 0,
            current: 1,
            pageSize: 10,
          });
          let loading = ref(false);
          const columns = [
            {
              title: '会员id',
              dataIndex: 'memberId',
              key: 'memberId',
            },
            {
              title: '姓名',
              dataIndex: 'name',
              key: 'name',
            },
            {
              title: '身份证',
              dataIndex: 'idCard',
              key: 'idCard',
            },
            {
              title: '旅客类型',
              dataIndex: 'type',
              key: 'type',
            },
            {
              title: '操作',
              dataIndex: 'operation'
            }
          ];
      
          const onAdd = () => {
            passenger.value = {};
            visible.value = true;
          };
      
          const onEdit = (record) => {
            passenger.value = window.Tool.copy(record);
            visible.value = true;
          };
      
          const onDelete = (record) => {
            axios.delete("/member/passenger/delete/" + record.id).then((response) => {
              const data = response.data;
              if (data.success) {
                notification.success({description: "删除成功!"});
                handleQuery({
                  page: pagination.value.current,
                  size: pagination.value.pageSize,
                });
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleOk = () => {
            axios.post("/member/passenger/save", passenger.value).then((response) => {
              let data = response.data;
              if (data.success) {
                notification.success({description: "保存成功!"});
                visible.value = false;
                handleQuery({
                  page: pagination.value.current,
                  size: pagination.value.pageSize
                });
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleQuery = (param) => {
            if (!param) {
              param = {
                page: 1,
                size: pagination.value.pageSize
              };
            }
            loading.value = true;
            axios.get("/member/passenger/query-list", {
              params: {
                page: param.page,
                size: param.size
              }
            }).then((response) => {
              loading.value = false;
              let data = response.data;
              if (data.success) {
                passengers.value = data.content.list;
                // 设置分页控件的值
                pagination.value.current = param.page;
                pagination.value.total = data.content.total;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleTableChange = (pagination) => {
            // console.log("看看自带的分页参数都有啥:" + pagination);
            handleQuery({
              page: pagination.current,
              size: pagination.pageSize
            });
          };
      
          onMounted(() => {
            handleQuery({
              page: 1,
              size: pagination.value.pageSize
            });
          });
      
          return {
            PASSENGER_TYPE_ARRAY,
            passenger,
            visible,
            passengers,
            pagination,
            columns,
            handleTableChange,
            handleQuery,
            loading,
            onAdd,
            handleOk,
            onEdit,
            onDelete
          };
        },
      });
      </script>
      
    • admin/package.json

      "scripts": {
        "admin-dev": "vue-cli-service serve --mode dev --port 9001",
        "admin-prod": "vue-cli-service serve --mode prod --port 9001",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint"
      },
      
    • web/package.json

      "private": true,
      "scripts": {
        "web-dev": "vue-cli-service serve --mode dev --port 9000",
        "web-prod": "vue-cli-service serve --mode prod --port 9000",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint"
      },
      
    • 测试效果

在这里插入图片描述

  • 会员端增加余票查询页面,功能和控台端完全一致

    操作:将前面控台端的代码复制到用户端

    • DailyTrainTicketController.java

      package com.neilxu.train.business.controller;
      
      import com.neilxu.train.business.req.DailyTrainTicketQueryReq;
      import com.neilxu.train.business.resp.DailyTrainTicketQueryResp;
      import com.neilxu.train.business.service.DailyTrainTicketService;
      import com.neilxu.train.common.resp.CommonResp;
      import com.neilxu.train.common.resp.PageResp;
      import jakarta.annotation.Resource;
      import jakarta.validation.Valid;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      @RequestMapping("/daily-train-ticket")
      public class DailyTrainTicketController {
      
          @Resource
          private DailyTrainTicketService dailyTrainTicketService;
      
          @GetMapping("/query-list")
          public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) {
              PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);
              return new CommonResp<>(list);
          }
      
      }
      
    • StationController.java

      package com.neilxu.train.business.controller;
      
      import com.neilxu.train.business.resp.StationQueryResp;
      import com.neilxu.train.business.service.StationService;
      import com.neilxu.train.common.resp.CommonResp;
      import jakarta.annotation.Resource;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.List;
      
      @RestController
      @RequestMapping("/station")
      public class StationController {
      
          @Resource
          private StationService stationService;
      
          @GetMapping("/query-all")
          public CommonResp<List<StationQueryResp>> queryList() {
              List<StationQueryResp> list = stationService.queryAll();
              return new CommonResp<>(list);
          }
      
      }
      
    • TrainController.java

      package com.neilxu.train.business.controller;
      
      import com.neilxu.train.business.resp.TrainQueryResp;
      import com.neilxu.train.business.service.TrainService;
      import com.neilxu.train.common.resp.CommonResp;
      import jakarta.annotation.Resource;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.List;
      
      @RestController
      @RequestMapping("/train")
      public class TrainController {
      
          @Resource
          private TrainService trainService;
      
          @GetMapping("/query-all")
          public CommonResp<List<TrainQueryResp>> queryList() {
              List<TrainQueryResp> list = trainService.queryAll();
              return new CommonResp<>(list);
          }
      
      }
      
    • station-select.vue

      <template>
        <a-select v-model:value="name" show-search allowClear
                  :filterOption="filterNameOption"
                  @change="onChange" placeholder="请选择车站"
                  :style="'width: ' + localWidth">
          <a-select-option v-for="item in stations" :key="item.name" :value="item.name" :label="item.name + item.namePinyin + item.namePy">
            {{item.name}} {{item.namePinyin}} ~ {{item.namePy}}
          </a-select-option>
        </a-select>
      </template>
      
      <script>
      
      import {defineComponent, onMounted, ref, watch} from 'vue';
      import axios from "axios";
      import {notification} from "ant-design-vue";
      
      export default defineComponent({
        name: "station-select-view",
        props: ["modelValue", "width"],
        emits: ['update:modelValue', 'change'],
        setup(props, {emit}) {
          const name = ref();
          const stations = ref([]);
          const localWidth = ref(props.width);
          if (Tool.isEmpty(props.width)) {
            localWidth.value = "100%";
          }
      
          // 利用watch,动态获取父组件的值,如果放在onMounted或其它方法里,则只有第一次有效
          watch(() => props.modelValue, ()=>{
            console.log("props.modelValue", props.modelValue);
            name.value = props.modelValue;
          }, {immediate: true});
      
          /**
           * 查询所有的车站,用于车站下拉框
           */
          const queryAllStation = () => {
            axios.get("/business/station/query-all").then((response) => {
              let data = response.data;
              if (data.success) {
                stations.value = data.content;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          /**
           * 车站下拉框筛选
           */
          const filterNameOption = (input, option) => {
            console.log(input, option);
            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
          };
      
          /**
           * 将当前组件的值响应给父组件
           * @param value
           */
          const onChange = (value) => {
            emit('update:modelValue', value);
            let station = stations.value.filter(item => item.code === value)[0];
            if (Tool.isEmpty(station)) {
              station = {};
            }
            emit('change', station);
          };
      
          onMounted(() => {
            queryAllStation();
          });
      
          return {
            name,
            stations,
            filterNameOption,
            onChange,
            localWidth
          };
        },
      });
      </script>
      
    • train-select.vue

      <template>
        <a-select v-model:value="trainCode" show-search allowClear
                  :filterOption="filterTrainCodeOption"
                  @change="onChange" placeholder="请选择车次"
                  :style="'width: ' + localWidth">
          <a-select-option v-for="item in trains" :key="item.code" :value="item.code" :label="item.code + item.start + item.end">
            {{item.code}} {{item.start}} ~ {{item.end}}
          </a-select-option>
        </a-select>
      </template>
      
      <script>
      
      import {defineComponent, onMounted, ref, watch} from 'vue';
      import axios from "axios";
      import {notification} from "ant-design-vue";
      
      export default defineComponent({
        name: "train-select-view",
        props: ["modelValue", "width"],
        emits: ['update:modelValue', 'change'],
        setup(props, {emit}) {
          const trainCode = ref();
          const trains = ref([]);
          const localWidth = ref(props.width);
          if (Tool.isEmpty(props.width)) {
            localWidth.value = "100%";
          }
      
          // 利用watch,动态获取父组件的值,如果放在onMounted或其它方法里,则只有第一次有效
          watch(() => props.modelValue, ()=>{
            console.log("props.modelValue", props.modelValue);
            trainCode.value = props.modelValue;
          }, {immediate: true});
      
          /**
           * 查询所有的车次,用于车次下拉框
           */
          const queryAllTrain = () => {
            axios.get("/business/train/query-all").then((response) => {
              let data = response.data;
              if (data.success) {
                trains.value = data.content;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          /**
           * 车次下拉框筛选
           */
          const filterTrainCodeOption = (input, option) => {
            console.log(input, option);
            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
          };
      
          /**
           * 将当前组件的值响应给父组件
           * @param value
           */
          const onChange = (value) => {
            emit('update:modelValue', value);
            let train = trains.value.filter(item => item.code === value)[0];
            if (Tool.isEmpty(train)) {
              train = {};
            }
            emit('change', train);
          };
      
          onMounted(() => {
            queryAllTrain();
          });
      
          return {
            trainCode,
            trains,
            filterTrainCodeOption,
            onChange,
            localWidth
          };
        },
      });
      </script>
      
    • the-header.vue和the-sider.vue

      <a-menu-item key="/ticket">
        <router-link to="/ticket">
          <user-outlined /> &nbsp; 余票查询
        </router-link>
      </a-menu-item>
      
    • web/src/router/index.js

      import { createRouter, createWebHistory } from 'vue-router'
      import store from "@/store";
      import {notification} from "ant-design-vue";
      
      const routes = [{
        path: '/login',
        component: () => import('../views/login.vue')
      }, {
        path: '/',
        component: () => import('../views/main.vue'),
        meta: {
          loginRequire: true
        },
        children: [{
          path: 'welcome',
          component: () => import('../views/main/welcome.vue'),
        }, {
          path: 'passenger',
          component: () => import('../views/main/passenger.vue'),
        }, {
          path: 'ticket',
          component: () => import('../views/main/ticket.vue'),
        }]
      }, {
        path: '',
        redirect: '/welcome'
      }];
      
      const router = createRouter({
        history: createWebHistory(process.env.BASE_URL),
        routes
      })
      
      // 路由登录拦截
      router.beforeEach((to, from, next) => {
        // 要不要对meta.loginRequire属性做监控拦截
        if (to.matched.some(function (item) {
          console.log(item, "是否需要登录校验:", item.meta.loginRequire || false);
          return item.meta.loginRequire
        })) {
          const _member = store.state.member;
          console.log("页面登录校验开始:", _member);
          if (!_member.token) {
            console.log("用户未登录或登录超时!");
            notification.error({ description: "未登录或登录超时" });
            next('/login');
          } else {
            next();
          }
        } else {
          next();
        }
      });
      
      export default router
      
    • ticket.vue

      <template>
        <p>
          <a-space>
            <train-select-view v-model="params.trainCode" width="200px"></train-select-view>
            <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
            <station-select-view v-model="params.start" width="200px"></station-select-view>
            <station-select-view v-model="params.end" width="200px"></station-select-view>
            <a-button type="primary" @click="handleQuery()">查找</a-button>
          </a-space>
        </p>
        <a-table :dataSource="dailyTrainTickets"
                 :columns="columns"
                 :pagination="pagination"
                 @change="handleTableChange"
                 :loading="loading">
          <template #bodyCell="{ column, record }">
            <template v-if="column.dataIndex === 'operation'">
            </template>
            <template v-else-if="column.dataIndex === 'station'">
              {{record.start}}<br/>
              {{record.end}}
            </template>
            <template v-else-if="column.dataIndex === 'time'">
              {{record.startTime}}<br/>
              {{record.endTime}}
            </template>
            <template v-else-if="column.dataIndex === 'duration'">
              {{calDuration(record.startTime, record.endTime)}}<br/>
              <div v-if="record.startTime.replaceAll(':', '') >= record.endTime.replaceAll(':', '')">
                次日到达
              </div>
              <div v-else>
                当日到达
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'ydz'">
              <div v-if="record.ydz >= 0">
                {{record.ydz}}<br/>
                {{record.ydzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'edz'">
              <div v-if="record.edz >= 0">
                {{record.edz}}<br/>
                {{record.edzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'rw'">
              <div v-if="record.rw >= 0">
                {{record.rw}}<br/>
                {{record.rwPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'yw'">
              <div v-if="record.yw >= 0">
                {{record.yw}}<br/>
                {{record.ywPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
          </template>
        </a-table>
      </template>
      
      <script>
      import { defineComponent, ref, onMounted } from 'vue';
      import {notification} from "ant-design-vue";
      import axios from "axios";
      import TrainSelectView from "@/components/train-select";
      import StationSelectView from "@/components/station-select";
      import dayjs from "dayjs";
      
      export default defineComponent({
        name: "ticket-view",
        components: {StationSelectView, TrainSelectView},
        setup() {
          const visible = ref(false);
          let dailyTrainTicket = ref({
            id: undefined,
            date: undefined,
            trainCode: undefined,
            start: undefined,
            startPinyin: undefined,
            startTime: undefined,
            startIndex: undefined,
            end: undefined,
            endPinyin: undefined,
            endTime: undefined,
            endIndex: undefined,
            ydz: undefined,
            ydzPrice: undefined,
            edz: undefined,
            edzPrice: undefined,
            rw: undefined,
            rwPrice: undefined,
            yw: undefined,
            ywPrice: undefined,
            createTime: undefined,
            updateTime: undefined,
          });
          const dailyTrainTickets = ref([]);
          // 分页的三个属性名是固定的
          const pagination = ref({
            total: 0,
            current: 1,
            pageSize: 10,
          });
          let loading = ref(false);
          const params = ref({});
          const columns = [
            {
              title: '日期',
              dataIndex: 'date',
              key: 'date',
            },
            {
              title: '车次编号',
              dataIndex: 'trainCode',
              key: 'trainCode',
            },
            {
              title: '车站',
              dataIndex: 'station',
            },
            {
              title: '时间',
              dataIndex: 'time',
            },
            {
              title: '历时',
              dataIndex: 'duration',
            },
            // {
            //   title: '出发站',
            //   dataIndex: 'start',
            //   key: 'start',
            // },
            // {
            //   title: '出发站拼音',
            //   dataIndex: 'startPinyin',
            //   key: 'startPinyin',
            // },
            // {
            //   title: '出发时间',
            //   dataIndex: 'startTime',
            //   key: 'startTime',
            // },
            // {
            //   title: '出发站序',
            //   dataIndex: 'startIndex',
            //   key: 'startIndex',
            // },
            // {
            //   title: '到达站',
            //   dataIndex: 'end',
            //   key: 'end',
            // },
            // {
            //   title: '到达站拼音',
            //   dataIndex: 'endPinyin',
            //   key: 'endPinyin',
            // },
            // {
            //   title: '到站时间',
            //   dataIndex: 'endTime',
            //   key: 'endTime',
            // },
            // {
            //   title: '到站站序',
            //   dataIndex: 'endIndex',
            //   key: 'endIndex',
            // },
            {
              title: '一等座',
              dataIndex: 'ydz',
              key: 'ydz',
            },
            // {
            //   title: '一等座票价',
            //   dataIndex: 'ydzPrice',
            //   key: 'ydzPrice',
            // },
            {
              title: '二等座',
              dataIndex: 'edz',
              key: 'edz',
            },
            // {
            //   title: '二等座票价',
            //   dataIndex: 'edzPrice',
            //   key: 'edzPrice',
            // },
            {
              title: '软卧',
              dataIndex: 'rw',
              key: 'rw',
            },
            // {
            //   title: '软卧票价',
            //   dataIndex: 'rwPrice',
            //   key: 'rwPrice',
            // },
            {
              title: '硬卧',
              dataIndex: 'yw',
              key: 'yw',
            },
            // {
            //   title: '硬卧票价',
            //   dataIndex: 'ywPrice',
            //   key: 'ywPrice',
            // },
          ];
      
      
          const handleQuery = (param) => {
            if (!param) {
              param = {
                page: 1,
                size: pagination.value.pageSize
              };
            }
            loading.value = true;
            axios.get("/business/daily-train-ticket/query-list", {
              params: {
                page: param.page,
                size: param.size,
                trainCode: params.value.trainCode,
                date: params.value.date,
                start: params.value.start,
                end: params.value.end
              }
            }).then((response) => {
              loading.value = false;
              let data = response.data;
              if (data.success) {
                dailyTrainTickets.value = data.content.list;
                // 设置分页控件的值
                pagination.value.current = param.page;
                pagination.value.total = data.content.total;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleTableChange = (page) => {
            // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
            pagination.value.pageSize = page.pageSize;
            handleQuery({
              page: page.current,
              size: page.pageSize
            });
          };
      
          const calDuration = (startTime, endTime) => {
            let diff = dayjs(endTime, 'HH:mm:ss').diff(dayjs(startTime, 'HH:mm:ss'), 'seconds');
            return dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss');
          };
      
          onMounted(() => {
            handleQuery({
              page: 1,
              size: pagination.value.pageSize
            });
          });
      
          return {
            dailyTrainTicket,
            visible,
            dailyTrainTickets,
            pagination,
            columns,
            handleTableChange,
            handleQuery,
            loading,
            params,
            calDuration
          };
        },
      });
      </script>
      
    • 测试

在这里插入图片描述

问题:用户端不需要根据车次查询,另外三个参数则必须输入
  • 修改余票查询页面,查询的三个参数必输,去掉车次查询条件

    • ticket.vue

      <template>
        <p>
          <a-space>
            <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
            <station-select-view v-model="params.start" width="200px"></station-select-view>
            <station-select-view v-model="params.end" width="200px"></station-select-view>
            <a-button type="primary" @click="handleQuery()">查找</a-button>
          </a-space>
        </p>
        <a-table :dataSource="dailyTrainTickets"
                 :columns="columns"
                 :pagination="pagination"
                 @change="handleTableChange"
                 :loading="loading">
          <template #bodyCell="{ column, record }">
            <template v-if="column.dataIndex === 'operation'">
            </template>
            <template v-else-if="column.dataIndex === 'station'">
              {{record.start}}<br/>
              {{record.end}}
            </template>
            <template v-else-if="column.dataIndex === 'time'">
              {{record.startTime}}<br/>
              {{record.endTime}}
            </template>
            <template v-else-if="column.dataIndex === 'duration'">
              {{calDuration(record.startTime, record.endTime)}}<br/>
              <div v-if="record.startTime.replaceAll(':', '') >= record.endTime.replaceAll(':', '')">
                次日到达
              </div>
              <div v-else>
                当日到达
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'ydz'">
              <div v-if="record.ydz >= 0">
                {{record.ydz}}<br/>
                {{record.ydzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'edz'">
              <div v-if="record.edz >= 0">
                {{record.edz}}<br/>
                {{record.edzPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'rw'">
              <div v-if="record.rw >= 0">
                {{record.rw}}<br/>
                {{record.rwPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
            <template v-else-if="column.dataIndex === 'yw'">
              <div v-if="record.yw >= 0">
                {{record.yw}}<br/>
                {{record.ywPrice}}¥
              </div>
              <div v-else>
                --
              </div>
            </template>
          </template>
        </a-table>
      </template>
      
      <script>
      import { defineComponent, ref, onMounted } from 'vue';
      import {notification} from "ant-design-vue";
      import axios from "axios";
      import StationSelectView from "@/components/station-select";
      import dayjs from "dayjs";
      
      export default defineComponent({
        name: "ticket-view",
        components: {StationSelectView},
        setup() {
          const visible = ref(false);
          let dailyTrainTicket = ref({
            id: undefined,
            date: undefined,
            trainCode: undefined,
            start: undefined,
            startPinyin: undefined,
            startTime: undefined,
            startIndex: undefined,
            end: undefined,
            endPinyin: undefined,
            endTime: undefined,
            endIndex: undefined,
            ydz: undefined,
            ydzPrice: undefined,
            edz: undefined,
            edzPrice: undefined,
            rw: undefined,
            rwPrice: undefined,
            yw: undefined,
            ywPrice: undefined,
            createTime: undefined,
            updateTime: undefined,
          });
          const dailyTrainTickets = ref([]);
          // 分页的三个属性名是固定的
          const pagination = ref({
            total: 0,
            current: 1,
            pageSize: 10,
          });
          let loading = ref(false);
          const params = ref({});
          const columns = [
            {
              title: '车次编号',
              dataIndex: 'trainCode',
              key: 'trainCode',
            },
            {
              title: '车站',
              dataIndex: 'station',
            },
            {
              title: '时间',
              dataIndex: 'time',
            },
            {
              title: '历时',
              dataIndex: 'duration',
            },
            {
              title: '一等座',
              dataIndex: 'ydz',
              key: 'ydz',
            },
            {
              title: '二等座',
              dataIndex: 'edz',
              key: 'edz',
            },
            {
              title: '软卧',
              dataIndex: 'rw',
              key: 'rw',
            },
            {
              title: '硬卧',
              dataIndex: 'yw',
              key: 'yw',
            },
          ];
      
      
          const handleQuery = (param) => {
            if (Tool.isEmpty(params.value.date)) {
              notification.error({description: "请输入日期"});
              return;
            }
            if (Tool.isEmpty(params.value.start)) {
              notification.error({description: "请输入出发地"});
              return;
            }
            if (Tool.isEmpty(params.value.end)) {
              notification.error({description: "请输入目的地"});
              return;
            }
            if (!param) {
              param = {
                page: 1,
                size: pagination.value.pageSize
              };
            }
            loading.value = true;
            axios.get("/business/daily-train-ticket/query-list", {
              params: {
                page: param.page,
                size: param.size,
                trainCode: params.value.trainCode,
                date: params.value.date,
                start: params.value.start,
                end: params.value.end
              }
            }).then((response) => {
              loading.value = false;
              let data = response.data;
              if (data.success) {
                dailyTrainTickets.value = data.content.list;
                // 设置分页控件的值
                pagination.value.current = param.page;
                pagination.value.total = data.content.total;
              } else {
                notification.error({description: data.message});
              }
            });
          };
      
          const handleTableChange = (page) => {
            // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
            pagination.value.pageSize = page.pageSize;
            handleQuery({
              page: page.current,
              size: page.pageSize
            });
          };
      
          const calDuration = (startTime, endTime) => {
            let diff = dayjs(endTime, 'HH:mm:ss').diff(dayjs(startTime, 'HH:mm:ss'), 'seconds');
            return dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss');
          };
      
          onMounted(() => {
            // handleQuery({
            //   page: 1,
            //   size: pagination.value.pageSize
            // });
          });
      
          return {
            dailyTrainTicket,
            visible,
            dailyTrainTickets,
            pagination,
            columns,
            handleTableChange,
            handleQuery,
            loading,
            params,
            calDuration
          };
        },
      });
      </script>
      
    • 效果

在这里插入图片描述

七、增加订票页面并且实现车次信息传递

1.增加预订按钮,点击预订时,跳转到下单页面,并使用sessionStorage传递参数

  • order.vue

    下单页面

    <template>
      <div>{{ dailyTrainTicket }}</div>
    </template>
    
    <script>
    
    import {defineComponent} from 'vue';
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const dailyTrainTicket = SessionStorage.get("dailyTrainTicket") || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        return {
          dailyTrainTicket
        };
      },
    });
    </script>
    
  • ticket.vue

    <template>
      <p>
        <a-space>
          <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
          <station-select-view v-model="params.start" width="200px"></station-select-view>
          <station-select-view v-model="params.end" width="200px"></station-select-view>
          <a-button type="primary" @click="handleQuery()">查找</a-button>
        </a-space>
      </p>
      <a-table :dataSource="dailyTrainTickets"
               :columns="columns"
               :pagination="pagination"
               @change="handleTableChange"
               :loading="loading">
        <template #bodyCell="{ column, record }">
          <template v-if="column.dataIndex === 'operation'">
            <a-button type="primary" @click="toOrder(record)">预订</a-button>
          </template>
          <template v-else-if="column.dataIndex === 'station'">
            {{record.start}}<br/>
            {{record.end}}
          </template>
          <template v-else-if="column.dataIndex === 'time'">
            {{record.startTime}}<br/>
            {{record.endTime}}
          </template>
          <template v-else-if="column.dataIndex === 'duration'">
            {{calDuration(record.startTime, record.endTime)}}<br/>
            <div v-if="record.startTime.replaceAll(':', '') >= record.endTime.replaceAll(':', '')">
              次日到达
            </div>
            <div v-else>
              当日到达
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'ydz'">
            <div v-if="record.ydz >= 0">
              {{record.ydz}}<br/>
              {{record.ydzPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'edz'">
            <div v-if="record.edz >= 0">
              {{record.edz}}<br/>
              {{record.edzPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'rw'">
            <div v-if="record.rw >= 0">
              {{record.rw}}<br/>
              {{record.rwPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'yw'">
            <div v-if="record.yw >= 0">
              {{record.yw}}<br/>
              {{record.ywPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
        </template>
      </a-table>
    </template>
    
    <script>
    import { defineComponent, ref, onMounted } from 'vue';
    import {notification} from "ant-design-vue";
    import axios from "axios";
    import StationSelectView from "@/components/station-select";
    import dayjs from "dayjs";
    import router from "@/router";
    
    export default defineComponent({
      name: "ticket-view",
      components: {StationSelectView},
      setup() {
        const visible = ref(false);
        let dailyTrainTicket = ref({
          id: undefined,
          date: undefined,
          trainCode: undefined,
          start: undefined,
          startPinyin: undefined,
          startTime: undefined,
          startIndex: undefined,
          end: undefined,
          endPinyin: undefined,
          endTime: undefined,
          endIndex: undefined,
          ydz: undefined,
          ydzPrice: undefined,
          edz: undefined,
          edzPrice: undefined,
          rw: undefined,
          rwPrice: undefined,
          yw: undefined,
          ywPrice: undefined,
          createTime: undefined,
          updateTime: undefined,
        });
        const dailyTrainTickets = ref([]);
        // 分页的三个属性名是固定的
        const pagination = ref({
          total: 0,
          current: 1,
          pageSize: 10,
        });
        let loading = ref(false);
        const params = ref({});
        const columns = [
          {
            title: '车次编号',
            dataIndex: 'trainCode',
            key: 'trainCode',
          },
          {
            title: '车站',
            dataIndex: 'station',
          },
          {
            title: '时间',
            dataIndex: 'time',
          },
          {
            title: '历时',
            dataIndex: 'duration',
          },
          {
            title: '一等座',
            dataIndex: 'ydz',
            key: 'ydz',
          },
          {
            title: '二等座',
            dataIndex: 'edz',
            key: 'edz',
          },
          {
            title: '软卧',
            dataIndex: 'rw',
            key: 'rw',
          },
          {
            title: '硬卧',
            dataIndex: 'yw',
            key: 'yw',
          },
          {
            title: '操作',
            dataIndex: 'operation',
          },
        ];
    
    
        const handleQuery = (param) => {
          if (Tool.isEmpty(params.value.date)) {
            notification.error({description: "请输入日期"});
            return;
          }
          if (Tool.isEmpty(params.value.start)) {
            notification.error({description: "请输入出发地"});
            return;
          }
          if (Tool.isEmpty(params.value.end)) {
            notification.error({description: "请输入目的地"});
            return;
          }
          if (!param) {
            param = {
              page: 1,
              size: pagination.value.pageSize
            };
          }
          loading.value = true;
          axios.get("/business/daily-train-ticket/query-list", {
            params: {
              page: param.page,
              size: param.size,
              trainCode: params.value.trainCode,
              date: params.value.date,
              start: params.value.start,
              end: params.value.end
            }
          }).then((response) => {
            loading.value = false;
            let data = response.data;
            if (data.success) {
              dailyTrainTickets.value = data.content.list;
              // 设置分页控件的值
              pagination.value.current = param.page;
              pagination.value.total = data.content.total;
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const handleTableChange = (page) => {
          // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
          pagination.value.pageSize = page.pageSize;
          handleQuery({
            page: page.current,
            size: page.pageSize
          });
        };
    
        const calDuration = (startTime, endTime) => {
          let diff = dayjs(endTime, 'HH:mm:ss').diff(dayjs(startTime, 'HH:mm:ss'), 'seconds');
          return dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss');
        };
    
        const toOrder = (record) => {
          dailyTrainTicket.value = Tool.copy(record);
          SessionStorage.set("dailyTrainTicket", dailyTrainTicket.value);
          router.push("/order")
        };
    
        onMounted(() => {
          // handleQuery({
          //   page: 1,
          //   size: pagination.value.pageSize
          // });
        });
    
        return {
          dailyTrainTicket,
          visible,
          dailyTrainTickets,
          pagination,
          columns,
          handleTableChange,
          handleQuery,
          loading,
          params,
          calDuration,
          toOrder
        };
      },
    });
    </script>
    
  • 路由

    import { createRouter, createWebHistory } from 'vue-router'
    import store from "@/store";
    import {notification} from "ant-design-vue";
    
    const routes = [{
      path: '/login',
      component: () => import('../views/login.vue')
    }, {
      path: '/',
      component: () => import('../views/main.vue'),
      meta: {
        loginRequire: true
      },
      children: [{
        path: 'welcome',
        component: () => import('../views/main/welcome.vue'),
      }, {
        path: 'passenger',
        component: () => import('../views/main/passenger.vue'),
      }, {
        path: 'ticket',
        component: () => import('../views/main/ticket.vue'),
      }, {
        path: 'order',
        component: () => import('../views/main/order.vue'),
      }]
    }, {
      path: '',
      redirect: '/welcome'
    }];
    
    const router = createRouter({
      history: createWebHistory(process.env.BASE_URL),
      routes
    })
    
    // 路由登录拦截
    router.beforeEach((to, from, next) => {
      // 要不要对meta.loginRequire属性做监控拦截
      if (to.matched.some(function (item) {
        console.log(item, "是否需要登录校验:", item.meta.loginRequire || false);
        return item.meta.loginRequire
      })) {
        const _member = store.state.member;
        console.log("页面登录校验开始:", _member);
        if (!_member.token) {
          console.log("用户未登录或登录超时!");
          notification.error({ description: "未登录或登录超时" });
          next('/login');
        } else {
          next();
        }
      } else {
        next();
      }
    });
    
    export default router
    
  • 效果

    点击预定

在这里插入图片描述

2.为余票查询页面缓存查询参数,方便用户使用;将session key写成常量,方便统一维护,可以避免多个功能使用同一个key

  • web/public/js/session-storage.js

    // 所有的session key都在这里统一定义,可以避免多个功能使用同一个key
    SESSION_ORDER = "SESSION_ORDER";
    SESSION_TICKET_PARAMS = "SESSION_TICKET_PARAMS";
    
  • web/src/views/main/order.vue

    export default defineComponent({
      name: "order-view",
      setup() {
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        return {
          dailyTrainTicket
        };
      },
    });
    
  • web/src/views/main/ticket.vue

    <template>
      <p>
        <a-space>
          <a-date-picker v-model:value="params.date" valueFormat="YYYY-MM-DD" placeholder="请选择日期"></a-date-picker>
          <station-select-view v-model="params.start" width="200px"></station-select-view>
          <station-select-view v-model="params.end" width="200px"></station-select-view>
          <a-button type="primary" @click="handleQuery()">查找</a-button>
        </a-space>
      </p>
      <a-table :dataSource="dailyTrainTickets"
               :columns="columns"
               :pagination="pagination"
               @change="handleTableChange"
               :loading="loading">
        <template #bodyCell="{ column, record }">
          <template v-if="column.dataIndex === 'operation'">
            <a-button type="primary" @click="toOrder(record)">预订</a-button>
          </template>
          <template v-else-if="column.dataIndex === 'station'">
            {{record.start}}<br/>
            {{record.end}}
          </template>
          <template v-else-if="column.dataIndex === 'time'">
            {{record.startTime}}<br/>
            {{record.endTime}}
          </template>
          <template v-else-if="column.dataIndex === 'duration'">
            {{calDuration(record.startTime, record.endTime)}}<br/>
            <div v-if="record.startTime.replaceAll(':', '') >= record.endTime.replaceAll(':', '')">
              次日到达
            </div>
            <div v-else>
              当日到达
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'ydz'">
            <div v-if="record.ydz >= 0">
              {{record.ydz}}<br/>
              {{record.ydzPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'edz'">
            <div v-if="record.edz >= 0">
              {{record.edz}}<br/>
              {{record.edzPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'rw'">
            <div v-if="record.rw >= 0">
              {{record.rw}}<br/>
              {{record.rwPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
          <template v-else-if="column.dataIndex === 'yw'">
            <div v-if="record.yw >= 0">
              {{record.yw}}<br/>
              {{record.ywPrice}}¥
            </div>
            <div v-else>
              --
            </div>
          </template>
        </template>
      </a-table>
    </template>
    
    <script>
    import { defineComponent, ref, onMounted } from 'vue';
    import {notification} from "ant-design-vue";
    import axios from "axios";
    import StationSelectView from "@/components/station-select";
    import dayjs from "dayjs";
    import router from "@/router";
    
    export default defineComponent({
      name: "ticket-view",
      components: {StationSelectView},
      setup() {
        const visible = ref(false);
        let dailyTrainTicket = ref({
          id: undefined,
          date: undefined,
          trainCode: undefined,
          start: undefined,
          startPinyin: undefined,
          startTime: undefined,
          startIndex: undefined,
          end: undefined,
          endPinyin: undefined,
          endTime: undefined,
          endIndex: undefined,
          ydz: undefined,
          ydzPrice: undefined,
          edz: undefined,
          edzPrice: undefined,
          rw: undefined,
          rwPrice: undefined,
          yw: undefined,
          ywPrice: undefined,
          createTime: undefined,
          updateTime: undefined,
        });
        const dailyTrainTickets = ref([]);
        // 分页的三个属性名是固定的
        const pagination = ref({
          total: 0,
          current: 1,
          pageSize: 10,
        });
        let loading = ref(false);
        const params = ref({});
        const columns = [
          {
            title: '车次编号',
            dataIndex: 'trainCode',
            key: 'trainCode',
          },
          {
            title: '车站',
            dataIndex: 'station',
          },
          {
            title: '时间',
            dataIndex: 'time',
          },
          {
            title: '历时',
            dataIndex: 'duration',
          },
          {
            title: '一等座',
            dataIndex: 'ydz',
            key: 'ydz',
          },
          {
            title: '二等座',
            dataIndex: 'edz',
            key: 'edz',
          },
          {
            title: '软卧',
            dataIndex: 'rw',
            key: 'rw',
          },
          {
            title: '硬卧',
            dataIndex: 'yw',
            key: 'yw',
          },
          {
            title: '操作',
            dataIndex: 'operation',
          },
        ];
    
    
        const handleQuery = (param) => {
          if (Tool.isEmpty(params.value.date)) {
            notification.error({description: "请输入日期"});
            return;
          }
          if (Tool.isEmpty(params.value.start)) {
            notification.error({description: "请输入出发地"});
            return;
          }
          if (Tool.isEmpty(params.value.end)) {
            notification.error({description: "请输入目的地"});
            return;
          }
          if (!param) {
            param = {
              page: 1,
              size: pagination.value.pageSize
            };
          }
    
          // 保存查询参数
          SessionStorage.set(SESSION_TICKET_PARAMS, params.value);
    
          loading.value = true;
          axios.get("/business/daily-train-ticket/query-list", {
            params: {
              page: param.page,
              size: param.size,
              trainCode: params.value.trainCode,
              date: params.value.date,
              start: params.value.start,
              end: params.value.end
            }
          }).then((response) => {
            loading.value = false;
            let data = response.data;
            if (data.success) {
              dailyTrainTickets.value = data.content.list;
              // 设置分页控件的值
              pagination.value.current = param.page;
              pagination.value.total = data.content.total;
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const handleTableChange = (page) => {
          // console.log("看看自带的分页参数都有啥:" + JSON.stringify(page));
          pagination.value.pageSize = page.pageSize;
          handleQuery({
            page: page.current,
            size: page.pageSize
          });
        };
    
        const calDuration = (startTime, endTime) => {
          let diff = dayjs(endTime, 'HH:mm:ss').diff(dayjs(startTime, 'HH:mm:ss'), 'seconds');
          return dayjs('00:00:00', 'HH:mm:ss').second(diff).format('HH:mm:ss');
        };
    
        const toOrder = (record) => {
          dailyTrainTicket.value = Tool.copy(record);
          SessionStorage.set(SESSION_ORDER, dailyTrainTicket.value);
          router.push("/order")
        };
    
        onMounted(() => {
          //  "|| {}"是常用技巧,可以避免空指针异常
          params.value = SessionStorage.get(SESSION_TICKET_PARAMS) || {};
          if (Tool.isNotEmpty(params.value)) {
            handleQuery({
              page: 1,
              size: pagination.value.pageSize
            });
          }
        });
    
        return {
          dailyTrainTicket,
          visible,
          dailyTrainTickets,
          pagination,
          columns,
          handleTableChange,
          handleQuery,
          loading,
          params,
          calDuration,
          toOrder
        };
      },
    });
    </script>
    
  • 效果

    只要不关闭页面,可以缓存查询条件

在这里插入图片描述

3.美化车次信息的显示

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
      </div>
    </template>
    
    <script>
    
    import {defineComponent} from 'vue';
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        return {
          dailyTrainTicket
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    </style>
    
  • 效果

在这里插入图片描述

4.订单页面显示座位信息

  • 重新生成下枚举文件

    修改EnumGenerator.java,生成web/src/assets/js/enums.js

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
    </template>
    
    <script>
    
    import {defineComponent} from 'vue';
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
    
        return {
          dailyTrainTicket,
          seatTypes
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    </style>
    
  • 效果

在这里插入图片描述

八、订票页面勾选乘客并显示购票列表

1.订票页面,查询我的所有的乘客(可以在新增乘客的时候,增加一个校验:超过50个乘客,就不能再新增了)

  • PassengerService.java

    /**
     * 查询我的所有乘客
     */
    public List<PassengerQueryResp> queryMine() {
        PassengerExample passengerExample = new PassengerExample();
        passengerExample.setOrderByClause("name asc");
        PassengerExample.Criteria criteria = passengerExample.createCriteria();
        criteria.andMemberIdEqualTo(LoginMemberContext.getId());
        List<Passenger> list = passengerMapper.selectByExample(passengerExample);
        return BeanUtil.copyToList(list, PassengerQueryResp.class);
    }
    
  • PassengerController.java

    @GetMapping("/query-mine")
    public CommonResp<List<PassengerQueryResp>> queryMine() {
        List<PassengerQueryResp> list = passengerService.queryMine();
        return new CommonResp<>(list);
    }
    
  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      {{passengers}}
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    </style>
    
  • 效果

在这里插入图片描述

2.订票页面,显示我的乘客复选框

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
      <br/>
      选中的乘客:{{passengerChecks}}
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item.id
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    </style>
    
  • 效果

在这里插入图片描述

3.订票页面,为勾选的乘客构造购票数据

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
      <br/>
      选中的乘客:{{passengerChecks}}
      <br/>
      购票列表:{{tickets}}
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    </style>
    
  • 效果

在这里插入图片描述

4.订票页面,优化购票列表的展示

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
      <br/>
      选中的乘客:{{passengerChecks}}
      <br/>
      购票列表:{{tickets}}
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    </style>
    
  • 效果

在这里插入图片描述

5.订票页面,勾选乘客后提交,显示购票列表确认框

  • web/src/views/main/order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    </style>
    
  • 效果

在这里插入图片描述

在这里插入图片描述

九、分解选座购票功能的前后端逻辑

12306规则:

  • 只有全部是一等座或全部是二等座才支持选座
  • 余票小于一定数量时,不允许选座(本项目以20为例)

在这里插入图片描述

  • 构造两个重要的响应式变量:

    // 0:不支持选座;1:选一等座;2:选二等座
    const chooseSeatType = ref(0);
    
    // 选择的座位
    // {
    //   A1: false, C1: true,D1: false, F1: false,
    //   A2: false, C2: false,D2: true, F2: false
    // }
    const chooseSeatObj = ref({});
    
  • 最终购票tickets:

    // seat可选,当无选座时,seat为空
    [{
      passengerId: 123,
      passengerType: "1",
      seatTypeCode: "1",
      passengerName: "张三",
      passengerIdCard: "12323132132",
      seat: "C1"
    }, {
      passengerId: 123,
      passengerType: "1",
      seatTypeCode: "1",
      passengerName: "李四",
      passengerIdCard: "12323132132",
      seat: "D2"
    }]
    
  • 座位售卖详情,比如有ABCDE五个站,sell=0110,则AB未被购买,AC已被购买

    在这里插入图片描述

  • 后端购票逻辑,分成选座和不选座
    不选座,以购买一等座为例:遍历一等座车厢,每个车厢从1号座位开始找,未被购买的,就选中它
    选座,以购买两张一等座AB为例:遍历一等座车厢,每个车厢从1号座位开始找A列座位,未被购买的,就预选中它;再挑它旁边的B,如果也未被购买,则最终选中这两个座位,如果B已被购买,则回到第一步,继续找未被购买的A座。

    从第二个座位开始,需要计算和第一个座位的偏移值,可以减少循环,提高选座效率

    举例:当选择A1和C2座位,遍历找到A1座位,索引为0,则C2不需要再遍历,直接计算出偏移值是5,即索引是5,看可以不可选就行了

十、订票页面增加选座效果

这节是前端的选座逻辑的实现

1.勾选乘客后,提交时,校验余票是否足够(前端校验不一定准,但前端校验可以减轻后端很多压力)

  • order.vue

        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 弹出确认界面
          visible.value = true;
    
        };
    

2.根据购票列表,计算出是否支持选座

只有都选择一等座或都选二等座,才可以支持选座

获取到选座类型后,如果是一等座得到一等座的选座对象,二等座得到二等座的选座对象,如果是不可选,选座对象为空

  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          选座类型chooseSeatType:{{chooseSeatType}}
          <br/>
          选座对象chooseSeatType:{{chooseSeatObj}}
          <br/>
          座位类型SEAT_COL_ARRAY:{{SEAT_COL_ARRAY}}
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    </style>
    
  • 效果

在这里插入图片描述

在这里插入图片描述

3.根据购票列表,展示选座按钮

  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          选座对象chooseSeatType:{{chooseSeatObj}}
          <br/>
          <div v-if="chooseSeatType === 0" style="color: red;">
            您购买的车票不支持选座
            <div>12306规则:只有全部是一等座或全部是二等座才支持选座</div>
            <div>12306规则:余票小于一定数量时,不允许选座(本项目以20为例)</div>
          </div>
          <div v-else style="text-align: center">
            <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                      v-model:checked="chooseSeatObj[item.code + '1']" :checked-children="item.desc" :un-checked-children="item.desc" />
            <div v-if="tickets.length > 1">
              <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                        v-model:checked="chooseSeatObj[item.code + '2']" :checked-children="item.desc" :un-checked-children="item.desc" />
            </div>
            <div style="color: #999999">提示:您可以选择{{tickets.length}}个座位</div>
          </div>
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    
    .order-tickets .choose-seat-item {
      margin: 5px 5px;
    }
    </style>
    
  • 效果
    在这里插入图片描述

4.余票小于20张时,不允许选座

  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          <div v-if="chooseSeatType === 0" style="color: red;">
            您购买的车票不支持选座
            <div>12306规则:只有全部是一等座或全部是二等座才支持选座</div>
            <div>12306规则:余票小于一定数量时,不允许选座(本项目以20为例)</div>
          </div>
          <div v-else style="text-align: center">
            <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                      v-model:checked="chooseSeatObj[item.code + '1']" :checked-children="item.desc" :un-checked-children="item.desc" />
            <div v-if="tickets.length > 1">
              <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                        v-model:checked="chooseSeatObj[item.code + '2']" :checked-children="item.desc" :un-checked-children="item.desc" />
            </div>
            <div style="color: #999999">提示:您可以选择{{tickets.length}}个座位</div>
          </div>
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
    
            // 余票小于20张时,不允许选座,否则选座成功率不高,影响出票
            if (chooseSeatType.value !== 0) {
              for (let i = 0; i < seatTypes.length; i++) {
                let seatType = seatTypes[i];
                // 找到同类型座位
                if (ticketSeatTypeCodesSet[0] === seatType.code) {
                  // 判断余票,小于20张就不支持选座
                  if (seatType.count < 20) {
                    console.log("余票小于20张就不支持选座")
                    chooseSeatType.value = 0;
                    break;
                  }
                }
              }
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    
    .order-tickets .choose-seat-item {
      margin: 5px 5px;
    }
    </style>
    

5.确认提交时,计算出最终每个乘客所选的座位

  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消"
               @ok="handleOk">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          <div v-if="chooseSeatType === 0" style="color: red;">
            您购买的车票不支持选座
            <div>12306规则:只有全部是一等座或全部是二等座才支持选座</div>
            <div>12306规则:余票小于一定数量时,不允许选座(本项目以20为例)</div>
          </div>
          <div v-else style="text-align: center">
            <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                      v-model:checked="chooseSeatObj[item.code + '1']" :checked-children="item.desc" :un-checked-children="item.desc" />
            <div v-if="tickets.length > 1">
              <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                        v-model:checked="chooseSeatObj[item.code + '2']" :checked-children="item.desc" :un-checked-children="item.desc" />
            </div>
            <div style="color: #999999">提示:您可以选择{{tickets.length}}个座位</div>
          </div>
          <br/>
          最终购票:{{tickets}}
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
    
            // 余票小于20张时,不允许选座,否则选座成功率不高,影响出票
            if (chooseSeatType.value !== 0) {
              for (let i = 0; i < seatTypes.length; i++) {
                let seatType = seatTypes[i];
                // 找到同类型座位
                if (ticketSeatTypeCodesSet[0] === seatType.code) {
                  // 判断余票,小于20张就不支持选座
                  if (seatType.count < 20) {
                    console.log("余票小于20张就不支持选座")
                    chooseSeatType.value = 0;
                    break;
                  }
                }
              }
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        const handleOk = () => {
          console.log("选好的座位:", chooseSeatObj.value);
    
          // 设置每张票的座位
          // 先清空购票列表的座位,有可能之前选了并设置座位了,但选座数不对被拦截了,又重新选一遍
          for (let i = 0; i < tickets.value.length; i++) {
            tickets.value[i].seat = null;
          }
          let i = -1;
          // 要么不选座位,要么所选座位应该等于购票数,即i === (tickets.value.length - 1)
          for (let key in chooseSeatObj.value) {
            if (chooseSeatObj.value[key]) {
              i++;
              if (i > tickets.value.length - 1) {
                notification.error({description: '所选座位数大于购票数'});
                return;
              }
              tickets.value[i].seat = key;
            }
          }
          if (i > -1 && i < (tickets.value.length - 1)) {
            notification.error({description: '所选座位数小于购票数'});
            return;
          }
    
          console.log("最终购票:", tickets.value);
        }
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
          handleOk,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    
    .order-tickets .choose-seat-item {
      margin: 5px 5px;
    }
    </style>
    
  • 效果

在这里插入图片描述

6.chooseSeatObj先清空,再初始化,保证两排座位是有序的

  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消"
               @ok="handleOk">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          <div v-if="chooseSeatType === 0" style="color: red;">
            您购买的车票不支持选座
            <div>12306规则:只有全部是一等座或全部是二等座才支持选座</div>
            <div>12306规则:余票小于一定数量时,不允许选座(本项目以20为例)</div>
          </div>
          <div v-else style="text-align: center">
            <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                      v-model:checked="chooseSeatObj[item.code + '1']" :checked-children="item.desc" :un-checked-children="item.desc" />
            <div v-if="tickets.length > 1">
              <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                        v-model:checked="chooseSeatObj[item.code + '2']" :checked-children="item.desc" :un-checked-children="item.desc" />
            </div>
            <div style="color: #999999">提示:您可以选择{{tickets.length}}个座位</div>
          </div>
          <br/>
          最终购票:{{tickets}}
          最终选座:{{chooseSeatObj}}
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1",
        //   seat: "C1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          chooseSeatObj.value = {};
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
    
            // 余票小于20张时,不允许选座,否则选座成功率不高,影响出票
            if (chooseSeatType.value !== 0) {
              for (let i = 0; i < seatTypes.length; i++) {
                let seatType = seatTypes[i];
                // 找到同类型座位
                if (ticketSeatTypeCodesSet[0] === seatType.code) {
                  // 判断余票,小于20张就不支持选座
                  if (seatType.count < 20) {
                    console.log("余票小于20张就不支持选座")
                    chooseSeatType.value = 0;
                    break;
                  }
                }
              }
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        const handleOk = () => {
          console.log("选好的座位:", chooseSeatObj.value);
    
          // 设置每张票的座位
          // 先清空购票列表的座位,有可能之前选了并设置座位了,但选座数不对被拦截了,又重新选一遍
          for (let i = 0; i < tickets.value.length; i++) {
            tickets.value[i].seat = null;
          }
          let i = -1;
          // 要么不选座位,要么所选座位应该等于购票数,即i === (tickets.value.length - 1)
          for (let key in chooseSeatObj.value) {
            if (chooseSeatObj.value[key]) {
              i++;
              if (i > tickets.value.length - 1) {
                notification.error({description: '所选座位数大于购票数'});
                return;
              }
              tickets.value[i].seat = key;
            }
          }
          if (i > -1 && i < (tickets.value.length - 1)) {
            notification.error({description: '所选座位数小于购票数'});
            return;
          }
    
          console.log("最终购票:", tickets.value);
        }
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
          handleOk,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    
    .order-tickets .choose-seat-item {
      margin: 5px 5px;
    }
    </style>
    
  • 效果

在这里插入图片描述

十、增加确认订单表并生成前后端代码

  • 新增表

    sql/business.sql

    这里的车票使用json类型,实际上也可以使用子表来做

    drop table if exists `confirm_order`;
    create table `confirm_order` (
      `id` bigint not null comment 'id',
      `member_id` bigint not null comment '会员id',
      `date` date not null comment '日期',
      `train_code` varchar(20) not null comment '车次编号',
      `start` varchar(20) not null comment '出发站',
      `end` varchar(20) not null comment '到达站',
      `daily_train_ticket_id` bigint not null comment '余票ID',
      `tickets` json not null comment '车票',
      `status` char(1) not null comment '订单状态|枚举[ConfirmOrderStatusEnum]',
      `create_time` datetime(3) comment '新增时间',
      `update_time` datetime(3) comment '修改时间',
      primary key (`id`),
      index `date_train_code_index` (`date`, `train_code`)
    ) engine=innodb default charset=utf8mb4 comment='确认订单';
    
  • ConfirmOrderStatusEnum

    enum也是可以用lombok注解的

    package com.neilxu.train.business.enums;
    
    public enum ConfirmOrderStatusEnum {
    
        INIT("I", "初始"),
        PENDING("P", "处理中"),
        SUCCESS("S", "成功"),
        FAILURE("F", "失败"),
        EMPTY("E", "无票"),
        CANCEL("C", "取消");
    
        private String code;
    
        private String desc;
    
        ConfirmOrderStatusEnum(String code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    
        @Override    public String toString() {
            return "ConfirmOrderStatusEnum{" +
                    "code='" + code + '\'' +
                    ", desc='" + desc + '\'' +
                    "} " + super.toString();
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        public String getDesc() {
            return desc;
        }
    
    }
    
  • 修改generator-config-business.xml、ServerGenerator.java、EnumGenerator.java生成代码

    操作同之前,注意是admin

  • 修改路由、侧边栏

    操作同之前,注意是admin

  • 效果

在这里插入图片描述

十一、后端增加确认下单购票接口

这节整理下后端确认下单购票接口的逻辑

  • com.neilxu.train.business.req.ConfirmOrderTicketReq

    package com.neilxu.train.business.req;
    
    import jakarta.validation.constraints.NotBlank;
    import jakarta.validation.constraints.NotNull;
    import lombok.Data;
    
    @Data
    public class ConfirmOrderTicketReq {
    
        /**
         * 乘客ID
         */
        @NotNull(message = "【乘客ID】不能为空")
        private Long passengerId;
    
        /**
         * 乘客票种
         */
        @NotBlank(message = "【乘客票种】不能为空")
        private String passengerType;
    
        /**
         * 乘客名称
         */
        @NotBlank(message = "【乘客名称】不能为空")
        private String passengerName;
    
        /**
         * 乘客身份证
         */
        @NotBlank(message = "【乘客身份证】不能为空")
        private String passengerIdCard;
    
        /**
         * 座位类型code
         */
        @NotBlank(message = "【座位类型code】不能为空")
        private String seatTypeCode;
    
        /**
         * 选座,可空,值示例:A1
         */
        private String seat;
    
    }
    
  • ConfirmOrderSaveReq.java ——> ConfirmOrderDoReq.java

    package com.neilxu.train.business.req;
    
    import com.fasterxml.jackson.annotation.JsonFormat;
    import jakarta.validation.constraints.NotBlank;
    import jakarta.validation.constraints.NotNull;
    import lombok.Data;
    
    import java.util.Date;
    import java.util.List;
    
    @Data
    public class ConfirmOrderDoReq {
    
        /**
         * 会员id
         */
        @NotNull(message = "【会员id】不能为空")
        private Long memberId;
    
        /**
         * 日期
         */
        @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
        @NotNull(message = "【日期】不能为空")
        private Date date;
    
        /**
         * 车次编号
         */
        @NotBlank(message = "【车次编号】不能为空")
        private String trainCode;
    
        /**
         * 出发站
         */
        @NotBlank(message = "【出发站】不能为空")
        private String start;
    
        /**
         * 到达站
         */
        @NotBlank(message = "【到达站】不能为空")
        private String end;
    
        /**
         * 余票ID
         */
        @NotNull(message = "【余票ID】不能为空")
        private Long dailyTrainTicketId;
    
        /**
         * 车票
         */
        @NotBlank(message = "【车票】不能为空")
        private List<ConfirmOrderTicketReq> tickets;
    
        @Override
        public String toString() {
            return "ConfirmOrderDoReq{" +
                    "memberId=" + memberId +
                    ", date=" + date +
                    ", trainCode='" + trainCode + '\'' +
                    ", start='" + start + '\'' +
                    ", end='" + end + '\'' +
                    ", dailyTrainTicketId=" + dailyTrainTicketId +
                    ", tickets=" + tickets +
                    '}';
        }
    }
    
  • ConfirmOrderAdminController.java

    package com.neilxu.train.business.controller.admin;
    
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.service.ConfirmOrderService;
    import com.neilxu.train.common.resp.CommonResp;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    ;
    
    @RestController
    @RequestMapping("/admin/confirm-order")
    public class ConfirmOrderAdminController {
    
        @Resource
        private ConfirmOrderService confirmOrderService;
    
        @PostMapping("/save")
        public CommonResp<Object> save(@Valid @RequestBody ConfirmOrderDoReq req) {
            confirmOrderService.save(req);
            return new CommonResp<>();
        }
    }
    
  • ConfirmOrderService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import com.neilxu.train.business.domain.ConfirmOrder;
    import com.neilxu.train.business.domain.ConfirmOrderExample;
    import com.neilxu.train.business.mapper.ConfirmOrderMapper;
    import com.neilxu.train.business.req.ConfirmOrderQueryReq;
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.resp.ConfirmOrderQueryResp;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class ConfirmOrderService {
    
        private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
    
        @Resource
        private ConfirmOrderMapper confirmOrderMapper;
    
        public void save(ConfirmOrderDoReq req) {
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
            if (ObjectUtil.isNull(confirmOrder.getId())) {
                confirmOrder.setId(SnowUtil.getSnowflakeNextId());
                confirmOrder.setCreateTime(now);
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.insert(confirmOrder);
            } else {
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.updateByPrimaryKey(confirmOrder);
            }
        }
    
        public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.setOrderByClause("id desc");
            ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
    
            PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
    
            PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            confirmOrderMapper.deleteByPrimaryKey(id);
        }
    
        public void doConfirm(ConfirmOrderDoReq req) {
            //省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,ticket条数>0,同乘客同车次是否已经买过
    
            //保存确认订单表,状态初始
    
            //查出余票记录,需要得到真实的库存
    
            //扣减余票数量,并判断余票是否足够
    
            //选座
              //一个车厢一个车厢的获取座位数据
              //挑选符合条件的座位,如果这个车厢不满足,则进入下一个车厢(多个选座应该在同一车厢)
    
            //选中座位后事务处理
              //座位表修改售卖情况sell
              //余票详情表修改余票
              //为会员增加购票记录
              //更新确认订单为成功
        }
    }
    
  • ConfirmOrderController.java

    package com.neilxu.train.business.controller;
    
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.service.ConfirmOrderService;
    import com.neilxu.train.common.resp.CommonResp;
    import jakarta.annotation.Resource;
    import jakarta.validation.Valid;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping("/confirm-order")
    public class ConfirmOrderController {
    
        @Resource
        private ConfirmOrderService confirmOrderService;
    
        @PostMapping("/do")
        public CommonResp<Object> doConfirm(@Valid @RequestBody ConfirmOrderDoReq req) {
            confirmOrderService.doConfirm(req);
            return new CommonResp<>();
        }
    
    }
    
  • order.vue

    <template>
      <div class="order-train">
        <span class="order-train-main">{{dailyTrainTicket.date}}</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.start}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.startTime}})</span>&nbsp;
        <span class="order-train-main">——</span>&nbsp;
        <span class="order-train-main">{{dailyTrainTicket.end}}</span>站
        <span class="order-train-main">({{dailyTrainTicket.endTime}})</span>&nbsp;
    
        <div class="order-train-ticket">
          <span v-for="item in seatTypes" :key="item.type">
            <span>{{item.desc}}</span>:
            <span class="order-train-ticket-main">{{item.price}}¥</span>&nbsp;
            <span class="order-train-ticket-main">{{item.count}}</span>&nbsp;张票&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
          </span>
        </div>
      </div>
      <a-divider></a-divider>
      <b>勾选要购票的乘客:</b>&nbsp;
      <a-checkbox-group v-model:value="passengerChecks" :options="passengerOptions" />
    
      <div class="order-tickets">
        <a-row class="order-tickets-header" v-if="tickets.length > 0">
          <a-col :span="2">乘客</a-col>
          <a-col :span="6">身份证</a-col>
          <a-col :span="4">票种</a-col>
          <a-col :span="4">座位类型</a-col>
        </a-row>
        <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
          <a-col :span="2">{{ticket.passengerName}}</a-col>
          <a-col :span="6">{{ticket.passengerIdCard}}</a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.passengerType" style="width: 100%">
              <a-select-option v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
          <a-col :span="4">
            <a-select v-model:value="ticket.seatTypeCode" style="width: 100%">
              <a-select-option v-for="item in seatTypes" :key="item.code" :value="item.code">
                {{item.desc}}
              </a-select-option>
            </a-select>
          </a-col>
        </a-row>
      </div>
      <div v-if="tickets.length > 0">
        <a-button type="primary" size="large" @click="finishCheckPassenger">提交订单</a-button>
      </div>
    
      <a-modal v-model:visible="visible" title="请核对以下信息"
               style="top: 50px; width: 800px"
               ok-text="确认" cancel-text="取消"
               @ok="handleOk">
        <div class="order-tickets">
          <a-row class="order-tickets-header" v-if="tickets.length > 0">
            <a-col :span="3">乘客</a-col>
            <a-col :span="15">身份证</a-col>
            <a-col :span="3">票种</a-col>
            <a-col :span="3">座位类型</a-col>
          </a-row>
          <a-row class="order-tickets-row" v-for="ticket in tickets" :key="ticket.passengerId">
            <a-col :span="3">{{ticket.passengerName}}</a-col>
            <a-col :span="15">{{ticket.passengerIdCard}}</a-col>
            <a-col :span="3">
              <span v-for="item in PASSENGER_TYPE_ARRAY" :key="item.code">
                <span v-if="item.code === ticket.passengerType">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
            <a-col :span="3">
              <span v-for="item in seatTypes" :key="item.code">
                <span v-if="item.code === ticket.seatTypeCode">
                  {{item.desc}}
                </span>
              </span>
            </a-col>
          </a-row>
          <br/>
          <div v-if="chooseSeatType === 0" style="color: red;">
            您购买的车票不支持选座
            <div>12306规则:只有全部是一等座或全部是二等座才支持选座</div>
            <div>12306规则:余票小于一定数量时,不允许选座(本项目以20为例)</div>
          </div>
          <div v-else style="text-align: center">
            <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                      v-model:checked="chooseSeatObj[item.code + '1']" :checked-children="item.desc" :un-checked-children="item.desc" />
            <div v-if="tickets.length > 1">
              <a-switch class="choose-seat-item" v-for="item in SEAT_COL_ARRAY" :key="item.code"
                        v-model:checked="chooseSeatObj[item.code + '2']" :checked-children="item.desc" :un-checked-children="item.desc" />
            </div>
            <div style="color: #999999">提示:您可以选择{{tickets.length}}个座位</div>
          </div>
          <br/>
          最终购票:{{tickets}}
          最终选座:{{chooseSeatObj}}
        </div>
      </a-modal>
    </template>
    
    <script>
    
    import {defineComponent, ref, onMounted, watch, computed} from 'vue';
    import axios from "axios";
    import {notification} from "ant-design-vue";
    
    export default defineComponent({
      name: "order-view",
      setup() {
        const passengers = ref([]);
        const passengerOptions = ref([]);
        const passengerChecks = ref([]);
        const dailyTrainTicket = SessionStorage.get(SESSION_ORDER) || {};
        console.log("下单的车次信息", dailyTrainTicket);
    
        const SEAT_TYPE = window.SEAT_TYPE;
        console.log(SEAT_TYPE)
        // 本车次提供的座位类型seatTypes,含票价,余票等信息,例:
        // {
        //   type: "YDZ",
        //   code: "1",
        //   desc: "一等座",
        //   count: "100",
        //   price: "50",
        // }
        // 关于SEAT_TYPE[KEY]:当知道某个具体的属性xxx时,可以用obj.xxx,当属性名是个变量时,可以使用obj[xxx]
        const seatTypes = [];
        for (let KEY in SEAT_TYPE) {
          let key = KEY.toLowerCase();
          if (dailyTrainTicket[key] >= 0) {
            seatTypes.push({
              type: KEY,
              code: SEAT_TYPE[KEY]["code"],
              desc: SEAT_TYPE[KEY]["desc"],
              count: dailyTrainTicket[key],
              price: dailyTrainTicket[key + 'Price'],
            })
          }
        }
        console.log("本车次提供的座位:", seatTypes)
        // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
        // {
        //   passengerId: 123,
        //   passengerType: "1",
        //   passengerName: "张三",
        //   passengerIdCard: "12323132132",
        //   seatTypeCode: "1",
        //   seat: "C1"
        // }
        const tickets = ref([]);
        const PASSENGER_TYPE_ARRAY = window.PASSENGER_TYPE_ARRAY;
        const visible = ref(false);
    
        // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
        watch(() => passengerChecks.value, (newVal, oldVal)=>{
          console.log("勾选乘客发生变化", newVal, oldVal)
          // 每次有变化时,把购票列表清空,重新构造列表
          tickets.value = [];
          passengerChecks.value.forEach((item) => tickets.value.push({
            passengerId: item.id,
            passengerType: item.type,
            seatTypeCode: seatTypes[0].code,
            passengerName: item.name,
            passengerIdCard: item.idCard
          }))
        }, {immediate: true});
    
        // 0:不支持选座;1:选一等座;2:选二等座
        const chooseSeatType = ref(0);
        // 根据选择的座位类型,计算出对应的列,比如要选的是一等座,就筛选出ACDF,要选的是二等座,就筛选出ABCDF
        const SEAT_COL_ARRAY = computed(() => {
          return window.SEAT_COL_ARRAY.filter(item => item.type === chooseSeatType.value);
        });
        // 选择的座位
        // {
        //   A1: false, C1: true,D1: false, F1: false,
        //   A2: false, C2: false,D2: true, F2: false
        // }
        const chooseSeatObj = ref({});
        watch(() => SEAT_COL_ARRAY.value, () => {
          chooseSeatObj.value = {};
          for (let i = 1; i <= 2; i++) {
            SEAT_COL_ARRAY.value.forEach((item) => {
              chooseSeatObj.value[item.code + i] = false;
            })
          }
          console.log("初始化两排座位,都是未选中:", chooseSeatObj.value);
        }, {immediate: true});
    
        const handleQueryPassenger = () => {
          axios.get("/member/passenger/query-mine").then((response) => {
            let data = response.data;
            if (data.success) {
              passengers.value = data.content;
              passengers.value.forEach((item) => passengerOptions.value.push({
                label: item.name,
                value: item
              }))
            } else {
              notification.error({description: data.message});
            }
          });
        };
    
        const finishCheckPassenger = () => {
          console.log("购票列表:", tickets.value);
    
          if (tickets.value.length > 5) {
            notification.error({description: '最多只能购买5张车票'});
            return;
          }
    
          // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
          // 前端校验不一定准,但前端校验可以减轻后端很多压力
          // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
          let seatTypesTemp = Tool.copy(seatTypes);
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            for (let j = 0; j < seatTypesTemp.length; j++) {
              let seatType = seatTypesTemp[j];
              // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
              if (ticket.seatTypeCode === seatType.code) {
                seatType.count--;
                if (seatType.count < 0) {
                  notification.error({description: seatType.desc + '余票不足'});
                  return;
                }
              }
            }
          }
          console.log("前端余票校验通过");
    
          // 判断是否支持选座,只有纯一等座和纯二等座支持选座
          // 先筛选出购票列表中的所有座位类型,比如四张表:[1, 1, 2, 2]
          let ticketSeatTypeCodes = [];
          for (let i = 0; i < tickets.value.length; i++) {
            let ticket = tickets.value[i];
            ticketSeatTypeCodes.push(ticket.seatTypeCode);
          }
          // 为购票列表中的所有座位类型去重:[1, 2]
          const ticketSeatTypeCodesSet = Array.from(new Set(ticketSeatTypeCodes));
          console.log("选好的座位类型:", ticketSeatTypeCodesSet);
          if (ticketSeatTypeCodesSet.length !== 1) {
            console.log("选了多种座位,不支持选座");
            chooseSeatType.value = 0;
          } else {
            // ticketSeatTypeCodesSet.length === 1,即只选择了一种座位(不是一个座位,是一种座位)
            if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.YDZ.code) {
              console.log("一等座选座");
              chooseSeatType.value = SEAT_TYPE.YDZ.code;
            } else if (ticketSeatTypeCodesSet[0] === SEAT_TYPE.EDZ.code) {
              console.log("二等座选座");
              chooseSeatType.value = SEAT_TYPE.EDZ.code;
            } else {
              console.log("不是一等座或二等座,不支持选座");
              chooseSeatType.value = 0;
            }
    
            // 余票小于20张时,不允许选座,否则选座成功率不高,影响出票
            if (chooseSeatType.value !== 0) {
              for (let i = 0; i < seatTypes.length; i++) {
                let seatType = seatTypes[i];
                // 找到同类型座位
                if (ticketSeatTypeCodesSet[0] === seatType.code) {
                  // 判断余票,小于20张就不支持选座
                  if (seatType.count < 20) {
                    console.log("余票小于20张就不支持选座")
                    chooseSeatType.value = 0;
                    break;
                  }
                }
              }
            }
          }
    
          // 弹出确认界面
          visible.value = true;
    
        };
    
        const handleOk = () => {
          console.log("选好的座位:", chooseSeatObj.value);
    
          // 设置每张票的座位
          // 先清空购票列表的座位,有可能之前选了并设置座位了,但选座数不对被拦截了,又重新选一遍
          for (let i = 0; i < tickets.value.length; i++) {
            tickets.value[i].seat = null;
          }
          let i = -1;
          // 要么不选座位,要么所选座位应该等于购票数,即i === (tickets.value.length - 1)
          for (let key in chooseSeatObj.value) {
            if (chooseSeatObj.value[key]) {
              i++;
              if (i > tickets.value.length - 1) {
                notification.error({description: '所选座位数大于购票数'});
                return;
              }
              tickets.value[i].seat = key;
            }
          }
          if (i > -1 && i < (tickets.value.length - 1)) {
            notification.error({description: '所选座位数小于购票数'});
            return;
          }
    
          console.log("最终购票:", tickets.value);
    
          axios.post("/business/confirm-order/do", {
            dailyTrainTicketId: dailyTrainTicket.id,
            date: dailyTrainTicket.date,
            trainCode: dailyTrainTicket.trainCode,
            start: dailyTrainTicket.start,
            end: dailyTrainTicket.end,
            tickets: tickets.value
          }).then((response) => {
            let data = response.data;
            if (data.success) {
              notification.success({description: "下单成功!"});
            } else {
              notification.error({description: data.message});
            }
          });
        }
    
        onMounted(() => {
          handleQueryPassenger();
        });
    
        return {
          passengers,
          dailyTrainTicket,
          seatTypes,
          passengerOptions,
          passengerChecks,
          tickets,
          PASSENGER_TYPE_ARRAY,
          visible,
          finishCheckPassenger,
          chooseSeatType,
          chooseSeatObj,
          SEAT_COL_ARRAY,
          handleOk,
        };
      },
    });
    </script>
    
    <style>
    .order-train .order-train-main {
      font-size: 18px;
      font-weight: bold;
    }
    .order-train .order-train-ticket {
      margin-top: 15px;
    }
    .order-train .order-train-ticket .order-train-ticket-main {
      color: red;
      font-size: 18px;
    }
    
    .order-tickets {
      margin: 10px 0;
    }
    .order-tickets .ant-col {
      padding: 5px 10px;
    }
    .order-tickets .order-tickets-header {
      background-color: cornflowerblue;
      border: solid 1px cornflowerblue;
      color: white;
      font-size: 16px;
      padding: 5px 0;
    }
    .order-tickets .order-tickets-row {
      border: solid 1px cornflowerblue;
      border-top: none;
      vertical-align: middle;
      line-height: 30px;
    }
    
    .order-tickets .choose-seat-item {
      margin: 5px 5px;
    }
    </style>
    

十二、确认下单接口数据初始化

小tips:

ctrl + alt + v 可以快速提取变量

  • DailyTrainTicketService.java

    public DailyTrainTicket selectByUnique(Date date, String trainCode, String start, String end) {
        DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
        dailyTrainTicketExample.createCriteria()
                .andDateEqualTo(date)
                .andTrainCodeEqualTo(trainCode)
                .andStartEqualTo(start)
                .andEndEqualTo(end);
        List<DailyTrainTicket> list = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
        if (CollUtil.isNotEmpty(list)) {
            return list.get(0);
        } else {
            return null;
        }
    }
    
  • ConfirmOrderService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.util.ObjectUtil;
    import com.alibaba.fastjson.JSON;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.enums.ConfirmOrderStatusEnum;
    import com.neilxu.train.common.context.LoginMemberContext;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import com.neilxu.train.business.domain.ConfirmOrder;
    import com.neilxu.train.business.domain.ConfirmOrderExample;
    import com.neilxu.train.business.mapper.ConfirmOrderMapper;
    import com.neilxu.train.business.req.ConfirmOrderQueryReq;
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.resp.ConfirmOrderQueryResp;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class ConfirmOrderService {
    
        private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
    
        @Resource
        private ConfirmOrderMapper confirmOrderMapper;
    
        @Resource
        private DailyTrainTicketService dailyTrainTicketService;
    
        public void save(ConfirmOrderDoReq req) {
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
            if (ObjectUtil.isNull(confirmOrder.getId())) {
                confirmOrder.setId(SnowUtil.getSnowflakeNextId());
                confirmOrder.setCreateTime(now);
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.insert(confirmOrder);
            } else {
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.updateByPrimaryKey(confirmOrder);
            }
        }
    
        public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.setOrderByClause("id desc");
            ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
    
            PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
    
            PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            confirmOrderMapper.deleteByPrimaryKey(id);
        }
    
        public void doConfirm(ConfirmOrderDoReq req) {
            // 省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,tickets条数>0,同乘客同车次是否已买过
    
            Date date = req.getDate();
            String trainCode = req.getTrainCode();
            String start = req.getStart();
            String end = req.getEnd();
    
            // 保存确认订单表,状态初始
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = new ConfirmOrder();
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrder.setMemberId(LoginMemberContext.getId());
            confirmOrder.setDate(date);
            confirmOrder.setTrainCode(trainCode);
            confirmOrder.setStart(start);
            confirmOrder.setEnd(end);
            confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
            confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrder.setTickets(JSON.toJSONString(req.getTickets()));
            confirmOrderMapper.insert(confirmOrder);
    
            // 查出余票记录,需要得到真实的库存
            DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
            LOG.info("查出余票记录:{}", dailyTrainTicket);
    
            // 扣减余票数量,并判断余票是否足够
    
            // 选座
    
            // 一个车厢一个车厢的获取座位数据
    
            // 挑选符合条件的座位,如果这个车厢不满足,则进入下个车厢(多个选座应该在同一个车厢)
    
            // 选中座位后事务处理:
    
            // 座位表修改售卖情况sell;
            // 余票详情表修改余票;
            // 为会员增加购票记录
            // 更新确认订单为成功
        }
    }
    

十三、预扣减库存并判断余票是否足够

  • BusinessExceptionEnum.java

    CONFIRM_ORDER_TICKET_COUNT_ERROR("余票不足"),
    
  • ConfirmOrderService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.util.EnumUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.alibaba.fastjson.JSON;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.ConfirmOrder;
    import com.neilxu.train.business.domain.ConfirmOrderExample;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.enums.ConfirmOrderStatusEnum;
    import com.neilxu.train.business.enums.SeatTypeEnum;
    import com.neilxu.train.business.mapper.ConfirmOrderMapper;
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.req.ConfirmOrderQueryReq;
    import com.neilxu.train.business.req.ConfirmOrderTicketReq;
    import com.neilxu.train.business.resp.ConfirmOrderQueryResp;
    import com.neilxu.train.common.context.LoginMemberContext;
    import com.neilxu.train.common.exception.BusinessException;
    import com.neilxu.train.common.exception.BusinessExceptionEnum;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class ConfirmOrderService {
    
        private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
    
        @Resource
        private ConfirmOrderMapper confirmOrderMapper;
    
        @Resource
        private DailyTrainTicketService dailyTrainTicketService;
    
        public void save(ConfirmOrderDoReq req) {
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
            if (ObjectUtil.isNull(confirmOrder.getId())) {
                confirmOrder.setId(SnowUtil.getSnowflakeNextId());
                confirmOrder.setCreateTime(now);
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.insert(confirmOrder);
            } else {
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.updateByPrimaryKey(confirmOrder);
            }
        }
    
        public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.setOrderByClause("id desc");
            ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
    
            PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
    
            PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            confirmOrderMapper.deleteByPrimaryKey(id);
        }
    
        public void doConfirm(ConfirmOrderDoReq req) {
            // 省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,tickets条数>0,同乘客同车次是否已买过
    
            Date date = req.getDate();
            String trainCode = req.getTrainCode();
            String start = req.getStart();
            String end = req.getEnd();
    
            // 保存确认订单表,状态初始
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = new ConfirmOrder();
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrder.setMemberId(LoginMemberContext.getId());
            confirmOrder.setDate(date);
            confirmOrder.setTrainCode(trainCode);
            confirmOrder.setStart(start);
            confirmOrder.setEnd(end);
            confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
            confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrder.setTickets(JSON.toJSONString(req.getTickets()));
            confirmOrderMapper.insert(confirmOrder);
    
            // 查出余票记录,需要得到真实的库存
            DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
            LOG.info("查出余票记录:{}", dailyTrainTicket);
    
            // 预扣减余票数量,并判断余票是否足够
            reduceTickets(req, dailyTrainTicket);
    
            // 选座
    
            // 一个车箱一个车箱的获取座位数据
    
            // 挑选符合条件的座位,如果这个车箱不满足,则进入下个车箱(多个选座应该在同一个车厢)
    
            // 选中座位后事务处理:
    
            // 座位表修改售卖情况sell;
            // 余票详情表修改余票;
            // 为会员增加购票记录
            // 更新确认订单为成功
        }
    
        private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
            for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
                String seatTypeCode = ticketReq.getSeatTypeCode();
                SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
                switch (seatTypeEnum) {
                    case YDZ -> {
                        int countLeft = dailyTrainTicket.getYdz() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setYdz(countLeft);
                    }
                    case EDZ -> {
                        int countLeft = dailyTrainTicket.getEdz() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setEdz(countLeft);
                    }
                    case RW -> {
                        int countLeft = dailyTrainTicket.getRw() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setRw(countLeft);
                    }
                    case YW -> {
                        int countLeft = dailyTrainTicket.getYw() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setYw(countLeft);
                    }
                }
            }
        }
    }
    

十四、计算多个选座之间的偏移值

  • ConfirmOrderService.java

    package com.neilxu.train.business.service;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.date.DateTime;
    import cn.hutool.core.util.EnumUtil;
    import cn.hutool.core.util.ObjectUtil;
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.fastjson.JSON;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.ConfirmOrder;
    import com.neilxu.train.business.domain.ConfirmOrderExample;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.enums.ConfirmOrderStatusEnum;
    import com.neilxu.train.business.enums.SeatColEnum;
    import com.neilxu.train.business.enums.SeatTypeEnum;
    import com.neilxu.train.business.mapper.ConfirmOrderMapper;
    import com.neilxu.train.business.req.ConfirmOrderDoReq;
    import com.neilxu.train.business.req.ConfirmOrderQueryReq;
    import com.neilxu.train.business.req.ConfirmOrderTicketReq;
    import com.neilxu.train.business.resp.ConfirmOrderQueryResp;
    import com.neilxu.train.common.context.LoginMemberContext;
    import com.neilxu.train.common.exception.BusinessException;
    import com.neilxu.train.common.exception.BusinessExceptionEnum;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class ConfirmOrderService {
    
        public static final ArrayList<Object> OBJECT = new ArrayList<>();
        private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
    
        @Resource
        private ConfirmOrderMapper confirmOrderMapper;
    
        @Resource
        private DailyTrainTicketService dailyTrainTicketService;
    
        public void save(ConfirmOrderDoReq req) {
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
            if (ObjectUtil.isNull(confirmOrder.getId())) {
                confirmOrder.setId(SnowUtil.getSnowflakeNextId());
                confirmOrder.setCreateTime(now);
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.insert(confirmOrder);
            } else {
                confirmOrder.setUpdateTime(now);
                confirmOrderMapper.updateByPrimaryKey(confirmOrder);
            }
        }
    
        public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) {
            ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
            confirmOrderExample.setOrderByClause("id desc");
            ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
    
            PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
    
            PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            confirmOrderMapper.deleteByPrimaryKey(id);
        }
    
        public void doConfirm(ConfirmOrderDoReq req) {
            // 省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,tickets条数>0,同乘客同车次是否已买过
    
            Date date = req.getDate();
            String trainCode = req.getTrainCode();
            String start = req.getStart();
            String end = req.getEnd();
            List<ConfirmOrderTicketReq> tickets = req.getTickets();
    
            // 保存确认订单表,状态初始
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = new ConfirmOrder();
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrder.setMemberId(LoginMemberContext.getId());
            confirmOrder.setDate(date);
            confirmOrder.setTrainCode(trainCode);
            confirmOrder.setStart(start);
            confirmOrder.setEnd(end);
            confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
            confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
            confirmOrder.setTickets(JSON.toJSONString(tickets));
            confirmOrderMapper.insert(confirmOrder);
    
            // 查出余票记录,需要得到真实的库存
            DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
            LOG.info("查出余票记录:{}", dailyTrainTicket);
    
            // 预扣减余票数量,并判断余票是否足够
            reduceTickets(req, dailyTrainTicket);
    
            // 计算相对第一个座位的偏移值
            // 比如选择的是C1,D2,则偏移值是:[0,5]
            // 比如选择的是A1,B1,C1,则偏移值是:[0,1,2]
            ConfirmOrderTicketReq ticketReq0 = tickets.get(0);
            if(StrUtil.isNotBlank(ticketReq0.getSeat())) {
                LOG.info("本次购票有选座");
                // 查出本次选座的座位类型都有哪些列,用于计算所选座位与第一个座位的偏离值
                List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketReq0.getSeatTypeCode());
                LOG.info("本次选座的座位类型包含的列:{}", colEnumList);
    
                // 组成和前端两排选座一样的列表,用于作参照的座位列表,例:referSeatList = {A1, C1, D1, F1, A2, C2, D2, F2}
                List<String> referSeatList = new ArrayList<>();
                for (int i = 1; i <= 2; i++) {
                    for (SeatColEnum seatColEnum : colEnumList) {
                        referSeatList.add(seatColEnum.getCode() + i);
                    }
                }
                LOG.info("用于作参照的两排座位:{}", referSeatList);
    
                List<Integer> offsetList = new ArrayList<>();
                // 绝对偏移值,即:在参照座位列表中的位置
                List<Integer> aboluteOffsetList = new ArrayList<>();
                for (ConfirmOrderTicketReq ticketReq : tickets) {
                    int index = referSeatList.indexOf(ticketReq.getSeat());
                    aboluteOffsetList.add(index);
                }
                LOG.info("计算得到所有座位的绝对偏移值:{}", aboluteOffsetList);
                for (Integer index : aboluteOffsetList) {
                    int offset = index - aboluteOffsetList.get(0);
                    offsetList.add(offset);
                }
                LOG.info("计算得到所有座位的相对第一个座位的偏移值:{}", offsetList);
    
            } else {
                LOG.info("本次购票没有选座");
    
            }
    
            // 选座
    
            // 一个车箱一个车箱的获取座位数据
    
            // 挑选符合条件的座位,如果这个车箱不满足,则进入下个车箱(多个选座应该在同一个车厢)
    
            // 选中座位后事务处理:
    
            // 座位表修改售卖情况sell;
            // 余票详情表修改余票;
            // 为会员增加购票记录
            // 更新确认订单为成功
        }
    
        private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
            for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
                String seatTypeCode = ticketReq.getSeatTypeCode();
                SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
                switch (seatTypeEnum) {
                    case YDZ -> {
                        int countLeft = dailyTrainTicket.getYdz() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setYdz(countLeft);
                    }
                    case EDZ -> {
                        int countLeft = dailyTrainTicket.getEdz() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setEdz(countLeft);
                    }
                    case RW -> {
                        int countLeft = dailyTrainTicket.getRw() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setRw(countLeft);
                    }
                    case YW -> {
                        int countLeft = dailyTrainTicket.getYw() - 1;
                        if (countLeft < 0) {
                            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                        }
                        dailyTrainTicket.setYw(countLeft);
                    }
                }
            }
        }
    }
    
  • 测试

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1556751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图的基础和图的遍历(--蓝桥云)

图的基础概念 度数&#xff1a;出边入边的条数 有向边&#xff1a;有箭头 图的存储方式 //邻接表 List<int []> list[N] list<x>//存放x的所有出点的信息 list[i][j]{first,second}//其中first表示从i出发的某个出点的编号&#xff08;这个出点是i的第j个出点&…

【Entity Framework】EF中DbSet类详解

【Entity Framework】EF中DbSet类详解 文章目录 【Entity Framework】EF中DbSet类详解一、概述二、定义DbSet2.1 具有DbSet属性的DbContext2.2 具有 IDbSet 属性的 DbContext 2.3 具有 IDbSet 属性的 DbContext三、DbSet属性四、DbSet方法五、DbContext动态生成DbSet 一、概述 …

【JavaSE】java刷题--数组练习

前言 本篇讲解了一些数组相关题目&#xff08;主要以代码的形式呈现&#xff09;&#xff0c;主要目的在于巩固数组相关知识。 上一篇 数组 讲解了一维数组和二维数组的基础知识~ 欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎…

JavaEE 初阶篇-深入了解多线程安全问题(出现线程不安全的原因与解决线程不安全的方法)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 多线程安全问题概述 1.1 线程不安全的实际例子 2.0 出现线程不安全的原因 2.1 线程在系统中是随机调度且抢占式执行的模式 2.2 多个线程同时修改同一个变量 2.3 线…

C++基础之虚函数(十七)

一.什么是多态 多态是在有继承关系的类中&#xff0c;调用同一个指令&#xff08;函数&#xff09;&#xff0c;不同对象会有不同行为。 二.什么是虚函数 概念&#xff1a;首先虚函数是存在于类的成员函数中&#xff0c;通过virtual关键字修饰的成员函数叫虚函数。 性质&am…

c语言:用do-while输出前40项的斐波那契数值

求Fibonacci数列的前40个元素。该数列的特点是第1、2两个数为1、1。从第3个数开始&#xff0c;每数是其前两个数之和。 分析&#xff1a;从题意可以用如下等式来表示斐波那契数列&#xff1a; 1&#xff0c; 1&#xff0c; 2&#xff0c; 3&#xff0c; 5&#xff0c; 8&#x…

如何确保实物档案的安全

确保实物档案的安全有以下几个关键点&#xff1a; 1. 建立完善的安全措施&#xff1a;为实物档案建立专门的存储区域&#xff0c;控制进出口&#xff0c;限制访问权限&#xff0c;并使用安全锁和监控设备等物理安保措施。 2. 规范档案管理制度&#xff1a;建立档案管理制度&…

深度学习入门简单实现一个神经网络

实现一个三层神经网络 引言测试数据 代码 引言 今天我们实现一个简单的神经网络 俩个输入神经元 隐藏层两个神经元 一个输出神经元 激活函数我们使用sigmoid 优化方法使用梯度下降 我们前期准备是需要把这些神经元的关系理清楚 x1&#xff1a;第一个输入 x2&#xff1a;第二个…

Android ImageView 的scaleType 属性图解

目录 前言测试素材测试布局xmlscaleType前言 一、ScaleType.FIT_CENTER 默认二、ScaleType.FIT_START三、ScaleType.FIT_END四、ScaleType.FIT_XY五、ScaleType.CENTER六、ScaleType.CENTER_CROP七、ScaleType.CENTER_INSIDE八、ScaleType.MATRIX 前言 原文链接&#xff1a; A…

接口自动化框架搭建(三):pytest库安装

1&#xff0c;使用命令行安装 前提条件&#xff1a;已安装python环境 pip install pytest 或者 pip3 install pytest2&#xff0c;从编译器pycharme中安装

STM32 | PWM脉冲宽度调制(第五天呼吸灯源码解析)

STM32 | PWM脉冲宽度调制(第五天)PWM 技术在以下其他机器学习领域和应用中也可以发挥作用: 自然语言处理 (NLP):调节文本生成模型(例如 GPT-3)的输出长度和多样性。 强化学习:控制代理在环境中采取行动的频率和持续时间。 时间序列预测:调节模型预测未来事件的时间间隔…

HTLM 之 vscode 插件推荐

文章目录 vscode 插件live Serverprettiersetting 保存这个文档的更改Material Theme / Material Theme icon vscode 插件 live Server prettier setting 搜索 format default 保存这个文档的更改 cmds // mac ctrls // win Material Theme / Material Theme icon 来更换…

命名空间【C++】(超详细)

文章目录 命名空间的概念命名空间的定义命名空间定义的位置作用域每一个命名空间都是一个独立的域作用域符&#xff1a;&#xff1a; 编译器找一个变量/函数等的定义&#xff0c;寻找域的顺序为什么要有命名空间&#xff1f;1.解决库与程序员定义的同名的重定义问题2.解决程序员…

腾讯 tengine 替代 nginx

下载地址 变更列表 - The Tengine Web Server 解压 tar -xvf 安装包.gz 进入到解压目录 cd 解压目录 使用 ./configure 命令来指定安装目录,这边指定安装到 /opt/tengine/install路径下 新建install目录 ./configure --prefix/opt/tengine/install 检查是否有缺失的依…

Windows中忘记MySQL ROOT密码的解决方法

在需要ROOT身份登录MySQL但又忘记密码时&#xff0c;可以先已管理员身份运行cmd命令窗口,输入以下命令停止MySQL服务 net stop mysql 随后cd到MySQL安装目录下的bin目录下 D: //我的安装在D盘 cd D:\MySQL\bin 使用跳过权限验证的方式起启动MySQL mysqld --console --skip-g…

双非计算机考研目标211,选11408还是22408更稳?

求稳得话&#xff0c;11408比22408要稳&#xff01; 很多同学只知道&#xff0c;11408和22408在考察的科目上有区别&#xff0c;比如&#xff1a; 11408考的是考研数学一和英语一&#xff0c;22408考察的是考研数学二和英语二&#xff1a; 考研数学一和考研数学二的区别大吗…

PCL点云处理之 基于垂直度检测与距离聚类 的路面点云提取方案 (二百三十九)

PCL点云处理之 基于垂直度检测与距离聚类 的路面点云提取方案 (二百三十九) 一、算法流程二、具体步骤1.垂直度检测与渲染1.代码2.效果2.水平分布点云提取1.代码2.效果3.路面连通点云提取1.代码2.效果三、完整代码四、参考文献一、算法流程

Java:链表

一、链表简介 1、链表与顺序表的区别 上一篇博客我介绍了顺序表&#xff0c;这次我们来认识认识链表&#xff01;先来看看二者的区别&#xff1a; 顺序表&#xff1a;由于顺序表实际上是一个数组&#xff0c;因此它在物理上是连续的&#xff0c;逻辑上也是连续的&#xff01; …

文件上传漏洞-黑名单检测

黑名单检测 一般情况下&#xff0c;代码文件里会有一个数组或者列表&#xff0c;该数组或者列表里会包含一些非法的字符或者字符串&#xff0c;当数据包中含有符合该列表的字符串时&#xff0c;即认定该数据包是非法的。 如下图&#xff0c;定义了一个数组$deny_ext array(.a…