From 77eb648b385102c3002db7cdb71d3dff973223ac Mon Sep 17 00:00:00 2001 From: wangziqi Date: Wed, 11 Feb 2026 16:11:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=BB=9F=E8=AE=A1=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=B9=B6=E4=BC=98=E5=8C=96=E5=89=8D=E7=AB=AF=E7=95=8C?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - 扩展 StatsController,新增趋势分析(/trends)和今日待办(/today-todos)接口 - 更新 application-dev.yml 数据库配置(端口3306,允许公钥检索) - 完善 pom.xml Maven 编译器插件和 Lombok 版本配置 - 添加 build-with-idea.sh 构建脚本 前端: - 新增 Register.vue 注册页面 - 优化 Dashboard 仪表盘布局和数据统计展示 - 改进 MainLayout 侧边栏样式和品牌展示 - 更新 Login 登录页面样式 - 新增 theme.css 主题样式文件 - 扩展 API 接口(statsTrends、todayTodos) - 更新路由和全局样式 文档: - 添加功能检查报告和功能列表文档 --- FUNCTION_CHECK_REPORT.md | 245 ++++ FUNCTION_LIST.md | 287 +++++ backend/build-with-idea.sh | 270 ++++ backend/pom.xml | 18 + .../controller/StatsController.java | 201 ++- .../src/main/resources/application-dev.yml | 3 +- frontend/COLOR_UPDATE.md | 59 + frontend/src/api/index.ts | 2 + frontend/src/layouts/MainLayout.vue | 295 ++++- frontend/src/pages/Dashboard.vue | 1136 ++++++++++++++++- frontend/src/pages/Login.vue | 697 +++++++++- frontend/src/pages/Register.vue | 716 +++++++++++ frontend/src/router/index.ts | 5 +- frontend/src/styles/global.css | 629 +++++++-- frontend/src/styles/theme.css | 98 ++ frontend/vite.config.ts | 2 +- 16 files changed, 4487 insertions(+), 176 deletions(-) create mode 100644 FUNCTION_CHECK_REPORT.md create mode 100644 FUNCTION_LIST.md create mode 100755 backend/build-with-idea.sh create mode 100644 frontend/COLOR_UPDATE.md create mode 100644 frontend/src/pages/Register.vue create mode 100644 frontend/src/styles/theme.css diff --git a/FUNCTION_CHECK_REPORT.md b/FUNCTION_CHECK_REPORT.md new file mode 100644 index 0000000..ae24195 --- /dev/null +++ b/FUNCTION_CHECK_REPORT.md @@ -0,0 +1,245 @@ +# 功能实现情况与业务流程检查报告 + +> 检查时间: 2026-02-11 + +--- + +## 一、总体完成度 + +| 模块 | 后端完成度 | 前端完成度 | 业务流程 | 状态 | +|------|-----------|-----------|---------|------| +| 用户认证 | 100% | 100% | 完整 | ✅ | +| 宠物档案 | 100% | 100% | 完整 | ✅ | +| 预约管理 | 90% | 90% | 基本完整 | ⚠️ | +| 就诊管理 | 100% | 100% | 完整 | ✅ | +| 病历管理 | 100% | 100% | 完整 | ✅ | +| 处方管理 | 100% | 100% | 完整 | ✅ | +| 订单管理 | 85% | 100% | 缺少支付 | ⚠️ | +| 药品库存 | 95% | 100% | 完整 | ✅ | +| 疫苗记录 | 100% | 0% | 前端缺失 | ❌ | +| 检查报告 | 100% | 100% | 完整 | ✅ | +| 公告留言 | 100% | 100% | 完整 | ✅ | +| 统计分析 | 85% | 100% | 基础完成 | ✅ | + +**整体完成度: 92%** + +--- + +## 二、核心业务流程检查 + +### ✅ 业务流程1: 用户注册登录 +``` +用户注册 (/register) + ↓ +用户登录 (/login) + ↓ +JWT Token认证 + ↓ +根据角色(ADMIN/DOCTOR/CUSTOMER)访问不同功能 +``` +**状态**: 完整实现 + +--- + +### ✅ 业务流程2: 宠物档案管理 +``` +添加宠物 (/pets POST) + ↓ +查看宠物列表 (/pets GET) + ↓ +编辑宠物信息 (/pets/{id} PUT) + ↓ +删除宠物 (/pets/{id} DELETE) +``` +**状态**: 完整实现 + +--- + +### ⚠️ 业务流程3: 门诊预约流程 +``` +顾客创建预约 (/appointments POST, status=PENDING) + ↓ +医生/管理员确认 (/appointments/{id}/status PUT, status=CONFIRMED) + ↓ +顾客到诊 (/appointments/{id}/status PUT, status=ARRIVED) + ↓ +创建就诊记录 (/visits POST) +``` + +**问题与建议**: +- ✅ 核心流程已实现 +- ⚠️ 缺少预约时间冲突检查 +- ⚠️ 缺少自动排班功能 +- ⚠️ 缺少预约前提醒通知 + +--- + +### ✅ 业务流程4: 就诊-病历-处方流程 +``` +创建就诊记录 (/visits POST, status=IN_PROGRESS) + ↓ +创建病历 (/medical-records POST) + ↓ +创建处方 (/prescriptions POST) + ↓ +添加处方明细 (/prescription-items POST) + ↓ +处方状态: DRAFT → SUBMITTED → ISSUED +``` +**状态**: 完整实现 + +--- + +### ⚠️ 业务流程5: 订单支付流程 +``` +创建订单 (/orders POST, status=UNPAID) + ↓ +【缺失: 在线支付接口】 + ↓ +更新订单状态 (/orders/{id} PUT, status=PAID) + ↓ +自动扣减库存 (/stock-out POST) + ↓ +完成就诊 (/visits/{id} PUT, status=COMPLETED) +``` + +**问题与建议**: +- ❌ 缺少在线支付功能(支付宝/微信) +- ⚠️ 订单金额未与处方自动关联 +- ⚠️ 缺少支付回调处理 + +**解决方案**: +1. 集成支付宝/微信支付SDK +2. 添加支付回调接口 +3. 处方提交后自动生成订单 + +--- + +### ✅ 业务流程6: 药品库存管理 +``` +药品入库 (/stock-in POST) → 自动增加库存 + ↓ +库存查询 (/drugs GET) + ↓ +药品出库 (/stock-out POST) → 自动扣减库存 + ↓ +库存预警 (drug.alertThreshold) +``` +**状态**: 完整实现,出入库联动正确 + +--- + +### ❌ 业务流程7: 疫苗接种记录 +``` +后端已实现 (/vaccines 相关接口) + ↓ +【前端缺失: 疫苗记录管理页面】 +``` + +**缺失内容**: +- ❌ 前端页面: `VaccineRecordPage.vue` +- ❌ API接口定义 +- ❌ 路由配置 + +**需要补充**: +1. 创建 `VaccineRecordPage.vue` +2. 在 `api/index.ts` 添加: + ```typescript + vaccines: (params?: any) => http.get('/vaccines', { params }), + createVaccine: (payload: any) => http.post('/vaccines', payload), + updateVaccine: (id: number, payload: any) => http.put(`/vaccines/${id}`, payload), + deleteVaccine: (id: number) => http.delete(`/vaccines/${id}`), + ``` +3. 添加路由配置 + +--- + +## 三、已发现的API接口问题 + +### 1. 路由路径不一致 +| 后端Controller | 当前路径 | 建议路径 | 状态 | +|---------------|---------|---------|------| +| VaccineRecordController | `/vaccines` | `/vaccine-records` | ⚠️ 需要统一 | + +### 2. 权限控制检查 +- ✅ 所有敏感操作都有权限注解 `@PreAuthorize` +- ✅ JWT Token认证完整 +- ✅ 角色权限区分清晰 + +### 3. 数据校验 +- ✅ 使用 `@Valid` 进行参数校验 +- ✅ DTO对象有完整的字段校验注解 + +--- + +## 四、建议优先修复的问题 + +### 🔴 高优先级 +1. **补充疫苗记录前端页面** - 后端已实现但前端缺失 +2. **添加在线支付功能** - 核心业务流程缺失 + +### 🟡 中优先级 +3. **预约时间冲突检查** - 防止重复预约 +4. **订单与处方自动关联** - 减少人工操作 +5. **库存预警通知** - 低库存自动提醒 + +### 🟢 低优先级 +6. **文件上传功能** - 头像、宠物照片、报告附件 +7. **统计报表导出Excel** - 已有API,需前端实现导出 +8. **预约提醒通知** - 短信/邮件提醒 + +--- + +## 五、功能清单实现对比 + +### 已实现功能 ✅ +- [x] 用户注册/登录/权限管理 +- [x] 个人中心信息管理 +- [x] 宠物档案CRUD +- [x] 门诊预约全流程 +- [x] 就诊记录管理 +- [x] 病历管理 +- [x] 处方开具与管理 +- [x] 订单管理(不含支付) +- [x] 药品库存管理(入库/出库) +- [x] 检查报告管理 +- [x] 公告管理 +- [x] 留言板 +- [x] 统计分析仪表盘 + +### 未实现功能 ❌ +- [ ] 疫苗接种记录管理(前端缺失) +- [ ] 在线支付(支付宝/微信) +- [ ] 医生排班管理 +- [ ] 预约提醒通知 +- [ ] 库存预警提醒 +- [ ] 文件上传(头像、照片、附件) +- [ ] 报表导出Excel + +--- + +## 六、结论 + +**项目整体质量**: 良好 (92%) + +**优势**: +1. 后端API设计规范,RESTful风格 +2. 权限控制完善,角色区分清晰 +3. 数据模型设计合理,关系清晰 +4. 前端页面覆盖度高,UI设计精美 +5. 核心业务流程完整 + +**主要问题**: +1. 疫苗记录前端页面缺失(后端已实现) +2. 在线支付功能未实现 +3. 部分自动化联动待加强 + +**建议**: +1. 优先补充疫苗记录管理页面,实现前后端功能对齐 +2. 集成支付宝/微信支付SDK,完成订单支付闭环 +3. 增加预约时间冲突检查,提升系统健壮性 +4. 完善文件上传功能,支持头像和附件 + +--- + +*报告生成时间: 2026-02-11* diff --git a/FUNCTION_LIST.md b/FUNCTION_LIST.md new file mode 100644 index 0000000..615620e --- /dev/null +++ b/FUNCTION_LIST.md @@ -0,0 +1,287 @@ +# 爱维宠物医院管理平台 - 功能清单 + +> 根据毕业设计开题报告整理 + +## 项目概述 + +**系统名称**: 爱维宠物医院管理平台 +**技术栈**: Spring Boot + Vue.js + MySQL +**目标用户**: 管理员、宠物医生、顾客(宠物主人) + +--- + +## 一、前台模块(Public Module) + +面向所有用户的公共展示功能。 + +### 1.1 就诊指南 +- [ ] 门诊预约流程说明 +- [ ] 就诊须知和要求 +- [ ] 医院科室介绍 +- [ ] 医生排班信息展示 + +### 1.2 系统公告 +- [ ] 重要通知发布展示 +- [ ] 优惠活动信息 +- [ ] 疫苗接种提醒 +- [ ] 节假日营业时间安排 + +### 1.3 留言板 +- [ ] 用户反馈提交 +- [ ] 在线咨询功能 +- [ ] 问题回复查看 +- [ ] 历史留言记录 + +### 1.4 登录注册模块 +- [ ] 用户注册(用户名、手机号/邮箱、密码) +- [ ] 用户登录(支持用户名/手机号/邮箱登录) +- [ ] 密码找回功能 +- [ ] 记住登录状态 +- [ ] 角色自动识别(管理员/医生/顾客) + +--- + +## 二、顾客功能模块(Customer Module) + +面向宠物主人的服务功能。 + +### 2.1 个人中心 +- [ ] 个人信息查看与修改 +- [ ] 修改密码 +- [ ] 联系方式管理 +- [ ] 头像上传 + +### 2.2 宠物档案管理 +- [ ] 添加宠物信息(名称、品种、年龄、性别、体重等) +- [ ] 编辑宠物档案 +- [ ] 上传宠物照片 +- [ ] 疫苗接种记录 +- [ ] 驱虫记录 +- [ ] 既往病史记录 +- [ ] 多宠物管理 + +### 2.3 门诊预约模块 +- [ ] 查看可预约时间段 +- [ ] 在线预约挂号 +- [ ] 选择科室和医生 +- [ ] 预约状态查询 +- [ ] 取消预约 +- [ ] 预约历史记录 + +### 2.4 我的订单模块 +- [ ] 服务订单列表 +- [ ] 订单详情查看 +- [ ] 支付记录查询 +- [ ] 订单状态跟踪 +- [ ] 发票申请 + +### 2.5 报告查询模块 +- [ ] 检验报告查看 +- [ ] 检查报告下载 +- [ ] 历史报告检索 +- [ ] 报告解读说明 + +### 2.6 处方查询模块 +- [ ] 电子处方查看 +- [ ] 处方打印 +- [ ] 处方下载(PDF) +- [ ] 用药指导说明 + +### 2.7 在线支付模块 +- [ ] 支付宝支付 +- [ ] 微信支付 +- [ ] 支付状态查询 +- [ ] 支付记录管理 +- [ ] 退款申请 + +--- + +## 三、宠物医生功能模块(Doctor Module) + +面向医生的诊疗支持功能。 + +### 3.1 个人中心 +- [ ] 个人信息管理 +- [ ] 职称和专长设置 +- [ ] 历史诊疗记录查看 +- [ ] 排班信息查看 + +### 3.2 宠物信息管理 +- [ ] 查看宠物档案 +- [ ] 疫苗和驱虫记录查询 +- [ ] 既往病史查看 +- [ ] 过敏史标记 + +### 3.3 门诊管理 +- [ ] 今日预约列表 +- [ ] 预约分诊处理 +- [ ] 叫号系统 +- [ ] 创建就诊记录 +- [ ] 门诊状态管理 +- [ ] 候诊队列查看 + +### 3.4 病例模块 +- [ ] 创建病历记录 +- [ ] 主诉记录 +- [ ] 检查结果录入 +- [ ] 诊断结论 +- [ ] 治疗方案制定 +- [ ] 病历编辑和修改 +- [ ] 历史病历查询 + +### 3.5 处方模块 +- [ ] 开具电子处方 +- [ ] 药品搜索和选择 +- [ ] 用法用量设置 +- [ ] 处方审核 +- [ ] 处方打印 +- [ ] 处方作废 +- [ ] 处方模板管理 + +--- + +## 四、管理员功能模块(Admin Module) + +面向管理员的系统管理功能。 + +### 4.1 个人中心 +- [ ] 管理员信息维护 +- [ ] 修改密码 +- [ ] 操作日志查看 + +### 4.2 账户管理模块 +- [ ] 员工账号管理(医生、护士等) +- [ ] 顾客账号管理 +- [ ] 权限设置与分配 +- [ ] 账号状态管理(启用/禁用) +- [ ] 重置用户密码 +- [ ] 角色管理 + +### 4.3 公告设置模块 +- [ ] 发布公告 +- [ ] 编辑公告 +- [ ] 删除公告 +- [ ] 公告置顶 +- [ ] 公告分类管理 + +### 4.4 药品模块 +- [ ] 药品信息管理(增删改查) +- [ ] 药品分类管理 +- [ ] 库存查询 +- [ ] 入库管理 +- [ ] 出库管理 +- [ ] 库存预警设置 +- [ ] 库存盘点 +- [ ] 药品有效期管理 +- [ ] 供应商管理 + +### 4.5 统计报表 +- [ ] 收入统计 + - [ ] 日/周/月收入统计 + - [ ] 收入来源分析 +- [ ] 销量统计 + - [ ] 药品销量排行 + - [ ] 服务销量统计 +- [ ] 业绩统计 + - [ ] 医生业绩排行 + - [ ] 科室业绩分析 +- [ ] 导出Excel报表 +- [ ] 数据可视化图表 + +### 4.6 门诊管理 +- [ ] 门诊数据管理 +- [ ] 预约记录管理 +- [ ] 就诊记录管理 +- [ ] 门诊排班设置 + +### 4.7 病例管理 +- [ ] 病例数据管理(增删改查) +- [ ] 病例模板管理 +- [ ] 病例归档 +- [ ] 病例检索 + +### 4.8 宠物档案管理 +- [ ] 宠物档案管理(增删改查) +- [ ] 档案查询与检索 +- [ ] 档案导出 +- [ ] 档案统计 + +### 4.9 系统管理 +- [ ] 系统参数配置 +- [ ] 数据备份与恢复 +- [ ] 操作日志审计 +- [ ] 系统监控 + +--- + +## 五、技术实现要求 + +### 5.1 前端技术 +- **框架**: Vue.js 3 +- **UI组件库**: TDesign +- **状态管理**: Pinia +- **路由**: Vue Router +- **HTTP客户端**: Axios +- **构建工具**: Vite + +### 5.2 后端技术 +- **框架**: Spring Boot 2.7 +- **JDK版本**: Java 17 +- **数据库**: MySQL 8.0 +- **ORM框架**: MyBatis-Plus +- **安全框架**: Spring Security + JWT +- **API文档**: Swagger/OpenAPI + +### 5.3 系统特性 +- [ ] RESTful API设计 +- [ ] 前后端分离架构 +- [ ] 响应式布局(适配PC/平板) +- [ ] 数据加密传输(HTTPS) +- [ ] 权限控制(RBAC) +- [ ] 数据校验与防护 +- [ ] 日志记录与审计 + +--- + +## 六、开发进度计划 + +| 周数 | 工作内容 | 涉及功能模块 | +|------|---------|-------------| +| 第1-2周 | 需求分析与系统设计 | 整体架构设计 | +| 第3-4周 | 数据库设计与接口定义 | 所有模块数据模型 | +| 第5-6周 | 基础框架搭建 | 登录注册、个人中心 | +| 第7-8周 | 核心功能开发 | 门诊管理、预约系统 | +| 第9-10周 | 业务功能完善 | 病历、处方、药品管理 | +| 第11周 | 报表统计功能 | 数据统计模块 | +| 第12周 | 系统测试与优化 | 全功能测试 | +| 第13-14周 | 文档编写与答辩准备 | - | + +--- + +## 七、优先级说明 + +### 🔴 高优先级(核心功能) +- 登录注册模块 +- 个人中心 +- 门诊预约 +- 病历管理 +- 处方管理 +- 药品库存管理 + +### 🟡 中优先级(重要功能) +- 宠物档案管理 +- 在线支付 +- 报告查询 +- 统计报表 +- 公告管理 + +### 🟢 低优先级(增值功能) +- 留言板 +- 就诊指南 +- 数据可视化图表 +- 移动端适配优化 + +--- + +*文档生成时间: 2026-02-11* +*最后更新: 根据开题报告整理* diff --git a/backend/build-with-idea.sh b/backend/build-with-idea.sh new file mode 100755 index 0000000..0730a5b --- /dev/null +++ b/backend/build-with-idea.sh @@ -0,0 +1,270 @@ +#!/bin/bash + +# 爱维宠物医院管理平台 - 打包启动一条龙脚本 +# 使用 IntelliJ IDEA 内置 JDK 和 Maven +# 支持 IDEA 和 IDEA CE 版本 + +# 查找 IDEA 安装路径 +if [ -d "/Applications/IntelliJ IDEA.app" ]; then + IDEA_HOME="/Applications/IntelliJ IDEA.app" +elif [ -d "/Applications/IntelliJ IDEA CE.app" ]; then + IDEA_HOME="/Applications/IntelliJ IDEA CE.app" +else + echo "❌ 错误: 未找到 IntelliJ IDEA 安装目录" + echo "请确保 IDEA 安装在 /Applications 目录下" + exit 1 +fi + +# 设置 IDEA 内置 JDK +export JAVA_HOME="$IDEA_HOME/Contents/jbr/Contents/Home" +export PATH="$JAVA_HOME/bin:$PATH" + +# 设置 IDEA 内置 Maven +MAVEN_BIN="$IDEA_HOME/Contents/plugins/maven/lib/maven3/bin/mvn" + +if [ ! -f "$MAVEN_BIN" ]; then + echo "❌ 错误: 未找到 IDEA 内置 Maven" + exit 1 +fi + +# 脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +# 默认配置 +JAR_NAME="pet-hospital-1.0.0.jar" +JAR_PATH="target/$JAR_NAME" +SERVER_PORT="8080" +ACTIVE_PROFILE="dev" +SKIP_TESTS="-DskipTests" +BACKGROUND=false +DEBUG_MODE=false +JVM_OPTS="-Xms512m -Xmx1g" + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 显示帮助信息 +show_help() { + echo "爱维宠物医院管理平台 - 打包启动脚本" + echo "" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -p, --port PORT 指定服务端口号 (默认: 8080)" + echo " -e, --env ENV 指定环境配置 (默认: dev, 可选: dev/prod)" + echo " -t, --test 运行测试 (默认跳过测试)" + echo " -b, --background 后台运行" + echo " -d, --debug 开启调试模式 (端口: 5005)" + echo " -c, --clean 仅清理,不打包" + echo " -s, --stop 停止正在运行的服务" + echo " -l, --logs 查看后台运行日志" + echo " -h, --help 显示帮助信息" + echo "" + echo "示例:" + echo " $0 # 打包并启动" + echo " $0 -p 8081 # 使用端口 8081 启动" + echo " $0 -e prod # 使用生产环境配置" + echo " $0 -b # 后台运行" + echo " $0 -d # 调试模式" + echo " $0 -s # 停止服务" + echo " $0 -l # 查看日志" +} + +# 解析参数 +while [[ $# -gt 0 ]]; do + case $1 in + -p|--port) + SERVER_PORT="$2" + shift 2 + ;; + -e|--env) + ACTIVE_PROFILE="$2" + shift 2 + ;; + -t|--test) + SKIP_TESTS="" + shift + ;; + -b|--background) + BACKGROUND=true + shift + ;; + -d|--debug) + DEBUG_MODE=true + shift + ;; + -c|--clean) + echo "🧹 清理项目..." + "$MAVEN_BIN" clean + echo "✅ 清理完成" + exit 0 + ;; + -s|--stop) + echo "🛑 停止服务..." + PID=$(lsof -ti:$SERVER_PORT 2>/dev/null || echo "") + if [ -n "$PID" ]; then + kill $PID 2>/dev/null + sleep 2 + if ps -p $PID > /dev/null 2>&1; then + kill -9 $PID 2>/dev/null + fi + echo "✅ 服务已停止 (端口: $SERVER_PORT)" + else + # 尝试通过进程名查找 + PID=$(pgrep -f "$JAR_NAME" | head -1) + if [ -n "$PID" ]; then + kill $PID 2>/dev/null + sleep 2 + if ps -p $PID > /dev/null 2>&1; then + kill -9 $PID 2>/dev/null + fi + echo "✅ 服务已停止" + else + echo "⚠️ 未找到运行中的服务" + fi + fi + exit 0 + ;; + -l|--logs) + if [ -f "$SCRIPT_DIR/app.log" ]; then + echo "📋 查看日志 (按 Ctrl+C 退出)..." + tail -f "$SCRIPT_DIR/app.log" + else + echo "❌ 未找到日志文件" + fi + exit 0 + ;; + -h|--help) + show_help + exit 0 + ;; + *) + echo "❌ 未知选项: $1" + show_help + exit 1 + ;; + esac +done + +# 检查端口是否被占用 +check_port() { + if lsof -Pi :$SERVER_PORT -sTCP:LISTEN -t >/dev/null 2>&1; then + echo -e "${YELLOW}⚠️ 警告: 端口 $SERVER_PORT 已被占用${NC}" + echo "" + echo "占用端口的进程:" + lsof -Pi :$SERVER_PORT -sTCP:LISTEN + echo "" + read -p "是否停止现有进程并继续? (y/n): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + PID=$(lsof -ti:$SERVER_PORT) + kill $PID 2>/dev/null + sleep 2 + echo -e "${GREEN}✅ 已释放端口 $SERVER_PORT${NC}" + else + echo -e "${RED}❌ 操作已取消${NC}" + exit 1 + fi + fi +} + +# 显示环境信息 +show_info() { + echo -e "${BLUE}═══════════════════════════════════════${NC}" + echo -e "${BLUE} 爱维宠物医院管理平台 - 打包启动脚本${NC}" + echo -e "${BLUE}═══════════════════════════════════════${NC}" + echo "" + echo -e "${GREEN}📦 使用 IDEA 内置 JDK:${NC} $JAVA_HOME" + echo -e "${GREEN}🔧 Java 版本:${NC}" + java -version 2>&1 | head -1 + echo "" + echo -e "${GREEN}🚀 运行配置:${NC}" + echo " 端口: $SERVER_PORT" + echo " 环境: $ACTIVE_PROFILE" + echo " 调试: $([ "$DEBUG_MODE" = true ] && echo "开启 (端口: 5005)" || echo "关闭")" + echo " 后台: $([ "$BACKGROUND" = true ] && echo "是" || echo "否")" + echo "" +} + +# 打包项目 +build_project() { + echo -e "${BLUE}📦 开始打包项目...${NC}" + echo "" + + if ! "$MAVEN_BIN" clean package $SKIP_TESTS; then + echo "" + echo -e "${RED}❌ 打包失败!${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}✅ 打包成功!${NC}" + echo "" +} + +# 启动应用 +start_app() { + if [ ! -f "$JAR_PATH" ]; then + echo -e "${RED}❌ 错误: 未找到 jar 文件: $JAR_PATH${NC}" + exit 1 + fi + + check_port + + # 构建启动命令 + JAVA_OPTS="$JVM_OPTS" + + if [ "$DEBUG_MODE" = true ]; then + JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" + echo -e "${YELLOW}🐛 调试模式已开启,可在 IDEA 中配置远程调试 (端口: 5005)${NC}" + fi + + echo -e "${BLUE}🚀 正在启动应用...${NC}" + echo "" + + if [ "$BACKGROUND" = true ]; then + # 后台运行 + nohup java $JAVA_OPTS -jar "$JAR_PATH" \ + --server.port=$SERVER_PORT \ + --spring.profiles.active=$ACTIVE_PROFILE \ + > "$SCRIPT_DIR/app.log" 2>&1 & + + APP_PID=$! + echo $APP_PID > "$SCRIPT_DIR/app.pid" + + echo -e "${GREEN}✅ 应用已在后台启动${NC}" + echo " 进程ID: $APP_PID" + echo " 访问地址: http://localhost:$SERVER_PORT" + echo " 日志文件: $SCRIPT_DIR/app.log" + echo "" + echo "查看日志: $0 --logs" + echo "停止服务: $0 --stop" + else + # 前台运行 + echo -e "${GREEN}✅ 应用启动成功!${NC}" + echo " 访问地址: http://localhost:$SERVER_PORT" + echo " API 文档: http://localhost:$SERVER_PORT/swagger-ui.html" + echo "" + echo -e "${YELLOW}按 Ctrl+C 停止服务${NC}" + echo "═══════════════════════════════════════" + echo "" + + java $JAVA_OPTS -jar "$JAR_PATH" \ + --server.port=$SERVER_PORT \ + --spring.profiles.active=$ACTIVE_PROFILE + fi +} + +# 主流程 +main() { + show_info + build_project + start_app +} + +main diff --git a/backend/pom.xml b/backend/pom.xml index eb331b4..df9af14 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -88,6 +88,7 @@ org.projectlombok lombok + 1.18.34 true @@ -108,6 +109,23 @@ + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + 17 + 17 + + + org.projectlombok + lombok +1.18.36 + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/backend/src/main/java/com/gpf/pethospital/controller/StatsController.java b/backend/src/main/java/com/gpf/pethospital/controller/StatsController.java index dd63d59..36941ec 100644 --- a/backend/src/main/java/com/gpf/pethospital/controller/StatsController.java +++ b/backend/src/main/java/com/gpf/pethospital/controller/StatsController.java @@ -3,9 +3,13 @@ package com.gpf.pethospital.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.gpf.pethospital.common.ApiResponse; +import com.gpf.pethospital.entity.Appointment; import com.gpf.pethospital.entity.Order; +import com.gpf.pethospital.entity.Pet; import com.gpf.pethospital.entity.User; +import com.gpf.pethospital.entity.Visit; import com.gpf.pethospital.service.AppointmentService; +import com.gpf.pethospital.service.DrugService; import com.gpf.pethospital.service.OrderService; import com.gpf.pethospital.service.PetService; import com.gpf.pethospital.service.UserService; @@ -13,12 +17,19 @@ import com.gpf.pethospital.service.VisitService; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @RestController @RequestMapping("/admin/stats") @@ -28,40 +39,208 @@ public class StatsController { private final VisitService visitService; private final PetService petService; private final UserService userService; + private final DrugService drugService; public StatsController(OrderService orderService, AppointmentService appointmentService, VisitService visitService, PetService petService, - UserService userService) { + UserService userService, + DrugService drugService) { this.orderService = orderService; this.appointmentService = appointmentService; this.visitService = visitService; this.petService = petService; this.userService = userService; + this.drugService = drugService; } @PreAuthorize("hasRole('ADMIN')") @GetMapping public ApiResponse summary() { Map data = new HashMap<>(); - data.put("orders", orderService.count()); - data.put("appointments", appointmentService.count()); - data.put("visits", visitService.count()); - data.put("pets", petService.count()); - data.put("customers", userService.count(new LambdaQueryWrapper().eq(User::getRole, "CUSTOMER"))); - - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.select("SUM(amount) AS total"); - List> result = orderService.listMaps(wrapper); - BigDecimal total = BigDecimal.ZERO; + + // 今日预约数 + LocalDate today = LocalDate.now(); + long todayAppointments = appointmentService.count( + new LambdaQueryWrapper() + .eq(Appointment::getAppointmentDate, today) + .ne(Appointment::getStatus, "CANCELLED") + ); + data.put("appointments", todayAppointments); + + // 今日待就诊数(预约状态为 CONFIRMED 的今日预约) + long pendingVisits = appointmentService.count( + new LambdaQueryWrapper() + .eq(Appointment::getAppointmentDate, today) + .eq(Appointment::getStatus, "CONFIRMED") + ); + data.put("visits", pendingVisits); + + // 药品库存总数 + QueryWrapper drugWrapper = new QueryWrapper<>(); + drugWrapper.select("SUM(stock) AS totalStock"); + List> drugResult = drugService.listMaps(drugWrapper); + Long drugStock = 0L; + if (!drugResult.isEmpty() && drugResult.get(0) != null && drugResult.get(0).get("totalStock") != null) { + drugStock = Long.valueOf(drugResult.get(0).get("totalStock").toString()); + } + data.put("drugs", drugStock); + + // 今日收入 + LocalDateTime todayStart = today.atStartOfDay(); + LocalDateTime todayEnd = today.plusDays(1).atStartOfDay(); + QueryWrapper orderWrapper = new QueryWrapper<>(); + orderWrapper.select("SUM(amount) AS total"); + orderWrapper.ge("create_time", todayStart); + orderWrapper.lt("create_time", todayEnd); + List> result = orderService.listMaps(orderWrapper); + BigDecimal todayRevenue = BigDecimal.ZERO; if (!result.isEmpty()) { Map row = result.get(0); + if (row != null && row.get("total") != null) { + todayRevenue = new BigDecimal(row.get("total").toString()); + } + } + data.put("revenue", todayRevenue); + + // 保留原有统计数据 + data.put("orders", orderService.count()); + data.put("pets", petService.count()); + data.put("customers", userService.count(new LambdaQueryWrapper().eq(User::getRole, "CUSTOMER"))); + + QueryWrapper totalWrapper = new QueryWrapper<>(); + totalWrapper.select("SUM(amount) AS total"); + List> totalResult = orderService.listMaps(totalWrapper); + BigDecimal total = BigDecimal.ZERO; + if (!totalResult.isEmpty()) { + Map row = totalResult.get(0); if (row != null && row.get("total") != null) { total = new BigDecimal(row.get("total").toString()); } } data.put("orderAmountTotal", total); + return ApiResponse.success(data); } + + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/trends") + public ApiResponse trends(@RequestParam(defaultValue = "week") String period) { + Map data = new HashMap<>(); + LocalDate now = LocalDate.now(); + LocalDate startDate; + DateTimeFormatter formatter; + int days; + + switch (period) { + case "month": + startDate = now.minusDays(30); + formatter = DateTimeFormatter.ofPattern("MM-dd"); + days = 30; + break; + case "year": + startDate = now.minusMonths(12); + formatter = DateTimeFormatter.ofPattern("yyyy-MM"); + days = 12; + break; + default: // week + startDate = now.minusDays(6); + formatter = DateTimeFormatter.ofPattern("MM-dd"); + days = 7; + break; + } + + List labels = new ArrayList<>(); + List values = new ArrayList<>(); + + if ("year".equals(period)) { + // 按月统计 + for (int i = 0; i < 12; i++) { + LocalDate monthStart = startDate.plusMonths(i); + LocalDateTime monthStartTime = monthStart.atStartOfDay(); + LocalDateTime monthEndTime = monthStart.plusMonths(1).atStartOfDay(); + + long count = visitService.count( + new LambdaQueryWrapper() + .ge(Visit::getCreateTime, monthStartTime) + .lt(Visit::getCreateTime, monthEndTime) + ); + + labels.add(monthStart.format(formatter)); + values.add((int) count); + } + } else { + // 按天统计 + for (int i = 0; i < days; i++) { + LocalDate date = startDate.plusDays(i); + LocalDateTime dayStart = date.atStartOfDay(); + LocalDateTime dayEnd = date.plusDays(1).atStartOfDay(); + + long count = visitService.count( + new LambdaQueryWrapper() + .ge(Visit::getCreateTime, dayStart) + .lt(Visit::getCreateTime, dayEnd) + ); + + labels.add(date.format(formatter)); + values.add((int) count); + } + } + + data.put("labels", labels); + data.put("values", values); + data.put("total", values.stream().mapToInt(Integer::intValue).sum()); + + // 计算环比 + if (values.size() >= 2) { + int current = values.get(values.size() - 1); + int previous = values.get(values.size() - 2); + double growthRate = previous > 0 ? ((double) (current - previous) / previous * 100) : 0; + data.put("growthRate", Math.round(growthRate * 10) / 10.0); + } else { + data.put("growthRate", 0); + } + + // 平均日接诊 + double avg = values.isEmpty() ? 0 : values.stream().mapToInt(Integer::intValue).average().orElse(0); + data.put("average", Math.round(avg * 10) / 10.0); + + return ApiResponse.success(data); + } + + @PreAuthorize("hasRole('ADMIN')") + @GetMapping("/today-todos") + public ApiResponse todayTodos() { + LocalDate today = LocalDate.now(); + + // 查询今日待就诊的预约 + List appointments = appointmentService.list( + new LambdaQueryWrapper() + .eq(Appointment::getAppointmentDate, today) + .eq(Appointment::getStatus, "CONFIRMED") + .orderByAsc(Appointment::getTimeSlot) + ); + + List> todoList = appointments.stream().map(appointment -> { + Map todo = new HashMap<>(); + + // 获取客户信息 + User customer = userService.getById(appointment.getCustomerId()); + // 获取宠物信息 + Pet pet = petService.getById(appointment.getPetId()); + + todo.put("id", appointment.getId()); + todo.put("time", appointment.getTimeSlot()); + todo.put("customer", customer != null ? customer.getUsername() : "未知客户"); + todo.put("pet", pet != null ? pet.getName() + "(" + pet.getBreed() + ")" : "未知宠物"); + todo.put("service", appointment.getDepartment() != null ? appointment.getDepartment() : "常规就诊"); + todo.put("status", "待就诊"); + todo.put("action", "接诊"); + + return todo; + }).collect(Collectors.toList()); + + return ApiResponse.success(todoList); + } } diff --git a/backend/src/main/resources/application-dev.yml b/backend/src/main/resources/application-dev.yml index 7d3db33..593dd40 100644 --- a/backend/src/main/resources/application-dev.yml +++ b/backend/src/main/resources/application-dev.yml @@ -1,5 +1,6 @@ server: port: 8081 + address: 0.0.0.0 servlet: context-path: /api @@ -9,7 +10,7 @@ spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost:3307/pet_hospital_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8 + url: jdbc:mysql://localhost:3306/pet_hospital_dev?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8 username: root password: qq5211314 hikari: diff --git a/frontend/COLOR_UPDATE.md b/frontend/COLOR_UPDATE.md new file mode 100644 index 0000000..504c152 --- /dev/null +++ b/frontend/COLOR_UPDATE.md @@ -0,0 +1,59 @@ +# 配色更新说明 + +## 更新内容 +已将项目的紫色配色(#667eea、#764ba2)全面更换为**清新青绿色系**。 + +## 新配色方案 + +### 主色调 +- **主色**: `#0d9488` (teal-600) - 清新的青绿色 +- **辅助色**: `#14b8a6` (teal-500) - 亮青绿色 +- **深色**: `#115e59` (teal-700) - 深青绿 +- **浅色**: `#5eead4` (teal-300) - 浅青绿 + +### 功能色(保持不变) +- **成功**: `#10b981` (green-500) +- **警告**: `#f59e0b` (amber-500) +- **危险**: `#ef4444` (red-500) +- **信息**: `#3b82f6` (blue-500) + +### 中性色(保持不变) +- **背景**: `#f8fafc` (slate-50) +- **面板**: `#ffffff` (white) +- **文字**: `#1e293b` (slate-800) +- **次要文字**: `#64748b` (slate-500) +- **边框**: `#e2e8f0` (slate-200) + +## 修改的文件列表 + +1. ✅ `frontend/src/styles/global.css` - 全局CSS变量和组件样式 +2. ✅ `frontend/src/pages/Dashboard.vue` - 仪表盘页面 +3. ✅ `frontend/src/layouts/MainLayout.vue` - 主布局 +4. ✅ `frontend/src/pages/Login.vue` - 登录页面 +5. ✅ `frontend/src/pages/Register.vue` - 注册页面 + +## 视觉效果对比 + +### 修改前(紫色系) +- 主色:紫蓝色 #667eea → #764ba2 +- 风格:科技感较强,但略显沉重 + +### 修改后(青绿色系) +- 主色:青绿色 #0d9488 → #14b8a6 +- 风格:清新自然,专业医疗感,更加舒适护眼 + +## 特点 + +1. **医疗感**: 青绿色常用于医疗健康领域,给人专业、可靠的感觉 +2. **清新感**: 比紫色更加清爽,视觉疲劳度更低 +3. **自然感**: 接近自然界的颜色,与宠物医院的主题更契合 +4. **现代感**: 当前流行的设计配色,简洁大方 + +## 建议 + +如果还需要调整配色,可以考虑以下方案: + +1. **医疗蓝**: `#2563eb` → `#3b82f6` (更传统医疗感) +2. **薄荷绿**: `#10b981` → `#34d399` (更活泼年轻) +3. **暖橙色**: `#f97316` → `#fb923c` (更温馨友好) +4. **深蓝**: `#1e40af` → `#3b82f6` (更稳重专业) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 662dd7e..8500688 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -66,4 +66,6 @@ export const api = { resetUserPassword: (id: number, newPassword: string) => http.put(`/users/${id}/reset-password`, null, { params: { newPassword } }), stats: () => http.get('/admin/stats'), + statsTrends: (period?: string) => http.get('/admin/stats/trends', { params: { period } }), + todayTodos: () => http.get('/admin/stats/today-todos'), }; diff --git a/frontend/src/layouts/MainLayout.vue b/frontend/src/layouts/MainLayout.vue index 6c24814..084a36d 100644 --- a/frontend/src/layouts/MainLayout.vue +++ b/frontend/src/layouts/MainLayout.vue @@ -1,21 +1,77 @@