分享一套锋哥原创的基于Java的微信小程序实验室预约管理系统(SpringBoot4+Vue3)

大家好,我是Java1234_小锋老师,分享一套锋哥原创的基于Java的微信小程序实验室预约管理系统(SpringBoot4+Vue3)

项目介绍

随着高校教学与科研规模的不断扩大,实验室作为重要的教学与实验资源,其预约与使用管理日益复杂。传统的实验室预约方式主要依靠人工登记、电话预约或纸质表单,存在信息不透明、预约冲突频繁、审核效率低下、使用情况难以追踪等诸多问题,已难以满足现代高校精细化管理的需求。为解决上述问题,本文设计并实现了一套微信小程序实验室预约管理系统。

本系统采用前后端分离的架构模式进行开发,包含三个终端:面向学生的微信小程序端、面向管理员的 Web 管理后台以及面向老师的审核端。学生通过微信小程序随时随地浏览实验室、提交预约申请、到场签到签退;管理员通过基于 Vue3 的管理后台维护实验室、设备、公告等信息并审核预约;老师端则可对本人相关的预约进行审核。服务端采用 SpringBoot4 框架搭建 RESTful 接口,使用 MyBatis 作为持久层框架,数据存储采用 MySQL 数据库,并通过 JWT 实现无状态的身份认证。

系统主要实现了学生端的登录注册、首页浏览、实验室查询、在线预约、我的预约(取消、签到、签退)、公告查看、个人中心等功能,以及管理后台的首页统计、学生管理、老师管理、管理员管理、实验室分类管理、实验室管理、设备管理、预约审核、使用记录、公告管理、轮播图管理等功能。在预约提交环节,系统对同一实验室、同一日期、同一时段进行冲突校验,有效避免了重复预约。经过功能测试,系统运行稳定、操作便捷,能够较好地满足高校实验室预约管理的实际需求。

本文详细阐述了系统的需求分析、总体设计、数据库设计、详细实现与系统测试的全过程,并给出了关键功能模块的核心代码与设计图示。

源码下载 

链接: https://pan.baidu.com/s/1xIAd4fPzBMFQgcn9vRXa1Q?pwd=1234
提取码: 1234

系统展示

核心代码

package com.java1234.controller;

import com.java1234.common.Result;
import com.java1234.dto.PageResult;
import com.java1234.entity.Equipment;
import com.java1234.service.EquipmentService;
import com.java1234.util.WebAuth;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

/**
 * 设备管理(管理员)
 * @author 小锋老师
 */
@RestController
@RequestMapping("/equipment")
@RequiredArgsConstructor
public class EquipmentManageController {

    private final EquipmentService equipmentService;

    @GetMapping("/page")
    public Result<PageResult<Equipment>> page(HttpServletRequest request,
            @RequestParam(required = false) Long labId,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        WebAuth.requireAdmin(request);
        return Result.success(equipmentService.pageByLab(page, size, labId, keyword));
    }

    @PostMapping
    public Result<Void> add(HttpServletRequest request, @RequestBody Equipment e) {
        WebAuth.requireAdmin(request);
        equipmentService.save(e);
        return Result.success();
    }

    @PutMapping
    public Result<Void> update(HttpServletRequest request, @RequestBody Equipment e) {
        WebAuth.requireAdmin(request);
        equipmentService.update(e);
        return Result.success();
    }

    @DeleteMapping("/{id}")
    public Result<Void> delete(HttpServletRequest request, @PathVariable Long id) {
        WebAuth.requireAdmin(request);
        equipmentService.delete(id);
        return Result.success();
    }
}
<template>
  <div class="page-card">
    <el-form inline class="admin-table-toolbar">
      <el-button type="primary" @click="load">刷新</el-button>
      <el-button type="success" @click="open()">新增轮播</el-button>
    </el-form>
    <el-table :data="rows" v-loading="loading" class="mt" border stripe style="width: 100%">
      <el-table-column prop="id" width="64" label="ID" />
      <el-table-column label="预览" min-width="160">
        <template #default="{ row }">
          <div v-if="row.image" class="preview-img-wrap">
            <el-image :src="resolveImg(row.image)" fit="cover" class="preview-img" />
          </div>
          <div v-else class="preview-bg" :style="{ background: row.bgStyle || '#6366f1' }" />
        </template>
      </el-table-column>
      <el-table-column prop="sort" width="72" label="排序" />
      <el-table-column label="状态" width="88">
        <template #default="{ row }">
          <el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '启用' : '禁用' }}</el-tag>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" width="190">
        <template #default="{ row }">{{ formatDateTimeDisplay(row.createTime) }}</template>
      </el-table-column>
      <el-table-column width="150" label="操作">
        <template #default="{ row }">
          <el-button link @click="open(row)">编辑</el-button>
          <el-button link type="danger" @click="remove(row)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination class="mt" v-model:current-page="q.page" :total="total" :page-size="q.size" layout="total, prev, pager, next" @current-change="load" />
    <el-dialog v-model="vis" :title="f.id ? '编辑轮播' : '新增轮播'" width="560px" destroy-on-close>
      <el-form :model="f" label-width="88px">
        <el-form-item label="轮播图片">
          <el-input v-model="f.image" placeholder="可选,上传后自动填写;留空则使用渐变背景" />
          <el-upload :show-file-list="false" :http-request="upImg" accept="image/*" class="up">
            <el-button size="small">本地上传</el-button>
          </el-upload>
        </el-form-item>
        <el-form-item label="渐变背景">
          <el-input v-model="f.bgStyle" placeholder="无图时生效,如 linear-gradient(90deg,#6366f1,#8b5cf6)" />
        </el-form-item>
        <el-form-item label="跳转链接"><el-input v-model="f.linkUrl" placeholder="可选,小程序页面路径或外链" /></el-form-item>
        <el-form-item label="排序"><el-input-number v-model="f.sort" :min="0" :max="9999" /></el-form-item>
        <el-form-item label="状态"><el-switch v-model="f.status" :active-value="1" :inactive-value="0" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="vis = false">取消</el-button>
        <el-button type="primary" @click="ok">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import { adminApi } from '@/api/admin'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDateTimeDisplay } from '@/utils/datetimeDisplay'

const q = reactive({ page: 1, size: 10 })
const rows = ref([])
const total = ref(0)
const loading = ref(false)
const vis = ref(false)
const f = reactive({ sort: 0, status: 1 })

function resolveImg(path) {
  if (!path) return ''
  if (path.startsWith('http://') || path.startsWith('https://')) return path
  if (path.startsWith('/')) return path
  return '/api/uploads/' + path
}

async function load() {
  loading.value = true
  try {
    const r = await adminApi.bannerPage({ page: q.page, size: q.size })
    rows.value = r.data.records
    total.value = r.data.total
  } finally {
    loading.value = false
  }
}

function open(row) {
  if (row) {
    Object.assign(f, { ...row, status: row.status ? 1 : 0 })
  } else {
    Object.assign(f, { id: null, image: '', bgStyle: 'linear-gradient(90deg,#6366f1,#8b5cf6)', linkUrl: '', sort: 0, status: 1 })
  }
  vis.value = true
}

async function upImg({ file }) {
  const form = new FormData()
  form.append('file', file)
  const r = await adminApi.upload(form)
  f.image = r.data.url
  ElMessage.success('上传成功')
}

async function ok() {
  if (!f.image?.trim() && !f.bgStyle?.trim()) {
    ElMessage.warning('请上传轮播图片或填写渐变背景')
    return
  }
  if (f.id) await adminApi.bannerUp(f)
  else await adminApi.bannerAdd(f)
  vis.value = false
  ElMessage.success('已保存')
  load()
}

function remove(row) {
  ElMessageBox.confirm('确认删除该轮播?', { type: 'warning' })
    .then(() => adminApi.bannerDel(row.id))
    .then(() => {
      load()
      ElMessage.success('已删除')
    })
    .catch(() => {})
}

onMounted(load)
</script>
<style scoped>
.mt {
  margin-top: 12px;
  justify-content: flex-end;
}
.up {
  margin-top: 8px;
}
.preview-img-wrap,
.preview-bg {
  width: 160px;
  height: 72px;
  border-radius: 6px;
  overflow: hidden;
}
.preview-img {
  width: 160px;
  height: 72px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值