前言
💗博主介绍:全网CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者。
专注于Java、小程序技术领域和毕业项目实战💗✌Java、SSM+Vue、SpringBoot+Vue、NodeJS+Vue、微信小程序、Python、大数据、安卓。
你想要的我都有,你没有的,本团队亲历亲为开发。
统信打造硬核“服务工具”,让客户省心、放心、舒心!🌟文末获取源码+数据库🌟
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人
一、具体实现截图
简单介绍:
使用三端分离,会员端、控台端、服务端,便于开发和维护,同时将界面与功能逻辑分开,易于分配不同的资源。使用微服务生态组件:注册中心、配置中心、网关、限流降级、分布式事务、服务调用,保障服务高可用。将系统功能拆分成多个微服务应用,实现服务间低耦合,服务内功能高内聚。使用Redis、MQ等中间件,提升系统性能,保障服务高性能响应。
二、技术栈
①.SpringBoot3.0体系
自动配置与约定优于配置简化了项目配置,提供默认配置,减少了开发者的工作量。快速开发集成了大量常用功能,通过注解和约定,减少了样板代码,提高了开发效率。嵌入式服务器支持常见的嵌入式服务器,如Tomcat、Jetty,方便打包成可执行JAR文件。微服务支持提供了Spring Cloud等工具,支持微服务开发和管理。Spring生态系统整合可轻松整合Spring框架中的各种组件,如Spring Data、Spring Security等。广泛的社区支持拥有庞大的社区,提供了丰富的文档和解决方案。
Spring Boot的使用优势在于简化开发、提高效率、支持微服务等方面,使得Java开发更加便捷和灵活。如有新版本发布,请查阅最新文档以获取详细信息。
②.第三方组件集成
含:Mybatis、Redis、RocketMQ、Quartz;
③.设计亮点
WT单点登录、项目分层设计、分库分表、自制前后端代码生成器
④.前端Vue3
Vue CLI 5、Ref、Reactiev、Axios、Router、Vuex、Ant Design Vue、多环境配置、编译与部署
⑤.前高并发秒杀技术
高并发秒杀技术、缓存、限流、熔断、JWT令牌、令牌大闸、削峰填谷、分布式锁
三、为何选择我们?
①:强大、正规的团队
Ⅰ:专注全栈技术分享,汇总多年实战经验。拥有正规团队官网。
网站上传的项目均为博主自己收集和开发的,质量都可以得到保障。
适合自己懂一点程序开发的同学使用!
Ⅱ:(小程序端正在上线,敬请期待 > > >)
四:代码参考
只进行部分展示 -->
SpringBoot部分:
package com.company.train.business.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.company.train.business.req.ConfirmOrderDoReq;
import com.company.train.business.service.BeforeConfirmOrderService;
import com.company.train.business.service.ConfirmOrderService;
import com.company.train.common.exception.BusinessExceptionEnum;
import com.company.train.common.resp.CommonResp;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/confirm-order")
public class ConfirmOrderController {
private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderController.class);
@Resource
private BeforeConfirmOrderService beforeConfirmOrderService;
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${spring.profiles.active}")
private String env;
@Resource
private ConfirmOrderService confirmOrderService;
// 接口的资源名称不要和接口路径一致,会导致限流后走不到降级方法中
@SentinelResource(value = "confirmOrderDo", blockHandler = "doConfirmBlock")
@PostMapping("/do")
public CommonResp<Object> doConfirm(@Valid @RequestBody ConfirmOrderDoReq req) {
if (!env.equals("dev")) {
// 图形验证码校验
String imageCodeToken = req.getImageCodeToken();
String imageCode = req.getImageCode();
String imageCodeRedis = redisTemplate.opsForValue().get(imageCodeToken);
LOG.info("从redis中获取到的验证码:{}", imageCodeRedis);
if (ObjectUtils.isEmpty(imageCodeRedis)) {
return new CommonResp<>(false, "验证码已过期", null);
}
// 验证码校验,大小写忽略,提升体验,比如Oo Vv Ww容易混
if (!imageCodeRedis.equalsIgnoreCase(imageCode)) {
return new CommonResp<>(false, "验证码不正确", null);
} else {
// 验证通过后,移除验证码
redisTemplate.delete(imageCodeToken);
}
}
Long id = beforeConfirmOrderService.beforeDoConfirm(req);
return new CommonResp<>(String.valueOf(id));
}
@GetMapping("/query-line-count/{id}")
public CommonResp<Integer> queryLineCount(@PathVariable Long id) {
Integer count = confirmOrderService.queryLineCount(id);
return new CommonResp<>(count);
}
@GetMapping("/cancel/{id}")
public CommonResp<Integer> cancel(@PathVariable Long id) {
Integer count = confirmOrderService.cancel(id);
return new CommonResp<>(count);
}
/** 降级方法,需包含限流方法的所有参数和BlockException参数,且返回值要保持一致
* @param req
* @param e
*/
public CommonResp<Object> doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
LOG.info("ConfirmOrderController购票请求被限流:{}", req);
// throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
CommonResp<Object> commonResp = new CommonResp<>();
commonResp.setSuccess(false);
commonResp.setMessage(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION.getDesc());
return commonResp;
}
}
Vue端:
<template>
<div class="order-train">
<span class="order-train-main">{{dailyTrainTicket.date}}</span>
<span class="order-train-main">{{dailyTrainTicket.trainCode}}</span>次
<span class="order-train-main">{{dailyTrainTicket.start}}</span>站
<span class="order-train-main">({{dailyTrainTicket.startTime}})</span>
<span class="order-train-main">——</span>
<span class="order-train-main">{{dailyTrainTicket.end}}</span>站
<span class="order-train-main">({{dailyTrainTicket.endTime}})</span>
<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>
<span class="order-train-ticket-main">{{item.count}}</span> 张票
</span>
</div>
</div>
<a-divider></a-divider>
<b>勾选要购票的乘客:</b>
<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="showFirstImageCodeModal">
<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>
<div style="color: red">
体验排队购票,加入多人一起排队购票:
<a-input-number v-model:value="lineNumber" :min="0" :max="20" />
</div>
<!--<br/>-->
<!--最终购票:{{tickets}}-->
<!--最终选座:{{chooseSeatObj}}-->
</div>
</a-modal>
<!-- 第二层验证码 后端 -->
<a-modal v-model:visible="imageCodeModalVisible" :title="null" :footer="null" :closable="false"
style="top: 50px; width: 400px">
<p style="text-align: center; font-weight: bold; font-size: 18px">
使用服务端验证码削弱瞬时高峰<br/>
防止机器人刷票
</p>
<p>
<a-input v-model:value="imageCode" placeholder="图片验证码">
<template #suffix>
<img v-show="!!imageCodeSrc" :src="imageCodeSrc" alt="验证码" v-on:click="loadImageCode()"/>
</template>
</a-input>
</p>
<a-button type="danger" block @click="handleOk">输入验证码后开始购票</a-button>
</a-modal>
<!-- 第一层验证码 纯前端 -->
<a-modal v-model:visible="firstImageCodeModalVisible" :title="null" :footer="null" :closable="false"
style="top: 50px; width: 400px">
<p style="text-align: center; font-weight: bold; font-size: 18px">
使用纯前端验证码削弱瞬时高峰<br/>
减小后端验证码接口的压力
</p>
<p>
<a-input v-model:value="firstImageCodeTarget" placeholder="验证码">
<template #suffix>
{{firstImageCodeSourceA}} + {{firstImageCodeSourceB}}
</template>
</a-input>
</p>
<a-button type="danger" block @click="validFirstImageCode">提交验证码</a-button>
</a-modal>
<a-modal v-model:visible="lineModalVisible" title="排队购票" :footer="null" :maskClosable="false" :closable="false"
style="top: 50px; width: 400px">
<div class="book-line">
<div v-show="confirmOrderLineCount < 0">
<loading-outlined /> 系统正在处理中...
</div>
<div v-show="confirmOrderLineCount >= 0">
<loading-outlined /> 您前面还有{{confirmOrderLineCount}}位用户在购票,排队中,请稍候
</div>
</div>
<br/>
<a-button type="danger" @click="onCancelOrder">取消购票</a-button>
</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);
const lineModalVisible = ref(false);
const confirmOrderId = ref();
const confirmOrderLineCount = ref(-1);
const lineNumber = ref(5);
// 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
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 = () => {
if (Tool.isEmpty(imageCode.value)) {
notification.error({description: '验证码不能为空'});
return;
}
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,
imageCodeToken: imageCodeToken.value,
imageCode: imageCode.value,
lineNumber: lineNumber.value
}).then((response) => {
let data = response.data;
if (data.success) {
// notification.success({description: "下单成功!"});
visible.value = false;
imageCodeModalVisible.value = false;
lineModalVisible.value = true;
confirmOrderId.value = data.content;
queryLineCount();
} else {
notification.error({description: data.message});
}
});
}
/* ------------------- 定时查询订单状态 --------------------- */
// 确认订单后定时查询
let queryLineCountInterval;
// 定时查询订单结果/排队数量
const queryLineCount = () => {
confirmOrderLineCount.value = -1;
queryLineCountInterval = setInterval(function () {
axios.get("/business/confirm-order/query-line-count/" + confirmOrderId.value).then((response) => {
let data = response.data;
if (data.success) {
let result = data.content;
switch (result) {
case -1 :
notification.success({description: "购票成功!"});
lineModalVisible.value = false;
clearInterval(queryLineCountInterval);
break;
case -2:
notification.error({description: "购票失败!"});
lineModalVisible.value = false;
clearInterval(queryLineCountInterval);
break;
case -3:
notification.error({description: "抱歉,没票了!"});
lineModalVisible.value = false;
clearInterval(queryLineCountInterval);
break;
default:
confirmOrderLineCount.value = result;
}
} else {
notification.error({description: data.message});
}
});
}, 500);
};
/* ------------------- 第二层验证码 --------------------- */
const imageCodeModalVisible = ref();
const imageCodeToken = ref();
const imageCodeSrc = ref();
const imageCode = ref();
/**
* 加载图形验证码
*/
const loadImageCode = () => {
imageCodeToken.value = Tool.uuid(8);
imageCodeSrc.value = process.env.VUE_APP_SERVER + '/business/kaptcha/image-code/' + imageCodeToken.value;
};
const showImageCodeModal = () => {
loadImageCode();
imageCodeModalVisible.value = true;
};
/* ------------------- 第一层验证码 --------------------- */
const firstImageCodeSourceA = ref();
const firstImageCodeSourceB = ref();
const firstImageCodeTarget = ref();
const firstImageCodeModalVisible = ref();
/**
* 加载第一层验证码
*/
const loadFirstImageCode = () => {
// 获取1~10的数:Math.floor(Math.random()*10 + 1)
firstImageCodeSourceA.value = Math.floor(Math.random()*10 + 1) + 10;
firstImageCodeSourceB.value = Math.floor(Math.random()*10 + 1) + 20;
};
/**
* 显示第一层验证码弹出框
*/
const showFirstImageCodeModal = () => {
loadFirstImageCode();
firstImageCodeModalVisible.value = true;
};
/**
* 校验第一层验证码
*/
const validFirstImageCode = () => {
if (parseInt(firstImageCodeTarget.value) === parseInt(firstImageCodeSourceA.value + firstImageCodeSourceB.value)) {
// 第一层验证通过
firstImageCodeModalVisible.value = false;
showImageCodeModal();
} else {
notification.error({description: '验证码错误'});
}
};
/**
* 取消排队
*/
const onCancelOrder = () => {
axios.get("/business/confirm-order/cancel/" + confirmOrderId.value).then((response) => {
let data = response.data;
if (data.success) {
let result = data.content;
if (result === 1) {
notification.success({description: "取消成功!"});
// 取消成功时,不用再轮询排队结果
clearInterval(queryLineCountInterval);
lineModalVisible.value = false;
} else {
notification.error({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,
imageCodeToken,
imageCodeSrc,
imageCode,
showImageCodeModal,
imageCodeModalVisible,
loadImageCode,
firstImageCodeSourceA,
firstImageCodeSourceB,
firstImageCodeTarget,
firstImageCodeModalVisible,
showFirstImageCodeModal,
validFirstImageCode,
lineModalVisible,
confirmOrderId,
confirmOrderLineCount,
onCancelOrder,
lineNumber
};
},
});
</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>
⚠ 源码获取
微信号:Xiaojiacoding 👇🏻扫码获取联系方式👇🏻文章来源:https://www.toymoban.com/news/detail-828252.html
文章来源地址https://www.toymoban.com/news/detail-828252.html
到了这里,关于基于Springboot3+微服务实现12306高性能售票系统的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!