项目简介
本项目是一个基于Spring Boot + Vue.js的校园物业服务管理平台,旨在提供便捷的校园物业服务预约和管理功能。
技术栈
后端
- Spring Boot 2.7.x
- Spring Security
- MyBatis-Plus
- MySQL 8.0
- Redis
- JWT
前端
- Vue.js 3
- Element Plus
- Axios
- Vite
- Pinia
核心功能
-
用户管理
- 学生/教职工注册登录
- 角色权限控制
- 个人信息管理
-
报修服务
- 在线报修申请
- 维修进度追踪
- 维修评价反馈
-
物业公告
- 通知发布
- 公告管理
- 消息推送
-
设施预约
- 会议室预约
- 体育场地预约
- 预约记录查询
项目亮点
- 前后端分离架构
@Configuration
public class CorsConfig {@Beanpublic CorsFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();config.setAllowCredentials(true);config.addAllowedOriginPattern("*");config.addAllowedHeader("*");config.addAllowedMethod("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return new CorsFilter(source);}
}
- JWT认证
@Component
public class JwtUtil {@Value("${jwt.secret}")private String secret;public String generateToken(String username) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();}
}
- Redis缓存
@Configuration
@EnableCaching
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}
}
项目部署
- 采用Docker容器化部署
- Nginx反向代理
- Jenkins自动化部署
项目成果
- 显著提升校园物业服务效率
- 优化资源调配
- 提高用户满意度
- 实现数据可视化管理
技术难点解决
- 使用Redis解决高并发场景
- 采用分布式锁处理资源预约冲突
- 实现WebSocket推送实时消息
- 优化大数据量查询性能
总结与展望
本项目实现了校园物业服务的信息化、智能化管理,未来计划:
- 引入AI智能客服
- 开发移动端应用
- 接入物联网设备
- 扩展更多便民服务功能
校园物业服务平台功能详解
一、用户管理模块
1. 用户注册与登录
数据库设计
CREATE TABLE `user` (`id` bigint NOT NULL AUTO_INCREMENT,`username` varchar(50) NOT NULL COMMENT '用户名',`password` varchar(100) NOT NULL COMMENT '密码',`real_name` varchar(50) COMMENT '真实姓名',`user_type` tinyint NOT NULL COMMENT '用户类型:1学生 2教职工 3维修人员 4管理员',`student_id` varchar(20) COMMENT '学号/工号',`phone` varchar(11) COMMENT '手机号',`email` varchar(50) COMMENT '邮箱',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注册接口
@RestController
@RequestMapping("/api/user")
public class UserController {@PostMapping("/register")public Result register(@RequestBody UserRegisterDTO dto) {// 参数校验if (!validateUserInput(dto)) {return Result.error("参数不合法");}// 密码加密String encodedPassword = passwordEncoder.encode(dto.getPassword());// 保存用户信息User user = new User();BeanUtils.copyProperties(dto, user);user.setPassword(encodedPassword);userService.save(user);return Result.success();}
}
登录实现
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate JwtUtil jwtUtil;@Overridepublic LoginVO login(LoginDTO loginDTO) {// 验证用户名密码User user = userMapper.selectByUsername(loginDTO.getUsername());if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {throw new BusinessException("用户名或密码错误");}// 生成tokenString token = jwtUtil.generateToken(user.getUsername());// 存入RedisredisTemplate.opsForValue().set("token:" + token,user,24,TimeUnit.HOURS);return new LoginVO(token, user);}
}
2. 角色权限控制
角色表设计
CREATE TABLE `role` (`id` bigint NOT NULL AUTO_INCREMENT,`role_name` varchar(50) NOT NULL COMMENT '角色名称',`role_code` varchar(50) NOT NULL COMMENT '角色编码',PRIMARY KEY (`id`)
);CREATE TABLE `user_role` (`user_id` bigint NOT NULL,`role_id` bigint NOT NULL,PRIMARY KEY (`user_id`, `role_id`)
);
权限拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 获取tokenString token = request.getHeader("Authorization");if (StringUtils.isEmpty(token)) {throw new UnauthorizedException("未登录");}// 验证tokenString username = jwtUtil.validateToken(token);// 获取用户权限User user = (User) redisTemplate.opsForValue().get("token:" + token);List<String> permissions = roleService.getUserPermissions(user.getId());// 判断是否有权限RequiresPermissions annotation = ((HandlerMethod) handler).getMethodAnnotation(RequiresPermissions.class);if (annotation != null && !permissions.contains(annotation.value())) {throw new ForbiddenException("无权限访问");}return true;}
}
二、报修服务模块
1. 报修申请
报修表设计
CREATE TABLE `repair_order` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL COMMENT '申请人ID',`repair_type` tinyint NOT NULL COMMENT '报修类型:1水电 2家具 3空调 4其他',`location` varchar(100) NOT NULL COMMENT '报修地点',`description` varchar(500) NOT NULL COMMENT '问题描述',`images` varchar(500) COMMENT '图片地址,多个逗号分隔',`status` tinyint NOT NULL COMMENT '状态:0待处理 1已派单 2维修中 3已完成 4已评价',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
);
报修提交接口
@RestController
@RequestMapping("/api/repair")
public class RepairController {@PostMapping("/submit")public Result submit(@RequestBody RepairDTO dto) {// 上传图片List<String> imageUrls = uploadImages(dto.getImageFiles());// 创建工单RepairOrder order = new RepairOrder();BeanUtils.copyProperties(dto, order);order.setImages(String.join(",", imageUrls));order.setStatus(0);repairService.save(order);// 发送通知给管理员notifyAdmin(order);return Result.success();}
}
2. 维修进度追踪
进度更新接口
@Service
public class RepairServiceImpl implements RepairService {@Override@Transactionalpublic void updateProgress(RepairProgressDTO dto) {// 更新工单状态RepairOrder order = getById(dto.getOrderId());order.setStatus(dto.getStatus());updateById(order);// 记录处理日志RepairLog log = new RepairLog();log.setOrderId(dto.getOrderId());log.setOperator(dto.getOperator());log.setContent(dto.getContent());repairLogService.save(log);// 发送进度通知if (dto.getStatus() == 2) {// 维修中状态,通知用户预计到达时间notifyUser(order.getUserId(), "维修人员正在前往处理,预计" + dto.getEstimatedTime() + "到达");} else if (dto.getStatus() == 3) {// 完成状态,通知用户评价notifyUser(order.getUserId(), "维修已完成,请及时评价");}}
}
3. 维修评价
评价表设计
CREATE TABLE `repair_rating` (`id` bigint NOT NULL AUTO_INCREMENT,`order_id` bigint NOT NULL COMMENT '工单ID',`user_id` bigint NOT NULL COMMENT '评价人ID',`rating` tinyint NOT NULL COMMENT '评分:1-5',`comment` varchar(500) COMMENT '评价内容',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),UNIQUE KEY `uk_order_id` (`order_id`)
);
评价提交接口
@RestController
@RequestMapping("/api/repair")
public class RepairController {@PostMapping("/rate")public Result rate(@RequestBody RatingDTO dto) {// 检查工单状态RepairOrder order = repairService.getById(dto.getOrderId());if (order.getStatus() != 3) {return Result.error("工单状态不正确");}// 保存评价RepairRating rating = new RepairRating();BeanUtils.copyProperties(dto, rating);ratingService.save(rating);// 更新工单状态为已评价order.setStatus(4);repairService.updateById(order);// 统计维修人员评分updateRepairmanRating(order.getRepairmanId());return Result.success();}
}
校园物业服务平台功能详解(续)
三、物业公告模块
1. 通知公告表设计
CREATE TABLE `notice` (`id` bigint NOT NULL AUTO_INCREMENT,`title` varchar(100) NOT NULL COMMENT '标题',`content` text NOT NULL COMMENT '内容',`type` tinyint NOT NULL COMMENT '类型:1通知 2公告 3新闻',`status` tinyint NOT NULL COMMENT '状态:0草稿 1已发布 2已下架',`publisher_id` bigint NOT NULL COMMENT '发布人ID',`publish_time` datetime COMMENT '发布时间',`top` tinyint DEFAULT 0 COMMENT '是否置顶:0否 1是',`view_count` int DEFAULT 0 COMMENT '浏览次数',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 公告管理接口
@RestController
@RequestMapping("/api/notice")
@RequiresPermissions("notice")
public class NoticeController {@PostMapping("/publish")public Result publish(@RequestBody NoticeDTO dto) {Notice notice = new Notice();BeanUtils.copyProperties(dto, notice);notice.setStatus(1);notice.setPublishTime(new Date());notice.setPublisherId(getCurrentUserId());noticeService.save(notice);// 发送消息推送if (dto.isNeedPush()) {pushNoticeToUsers(notice);}return Result.success();}@PostMapping("/top/{id}")public Result setTop(@PathVariable Long id, @RequestParam Boolean top) {noticeService.update().set("top", top).eq("id", id).update();return Result.success();}
}
3. WebSocket消息推送
@ServerEndpoint("/websocket/{userId}")
@Component
public class WebSocketServer {private static Map<String, Session> sessionMap = new ConcurrentHashMap<>();@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {sessionMap.put(userId, session);}@OnClosepublic void onClose(@PathParam("userId") String userId) {sessionMap.remove(userId);}public void pushMessage(String userId, String message) {Session session = sessionMap.get(userId);if (session != null) {try {session.getBasicRemote().sendText(message);} catch (IOException e) {log.error("消息推送失败", e);}}}
}
四、设施预约模块
1. 设施和预约表设计
-- 设施表
CREATE TABLE `facility` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(50) NOT NULL COMMENT '名称',`type` tinyint NOT NULL COMMENT '类型:1会议室 2体育场地',`location` varchar(100) COMMENT '位置',`capacity` int COMMENT '容量',`description` varchar(500) COMMENT '描述',`status` tinyint NOT NULL COMMENT '状态:0停用 1启用',PRIMARY KEY (`id`)
);-- 预约表
CREATE TABLE `reservation` (`id` bigint NOT NULL AUTO_INCREMENT,`facility_id` bigint NOT NULL COMMENT '设施ID',`user_id` bigint NOT NULL COMMENT '预约人ID',`date` date NOT NULL COMMENT '预约日期',`start_time` time NOT NULL COMMENT '开始时间',`end_time` time NOT NULL COMMENT '结束时间',`purpose` varchar(200) COMMENT '用途',`status` tinyint NOT NULL COMMENT '状态:0待审核 1已通过 2已拒绝 3已取消',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_facility_date` (`facility_id`, `date`)
);
2. 预约服务实现
@Service
public class ReservationServiceImpl implements ReservationService {@Autowiredprivate RedisTemplate redisTemplate;@Override@Transactionalpublic Result reserve(ReservationDTO dto) {// 检查设施是否可用Facility facility = facilityService.getById(dto.getFacilityId());if (facility.getStatus() != 1) {return Result.error("设施不可用");}// 使用分布式锁防止并发预约String lockKey = "reservation:lock:" + dto.getFacilityId() + ":" + dto.getDate();boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);if (!locked) {return Result.error("操作太频繁,请稍后重试");}try {// 检查时间段是否已被预约boolean timeConflict = checkTimeConflict(dto);if (timeConflict) {return Result.error("该时间段已被预约");}// 创建预约记录Reservation reservation = new Reservation();BeanUtils.copyProperties(dto, reservation);reservation.setUserId(getCurrentUserId());reservation.setStatus(0);save(reservation);// 发送审核通知notifyAdmin(reservation);return Result.success();} finally {redisTemplate.delete(lockKey);}}private boolean checkTimeConflict(ReservationDTO dto) {return baseMapper.selectCount(new QueryWrapper<Reservation>().eq("facility_id", dto.getFacilityId()).eq("date", dto.getDate()).eq("status", 1).and(w -> w.between("start_time", dto.getStartTime(), dto.getEndTime()).or().between("end_time", dto.getStartTime(), dto.getEndTime()))) > 0;}
}
3. 预约记录查询
@RestController
@RequestMapping("/api/reservation")
public class ReservationController {@GetMapping("/list")public Result list(ReservationQueryDTO query) {Page<Reservation> page = new Page<>(query.getPage(), query.getSize());// 构建查询条件QueryWrapper<Reservation> wrapper = new QueryWrapper<Reservation>().eq(query.getFacilityId() != null, "facility_id", query.getFacilityId()).eq(query.getStatus() != null, "status", query.getStatus()).ge(query.getStartDate() != null, "date", query.getStartDate()).le(query.getEndDate() != null, "date", query.getEndDate()).orderByDesc("create_time");// 如果不是管理员,只能查看自己的预约if (!isAdmin()) {wrapper.eq("user_id", getCurrentUserId());}// 分页查询Page<ReservationVO> result = reservationService.page(page, wrapper).convert(reservation -> {ReservationVO vo = new ReservationVO();BeanUtils.copyProperties(reservation, vo);// 设置关联信息vo.setFacilityName(facilityService.getById(reservation.getFacilityId()).getName());vo.setUserName(userService.getById(reservation.getUserId()).getRealName());return vo;});return Result.success(result);}@PostMapping("/cancel/{id}")public Result cancel(@PathVariable Long id) {Reservation reservation = reservationService.getById(id);// 检查是否可以取消if (reservation.getStatus() != 0 && reservation.getStatus() != 1) {return Result.error("当前状态不可取消");}// 检查权限if (!isAdmin() && !reservation.getUserId().equals(getCurrentUserId())) {return Result.error("无权操作");}// 更新状态reservation.setStatus(3);reservationService.updateById(reservation);return Result.success();}
}
4. 预约统计分析
@Service
public class ReservationStatServiceImpl implements ReservationStatService {@Overridepublic Map<String, Object> getStatistics(String startDate, String endDate) {Map<String, Object> result = new HashMap<>();// 按设施类型统计预约次数List<Map<String, Object>> typeStats = baseMapper.selectMaps(new QueryWrapper<Reservation>().select("f.type", "count(*) as count").leftJoin("facility f", "facility_id = f.id").between("date", startDate, endDate).eq("status", 1).groupBy("f.type"));result.put("typeStats", typeStats);// 按时段统计使用率List<Map<String, Object>> timeStats = baseMapper.selectMaps(new QueryWrapper<Reservation>().select("DATE_FORMAT(start_time, '%H:00') as time_slot", "count(*) as count").between("date", startDate, endDate).eq("status", 1).groupBy("time_slot").orderByAsc("time_slot"));result.put("timeStats", timeStats);return result;}
}
WebSocket客服对话功能实现
一、数据库设计
1. 会话和消息表
-- 会话表
CREATE TABLE `chat_session` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL COMMENT '用户ID',`customer_service_id` bigint COMMENT '客服ID',`status` tinyint NOT NULL COMMENT '状态:0等待中 1进行中 2已结束',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,`end_time` datetime COMMENT '结束时间',PRIMARY KEY (`id`),KEY `idx_user_id` (`user_id`),KEY `idx_cs_id` (`customer_service_id`)
);-- 消息表
CREATE TABLE `chat_message` (`id` bigint NOT NULL AUTO_INCREMENT,`session_id` bigint NOT NULL COMMENT '会话ID',`sender_id` bigint NOT NULL COMMENT '发送者ID',`sender_type` tinyint NOT NULL COMMENT '发送者类型:1用户 2客服',`content` text NOT NULL COMMENT '消息内容',`msg_type` tinyint NOT NULL COMMENT '消息类型:1文本 2图片 3文件',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`),KEY `idx_session_id` (`session_id`)
);
二、WebSocket配置
1. WebSocket配置类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate ChatWebSocketHandler chatHandler;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(chatHandler, "/ws/chat").setAllowedOrigins("*").addInterceptors(new ChatHandshakeInterceptor());}
}
2. WebSocket拦截器
public class ChatHandshakeInterceptor implements HandshakeInterceptor {@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {// 获取token,验证用户身份String token = request.getHeaders().getFirst("Authorization");String userId = JwtUtil.getUserId(token);if (StringUtils.isEmpty(userId)) {return false;}// 将用户信息存入attributesattributes.put("userId", userId);return true;}@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception exception) {}
}
三、WebSocket处理器
1. 消息处理器
@Component
@Slf4j
public class ChatWebSocketHandler extends TextWebSocketHandler {@Autowiredprivate ChatSessionService chatSessionService;@Autowiredprivate ChatMessageService chatMessageService;// 保存所有客户端连接private static final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>();private static final Map<String, WebSocketSession> customerServiceSessions = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) {String userId = getUserId(session);String userType = getUserType(session);if ("USER".equals(userType)) {userSessions.put(userId, session);// 为用户分配客服assignCustomerService(userId);} else if ("CUSTOMER_SERVICE".equals(userType)) {customerServiceSessions.put(userId, session);}}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) {try {// 解析消息ChatMessageDTO messageDTO = JSON.parseObject(message.getPayload(), ChatMessageDTO.class);// 保存消息到数据库ChatMessage chatMessage = saveChatMessage(messageDTO);// 转发消息给接收方forwardMessage(chatMessage);} catch (Exception e) {log.error("处理消息失败", e);}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) {String userId = getUserId(session);String userType = getUserType(session);if ("USER".equals(userType)) {userSessions.remove(userId);// 结束会话endChatSession(userId);} else if ("CUSTOMER_SERVICE".equals(userType)) {customerServiceSessions.remove(userId);// 重新分配该客服的会话reassignSessions(userId);}}private void assignCustomerService(String userId) {// 查找空闲的客服String csId = findAvailableCustomerService();if (csId != null) {// 创建会话ChatSession session = chatSessionService.createSession(userId, csId);// 通知双方会话建立notifySessionCreated(session);} else {// 没有可用客服,将用户加入等待队列addToWaitingQueue(userId);}}private void forwardMessage(ChatMessage message) throws IOException {String receiverId = message.getSenderType() == 1 ? message.getSession().getCustomerServiceId().toString() :message.getSession().getUserId().toString();WebSocketSession receiverSession = message.getSenderType() == 1 ?customerServiceSessions.get(receiverId) :userSessions.get(receiverId);if (receiverSession != null && receiverSession.isOpen()) {receiverSession.sendMessage(new TextMessage(JSON.toJSONString(message)));}}
}
四、客服服务实现
1. 会话服务
@Service
public class ChatSessionServiceImpl implements ChatSessionService {@Override@Transactionalpublic ChatSession createSession(String userId, String csId) {ChatSession session = new ChatSession();session.setUserId(Long.valueOf(userId));session.setCustomerServiceId(Long.valueOf(csId));session.setStatus(1);save(session);// 创建欢迎消息ChatMessage welcomeMsg = new ChatMessage();welcomeMsg.setSessionId(session.getId());welcomeMsg.setSenderId(Long.valueOf(csId));welcomeMsg.setSenderType(2);welcomeMsg.setContent("您好,很高兴为您服务!");welcomeMsg.setMsgType(1);chatMessageService.save(welcomeMsg);return session;}@Overridepublic void endSession(Long sessionId) {update().set("status", 2).set("end_time", new Date()).eq("id", sessionId).update();}@Overridepublic List<ChatSessionVO> getCustomerServiceSessions(Long csId) {return baseMapper.selectSessionList(new QueryWrapper<ChatSession>().eq("customer_service_id", csId).eq("status", 1).orderByDesc("create_time"));}
}
2. 消息服务
@Service
public class ChatMessageServiceImpl implements ChatMessageService {@Overridepublic List<ChatMessageVO> getSessionMessages(Long sessionId) {return baseMapper.selectList(new QueryWrapper<ChatMessage>().eq("session_id", sessionId).orderByAsc("create_time")).stream().map(this::convertToVO).collect(Collectors.toList());}@Overridepublic void saveMessage(ChatMessageDTO dto) {ChatMessage message = new ChatMessage();BeanUtils.copyProperties(dto, message);// 处理特殊消息类型if (message.getMsgType() == 2) {// 图片消息,保存图片String imageUrl = uploadImage(dto.getImageData());message.setContent(imageUrl);} else if (message.getMsgType() == 3) {// 文件消息,保存文件String fileUrl = uploadFile(dto.getFileData());message.setContent(fileUrl);}save(message);}
}
五、前端实现
1. WebSocket连接管理
class ChatWebSocket {constructor() {this.ws = null;this.messageCallbacks = [];}connect() {const token = localStorage.getItem('token');this.ws = new WebSocket(`ws://localhost:8080/ws/chat`);this.ws.onopen = () => {console.log('WebSocket连接成功');// 发送认证信息this.ws.send(JSON.stringify({type: 'AUTH',token: token}));};this.ws.onmessage = (event) => {const message = JSON.parse(event.data);this.messageCallbacks.forEach(callback => callback(message));};this.ws.onclose = () => {console.log('WebSocket连接关闭');// 尝试重连setTimeout(() => this.connect(), 3000);};}sendMessage(message) {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify(message));}}onMessage(callback) {this.messageCallbacks.push(callback);}
}export default new ChatWebSocket();
2. 聊天组件
<template><div class="chat-window"><!-- 消息列表 --><div class="message-list" ref="messageList"><div v-for="msg in messages" :key="msg.id" :class="['message', msg.senderType === 1 ? 'message-right' : 'message-left']"><div class="avatar"><img :src="msg.senderType === 1 ? userAvatar : csAvatar"></div><div class="content"><template v-if="msg.msgType === 1">{{ msg.content }}</template><template v-else-if="msg.msgType === 2"><img :src="msg.content" class="message-image"></template><template v-else-if="msg.msgType === 3"><div class="file-message" @click="downloadFile(msg.content)"><i class="el-icon-document"></i><span>{{ getFileName(msg.content) }}</span></div></template></div></div></div><!-- 输入区域 --><div class="input-area"><div class="toolbar"><el-uploadaction="/api/chat/upload":show-file-list="false":before-upload="beforeImageUpload"><i class="el-icon-picture"></i></el-upload><el-uploadaction="/api/chat/upload":show-file-list="false":before-upload="beforeFileUpload"><i class="el-icon-folder"></i></el-upload></div><el-inputv-model="inputContent"type="textarea":rows="3"placeholder="请输入消息"@keyup.enter.native="sendMessage"></el-input><el-button type="primary" @click="sendMessage">发送</el-button></div></div>
</template><script>
import chatWebSocket from '@/utils/websocket';export default {data() {return {messages: [],inputContent: '',sessionId: null};},created() {chatWebSocket.connect();chatWebSocket.onMessage(this.handleMessage);},methods: {handleMessage(message) {this.messages.push(message);this.$nextTick(() => {this.scrollToBottom();});},sendMessage() {if (!this.inputContent.trim()) return;const message = {sessionId: this.sessionId,content: this.inputContent,msgType: 1,senderType: 1};chatWebSocket.sendMessage(message);this.inputContent = '';},beforeImageUpload(file) {const isImage = file.type.startsWith('image/');if (!isImage) {this.$message.error('只能上传图片文件!');return false;}const reader = new FileReader();reader.readAsDataURL(file);reader.onload = () => {const message = {sessionId: this.sessionId,msgType: 2,senderType: 1,imageData: reader.result};chatWebSocket.sendMessage(message);};return false;},scrollToBottom() {const messageList = this.$refs.messageList;messageList.scrollTop = messageList.scrollHeight;}}
};
</script><style scoped>
.chat-window {height: 600px;display: flex;flex-direction: column;
}.message-list {flex: 1;overflow-y: auto;padding: 20px;
}.message {display: flex;margin-bottom: 20px;
}.message-right {flex-direction: row-reverse;
}/* 其他样式省略 */
</style>