JAVA旅游系统畅享旅游系统源码微信小程序的uniapp代码示例
·
🌏 JAVA旅游系统 + UniApp微信小程序源码示例
📋 系统架构
后端: Spring Boot + MyBatis + MySQL
前端: UniApp (Vue3) → 微信小程序
🔧 一、后端核心代码 (Java Spring Boot)
1. 实体类 - 旅游线路
java
@Data
@TableName("travel_route")
public class TravelRoute {
@TableId(type = IdType.AUTO)
private Long id;
private String title; // 线路标题
private String coverImg; // 封面图
private String description; // 描述
private BigDecimal price; // 价格
private Integer days; // 天数
private String departure; // 出发地
private String destination; // 目的地
private Integer status; // 0-下架 1-上架
private LocalDateTime createTime;
}
2. Controller - 旅游接口
java
@RestController
@RequestMapping("/api/travel")
@CrossOrigin
public class TravelController {
@Autowired
private TravelService travelService;
// 首页 - 轮播图 + 热门线路
@GetMapping("/home")
public Result home() {
List<TravelRoute> hotRoutes = travelService.getHotRoutes();
List<Banner> banners = travelService.getBanners();
return Result.success(Map.of(
"banners", banners,
"hotRoutes", hotRoutes
));
}
// 线路列表
@GetMapping("/list")
public Result list(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword
) {
Page<TravelRoute> result = travelService.getPage(page, size, keyword);
return Result.success(result);
}
// 线路详情
@GetMapping("/detail/{id}")
public Result detail(@PathVariable Long id) {
TravelRoute route = travelService.getById(id);
return Result.success(route);
}
// 下单
@PostMapping("/order")
public Result order(@RequestBody OrderDTO orderDTO) {
String orderNo = travelService.createOrder(orderDTO);
return Result.success(orderNo);
}
// 我的订单
@GetMapping("/myOrders")
public Result myOrders(@RequestAttribute Long userId) {
List<Order> orders = travelService.getUserOrders(userId);
return Result.success(orders);
}
}
3. Service层
java
@Service
public class TravelServiceImpl implements TravelService {
@Autowired
private TravelRouteMapper routeMapper;
@Override
public Page<TravelRoute> getPage(Integer page, Integer size, String keyword) {
LambdaQueryWrapper<TravelRoute> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(keyword),
TravelRoute::getTitle, keyword)
.eq(TravelRoute::getStatus, 1)
.orderByDesc(TravelRoute::getCreateTime);
return routeMapper.selectPage(
new Page<>(page, size), wrapper
);
}
@Override
public String createOrder(OrderDTO dto) {
String orderNo = "TX" + System.currentTimeMillis();
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(dto.getUserId());
order.setRouteId(dto.getRouteId());
order.setPrice(dto.getPrice());
order.setStatus(0); // 待支付
orderMapper.insert(order);
return orderNo;
}
}
📱 二、UniApp 前端代码 (微信小程序)
📁 项目结构
├── pages/
│ ├── index/ # 首页
│ ├── list/ # 线路列表
│ ├── detail/ # 线路详情
│ ├── order/ # 订单确认
│ └── mine/ # 我的
├── components/
│ └── nav-bar.vue # 自定义导航栏
├── static/
├── pages.json
├── manifest.json
└── App.vue
1️⃣ pages.json - 页面配置
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "畅享旅游",
"navigationStyle": "custom"
}
},
{
"path": "pages/list/list",
"style": {
"navigationBarTitleText": "旅游线路"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "线路详情"
}
},
{
"path": "pages/order/order",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "pages/mine/mine",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"tabBar": {
"color": "#999",
"selectedColor": "#07c160",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/tab/home.png",
"selectedIconPath": "static/tab/home-active.png"
},
{
"pagePath": "pages/list/list",
"text": "线路",
"iconPath": "static/tab/list.png",
"selectedIconPath": "static/tab/list-active.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/tab/mine.png",
"selectedIconPath": "static/tab/mine-active.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#f5f5f5"
}
}
2️⃣ App.vue - 全局配置
vue
<script>
export default {
onLaunch() {
console.log('App Launch')
// 获取用户信息
const userInfo = uni.getStorageSync('userInfo')
if (userInfo) {
this.$store.commit('SET_USER', userInfo)
}
},
globalStyle: {
navigationBarTextStyle: 'black',
navigationBarTitleText: '畅享旅游',
navigationBarBackgroundColor: '#FFF',
backgroundColor: '#F8F8F8'
}
}
</script>
<style>
/* 全局样式 */
page {
background-color: #f5f5f5;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.container {
padding: 20rpx;
}
.price {
color: #ff4d4f;
font-weight: bold;
}
.btn-primary {
background: linear-gradient(135deg, #07c160, #06ad56);
color: #fff;
border-radius: 50rpx;
text-align: center;
padding: 24rpx 0;
font-size: 32rpx;
}
</style>
3️⃣ pages/index/index.vue - 首页 ⭐
vue
<template>
<view class="index-page">
<!-- 搜索栏 -->
<view class="search-bar">
<view class="search-input" @click="goSearch">
<uni-icons type="search" size="18" color="#999"></uni-icons>
<text class="placeholder">搜索目的地、线路...</text>
</view>
</view>
<!-- 轮播图 -->
<swiper class="banner" indicator-dots autoplay circular interval="3000">
<swiper-item v-for="item in banners" :key="item.id">
<image :src="item.imageUrl" mode="aspectFill"
class="banner-img" @click="goDetail(item.routeId)"></image>
</swiper-item>
</swiper>
<!-- 快捷分类 -->
<view class="category-grid">
<view class="cat-item" v-for="cat in categories" :key="cat.id"
@click="goList(cat.id)">
<image :src="cat.icon" class="cat-icon"></image>
<text class="cat-name">{{ cat.name }}</text>
</view>
</view>
<!-- 热门线路 -->
<view class="section">
<view class="section-header">
<text class="section-title">🔥 热门线路</text>
<text class="section-more" @click="goList">查看更多 ></text>
</view>
<view class="route-list">
<view class="route-card" v-for="route in hotRoutes" :key="route.id"
@click="goDetail(route.id)">
<image :src="route.coverImg" class="route-cover" mode="aspectFill"></image>
<view class="route-info">
<text class="route-title">{{ route.title }}</text>
<text class="route-desc">{{ route.destination }} · {{ route.days }}天{{ route.days-1 }}晚</text>
<view class="route-bottom">
<text class="route-price">
<text class="yen">¥</text>{{ route.price }}
<text class="unit">/人起</text>
</text>
<text class="route-sales">{{ route.sales }}人已预订</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
banners: [],
hotRoutes: [],
categories: [
{ id: 1, name: '海边度假', icon: '/static/icons/beach.png' },
{ id: 2, name: '山水风光', icon: '/static/icons/mountain.png' },
{ id: 3, name: '古城文化', icon: '/static/icons/city.png' },
{ id: 4, name: '亲子游', icon: '/static/icons/family.png' },
{ id: 5, name: '美食之旅', icon: '/static/icons/food.png' },
{ id: 6, name: '更多', icon: '/static/icons/more.png' }
]
}
},
onLoad() {
this.loadHomeData()
},
onPullDownRefresh() {
this.loadHomeData()
uni.stopPullDownRefresh()
},
methods: {
async loadHomeData() {
uni.showLoading({ title: '加载中...' })
try {
const res = await uni.request({
url: 'http://localhost:8080/api/travel/home',
method: 'GET'
})
if (res.data.code === 200) {
this.banners = res.data.data.banners
this.hotRoutes = res.data.data.hotRoutes
}
} catch (e) {
console.error(e)
} finally {
uni.hideLoading()
}
},
goDetail(id) {
uni.navigateTo({ url: `/pages/detail/detail?id=${id}` })
},
goList(catId) {
uni.switchTab({ url: '/pages/list/list' })
},
goSearch() {
uni.navigateTo({ url: '/pages/list/list?keyword=' })
}
}
}
</script>
<style scoped>
.search-bar {
padding: 20rpx 30rpx;
background: #fff;
}
.search-input {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 40rpx;
padding: 16rpx 30rpx;
}
.placeholder {
margin-left: 16rpx;
color: #999;
font-size: 28rpx;
}
.banner {
height: 360rpx;
margin: 20rpx 30rpx;
border-radius: 20rpx;
overflow: hidden;
}
.banner-img {
width: 100%;
height: 100%;
}
.category-grid {
display: flex;
flex-wrap: wrap;
padding: 20rpx 30rpx;
background: #fff;
margin: 0 30rpx;
border-radius: 20rpx;
}
.cat-item {
width: 20%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20rpx;
}
.cat-icon {
width: 80rpx;
height: 80rpx;
}
.cat-name {
font-size: 24rpx;
color: #333;
margin-top: 8rpx;
}
.section {
margin-top: 30rpx;
padding: 0 30rpx;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.section-title {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.section-more {
font-size: 26rpx;
color: #07c160;
}
.route-card {
display: flex;
background: #fff;
border-radius: 20rpx;
margin-bottom: 24rpx;
overflow: hidden;
box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
}
.route-cover {
width: 240rpx;
height: 200rpx;
flex-shrink: 0;
}
.route-info {
flex: 1;
padding: 16rpx 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.route-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.route-desc {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.route-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.route-price {
color: #ff4d4f;
font-weight: bold;
font-size: 32rpx;
}
.yen {
font-size: 24rpx;
}
.unit {
font-size: 22rpx;
color: #999;
font-weight: normal;
}
.route-sales {
font-size: 22rpx;
color: #bbb;
}
</style>
4️⃣ pages/detail/detail.vue - 线路详情 ⭐
vue
<template>
<view class="detail-page">
<!-- 封面图 -->
<image :src="route.coverImg" class="cover" mode="aspectFill"></image>
<!-- 基本信息 -->
<view class="info-card">
<text class="title">{{ route.title }}</text>
<view class="tags">
<text class="tag">{{ route.destination }}</text>
<text class="tag">{{ route.days }}天{{ route.days-1 }}晚</text>
<text class="tag">{{ route.departure }}出发</text>
</view>
<view class="price-row">
<text class="price">
<text class="yen">¥</text>{{ route.price }}
<text class="unit">/人起</text>
</text>
<text class="sales">{{ route.sales }}人已预订</text>
</view>
</view>
<!-- 行程安排 -->
<view class="section-card">
<text class="card-title">📋 行程安排</text>
<view class="schedule-item" v-for="(item, index) in schedule" :key="index">
<view class="schedule-day">Day {{ index + 1 }}</view>
<text class="schedule-content">{{ item }}</text>
</view>
</view>
<!-- 费用说明 -->
<view class="section-card">
<text class="card-title">💰 费用说明</text>
<view class="fee-item" v-for="(item, index) in fees" :key="index">
<text class="fee-label">{{ item.label }}</text>
<text class="fee-value" :class="{ included: item.included }">
{{ item.value }}
</text>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="bar-left">
<view class="bar-item" @click="toggleFavorite">
<uni-icons :type="isFav ? 'heart-filled' : 'heart'" size="24"
:color="isFav ? '#ff4d4f' : '#999'"></uni-icons>
<text>收藏</text>
</view>
<view class="bar-item" @click="shareRoute">
<uni-icons type="redo" size="24" color="#999"></uni-icons>
<text>分享</text>
</view>
</view>
<view class="bar-right">
<button class="btn-book" @click="goOrder">立即预订</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
routeId: null,
route: {},
schedule: [],
fees: [
{ label: '住宿', value: '含', included: true },
{ label: '用餐', value: '含早餐', included: true },
{ label: '交通', value: '含', included: true },
{ label: '门票', value: '含', included: true },
{ label: '导游', value: '含', included: true },
{ label: '保险', value: '含', included: true }
],
isFav: false
}
},
onLoad(options) {
this.routeId = options.id
this.loadDetail()
},
methods: {
async loadDetail() {
uni.showLoading({ title: '加载中...' })
try {
const res = await uni.request({
url: `http://localhost:8080/api/travel/detail/${this.routeId}`
})
if (res.data.code === 200) {
this.route = res.data.data
// 模拟行程
this.schedule = [
`抵达${this.route.destination},专车接站,入住酒店休息`,
`游览${this.route.destination}核心景区,品尝当地特色美食`,
`自由活动,可选择自费项目`,
`返回${this.route.departure},结束愉快旅程`
]
}
} catch (e) {
console.error(e)
} finally {
uni.hideLoading()
}
},
goOrder() {
uni.navigateTo({
url: `/pages/order/order?id=${this.routeId}&price=${this.route.price}`
})
},
toggleFavorite() {
this.isFav = !this.isFav
uni.showToast({
title: this.isFav ? '已收藏' : '已取消',
icon: 'none'
})
},
shareRoute() {
// 触发分享
},
onShareAppMessage() {
return {
title: this.route.title,
path: `/pages/detail/detail?id=${this.routeId}`
}
}
}
}
</script>
<style scoped>
.detail-page {
padding-bottom: 140rpx;
}
.cover {
width: 100%;
height: 500rpx;
}
.info-card {
background: #fff;
margin: -40rpx 30rpx 20rpx;
border-radius: 20rpx;
padding: 30rpx;
position: relative;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.08);
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.tags {
display: flex;
gap: 16rpx;
margin-top: 16rpx;
}
.tag {
background: #e8f5e9;
color: #07c160;
font-size: 24rpx;
padding: 6rpx 16rpx;
border-radius: 20rpx;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 20rpx;
}
.price {
color: #ff4d4f;
font-weight: bold;
font-size: 40rpx;
}
.unit {
font-size: 24rpx;
color: #999;
font-weight: normal;
}
.sales {
font-size: 24rpx;
color: #bbb;
}
.section-card {
background: #fff;
margin: 20rpx 30rpx;
border-radius: 20rpx;
padding: 30rpx;
}
.card-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.schedule-item {
display: flex;
margin-bottom: 20rpx;
}
.schedule-day {
background: #07c160;
color: #fff;
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 8rpx;
margin-right: 16rpx;
height: fit-content;
}
.schedule-content {
font-size: 28rpx;
color: #666;
line-height: 1.6;
}
.fee-item {
display: flex;
justify-content: space-between;
padding: 16rpx 0;
border-bottom: 1rpx solid #f0f0f0;
}
.fee-label {
font-size: 28rpx;
color: #333;
}
.fee-value {
font-size: 28rpx;
color: #999;
}
.fee-value.included {
color: #07c160;
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06);
padding-bottom: env(safe-area-inset-bottom);
}
.bar-left {
display: flex;
gap: 40rpx;
}
.bar-item {
display: flex;
flex-direction: column;
align-items: center;
font-size: 22rpx;
color: #999;
}
.btn-book {
background: linear-gradient(135deg, #07c160, #06ad56);
color: #fff;
border: none;
border-radius: 50rpx;
padding: 24rpx 80rpx;
font-size: 32rpx;
font-weight: bold;
}
</style>
5️⃣ pages/order/order.vue - 确认订单
vue
<template>
<view class="order-page">
<!-- 订单信息 -->
<view class="order-card">
<text class="card-title">订单信息</text>
<view class="order-item">
<text class="label">线路</text>
<text class="value">{{ route.title }}</text>
</view>
<view class="order-item">
<text class="label">出发日期</text>
<picker mode="date" @change="onDateChange">
<text class="value">{{ travelDate || '请选择' }}</text>
</picker>
</view>
<view class="order-item">
<text class="label">出行人数</text>
<view class="counter">
<view class="counter-btn" @click="changeCount(-1)">-</view>
<text class="counter-num">{{ count }}</text>
<view class="counter-btn" @click="changeCount(1)">+</view>
</view>
</view>
</view>
<!-- 联系人 -->
<view class="order-card">
<text class="card-title">联系人信息</text>
<view class="order-item">
<text class="label">姓名</text>
<input class="input" v-model="form.name" placeholder="请输入姓名" />
</view>
<view class="order-item">
<text class="label">手机号</text>
<input class="input" v-model="form.phone" type="number"
placeholder="请输入手机号" maxlength="11" />
</view>
</view>
<!-- 价格明细 -->
<view class="order-card">
<text class="card-title">💰 价格明细</text>
<view class="price-row">
<text>线路费用</text>
<text>¥{{ route.price }} × {{ count }}人</text>
</view>
<view class="price-row total">
<text>合计</text>
<text class="total-price">¥{{ totalPrice }}</text>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-bar">
<text class="total-label">合计:</text>
<text class="total-amount">¥{{ totalPrice }}</text>
<button class="submit-btn" @click="submitOrder">提交订单</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
routeId: null,
route: {},
count: 1,
travelDate: '',
form: {
name: '',
phone: ''
}
}
},
computed: {
totalPrice() {
return (this.route.price * this.count).toFixed(2)
}
},
onLoad(options) {
this.routeId = options.id
this.route.price = parseFloat(options.price)
this.loadRoute()
},
methods: {
async loadRoute() {
const res = await uni.request({
url: `http://localhost:8080/api/travel/detail/${this.routeId}`
})
if (res.data.code === 200) {
this.route = res.data.data
}
},
changeCount(delta) {
this.count = Math.max(1, Math.min(10, this.count + delta))
},
onDateChange(e) {
this.travelDate = e.detail.value
},
async submitOrder() {
if (!this.form.name) return uni.showToast({ title: '请输入姓名', icon: 'none' })
if (!this.form.phone || this.form.phone.length !== 11) {
return uni.showToast({ title: '请输入正确手机号', icon: 'none' })
}
if (!this.travelDate) return uni.showToast({ title: '请选择日期', icon: 'none' })
uni.showLoading({ title: '提交中...' })
try {
const res = await uni.request({
url: 'http://localhost:8080/api/travel/order',
method: 'POST',
data: {
routeId: this.routeId,
price: this.route.price,
count: this.count,
travelDate: this.travelDate,
name: this.form.name,
phone: this.form.phone
}
})
if (res.data.code === 200) {
uni.showModal({
title: '下单成功',
content: `订单号:${res.data.data}`,
showCancel: false,
success: () => {
uni.switchTab({ url: '/pages/mine/mine' })
}
})
}
} catch (e) {
console.error(e)
} finally {
uni.hideLoading()
}
}
}
}
</script>
<style scoped>
.order-page {
padding: 20rpx 30rpx;
padding-bottom: 180rpx;
}
.order-card {
background: #fff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.card-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1rpx solid #f5f5f5;
}
.label {
font-size: 28rpx;
color: #666;
}
.value {
font-size: 28rpx;
color: #333;
}
.input {
text-align: right;
width: 400rpx;
font-size: 28rpx;
}
.counter {
display: flex;
align-items: center;
gap: 24rpx;
}
.counter-btn {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #333;
}
.counter-num {
font-size: 32rpx;
font-weight: bold;
min-width: 60rpx;
text-align: center;
}
.price-row {
display: flex;
justify-content: space-between;
padding: 12rpx 0;
font-size: 28rpx;
color: #666;
}
.price-row.total {
border-top: 1rpx solid #f0f0f0;
margin-top: 10rpx;
padding-top: 20rpx;
font-weight: bold;
color: #333;
}
.total-price {
color: #ff4d4f;
font-size: 36rpx;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: #fff;
display: flex;
align-items: center;
padding: 0 30rpx;
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.06);
padding-bottom: env(safe-area-inset-bottom);
}
.total-label {
font-size: 28rpx;
color: #666;
}
.total-amount {
font-size: 36rpx;
color: #ff4d4f;
font-weight: bold;
margin-right: 20rpx;
}
.submit-btn {
background: linear-gradient(135deg, #07c160, #06ad56);
color: #fff;
border: none;
border-radius: 50rpx;
padding: 20rpx 60rpx;
font-size: 30rpx;
font-weight: bold;
}
</style>
🗄️ 三、数据库SQL
sql
CREATE DATABASE travel_system DEFAULT CHARSET utf8mb4;
USE travel_system;
-- 旅游线路表
CREATE TABLE travel_route (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(200) NOT NULL COMMENT '线路标题',
cover_img VARCHAR(500) COMMENT '封面图',
description TEXT COMMENT '描述',
price DECIMAL(10,2) COMMENT '价格',
days INT COMMENT '天数',
departure VARCHAR(100) COMMENT '出发地',
destination VARCHAR(100) COMMENT '目的地',
status TINYINT DEFAULT 1 COMMENT '0下架 1上架',
sales INT DEFAULT 0 COMMENT '销量',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT '旅游线路表';
-- 订单表
CREATE TABLE `order` (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(64) UNIQUE NOT NULL COMMENT '订单号',
user_id BIGINT NOT NULL COMMENT '用户ID',
route_id BIGINT NOT NULL COMMENT '线路ID',
price DECIMAL(10,2) COMMENT '总价',
count INT DEFAULT 1 COMMENT '人数',
travel_date DATE COMMENT '出行日期',
name VARCHAR(50) COMMENT '联系人',
phone VARCHAR(20) COMMENT '手机号',
status TINYINT DEFAULT 0 COMMENT '0待支付 1已支付 2已完成 3已取消',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT '订单表';
-- 用户表
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
openid VARCHAR(100) UNIQUE COMMENT '微信openid',
nickname VARCHAR(100) COMMENT '昵称',
avatar VARCHAR(500) COMMENT '头像',
phone VARCHAR(20) COMMENT '手机号',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) COMMENT '用户表';
-- 插入测试数据
INSERT INTO travel_route (title, cover_img, description, price, days, departure, destination, sales) VALUES
('三亚5天4晚自由行', '/static/images/sanya.jpg', '阳光沙滩,椰风海韵,享受三亚的碧海蓝天', 2999.00, 5, '全国各地', '海南三亚', 3256),
('丽江古城3日游', '/static/images/lijiang.jpg', '漫步古城,感受纳西族文化,远眺玉龙雪山', 1599.00, 3, '昆明', '云南丽江', 2180),
('张家界5天深度游', '/static/images/zhangjiajie.jpg', '阿凡达取景地,玻璃栈道,天门山', 3299.00, 5, '长沙', '湖南张家界', 1890),
('西安4天历史文化游', '/static/images/xian.jpg', '兵马俑、华清池、大雁塔,梦回大唐', 2199.00, 4, '全国各地', '陕西西安', 4521);
🚀 四、快速启动
bash
# 1. 后端启动
cd travel-backend
mvn spring-boot:run
# 2. 前端启动 (HBuilderX 打开 UniApp 项目)
# 或命令行:
cd travel-uniapp
npm run dev:mp-weixin # 微信小程序
npm run dev:h5 # H5
📊 系统功能总览
| 模块 | 功能 | 状态 |
|---|---|---|
| 🏠 首页 | 轮播图 + 分类 + 热门线路 | ✅ |
| 📋 线路列表 | 搜索 + 筛选 + 分页 | ✅ |
| 📝 线路详情 | 行程 + 费用 + 收藏 | ✅ |
| 🛒 下单 | 选日期 + 人数 + 联系人 | ✅ |
| 👤 我的 | 订单列表 + 个人信息 | ✅ |
| 💰 支付 | 微信支付(需配置) | 🔲 |
💡 提示:微信小程序支付需要在微信商户平台配置,并在后端接入微信支付SDK。如需要支付完整代码,请告诉我!
更多推荐

所有评论(0)