智约会议管理系统
智约会议管理系统
后台
项目搭建
1、导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sixkey</groupId>
<artifactId>meet-booking</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--mysql数据库驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.22</version>
</dependency>
<!--mybatis-plus依赖,已经包括了mybatis的依赖了-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!--mybatis-plus的模板引擎依赖,使用mybatis-plus必须要加这个依赖-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- 引入lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 引入springAop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 引入fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
<build>
<!-- 注:maven默认是不编译,因此加上如下resources才会生产对应的xml文件 目的:解决mybatis映射关系不对应问题 start =============== -->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</testResource>
</testResources>
<!-- 注:maven默认是不编译,因此加上如下resources才会生产对应的xml文件 目的:解决mybatis映射关系不对应问题 end =============== -->
<plugins>
<!-- maven打包插件 -> 将整个工程打成一个 fatjar -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 作用:项目打成jar,同时把本地jar包也引入进去 -->
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<!--添加配置跳过测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
<!-- 固定Jar包名字 -->
<finalName>meet-booking</finalName>
</build>
</project>
2、配置文件
server:
port: 8600
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
application:
name: meetBook
datasource:
url: jdbc:mysql://localhost:3306/数据库名?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai&tinyInt1isBit=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 数据库密码
type: com.alibaba.druid.pool.DruidDataSource
initial-size: 10
max-active: 150
min-idle: 10
max-wait: 5000
pool-prepared-statements: false
validation-query: SELECT 1
validation-query-timeout: 500
test-on-borrow: false
test-on-return: false
test-while-idle: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 30000
filters: stat,wall,log4j
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#==============================IP限流次数和时间配置==============================
IpLimit:
#请求次数
count: 10
#请求时间
time: 10
#==============================mybatis-plus配置==============================
mybatis-plus:
mapper-locations: classpath:/mapper/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: com.sixkey.entity
global-config:
# 数据库相关配置
db-config:
#主键类型 AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
id-type: AUTO
#字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
field-strategy: not_empty
#驼峰下划线转换
column-underline: true
db-type: mysql
logic-delete-field: is_delete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
#刷新mapper 调试神器
refresh: true
# 原生配置
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
主要模块
- 用户管理
- 公告管理
- 日志管理
- 会议室管理
- 预约管理
功能模块:
用户管理
1、新增用户出现报错
请求体返回时一直报错?原因是响应体属性无get方法
报错:Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation

2、特坑
进行用户数据分页请求时,后端数据一直没返回,但是用apifox测试时数据是完全返回的,浪费了我一个小时排坑
错误原因:原本apifox请求时的路径为:http://localhost:8600/user/list/1/10?name=&phone=&status= ,但是前端一直请求的路径为http://localhost:8600/user/list/1/10,本来我就想着先不带user的几个参数,恰好坑就在这里。必须要带上user的参数name=&phone=&status=,即使是空
解决:参数全部带齐:http://localhost:8600/user/list/1/10?name=&phone=&status=
3、前端捕获后端异常和超时异常
后端返回的异常前端一直无法正常捕获,最后给前端全局响应补上了一下代码
// request 拦截器
request.interceptors.response.use(
function (response) {
const res = response.data
const code = res.code
if (code === 403) {
ElMessage({
showClose: true,
message: res.message,
type: "error",
});
}
return res;
},
function (error) {
console.log(error);
let code = error.code;
let message = error.message;
//网络超时异常处理
if(code === 'ECONNABORTED' || message === "timeout of 10000ms exceeded" || message.includes("timeout")){
ElMessage({
showClose: true,
message: '请求超时,执行失败!',
type: "error",
});
}
return Promise.resolve(error);
}
);
日志管理
公告管理
预约管理
1、特坑:
新增会议室时一直报错,原来是数据库字段用了describe,这是mysql的关键字需要注意
2、使用定时任务修改会议主题
核心代码
package com.sixkey.task;
/**
* ClassName: UpdateMeetStatus
* Package: com.sixkey.task
* Description:
*
* @Author: @weixueshi
* @Create: 2023/10/11 - 12:48
* @Version: v1.0
*/
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.sixkey.constant.CommonConstant;
import com.sixkey.entity.Reservation;
import com.sixkey.service.IReservationService;
import com.sixkey.utils.JodaTimeUtils;
import com.sixkey.utils.TimeStringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 修改会议状态的定时任务
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class UpdateMeetStatusTask {
private final IReservationService reservationService;
/**
* 每分钟执行一次定时任务
*/
@Scheduled(cron = "0 */1 * * * ?")
public void UpdateMeetStatusTaskExecute() {
log.info("修改会议状态定时任务开始..........");
//把已结束的会议排除
QueryWrapper<Reservation> wrapper = new QueryWrapper<Reservation>().eq("status", CommonConstant.RESERVATION_STATUS_1).or().eq("status", CommonConstant.RESERVATION_STATUS_2);
List<Reservation> reservationList = reservationService.list(wrapper);
reservationList.stream().forEach(item -> {
//获取经处理过的字符串时间格式
String beginTime = TimeStringUtils.getBeginTime(item.getBeginTime());
String endTime = TimeStringUtils.getEndTime(item.getBeginTime());
//将字符串转为LocalDateTime
LocalDateTime beginLocalDateTime = LocalDateTime.parse(beginTime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));
LocalDateTime endLocalDateTime = LocalDateTime.parse(endTime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));
if(endLocalDateTime.isBefore(JodaTimeUtils.getCurrentDateTime())){
//如果当前时间在会议结束时间后
//将会议状态修改为已结束
item.setStatus(CommonConstant.RESERVATION_STATUS_3);
reservationService.updateById(item);
} else if (beginLocalDateTime.isBefore(JodaTimeUtils.getCurrentDateTime())) {
//如果当前时间在会议开始时间后
//将会议状态修改为进行中
item.setStatus(CommonConstant.RESERVATION_STATUS_2);
reservationService.updateById(item);
}
});
}
}
rabbitMQ异步通信
安装步骤:RabbitMQ消息队列 (sixkey-world.top)
注意:一定要开放15672和5672端口,不然无法访问
集成到项目中
导入依赖
<!--RabbitMQ依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
yml配置文件
spring:
rabbitmq:
host: 82.157.234.124
port: 5672
username: admin
password: 123
addresses: 82.157.234.124
publisher-confirm-type: CORRELATED #发布确认模式,消息是否被成功发送到交换机
publisher-returns: true
listener:
simple:
prefetch: 1
concurrency: 3
acknowledge-mode: manual #消费端手动确认
MQ配置类
- 消息转换器
/**
* MQ消息转换器 可以让MQ传对象之类
*/
@Configuration
public class RabbitMQConfig {
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
}
- 消息确认方法
@Component
@RequiredArgsConstructor
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
// 我们发送消息使用的是 private RabbitTemplate rabbitTemplate; 对象
// 如果不做设置的话 当前的rabbitTemplate 与当前的配置类没有任何关系!
private final RabbitTemplate rabbitTemplate;
// 设置 表示修饰一个非静态的void方法,在服务器加载Servlet的时候运行。并且只执行一次!
@PostConstruct
public void init(){
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setConfirmCallback(this);
}
/**
* 消息接收成功会走此方法
* 表示消息是否正确发送到了交换机上
* @param correlationData 消息的载体
* @param ack 判断是否发送到交换机上
* @param cause 原因
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息发送成功!");
}else {
System.out.println("消息发送失败!"+cause);
}
}
/**
* 消息如果没有正确发送到队列中,则会走这个方法!如果消息被正常处理,则这个方法不会走!
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息主体: " + new String(message.getBody()));
System.out.println("应答码: " + replyCode);
System.out.println("描述:" + replyText);
System.out.println("消息使用的交换器 exchange : " + exchange);
System.out.println("消息使用的路由键 routing : " + routingKey);
}
}
- MQ需要的交换机,队列,路由key常量定义
public class RabbitMQConstant {
/**短信队列*/
public final static String QUEUE_MEET_ROOM="meet_room";
/**通道*/
public final static String EXCHANGE_MEET_ROOM="meet_room.direct";
/**key*/
public final static String ROUTING_MEET_ROOM="meet_room";
}
MQ消息发送消息服务封装
/**
* 封装MQ发送消息方法
*/
@Service
@RequiredArgsConstructor
public class RabbitService {
private final RabbitTemplate rabbitTemplate;
/**
*
* @param exchange 交换机
* @param routingKey 路由
* @param message 消息
* @return
*/
public boolean sendMessage(String exchange, String routingKey, Object message){
rabbitTemplate.convertAndSend(exchange,routingKey,message);
return true;
}
}
MQ异步发送消息
@Slf4j
@Component
@RequiredArgsConstructor
public class UpdateMeetStatusTask {
private final RabbitService rabbitService;
public void UpdateMeetStatusTaskExecute() {
//当会议结束后通过rabbitMQ去修改会议所使用的会议室状态为未预约
log.info("MQ发送消息:{}",item.getRoomName()); rabbitService.sendMessage(RabbitMQConstant.EXCHANGE_MEET_ROOM,RabbitMQConstant.ROUTING_MEET_ROOM,item.getRoomName());
}
MQ接收消息
/**
* 接收MQ发送过来的消息
*/
@Component
@RequiredArgsConstructor
public class RabbitReceiver {
private final IRoomService roomService;
/**
* 接收消息会议结束信息同时修改会议室状态为未预约
* @param
* @param message
* @param channel
* @throws IOException
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = RabbitMQConstant.QUEUE_MEET_ROOM,durable = "true"),
exchange = @Exchange(value = RabbitMQConstant.EXCHANGE_MEET_ROOM),
key = {RabbitMQConstant.ROUTING_MEET_ROOM}
))
public void updateRoomStatus(String roomName, Message message, Channel channel) throws IOException {
if(null != roomName){
QueryWrapper<Room> wrapper = new QueryWrapper<Room>().eq("name", roomName);
Room room = roomService.getOne(wrapper);
room.setStatus(CommonConstant.ROOM_STATUS_NOT_RESERVATION);
roomService.updateById(room);
}
//手动确认消息
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
注意:MQ发送和接收消息时交换机和路由key值是一样的
异步发送短信
1、在异步方法上添加异步注解
2、自定义线程池
3、启动类开启异步注解
- 在异步方法上添加异步注解
/**
* 异步发送会议通知消息
*/
@Slf4j
@Service
public class AsyncServiceImpl implements IAsyncService {
/**
* 发送短信
* @param
* @param
*/
@Async("asyncExecutor") //指定自定义的线程池
public void sendSuccessMessage(String participants,String phone,String time){
try {
//将参会人号码提取出来依次发送信息
String[] split = participants.split(";");
for (int i = 0; i < split.length; i++) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", SendSmsConstant.ACCESS_KEY_ID, SendSmsConstant.ACCESS_KEY_SECRET);
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(split[i]);
/**
* 签名
*/
request.setSignName("文学博客");
/**
* 模板
*/
request.setTemplateCode("SMS_463677677");
request.setTemplateParam("{\"phone\":\""+phone+"\",\"time\":\""+time+"\"}");
client.getAcsResponse(request);
log.info("短信发送成功");
}
} catch (ServerException e) {
throw new RuntimeException(e);
} catch (ClientException e) {
throw new RuntimeException(e);
}
}
}
- 自定义线程池
/**
* 线程池配置
*/
@Configuration
public class PoolConfig {
@Bean
public ThreadPoolExecutor asyncExecutor(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
10,
10,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(30),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
return threadPoolExecutor;
}
}
- 启动类开启异步注解
@SpringBootApplication
//开启异步任务
@EnableAsync
public class MeetApplication
{
public static void main( String[] args ) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(MeetApplication.class, args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
System.out.println("\n----------------------------------------------------------\n\t" +
"会议室预约管理系统启动成功,并正在运行:\n\t" +
"Local: \t\thttp://localhost:" + port + "/\n\t" +
"External: \thttp://" + ip + ":" + port + "/\n\t" +
"----------------------------------------------------------");
}
}
定时修改任务
1、定时任务方法上添加注解
2、启动类添加定时注解
- 定时任务方法上添加注解
package com.sixkey.task;
/**
* ClassName: UpdateMeetStatus
* Package: com.sixkey.task
* Description:
*
* @Author: @weixueshi
* @Create: 2023/10/11 - 12:48
* @Version: v1.0
*/
/**
* 修改会议状态的定时任务
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class UpdateMeetStatusTask {
private final IReservationService reservationService;
private final RabbitService rabbitService;
/**
* 每分钟执行一次定时任务
*/
//定时任务注解
@Scheduled(cron = "0 */1 * * * ?")
public void UpdateMeetStatusTaskExecute() {
log.info("修改会议状态定时任务开始..........");
//把已结束的会议排除
QueryWrapper<Reservation> wrapper = new QueryWrapper<Reservation>().eq("status", CommonConstant.RESERVATION_STATUS_1).or().eq("status", CommonConstant.RESERVATION_STATUS_2);
List<Reservation> reservationList = reservationService.list(wrapper);
reservationList.stream().forEach(item -> {
//获取经处理过的字符串时间格式
String beginTime = TimeStringUtils.getBeginTime(item.getBeginTime());
String endTime = TimeStringUtils.getEndTime(item.getBeginTime());
//将字符串转为LocalDateTime
LocalDateTime beginLocalDateTime = LocalDateTime.parse(beginTime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));
LocalDateTime endLocalDateTime = LocalDateTime.parse(endTime, DateTimeFormat.forPattern("yyyy-MM-dd HH:mm"));
if(endLocalDateTime.isBefore(JodaTimeUtils.getCurrentDateTime())){
//如果当前时间在会议结束时间后
//将会议状态修改为已结束
item.setStatus(CommonConstant.RESERVATION_STATUS_3);
reservationService.updateById(item);
//当会议结束后通过rabbitMQ去修改会议所使用的会议室状态为未预约
log.info("MQ发送消息:{}",item.getRoomName());
rabbitService.sendMessage(RabbitMQConstant.EXCHANGE_MEET_ROOM,RabbitMQConstant.ROUTING_MEET_ROOM,item.getRoomName());
} else if (beginLocalDateTime.isBefore(JodaTimeUtils.getCurrentDateTime())) {
//如果当前时间在会议开始时间后
//将会议状态修改为进行中
item.setStatus(CommonConstant.RESERVATION_STATUS_2);
reservationService.updateById(item);
}
});
}
}
- 启动类添加定时注解
*
*/
@SpringBootApplication
//开启定时任务
@EnableScheduling
public class MeetApplication
{
public static void main( String[] args ) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(MeetApplication.class, args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
System.out.println("\n----------------------------------------------------------\n\t" +
"会议室预约管理系统启动成功,并正在运行:\n\t" +
"Local: \t\thttp://localhost:" + port + "/\n\t" +
"External: \thttp://" + ip + ":" + port + "/\n\t" +
"----------------------------------------------------------");
}
}
Vue3集成echarts
直接上一个例子即可
npm install echarts --save # 安装echarts
直接使用
<div>
<div style="font-size: 24px; color: #000000">会议统计</div>
<div>
<-- 定义一个id--></-->
<div id="reservation" style="width: 680px; height: 350px"></div>
</div>
</div>
引入
import * as echarts from "echarts";
<script setup>
import { onMounted } from "vue";
import * as echarts from "echarts";
onMounted(() => {
//一定要先加载,不然数据出不来
initReservation();
});
// 用户数据展示
function initReservation() {
var option = {
xAxis: {
type: "category",
data: ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
},
yAxis: {
type: "value",
},
series: [
{
data: [
120,
200,
150,
80,
70,
110,
130,
],
type: "bar",
},
],
};
var myChart = echarts.init(document.getElementById("reservation"));
myChart.setOption(option);
//随着屏幕大小调节图表
window.addEventListener("resize", () => {
myChart.resize();
});
}
</script>
使用pinia存储token
可以存储token,同样用户登录后返回的个人信息也可存储
安装pinia
npm install pinia
在main.js中引入
import { createPinia } from 'pinia'
const pinia = createPinia()
//挂载到全局
app.use(pinia)
具体使用:
-以存储token为例
在项目中新建stores目录,其下新建token.js
import { defineStore } from "pinia";
import { ref } from "vue";
export const useTokenStore = defineStore('token',()=>{
const token = ref('')
const setToken = (newToken)=>{
token.value = newToken;
}
const removeToken = ()=> {
token.value = ''
}
return {
token,setToken,removeToken
}
});
在具体组件中使用:
在Login.vue中使用
用户登录成功后存储后台返回的token值
导入useTokenStore
import { useTokenStore} from '@/stores/token';
//解构出tokenStore
const tokenStore = useTokenStore();
//登录成功后存储token值
tokenStore.setToken(res.data.token);
这里有一个bug,当刷新浏览器后数据就不在了,这是因为pinia是基于内存存储,我们需要使用pinia的插件persist来进行持久化存储
安装persist
npm install pinia-persistedstate-plugin
在main.js中引入并在pinia中使用插件
import { createPersistedState } from 'pinia-persistedstate-plugin'
const pinia = createPinia()
const persist = createPersistedState()
pinia.use(persist)
修改token.js文件
添加一个对象值
import { defineStore } from "pinia";
import { ref } from "vue";
export const useTokenStore = defineStore('token',()=>{
const token = ref('');
const setToken = (newToken)=>{
token.value = newToken;
}
const removeToken = ()=> {
token.value = ''
}
return {
token,setToken,removeToken
}
},{
persist: true //持久化存储
});
这里出现了一个小插曲,想在前置路由中通过判断token是否有值来进行路由跳转,原本是这样写的
import { createWebHistory, createRouter } from "vue-router";
import {LoginCheck} from '@/utils/loginCheck';
import { ElMessage } from "element-plus";
import { useTokenStore } from "@/stores/token";
//在这里就解构出对象,这种会报错说pinia是notActive的,就是没有被激活
const tokenStore = useTokenStore()
const history = createWebHistory();
// GOOD
router.beforeEach((to, from, next) => {
// const token = localStorage.getItem("token");
if(to.name === 'login'){
next()
}else if(tokenStore.token){
next()
}else{
ElMessage({
showClose: true,
message: "无权限,请重新登录!",
type: "error",
});
next({
name: 'login'
})
}
})
export default router;
改过之后是这样的
import { createWebHistory, createRouter } from "vue-router";
import {LoginCheck} from '@/utils/loginCheck';
import { ElMessage } from "element-plus";
import { useTokenStore } from "@/stores/token";
const history = createWebHistory();
// GOOD
router.beforeEach((to, from, next) => {
// const token = localStorage.getItem("token");
//在前置路由中解构对象就对了
const tokenStore = useTokenStore()
if(to.name === 'login'){
next()
}else if(tokenStore.token){
next()
}else{
ElMessage({
showClose: true,
message: "无权限,请重新登录!",
type: "error",
});
next({
name: 'login'
})
}
})
export default router;
vue3使用面包屑
<div style="margin-left: 10px;">
<el-breadcrumb :separator-icon="ArrowRight">
<-- -->route.matched是当前匹配到的路由</-->
<el-breadcrumb-item v-for="(item,index) in route.matched" :key="index"><span style="font-size: 15px; font-weight: bold;">{{ item.meta.title }}</span></el-breadcrumb-item>
</el-breadcrumb>
</div>
<script setup>
import { ArrowRight } from '@element-plus/icons-vue'
import { useRoute } from "vue-router";
const route = useRoute();
</script>
前台
会议室管理
- 获取所有会议室信息
- 根据id获取会议室信息
- crud会议室
- 用户预约会议室
- 用户取消会议室
- 管理员设置会议室容量
- 管理员设置预约时间
预约管理
- 用户预约会议室
- 选择开始和结束时间
- 查看预约状态(待确认、已确认、已完成)
预约功能
1、展示所有会议室信息,支持按条件筛选和排序。
2、会议室详情页:展示会议室的详细信息,包括预约情况。支持查看预约详情和进行预约和取消预约。
3、会议室分类:根据分类获取会议室
4、进行预约:选择开始和结束时间,查看预约状态(待确认、已确认、已完成)。
5、取消预约:用户可以在预约时间前取消预约。
6、查看会议室详情:用户可以查看会议室的详细信息,包括容量、可预约时间等。
7、日历视图:在会议室详情页提供一个日历视图,方便用户查看会议室的可预约时间段。
8、预订限制:为每个会议室设置预订限制,例如每天的最高预订数量、最长预订时间等。管理员可以在管理页面设置这些限制。
9、公告展示
我的功能
1、用户管理页:用户可以查看自己的预约记录,取消预约等操作
2、取消预约:用户可以在预约时间前取消预约。
3、查看预约记录:用户可以查看自己的预约记录,包括已确认和已完成的预约。
4、系统通知:当会议室预约状态发生变化(如预约成功、预约取消等)时,向相关用户发送通知。
5、用户评价:用户可以对使用过的会议室进行评价,提供反馈和建议,帮助其他用户更好地选择和使用会议室。
Uniapp搭建
1、问题合集
1、tabbar底部不显示原因
tabbar的list里面配置的页面顺序要和pages中配置的页面顺序要一致才能显示


2、Uniapp请求封装
1、设置请求基地址:base.js
2、封装请求和响应: request.js
3、应用在具体接口请求:index.js

1、base.js
//请求基地址
const BASE_URL = "http://localhost:8600"
module.exports = {
BASE_URL: BASE_URL
}
2、request.js
//const BASE_URL = `http://localhost:8600`;
import { BASE_URL} from './base.js';
export const request = (options) => {
return new Promise((resolve,reject) => {
uni.request({
url: BASE_URL + options.url,
method: options.method || 'GET',
//header: {token: uni.getStorageSync('user') ? uni.getStorageSync('user').token : ''},
data: options.data || {},
success: (res) => {
const data = res.data
//无权限
if(data.code === '401'){
uni.navigateTo({
url: '/pages/my/my'
})
} else if(data.code !== '200'){
uni.showToast({
icon: 'error',
title: data.message
})
}
resolve(data)
},
fail: (error) => {
uni.showToast({
icon: 'error',
title: '系统错误!'
})
reject(error)
}
})
})
}
3、index.js
import {request} from '@/utils/request.js'
export function getImage () { //登录
return request({
url:'/api/image',
method:'GET'
})
}
4、具体应用在index.vue中进行请求


3、setUp语法中使用uniapp的onLoad生命周期函数
//导入onLoad函数
import {
onLoad
} from "@dcloudio/uni-app";
onLoad((e) => {
console.log(e);
})
4、文件上传
<view @click="onChangeAvatar">
<uni-forms-item label="头像" required name="avatar">
<image style="width: 50px; height: 50px; border-radius: 50%;" mode="aspectFill"
:src="url"></image>
</uni-forms-item>
<text style="margin-left: 65px;font-size: 12px;">上传头像</text>
</view>
const url = ref('');
function onChangeAvatar() {
//调用小程序的接口
uni.chooseMedia({
count: 1,
mediaType: ['image'],
success(res) {
//获取本地路径
const {
tempFilePath
} = res.tempFiles[0];
//文件上传接口
uni.uploadFile({
//文件上传请求路径
url: 'http://localhost:8600/common/upload',
filePath: tempFilePath,
name: 'file',
success: (res) => {
if (res.statusCode === 200) {
const avatar = JSON.parse(res.data).data;
url.value = avatar;
userFormData.value.avatar = avatar;
}else{
uni.showToast({
title: "上传头像出现异常",
icon: 'none'
})
}
}
})
}
})
}
5、使用Pinia作数据状态管理
说明:Uniapp内置了Pinia,可以直接使用
具体步骤如下
- 在项目目录中新建stores,在此目录下创建一个js文件夹,具体使用,我这里命名为user.js用来存储用户登录后返回的用户信息
- 在main.js文件中引入
import { createSSRApp } from 'vue';
import * as Pinia from 'pinia';
export function createApp() {
const app = createSSRApp(App);
app.use(Pinia.createPinia());
return {
app,
Pinia, // 此处必须将 Pinia 返回
};
}
- user.js文件
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const userStore = defineStore('user', () => {
const userInfo = ref({});
return { userInfo };
});
- 在需要数据的页面引入即可:引入到login页面,保存用户登录后后台返回的用户信息,使用Pinia作全局状态管理,方便在任何一个组件中使用到用户信息。
import {
userStore
} from '@/stores/user.js';
const user = userStore();
function doLogin() {
valiForm.value.validate((valid) => {
if (!valid) {
//提交预约数据
login(userData.value).then(res => {
if (res.code === 200) {
//全局保存
user.userInfo.value = res.data.user;
}
}
})
}
})
};
- 在需要用到用户信息的组件中引入,然后直接取值使用即可
import {
userStore
} from '@/stores/user.js';
//引入Pinia中保存的user信息
const user = userStore();
//用户名
const name = ref('');
//头像
const avatar = ref('');
//从Pinia存储获取用户登录信息
function getUserInfo() {
name.value = user.userInfo.value.name;
avatar.value = user.userInfo.value.avatar;
};
项目部署
使用内网穿透工具sunny-Ngrok
网址:Sunny-Ngrok内网转发内网穿透 - 国内内网映射服务器
java程序运行在本地,将小程序代码打包上传进行一个体验版即可。