大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Java的微信小程序校园超市(小卖部)系统(SpringBoot4+Vue3)
项目介绍
随着移动互联网和智能手机的普及,校园购物方式正在发生深刻变化。传统校园超市(小卖部)存在排队结账时间长、商品信息不透明、库存管理依赖人工、缺乏线上下单渠道等问题,难以满足在校师生日益增长的便捷购物需求。微信小程序凭借“无需安装、用完即走”的轻量化特点,以及庞大的用户基础,成为构建校园电商应用的理想载体。
本文以“微信小程序校园超市(小卖部)系统”为研究对象,采用前后端分离的架构进行设计与实现。系统整体分为三端:面向学生用户的微信小程序端、面向管理员的Web管理后台,以及为二者提供统一数据服务的后端接口。后端采用 Spring Boot 4 框架并整合 MyBatis-Plus 持久层框架,使用 MySQL 8 作为数据库,通过 JWT 实现无状态的登录鉴权;管理后台采用 Vue 3 + Element Plus + ECharts 技术栈构建,实现了数据可视化的运营看板;用户端基于微信原生小程序开发,实现了商品浏览、购物车、下单、评价等完整的购物闭环。
系统主要实现了用户注册登录、首页推荐、商品分类浏览、商品详情、购物车管理、订单管理、收货地址管理、商品评价等用户端功能,以及数据统计仪表盘、分类管理、商品管理、轮播图管理、订单管理、用户管理、评价管理和公告管理等后台功能。经过功能测试,系统运行稳定、界面友好、逻辑正确,能够有效提升校园超市的运营效率和学生的购物体验,具有一定的实用价值。
源码下载
链接: https://pan.baidu.com/s/171tKPaZCATWKhNnCPrB4UA?pwd=1234
提取码: 1234
系统展示
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
核心代码
package com.java1234.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.java1234.common.PageResult; import com.java1234.common.Result; import com.java1234.entity.Notice; import com.java1234.service.NoticeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.Date; /** * 后台公告管理控制器 */ @RestController @RequestMapping("/api/admin/notice") public class AdminNoticeController { @Autowired private NoticeService noticeService; /** * 分页查询公告 */ @GetMapping("/page") public Result<PageResult<Notice>> page( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { Page<Notice> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Notice> wrapper = new LambdaQueryWrapper<>(); wrapper.orderByDesc(Notice::getCreateTime); noticeService.page(page, wrapper); return Result.success(new PageResult<>(page.getTotal(), page.getRecords())); } /** * 新增公告 */ @PostMapping public Result<Void> add(@RequestBody Notice notice) { notice.setCreateTime(new Date()); noticeService.save(notice); return Result.success(); } /** * 修改公告 */ @PutMapping public Result<Void> update(@RequestBody Notice notice) { noticeService.updateById(notice); return Result.success(); } /** * 删除公告 */ @DeleteMapping("/{id}") public Result<Void> delete(@PathVariable Long id) { noticeService.removeById(id); return Result.success(); } }<template> <div class="page-container"> <div class="page-header"> <h2>公告管理</h2> <el-button type="primary" @click="openDialog()"><el-icon><Plus /></el-icon> 新增公告</el-button> </div> <el-table :data="tableData" stripe border style="width: 100%;"> <el-table-column prop="id" label="ID" min-width="70" /> <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip /> <el-table-column prop="content" label="内容" min-width="300" show-overflow-tooltip /> <el-table-column label="状态" min-width="80"> <template #default="{ row }"> <el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '发布' : '下架' }}</el-tag> </template> </el-table-column> <el-table-column label="发布时间" min-width="170"> <template #default="{ row }">{{ formatDateTime(row.createTime) }}</template> </el-table-column> <el-table-column label="操作" min-width="160" fixed="right"> <template #default="{ row }"> <el-button type="primary" link @click="openDialog(row)">编辑</el-button> <el-button type="danger" link @click="handleDelete(row.id)">删除</el-button> </template> </el-table-column> </el-table> <el-pagination class="pagination" v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="total" layout="total, sizes, prev, pager, next" @change="loadData" /> <el-dialog v-model="dialogVisible" :title="form.id ? '编辑公告' : '新增公告'" width="600px"> <el-form :model="form" label-width="80px"> <el-form-item label="标题"><el-input v-model="form.title" /></el-form-item> <el-form-item label="内容"><el-input v-model="form.content" type="textarea" :rows="5" /></el-form-item> <el-form-item label="状态"> <el-switch v-model="form.status" :active-value="1" :inactive-value="0" active-text="发布" inactive-text="下架" /> </el-form-item> </el-form> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="handleSave">确定</el-button> </template> </el-dialog> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue' import { getNoticePage, addNotice, updateNotice, deleteNotice } from '@/api/index' import { formatDateTime } from '@/utils/date' import { ElMessage, ElMessageBox } from 'element-plus' const tableData = ref([]) const total = ref(0) const dialogVisible = ref(false) const query = reactive({ pageNum: 1, pageSize: 10 }) const form = reactive({ id: null, title: '', content: '', status: 1 }) async function loadData() { const res = await getNoticePage(query) tableData.value = res.data.records total.value = res.data.total } function openDialog(row) { if (row) Object.assign(form, row) else Object.assign(form, { id: null, title: '', content: '', status: 1 }) dialogVisible.value = true } async function handleSave() { if (form.id) await updateNotice(form) else await addNotice(form) ElMessage.success('保存成功') dialogVisible.value = false loadData() } function handleDelete(id) { ElMessageBox.confirm('确定删除?', '提示', { type: 'warning' }).then(async () => { await deleteNotice(id) ElMessage.success('删除成功') loadData() }).catch(() => {}) } onMounted(() => loadData()) </script> <style scoped> .pagination { margin-top: 16px; justify-content: flex-end; } </style><!-- 首页 --> <view class="container"> <!-- 轮播图 --> <swiper class="banner-swiper" indicator-dots autoplay circular interval="4000"> <swiper-item wx:for="{{banners}}" wx:key="id"> <image class="banner-img" src="{{item.image}}" mode="aspectFill" /> </swiper-item> </swiper> <!-- 公告 --> <view class="notice-bar" wx:if="{{notices.length > 0}}" bindtap="goNoticeList"> <text class="notice-icon">📢</text> <swiper class="notice-swiper" vertical autoplay circular interval="3000"> <swiper-item wx:for="{{notices}}" wx:key="id" catchtap="goNoticeDetail" data-id="{{item.id}}"> <text class="notice-text">{{item.title}}</text> </swiper-item> </swiper> <text class="notice-arrow">›</text> </view> <!-- 分类导航 --> <view class="card category-nav"> <view class="category-item" wx:for="{{categories}}" wx:key="id" bindtap="goCategory" data-id="{{item.id}}"> <view class="category-icon">{{item.name[0]}}</view> <text class="category-name">{{item.name}}</text> </view> </view> <!-- 推荐商品 --> <view class="section-title">🔥 热销推荐</view> <view class="product-grid"> <view class="product-item card" wx:for="{{products}}" wx:key="id" bindtap="goDetail" data-id="{{item.id}}"> <image class="product-cover" src="{{item.cover || '/images/default-product.png'}}" mode="aspectFill" /> <view class="product-info"> <text class="product-name">{{item.name}}</text> <view class="product-bottom"> <text class="price price-large">¥{{item.price}}</text> <text class="sales">已售{{item.sales}}</text> </view> </view> </view> </view> </view>