From cfae122685c4cf967217f0af349cb87a87e02525 Mon Sep 17 00:00:00 2001 From: wangziqi Date: Wed, 7 Jan 2026 14:28:50 +0800 Subject: [PATCH] Initial commit: Car Maintenance Management System Author: Yang Lu School: Liaoning Institute of Science and Technology Major: Computer Science and Technology Class: BZ246 Tech Stack: - Backend: Spring Boot 2.7.18 + JPA + MySQL - Frontend: HTML5 + CSS3 + JavaScript Features: - User Management (Admin/Staff/Customer roles) - Vehicle Archive Management - Service Order Management - Parts Inventory Management - Online Appointment Service - Data Statistics and Analysis Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 56 ++ QUICKSTART.md | 238 +++++++++ README.md | 489 ++++++++++++++++++ backend/pom.xml | 113 ++++ .../CarMaintenanceApplication.java | 23 + .../com/carmaintenance/config/WebConfig.java | 35 ++ .../controller/AppointmentController.java | 127 +++++ .../controller/AuthController.java | 87 ++++ .../controller/PartsInventoryController.java | 114 ++++ .../controller/ServiceOrderController.java | 140 +++++ .../controller/UserController.java | 128 +++++ .../controller/VehicleController.java | 112 ++++ .../com/carmaintenance/dto/LoginRequest.java | 12 + .../com/carmaintenance/dto/LoginResponse.java | 28 + .../java/com/carmaintenance/dto/Result.java | 54 ++ .../carmaintenance/entity/Appointment.java | 66 +++ .../com/carmaintenance/entity/Customer.java | 67 +++ .../carmaintenance/entity/PartsInventory.java | 72 +++ .../carmaintenance/entity/ServiceItem.java | 54 ++ .../carmaintenance/entity/ServiceOrder.java | 105 ++++ .../java/com/carmaintenance/entity/User.java | 58 +++ .../com/carmaintenance/entity/Vehicle.java | 75 +++ .../repository/AppointmentRepository.java | 20 + .../repository/CustomerRepository.java | 18 + .../repository/PartsInventoryRepository.java | 21 + .../repository/ServiceItemRepository.java | 21 + .../repository/ServiceOrderRepository.java | 25 + .../repository/UserRepository.java | 25 + .../repository/VehicleRepository.java | 21 + .../src/main/resources/application.properties | 43 ++ database/data.sql | 79 +++ database/schema.sql | 187 +++++++ frontend/admin/dashboard.html | 300 +++++++++++ frontend/css/common.css | 377 ++++++++++++++ frontend/css/dashboard.css | 286 ++++++++++ frontend/css/login.css | 152 ++++++ frontend/customer/dashboard.html | 361 +++++++++++++ frontend/js/admin-dashboard.js | 468 +++++++++++++++++ frontend/js/api.js | 194 +++++++ frontend/js/config.js | 52 ++ frontend/js/login.js | 102 ++++ frontend/login.html | 61 +++ frontend/staff/dashboard.html | 270 ++++++++++ git-push.bat | 44 ++ git-push.sh | 67 +++ 45 files changed, 5447 insertions(+) create mode 100644 .gitignore create mode 100644 QUICKSTART.md create mode 100644 README.md create mode 100644 backend/pom.xml create mode 100644 backend/src/main/java/com/carmaintenance/CarMaintenanceApplication.java create mode 100644 backend/src/main/java/com/carmaintenance/config/WebConfig.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/AppointmentController.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/AuthController.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/PartsInventoryController.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/ServiceOrderController.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/UserController.java create mode 100644 backend/src/main/java/com/carmaintenance/controller/VehicleController.java create mode 100644 backend/src/main/java/com/carmaintenance/dto/LoginRequest.java create mode 100644 backend/src/main/java/com/carmaintenance/dto/LoginResponse.java create mode 100644 backend/src/main/java/com/carmaintenance/dto/Result.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/Appointment.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/Customer.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/PartsInventory.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/ServiceItem.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/ServiceOrder.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/User.java create mode 100644 backend/src/main/java/com/carmaintenance/entity/Vehicle.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/AppointmentRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/CustomerRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/PartsInventoryRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/ServiceItemRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/ServiceOrderRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/UserRepository.java create mode 100644 backend/src/main/java/com/carmaintenance/repository/VehicleRepository.java create mode 100644 backend/src/main/resources/application.properties create mode 100644 database/data.sql create mode 100644 database/schema.sql create mode 100644 frontend/admin/dashboard.html create mode 100644 frontend/css/common.css create mode 100644 frontend/css/dashboard.css create mode 100644 frontend/css/login.css create mode 100644 frontend/customer/dashboard.html create mode 100644 frontend/js/admin-dashboard.js create mode 100644 frontend/js/api.js create mode 100644 frontend/js/config.js create mode 100644 frontend/js/login.js create mode 100644 frontend/login.html create mode 100644 frontend/staff/dashboard.html create mode 100644 git-push.bat create mode 100644 git-push.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00e1add --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Java编译文件 +*.class +*.jar +*.war +*.ear + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# IDE +.idea/ +*.iml +*.iws +*.ipr +.classpath +.project +.settings/ +.vscode/ +*.swp +*.swo +*~ + +# 系统文件 +.DS_Store +Thumbs.db +desktop.ini + +# 日志文件 +*.log +logs/ + +# 临时文件 +*.tmp +*.temp +*.bak + +# 数据库 +*.db +*.sqlite + +# Node modules (如果后续添加前端构建工具) +node_modules/ +dist/ + +# 环境配置 +.env +.env.local +.env.*.local diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..c21e50f --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,238 @@ +# 车管家4S店车辆维保管理系统 - 快速启动指南 + +## 快速启动步骤 + +### 第一步:准备环境 + +确保您的计算机已安装: +- ✅ JDK 1.8 或更高版本 +- ✅ Maven 3.6 或更高版本 +- ✅ MySQL 8.0 或更高版本 +- ✅ 现代浏览器(Chrome、Firefox、Edge等) + +### 第二步:创建数据库 + +1. 启动MySQL服务 + +2. 打开MySQL命令行或MySQL Workbench + +3. 执行数据库脚本: + +```sql +-- 方式一:使用MySQL命令行 +mysql -u root -p < database/schema.sql +mysql -u root -p < database/data.sql + +-- 方式二:在MySQL客户端中直接执行 +source D:/bs/yanglu/car-maintenance-system/database/schema.sql +source D:/bs/yanglu/car-maintenance-system/database/data.sql +``` + +### 第三步:配置数据库连接 + +编辑文件:`backend/src/main/resources/application.properties` + +修改以下配置: + +```properties +spring.datasource.username=root +spring.datasource.password=你的MySQL密码 +``` + +### 第四步:启动后端服务 + +#### 方式一:使用IDE(推荐) + +1. 使用 IntelliJ IDEA 或 Eclipse 打开 `backend` 文件夹 +2. 等待 Maven 下载依赖(首次可能需要几分钟) +3. 找到 `CarMaintenanceApplication.java` 文件 +4. 右键 -> Run 'CarMaintenanceApplication' +5. 看到以下信息表示启动成功: + +``` +======================================== +车管家4S店车辆维保管理系统启动成功! +访问地址: http://localhost:8080/api +======================================== +``` + +#### 方式二:使用命令行 + +```bash +# 进入backend目录 +cd backend + +# 打包项目 +mvn clean package -DskipTests + +# 运行项目 +java -jar target/car-maintenance-system-1.0.0.jar +``` + +### 第五步:启动前端服务 + +#### 方式一:使用 VS Code Live Server(推荐) + +1. 使用 VS Code 打开 `frontend` 文件夹 +2. 安装 "Live Server" 插件 +3. 右键点击 `login.html` +4. 选择 "Open with Live Server" +5. 浏览器自动打开:http://localhost:5500/login.html + +#### 方式二:直接打开文件 + +在文件浏览器中,双击打开 `frontend/login.html` 文件 + +注意:直接打开文件可能会遇到CORS跨域问题,建议使用Live Server。 + +### 第六步:登录系统 + +使用以下演示账号登录: + +| 角色 | 用户名 | 密码 | 说明 | +|------|--------|------|------| +| **管理员** | admin | 123456 | 拥有所有权限,可管理用户、车辆、工单、配件等 | +| **工作人员** | staff001 | 123456 | 可查看和处理分配的工单 | +| **客户** | customer001 | 123456 | 可查看自己的车辆、维保记录、在线预约 | + +--- + +## 常见问题解决 + +### 问题1:后端启动失败 - 端口被占用 + +**错误信息**:`Port 8080 was already in use` + +**解决方案**: +- 方式一:修改端口号 + 编辑 `application.properties`,添加: + ```properties + server.port=8081 + ``` + 同时修改前端 `js/config.js` 中的端口号 + +- 方式二:关闭占用端口的程序 + ```bash + # Windows + netstat -ano | findstr :8080 + taskkill /PID <进程ID> /F + + # Linux/Mac + lsof -i :8080 + kill -9 + ``` + +### 问题2:数据库连接失败 + +**错误信息**:`Communications link failure` + +**解决方案**: +1. 确认MySQL服务已启动 +2. 检查用户名和密码是否正确 +3. 确认数据库 `car_maintenance_db` 已创建 +4. 检查MySQL端口是否为3306 + +### 问题3:前端无法连接后端 + +**错误信息**:`Failed to fetch` 或 `CORS error` + +**解决方案**: +1. 确认后端服务已启动(访问 http://localhost:8080/api) +2. 检查前端配置文件 `js/config.js` 中的 `BASE_URL` +3. 使用Live Server启动前端,不要直接双击HTML文件 + +### 问题4:Maven依赖下载失败 + +**解决方案**: +1. 配置Maven国内镜像(阿里云) + 编辑 `~/.m2/settings.xml`,添加: + ```xml + + + aliyun + central + https://maven.aliyun.com/repository/public + + + ``` + +2. 重新下载依赖: + ```bash + mvn clean install -U + ``` + +--- + +## 测试功能 + +### 管理员测试流程 + +1. 使用 admin/123456 登录 +2. 查看系统概览统计数据 +3. 进入"用户管理",查看所有用户 +4. 进入"车辆管理",查看所有车辆信息 +5. 进入"工单管理",查看维保工单 +6. 进入"配件管理",查看库存情况(注意库存预警功能) +7. 进入"预约管理",查看客户预约 + +### 工作人员测试流程 + +1. 使用 staff001/123456 登录 +2. 查看分配给自己的工单 +3. 使用车辆查询功能,输入车牌号查询车辆信息 +4. 使用配件查询功能,搜索配件 + +### 客户测试流程 + +1. 使用 customer001/123456 登录 +2. 查看"我的车辆"页面 +3. 查看"维保记录" +4. 进入"在线预约",提交新的预约 +5. 在"我的预约"中查看预约状态 + +--- + +## 下一步 + +系统已成功运行!您可以: + +1. **添加更多测试数据** + - 创建新用户 + - 添加车辆档案 + - 创建维保工单 + - 管理配件库存 + +2. **自定义配置** + - 修改系统名称和Logo + - 调整颜色主题 + - 添加更多服务项目 + +3. **扩展功能** + - 添加统计报表 + - 实现消息通知 + - 增加数据导出功能 + - 开发移动端适配 + +4. **部署到生产环境** + - 参考 README.md 中的部署指南 + - 配置Nginx反向代理 + - 启用HTTPS安全连接 + +--- + +## 技术支持 + +如遇到问题,请: +1. 查看完整的 `README.md` 文档 +2. 检查后端控制台的错误日志 +3. 使用浏览器开发者工具(F12)查看网络请求 +4. 联系指导教师或技术支持 + +--- + +**祝您使用愉快!** + +车管家4S店车辆维保管理系统 +辽宁科技学院 - 计算机科学与技术专业 +作者:杨璐 +指导教师:刘慧宇 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e3f668 --- /dev/null +++ b/README.md @@ -0,0 +1,489 @@ +# 车管家4S店车辆维保管理系统 + +## 项目简介 + +基于 Spring Boot 与原生 HTML+CSS+JavaScript 的前后端分离技术,开发的功能完善的 B/S 架构车辆维保(4S 店)管理系统。该系统旨在为 4S 店提供一个集客户管理、车辆档案、维保流程、配件库存等功能于一体的高效信息化管理平台,同时为车主提供便捷的在线预约与查询服务。 + +**作者**: 杨璐 +**学校**: 辽宁科技学院 +**专业**: 计算机科学与技术 +**班级**: 计BZ246 +**学号**: 74133240622 +**指导教师**: 刘慧宇 +**日期**: 2025年1月 + +--- + +## 技术栈 + +### 后端技术 +- **Spring Boot 2.7.18** - 核心框架 +- **Spring Data JPA** - 数据访问层 +- **MySQL 8.0** - 关系型数据库 +- **Maven** - 项目管理工具 +- **Lombok** - 简化Java代码 +- **FastJSON** - JSON处理 + +### 前端技术 +- **HTML5** - 页面结构 +- **CSS3** - 样式设计 +- **JavaScript (ES6)** - 交互逻辑 +- **Fetch API** - HTTP请求 + +### 开发工具 +- **IntelliJ IDEA / Eclipse** - 后端开发IDE +- **VS Code** - 前端开发编辑器 +- **MySQL Workbench** - 数据库管理 +- **Postman** - API测试 + +--- + +## 系统功能 + +### 1. 用户管理 +- 用户注册与登录 +- 角色管理(管理员、工作人员、客户) +- 用户信息维护 +- 密码修改 + +### 2. 客户管理 +- 客户档案管理 +- 会员等级管理 +- 积分管理 +- 客户信息查询 + +### 3. 车辆档案管理 +- 车辆信息登记 +- 车辆档案查询 +- 车辆保养记录 +- 里程数管理 + +### 4. 维保工单管理 +- 工单创建与分配 +- 工单流转跟踪 +- 故障诊断记录 +- 费用计算 +- 工单状态管理 + +### 5. 配件库存管理 +- 配件信息管理 +- 库存数量管理 +- 库存预警 +- 配件出入库记录 + +### 6. 预约管理 +- 在线预约服务 +- 预约确认 +- 预约查询 +- 预约取消 + +--- + +## 项目结构 + +``` +car-maintenance-system/ +├── backend/ # Spring Boot后端 +│ ├── src/ +│ │ ├── main/ +│ │ │ ├── java/ +│ │ │ │ └── com/ +│ │ │ │ └── carmaintenance/ +│ │ │ │ ├── CarMaintenanceApplication.java # 主应用程序 +│ │ │ │ ├── config/ # 配置类 +│ │ │ │ │ └── WebConfig.java +│ │ │ │ ├── controller/ # 控制器层 +│ │ │ │ │ ├── AuthController.java +│ │ │ │ │ ├── UserController.java +│ │ │ │ │ ├── VehicleController.java +│ │ │ │ │ ├── ServiceOrderController.java +│ │ │ │ │ ├── PartsInventoryController.java +│ │ │ │ │ └── AppointmentController.java +│ │ │ │ ├── dto/ # 数据传输对象 +│ │ │ │ │ ├── Result.java +│ │ │ │ │ ├── LoginRequest.java +│ │ │ │ │ └── LoginResponse.java +│ │ │ │ ├── entity/ # 实体类 +│ │ │ │ │ ├── User.java +│ │ │ │ │ ├── Customer.java +│ │ │ │ │ ├── Vehicle.java +│ │ │ │ │ ├── ServiceOrder.java +│ │ │ │ │ ├── PartsInventory.java +│ │ │ │ │ ├── Appointment.java +│ │ │ │ │ └── ServiceItem.java +│ │ │ │ └── repository/ # 数据访问层 +│ │ │ │ ├── UserRepository.java +│ │ │ │ ├── CustomerRepository.java +│ │ │ │ ├── VehicleRepository.java +│ │ │ │ ├── ServiceOrderRepository.java +│ │ │ │ ├── PartsInventoryRepository.java +│ │ │ │ ├── AppointmentRepository.java +│ │ │ │ └── ServiceItemRepository.java +│ │ │ └── resources/ +│ │ │ └── application.properties # 应用配置 +│ │ └── test/ +│ └── pom.xml # Maven配置 +│ +├── frontend/ # HTML+CSS+JS前端 +│ ├── admin/ # 管理员界面 +│ │ └── dashboard.html +│ ├── staff/ # 工作人员界面 +│ │ └── dashboard.html +│ ├── customer/ # 客户界面 +│ │ └── dashboard.html +│ ├── css/ # 样式文件 +│ │ ├── common.css # 通用样式 +│ │ ├── login.css # 登录页样式 +│ │ └── dashboard.css # 仪表板样式 +│ ├── js/ # JavaScript文件 +│ │ ├── config.js # 配置文件 +│ │ ├── api.js # API调用工具 +│ │ ├── login.js # 登录逻辑 +│ │ └── admin-dashboard.js # 管理员仪表板逻辑 +│ └── login.html # 登录页面 +│ +├── database/ # 数据库脚本 +│ ├── schema.sql # 表结构SQL +│ └── data.sql # 初始数据SQL +│ +└── README.md # 项目说明文档 +``` + +--- + +## 数据库设计 + +### 主要数据表 + +1. **users** - 用户表 +2. **customers** - 客户信息表 +3. **vehicles** - 车辆档案表 +4. **service_orders** - 维保工单表 +5. **parts_inventory** - 配件库存表 +6. **parts_usage** - 配件使用记录表 +7. **appointments** - 预约记录表 +8. **service_items** - 服务项目表 +9. **system_logs** - 系统日志表 + +### ER图说明 + +``` +用户(User) 1---N 客户(Customer) +客户(Customer) 1---N 车辆(Vehicle) +车辆(Vehicle) 1---N 维保工单(ServiceOrder) +客户(Customer) 1---N 维保工单(ServiceOrder) +工单(ServiceOrder) N---N 配件(PartsInventory) [通过parts_usage] +客户(Customer) 1---N 预约(Appointment) +车辆(Vehicle) 1---N 预约(Appointment) +``` + +--- + +## 安装与部署 + +### 环境要求 + +- **JDK**: 1.8 或更高版本 +- **Maven**: 3.6 或更高版本 +- **MySQL**: 8.0 或更高版本 +- **浏览器**: Chrome、Firefox、Edge 等现代浏览器 + +### 1. 数据库配置 + +#### 创建数据库 + +```bash +# 登录MySQL +mysql -u root -p + +# 执行数据库脚本 +source database/schema.sql +source database/data.sql +``` + +或者直接在MySQL客户端中执行: + +```sql +-- 创建数据库 +CREATE DATABASE car_maintenance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 导入schema.sql和data.sql文件内容 +``` + +#### 修改数据库连接配置 + +编辑 `backend/src/main/resources/application.properties`: + +```properties +spring.datasource.url=jdbc:mysql://localhost:3306/car_maintenance_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai +spring.datasource.username=root +spring.datasource.password=你的密码 +``` + +### 2. 后端部署 + +#### 方式一:使用IDE运行 + +1. 使用 IntelliJ IDEA 或 Eclipse 打开 `backend` 文件夹 +2. 等待 Maven 依赖下载完成 +3. 运行 `CarMaintenanceApplication.java` 主类 +4. 看到 "车管家4S店车辆维保管理系统启动成功!" 表示启动成功 +5. 后端服务地址:http://localhost:8080/api + +#### 方式二:使用Maven命令行 + +```bash +# 进入backend目录 +cd backend + +# 清理并打包 +mvn clean package + +# 运行jar包 +java -jar target/car-maintenance-system-1.0.0.jar +``` + +### 3. 前端部署 + +#### 开发环境(使用Live Server) + +1. 使用 VS Code 打开 `frontend` 文件夹 +2. 安装 Live Server 插件 +3. 右键点击 `login.html`,选择 "Open with Live Server" +4. 浏览器自动打开:http://localhost:5500/login.html + +#### 生产环境(使用Nginx) + +```nginx +server { + listen 80; + server_name yourdomain.com; + + root /path/to/car-maintenance-system/frontend; + index login.html; + + location / { + try_files $uri $uri/ =404; + } + + # 代理后端API请求 + location /api/ { + proxy_pass http://localhost:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +--- + +## 使用说明 + +### 演示账号 + +系统提供了以下演示账号供测试使用: + +| 角色 | 用户名 | 密码 | 说明 | +|------|--------|------|------| +| 管理员 | admin | 123456 | 拥有所有权限 | +| 工作人员 | staff001 | 123456 | 处理工单和维保 | +| 客户 | customer001 | 123456 | 查看车辆和预约 | + +### 登录流程 + +1. 打开浏览器访问系统首页 +2. 输入用户名和密码 +3. 选择对应的登录角色 +4. 点击"登录"按钮 +5. 系统自动跳转到对应角色的仪表板 + +### 管理员功能 + +- **系统概览**: 查看系统统计数据 +- **用户管理**: 管理所有系统用户 +- **车辆管理**: 管理所有车辆档案 +- **工单管理**: 管理所有维保工单 +- **配件管理**: 管理配件库存 +- **预约管理**: 管理客户预约 + +### 工作人员功能 + +- 查看分配给自己的工单 +- 更新工单状态 +- 记录维修诊断 +- 添加使用配件 +- 完成工单 + +### 客户功能 + +- 查看自己的车辆 +- 查看维保记录 +- 在线预约服务 +- 查看预约状态 +- 查询工单进度 + +--- + +## API接口文档 + +### 认证接口 + +#### 用户登录 +``` +POST /api/auth/login +Content-Type: application/json + +Request Body: +{ + "username": "admin", + "password": "123456" +} + +Response: +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "TOKEN_1_1704614400000", + "userInfo": { + "userId": 1, + "username": "admin", + "realName": "系统管理员", + "phone": "13800138000", + "email": "admin@carmaintenance.com", + "role": "admin" + } + } +} +``` + +### 车辆管理接口 + +#### 获取所有车辆 +``` +GET /api/vehicles +Authorization: Bearer {token} + +Response: +{ + "code": 200, + "message": "操作成功", + "data": [...] +} +``` + +#### 根据ID获取车辆 +``` +GET /api/vehicles/{id} +Authorization: Bearer {token} +``` + +#### 创建车辆 +``` +POST /api/vehicles +Content-Type: application/json +Authorization: Bearer {token} + +Request Body: +{ + "customerId": 1, + "licensePlate": "京A12345", + "brand": "奔驰", + "model": "C200L", + "color": "黑色", + "vin": "LBVCU31J7DR123456" +} +``` + +### 工单管理接口 + +#### 获取所有工单 +``` +GET /api/orders +Authorization: Bearer {token} +``` + +#### 创建工单 +``` +POST /api/orders +Content-Type: application/json +Authorization: Bearer {token} + +Request Body: +{ + "vehicleId": 1, + "customerId": 1, + "serviceType": "maintenance", + "appointmentTime": "2025-01-10T09:00:00", + "faultDescription": "车辆到5000公里,需要小保养" +} +``` + +--- + +## 常见问题 + +### 1. 后端启动失败 + +**问题**: 端口被占用 +**解决**: 修改 `application.properties` 中的 `server.port` 为其他端口 + +**问题**: 数据库连接失败 +**解决**: 检查MySQL是否启动,确认用户名密码是否正确 + +### 2. 前端无法连接后端 + +**问题**: CORS跨域错误 +**解决**: 检查 `WebConfig.java` 中的CORS配置,确保前端地址在允许列表中 + +**问题**: API请求404 +**解决**: 确认后端服务已启动,检查 `config.js` 中的 `BASE_URL` 配置 + +### 3. 登录失败 + +**问题**: 用户名或密码错误 +**解决**: 使用演示账号,或检查数据库中的用户数据 + +--- + +## 开发团队 + +**作者**: 杨璐 +**学校**: 辽宁科技学院 +**专业**: 计算机科学与技术 +**班级**: 计BZ246 +**指导教师**: 刘慧宇 + +--- + +## 许可证 + +本项目为毕业设计作品,仅供学习和研究使用。 + +--- + +## 更新日志 + +### v1.0.0 (2025-01-07) +- 完成系统架构设计 +- 实现用户认证功能 +- 实现车辆档案管理 +- 实现维保工单管理 +- 实现配件库存管理 +- 实现预约管理功能 +- 完成前端页面设计 +- 完成数据库设计 + +--- + +## 联系方式 + +如有问题或建议,请联系: + +- 邮箱: 学生邮箱 +- 学校: 辽宁科技学院 +- 院系: 电子与信息工程学院 + +--- + +**祝使用愉快!** diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 0000000..944f905 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,113 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + com.carmaintenance + car-maintenance-system + 1.0.0 + 车管家4S店车辆维保管理系统 + 基于Spring Boot的车辆维保管理系统后端服务 + + + 1.8 + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + mysql + mysql-connector-java + 8.0.33 + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.projectlombok + lombok + true + + + + + com.alibaba + fastjson + 1.2.83 + + + + + org.apache.commons + commons-lang3 + + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/backend/src/main/java/com/carmaintenance/CarMaintenanceApplication.java b/backend/src/main/java/com/carmaintenance/CarMaintenanceApplication.java new file mode 100644 index 0000000..55b74f1 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/CarMaintenanceApplication.java @@ -0,0 +1,23 @@ +package com.carmaintenance; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +/** + * 车管家4S店车辆维保管理系统 - 主应用程序 + * @author 杨璐 + * @date 2025-01-07 + */ +@SpringBootApplication +@EnableJpaAuditing +public class CarMaintenanceApplication { + + public static void main(String[] args) { + SpringApplication.run(CarMaintenanceApplication.class, args); + System.out.println("\n========================================"); + System.out.println("车管家4S店车辆维保管理系统启动成功!"); + System.out.println("访问地址: http://localhost:8080/api"); + System.out.println("========================================\n"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/config/WebConfig.java b/backend/src/main/java/com/carmaintenance/config/WebConfig.java new file mode 100644 index 0000000..701f2ce --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/config/WebConfig.java @@ -0,0 +1,35 @@ +package com.carmaintenance.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Web配置 - CORS跨域配置 + */ +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Value("${cors.allowed-origins}") + private String allowedOrigins; + + @Value("${cors.allowed-methods}") + private String allowedMethods; + + @Value("${cors.allowed-headers}") + private String allowedHeaders; + + @Value("${cors.allow-credentials}") + private boolean allowCredentials; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns(allowedOrigins.split(",")) + .allowedMethods(allowedMethods.split(",")) + .allowedHeaders(allowedHeaders.split(",")) + .allowCredentials(allowCredentials) + .maxAge(3600); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/AppointmentController.java b/backend/src/main/java/com/carmaintenance/controller/AppointmentController.java new file mode 100644 index 0000000..648592b --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/AppointmentController.java @@ -0,0 +1,127 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.Appointment; +import com.carmaintenance.repository.AppointmentRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 预约管理控制器 + */ +@RestController +@RequestMapping("/appointments") +@CrossOrigin +public class AppointmentController { + + @Autowired + private AppointmentRepository appointmentRepository; + + /** + * 获取所有预约 + */ + @GetMapping + public Result> getAllAppointments() { + List appointments = appointmentRepository.findAll(); + return Result.success(appointments); + } + + /** + * 根据ID获取预约 + */ + @GetMapping("/{id}") + public Result getAppointmentById(@PathVariable Integer id) { + Appointment appointment = appointmentRepository.findById(id).orElse(null); + if (appointment == null) { + return Result.notFound("预约不存在"); + } + return Result.success(appointment); + } + + /** + * 根据客户ID获取预约列表 + */ + @GetMapping("/customer/{customerId}") + public Result> getAppointmentsByCustomerId(@PathVariable Integer customerId) { + List appointments = appointmentRepository.findByCustomerId(customerId); + return Result.success(appointments); + } + + /** + * 根据状态获取预约列表 + */ + @GetMapping("/status/{status}") + public Result> getAppointmentsByStatus(@PathVariable String status) { + try { + Appointment.AppointmentStatus appointmentStatus = Appointment.AppointmentStatus.valueOf(status); + List appointments = appointmentRepository.findByStatus(appointmentStatus); + return Result.success(appointments); + } catch (IllegalArgumentException e) { + return Result.error("状态参数错误"); + } + } + + /** + * 创建预约 + */ + @PostMapping + public Result createAppointment(@RequestBody Appointment appointment) { + appointment.setStatus(Appointment.AppointmentStatus.pending); + Appointment savedAppointment = appointmentRepository.save(appointment); + return Result.success("预约成功", savedAppointment); + } + + /** + * 更新预约 + */ + @PutMapping("/{id}") + public Result updateAppointment(@PathVariable Integer id, @RequestBody Appointment appointment) { + Appointment existingAppointment = appointmentRepository.findById(id).orElse(null); + if (existingAppointment == null) { + return Result.notFound("预约不存在"); + } + + if (appointment.getAppointmentTime() != null) + existingAppointment.setAppointmentTime(appointment.getAppointmentTime()); + if (appointment.getContactPhone() != null) + existingAppointment.setContactPhone(appointment.getContactPhone()); + if (appointment.getDescription() != null) + existingAppointment.setDescription(appointment.getDescription()); + if (appointment.getStatus() != null) + existingAppointment.setStatus(appointment.getStatus()); + if (appointment.getOrderId() != null) + existingAppointment.setOrderId(appointment.getOrderId()); + + Appointment updatedAppointment = appointmentRepository.save(existingAppointment); + return Result.success("更新成功", updatedAppointment); + } + + /** + * 取消预约 + */ + @PutMapping("/{id}/cancel") + public Result cancelAppointment(@PathVariable Integer id) { + Appointment appointment = appointmentRepository.findById(id).orElse(null); + if (appointment == null) { + return Result.notFound("预约不存在"); + } + + appointment.setStatus(Appointment.AppointmentStatus.cancelled); + Appointment updatedAppointment = appointmentRepository.save(appointment); + return Result.success("取消成功", updatedAppointment); + } + + /** + * 删除预约 + */ + @DeleteMapping("/{id}") + public Result deleteAppointment(@PathVariable Integer id) { + if (!appointmentRepository.existsById(id)) { + return Result.notFound("预约不存在"); + } + appointmentRepository.deleteById(id); + return Result.success("删除成功"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/AuthController.java b/backend/src/main/java/com/carmaintenance/controller/AuthController.java new file mode 100644 index 0000000..3ec2079 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/AuthController.java @@ -0,0 +1,87 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.LoginRequest; +import com.carmaintenance.dto.LoginResponse; +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.User; +import com.carmaintenance.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 认证控制器 + */ +@RestController +@RequestMapping("/auth") +@CrossOrigin +public class AuthController { + + @Autowired + private UserRepository userRepository; + + /** + * 用户登录 + */ + @PostMapping("/login") + public Result login(@RequestBody LoginRequest request) { + if (request.getUsername() == null || request.getPassword() == null) { + return Result.error("用户名和密码不能为空"); + } + + User user = userRepository.findByUsernameAndPassword( + request.getUsername(), + request.getPassword() + ).orElse(null); + + if (user == null) { + return Result.error("用户名或密码错误"); + } + + if (user.getStatus() != 1) { + return Result.error("账号已被禁用"); + } + + LoginResponse.UserInfo userInfo = new LoginResponse.UserInfo( + user.getUserId(), + user.getUsername(), + user.getRealName(), + user.getPhone(), + user.getEmail(), + user.getRole().name() + ); + + String token = "TOKEN_" + user.getUserId() + "_" + System.currentTimeMillis(); + + LoginResponse response = new LoginResponse(token, userInfo); + return Result.success("登录成功", response); + } + + /** + * 用户注册 + */ + @PostMapping("/register") + public Result register(@RequestBody User user) { + if (userRepository.existsByUsername(user.getUsername())) { + return Result.error("用户名已存在"); + } + + if (userRepository.existsByPhone(user.getPhone())) { + return Result.error("手机号已被注册"); + } + + user.setRole(User.UserRole.customer); + user.setStatus(1); + User savedUser = userRepository.save(user); + savedUser.setPassword(null); + + return Result.success("注册成功", savedUser); + } + + /** + * 退出登录 + */ + @PostMapping("/logout") + public Result logout() { + return Result.success("退出成功"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/PartsInventoryController.java b/backend/src/main/java/com/carmaintenance/controller/PartsInventoryController.java new file mode 100644 index 0000000..f20b75f --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/PartsInventoryController.java @@ -0,0 +1,114 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.PartsInventory; +import com.carmaintenance.repository.PartsInventoryRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 配件库存管理控制器 + */ +@RestController +@RequestMapping("/parts") +@CrossOrigin +public class PartsInventoryController { + + @Autowired + private PartsInventoryRepository partsInventoryRepository; + + /** + * 获取所有配件 + */ + @GetMapping + public Result> getAllParts() { + List parts = partsInventoryRepository.findAll(); + return Result.success(parts); + } + + /** + * 根据ID获取配件 + */ + @GetMapping("/{id}") + public Result getPartById(@PathVariable Integer id) { + PartsInventory part = partsInventoryRepository.findById(id).orElse(null); + if (part == null) { + return Result.notFound("配件不存在"); + } + return Result.success(part); + } + + /** + * 根据类别获取配件列表 + */ + @GetMapping("/category/{category}") + public Result> getPartsByCategory(@PathVariable String category) { + List parts = partsInventoryRepository.findByCategory(category); + return Result.success(parts); + } + + /** + * 获取库存预警配件 + */ + @GetMapping("/low-stock") + public Result> getLowStockParts() { + List allParts = partsInventoryRepository.findAll(); + List lowStockParts = allParts.stream() + .filter(part -> part.getStockQuantity() <= part.getMinStock()) + .collect(java.util.stream.Collectors.toList()); + return Result.success(lowStockParts); + } + + /** + * 创建配件 + */ + @PostMapping + public Result createPart(@RequestBody PartsInventory part) { + if (partsInventoryRepository.findByPartNo(part.getPartNo()).isPresent()) { + return Result.error("配件编号已存在"); + } + PartsInventory savedPart = partsInventoryRepository.save(part); + return Result.success("创建成功", savedPart); + } + + /** + * 更新配件 + */ + @PutMapping("/{id}") + public Result updatePart(@PathVariable Integer id, @RequestBody PartsInventory part) { + PartsInventory existingPart = partsInventoryRepository.findById(id).orElse(null); + if (existingPart == null) { + return Result.notFound("配件不存在"); + } + + if (part.getPartName() != null) existingPart.setPartName(part.getPartName()); + if (part.getCategory() != null) existingPart.setCategory(part.getCategory()); + if (part.getBrand() != null) existingPart.setBrand(part.getBrand()); + if (part.getModel() != null) existingPart.setModel(part.getModel()); + if (part.getUnit() != null) existingPart.setUnit(part.getUnit()); + if (part.getUnitPrice() != null) existingPart.setUnitPrice(part.getUnitPrice()); + if (part.getStockQuantity() != null) existingPart.setStockQuantity(part.getStockQuantity()); + if (part.getMinStock() != null) existingPart.setMinStock(part.getMinStock()); + if (part.getSupplier() != null) existingPart.setSupplier(part.getSupplier()); + if (part.getWarehouseLocation() != null) existingPart.setWarehouseLocation(part.getWarehouseLocation()); + if (part.getStatus() != null) existingPart.setStatus(part.getStatus()); + if (part.getRemark() != null) existingPart.setRemark(part.getRemark()); + + PartsInventory updatedPart = partsInventoryRepository.save(existingPart); + return Result.success("更新成功", updatedPart); + } + + /** + * 删除配件 + */ + @DeleteMapping("/{id}") + public Result deletePart(@PathVariable Integer id) { + if (!partsInventoryRepository.existsById(id)) { + return Result.notFound("配件不存在"); + } + partsInventoryRepository.deleteById(id); + return Result.success("删除成功"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/ServiceOrderController.java b/backend/src/main/java/com/carmaintenance/controller/ServiceOrderController.java new file mode 100644 index 0000000..05cc562 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/ServiceOrderController.java @@ -0,0 +1,140 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.ServiceOrder; +import com.carmaintenance.repository.ServiceOrderRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 维保工单管理控制器 + */ +@RestController +@RequestMapping("/orders") +@CrossOrigin +public class ServiceOrderController { + + @Autowired + private ServiceOrderRepository serviceOrderRepository; + + /** + * 获取所有工单 + */ + @GetMapping + public Result> getAllOrders() { + List orders = serviceOrderRepository.findAll(); + return Result.success(orders); + } + + /** + * 根据ID获取工单 + */ + @GetMapping("/{id}") + public Result getOrderById(@PathVariable Integer id) { + ServiceOrder order = serviceOrderRepository.findById(id).orElse(null); + if (order == null) { + return Result.notFound("工单不存在"); + } + return Result.success(order); + } + + /** + * 根据客户ID获取工单列表 + */ + @GetMapping("/customer/{customerId}") + public Result> getOrdersByCustomerId(@PathVariable Integer customerId) { + List orders = serviceOrderRepository.findByCustomerId(customerId); + return Result.success(orders); + } + + /** + * 根据车辆ID获取工单列表 + */ + @GetMapping("/vehicle/{vehicleId}") + public Result> getOrdersByVehicleId(@PathVariable Integer vehicleId) { + List orders = serviceOrderRepository.findByVehicleId(vehicleId); + return Result.success(orders); + } + + /** + * 根据状态获取工单列表 + */ + @GetMapping("/status/{status}") + public Result> getOrdersByStatus(@PathVariable String status) { + try { + ServiceOrder.OrderStatus orderStatus = ServiceOrder.OrderStatus.valueOf(status); + List orders = serviceOrderRepository.findByStatus(orderStatus); + return Result.success(orders); + } catch (IllegalArgumentException e) { + return Result.error("状态参数错误"); + } + } + + /** + * 创建工单 + */ + @PostMapping + public Result createOrder(@RequestBody ServiceOrder order) { + String orderNo = generateOrderNo(); + order.setOrderNo(orderNo); + order.setStatus(ServiceOrder.OrderStatus.pending); + order.setPaymentStatus(ServiceOrder.PaymentStatus.unpaid); + + ServiceOrder savedOrder = serviceOrderRepository.save(order); + return Result.success("工单创建成功", savedOrder); + } + + /** + * 更新工单 + */ + @PutMapping("/{id}") + public Result updateOrder(@PathVariable Integer id, @RequestBody ServiceOrder order) { + ServiceOrder existingOrder = serviceOrderRepository.findById(id).orElse(null); + if (existingOrder == null) { + return Result.notFound("工单不存在"); + } + + if (order.getStaffId() != null) existingOrder.setStaffId(order.getStaffId()); + if (order.getArrivalTime() != null) existingOrder.setArrivalTime(order.getArrivalTime()); + if (order.getStartTime() != null) existingOrder.setStartTime(order.getStartTime()); + if (order.getCompleteTime() != null) existingOrder.setCompleteTime(order.getCompleteTime()); + if (order.getCurrentMileage() != null) existingOrder.setCurrentMileage(order.getCurrentMileage()); + if (order.getFaultDescription() != null) existingOrder.setFaultDescription(order.getFaultDescription()); + if (order.getDiagnosisResult() != null) existingOrder.setDiagnosisResult(order.getDiagnosisResult()); + if (order.getServiceItems() != null) existingOrder.setServiceItems(order.getServiceItems()); + if (order.getPartsCost() != null) existingOrder.setPartsCost(order.getPartsCost()); + if (order.getLaborCost() != null) existingOrder.setLaborCost(order.getLaborCost()); + if (order.getTotalCost() != null) existingOrder.setTotalCost(order.getTotalCost()); + if (order.getStatus() != null) existingOrder.setStatus(order.getStatus()); + if (order.getPaymentStatus() != null) existingOrder.setPaymentStatus(order.getPaymentStatus()); + if (order.getRemark() != null) existingOrder.setRemark(order.getRemark()); + + ServiceOrder updatedOrder = serviceOrderRepository.save(existingOrder); + return Result.success("更新成功", updatedOrder); + } + + /** + * 删除工单 + */ + @DeleteMapping("/{id}") + public Result deleteOrder(@PathVariable Integer id) { + if (!serviceOrderRepository.existsById(id)) { + return Result.notFound("工单不存在"); + } + serviceOrderRepository.deleteById(id); + return Result.success("删除成功"); + } + + /** + * 生成工单编号 + */ + private String generateOrderNo() { + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + long count = serviceOrderRepository.count() + 1; + return "SO" + date + String.format("%04d", count); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/UserController.java b/backend/src/main/java/com/carmaintenance/controller/UserController.java new file mode 100644 index 0000000..731eccf --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/UserController.java @@ -0,0 +1,128 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.User; +import com.carmaintenance.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户管理控制器 + */ +@RestController +@RequestMapping("/users") +@CrossOrigin +public class UserController { + + @Autowired + private UserRepository userRepository; + + /** + * 获取所有用户 + */ + @GetMapping + public Result> getAllUsers() { + List users = userRepository.findAll(); + users.forEach(user -> user.setPassword(null)); + return Result.success(users); + } + + /** + * 根据ID获取用户 + */ + @GetMapping("/{id}") + public Result getUserById(@PathVariable Integer id) { + User user = userRepository.findById(id).orElse(null); + if (user == null) { + return Result.notFound("用户不存在"); + } + user.setPassword(null); + return Result.success(user); + } + + /** + * 根据角色获取用户列表 + */ + @GetMapping("/role/{role}") + public Result> getUsersByRole(@PathVariable String role) { + try { + User.UserRole userRole = User.UserRole.valueOf(role); + List users = userRepository.findByRole(userRole); + users.forEach(user -> user.setPassword(null)); + return Result.success(users); + } catch (IllegalArgumentException e) { + return Result.error("角色参数错误"); + } + } + + /** + * 创建用户 + */ + @PostMapping + public Result createUser(@RequestBody User user) { + if (userRepository.existsByUsername(user.getUsername())) { + return Result.error("用户名已存在"); + } + if (userRepository.existsByPhone(user.getPhone())) { + return Result.error("手机号已被使用"); + } + User savedUser = userRepository.save(user); + savedUser.setPassword(null); + return Result.success("创建成功", savedUser); + } + + /** + * 更新用户 + */ + @PutMapping("/{id}") + public Result updateUser(@PathVariable Integer id, @RequestBody User user) { + User existingUser = userRepository.findById(id).orElse(null); + if (existingUser == null) { + return Result.notFound("用户不存在"); + } + + if (user.getRealName() != null) existingUser.setRealName(user.getRealName()); + if (user.getPhone() != null) existingUser.setPhone(user.getPhone()); + if (user.getEmail() != null) existingUser.setEmail(user.getEmail()); + if (user.getStatus() != null) existingUser.setStatus(user.getStatus()); + + User updatedUser = userRepository.save(existingUser); + updatedUser.setPassword(null); + return Result.success("更新成功", updatedUser); + } + + /** + * 删除用户 + */ + @DeleteMapping("/{id}") + public Result deleteUser(@PathVariable Integer id) { + if (!userRepository.existsById(id)) { + return Result.notFound("用户不存在"); + } + userRepository.deleteById(id); + return Result.success("删除成功"); + } + + /** + * 修改密码 + */ + @PutMapping("/{id}/password") + public Result changePassword(@PathVariable Integer id, + @RequestParam String oldPassword, + @RequestParam String newPassword) { + User user = userRepository.findById(id).orElse(null); + if (user == null) { + return Result.notFound("用户不存在"); + } + + if (!user.getPassword().equals(oldPassword)) { + return Result.error("原密码错误"); + } + + user.setPassword(newPassword); + userRepository.save(user); + return Result.success("密码修改成功"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/controller/VehicleController.java b/backend/src/main/java/com/carmaintenance/controller/VehicleController.java new file mode 100644 index 0000000..ca7499a --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/controller/VehicleController.java @@ -0,0 +1,112 @@ +package com.carmaintenance.controller; + +import com.carmaintenance.dto.Result; +import com.carmaintenance.entity.Vehicle; +import com.carmaintenance.repository.VehicleRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 车辆管理控制器 + */ +@RestController +@RequestMapping("/vehicles") +@CrossOrigin +public class VehicleController { + + @Autowired + private VehicleRepository vehicleRepository; + + /** + * 获取所有车辆 + */ + @GetMapping + public Result> getAllVehicles() { + List vehicles = vehicleRepository.findAll(); + return Result.success(vehicles); + } + + /** + * 根据ID获取车辆 + */ + @GetMapping("/{id}") + public Result getVehicleById(@PathVariable Integer id) { + Vehicle vehicle = vehicleRepository.findById(id).orElse(null); + if (vehicle == null) { + return Result.notFound("车辆不存在"); + } + return Result.success(vehicle); + } + + /** + * 根据客户ID获取车辆列表 + */ + @GetMapping("/customer/{customerId}") + public Result> getVehiclesByCustomerId(@PathVariable Integer customerId) { + List vehicles = vehicleRepository.findByCustomerId(customerId); + return Result.success(vehicles); + } + + /** + * 根据车牌号查询车辆 + */ + @GetMapping("/plate/{licensePlate}") + public Result getVehicleByPlate(@PathVariable String licensePlate) { + Vehicle vehicle = vehicleRepository.findByLicensePlate(licensePlate).orElse(null); + if (vehicle == null) { + return Result.notFound("车辆不存在"); + } + return Result.success(vehicle); + } + + /** + * 创建车辆档案 + */ + @PostMapping + public Result createVehicle(@RequestBody Vehicle vehicle) { + if (vehicleRepository.findByLicensePlate(vehicle.getLicensePlate()).isPresent()) { + return Result.error("车牌号已存在"); + } + if (vehicle.getVin() != null && vehicleRepository.findByVin(vehicle.getVin()).isPresent()) { + return Result.error("车架号已存在"); + } + Vehicle savedVehicle = vehicleRepository.save(vehicle); + return Result.success("创建成功", savedVehicle); + } + + /** + * 更新车辆信息 + */ + @PutMapping("/{id}") + public Result updateVehicle(@PathVariable Integer id, @RequestBody Vehicle vehicle) { + Vehicle existingVehicle = vehicleRepository.findById(id).orElse(null); + if (existingVehicle == null) { + return Result.notFound("车辆不存在"); + } + + if (vehicle.getColor() != null) existingVehicle.setColor(vehicle.getColor()); + if (vehicle.getMileage() != null) existingVehicle.setMileage(vehicle.getMileage()); + if (vehicle.getLastMaintenanceDate() != null) + existingVehicle.setLastMaintenanceDate(vehicle.getLastMaintenanceDate()); + if (vehicle.getNextMaintenanceDate() != null) + existingVehicle.setNextMaintenanceDate(vehicle.getNextMaintenanceDate()); + if (vehicle.getStatus() != null) existingVehicle.setStatus(vehicle.getStatus()); + + Vehicle updatedVehicle = vehicleRepository.save(existingVehicle); + return Result.success("更新成功", updatedVehicle); + } + + /** + * 删除车辆 + */ + @DeleteMapping("/{id}") + public Result deleteVehicle(@PathVariable Integer id) { + if (!vehicleRepository.existsById(id)) { + return Result.notFound("车辆不存在"); + } + vehicleRepository.deleteById(id); + return Result.success("删除成功"); + } +} diff --git a/backend/src/main/java/com/carmaintenance/dto/LoginRequest.java b/backend/src/main/java/com/carmaintenance/dto/LoginRequest.java new file mode 100644 index 0000000..7c36d42 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/dto/LoginRequest.java @@ -0,0 +1,12 @@ +package com.carmaintenance.dto; + +import lombok.Data; + +/** + * 登录请求DTO + */ +@Data +public class LoginRequest { + private String username; + private String password; +} diff --git a/backend/src/main/java/com/carmaintenance/dto/LoginResponse.java b/backend/src/main/java/com/carmaintenance/dto/LoginResponse.java new file mode 100644 index 0000000..91004cf --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/dto/LoginResponse.java @@ -0,0 +1,28 @@ +package com.carmaintenance.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 登录响应DTO + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LoginResponse { + private String token; + private UserInfo userInfo; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class UserInfo { + private Integer userId; + private String username; + private String realName; + private String phone; + private String email; + private String role; + } +} diff --git a/backend/src/main/java/com/carmaintenance/dto/Result.java b/backend/src/main/java/com/carmaintenance/dto/Result.java new file mode 100644 index 0000000..48dc1cf --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/dto/Result.java @@ -0,0 +1,54 @@ +package com.carmaintenance.dto; + +import lombok.Data; + +/** + * 统一API响应结果 + */ +@Data +public class Result { + + private Integer code; + private String message; + private T data; + + private Result() {} + + private Result(Integer code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + public static Result success() { + return new Result<>(200, "操作成功", null); + } + + public static Result success(T data) { + return new Result<>(200, "操作成功", data); + } + + public static Result success(String message, T data) { + return new Result<>(200, message, data); + } + + public static Result error(String message) { + return new Result<>(500, message, null); + } + + public static Result error(Integer code, String message) { + return new Result<>(code, message, null); + } + + public static Result unauthorized(String message) { + return new Result<>(401, message, null); + } + + public static Result forbidden(String message) { + return new Result<>(403, message, null); + } + + public static Result notFound(String message) { + return new Result<>(404, message, null); + } +} diff --git a/backend/src/main/java/com/carmaintenance/entity/Appointment.java b/backend/src/main/java/com/carmaintenance/entity/Appointment.java new file mode 100644 index 0000000..0bb3435 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/Appointment.java @@ -0,0 +1,66 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 预约记录实体 + */ +@Data +@Entity +@Table(name = "appointments") +@EntityListeners(AuditingEntityListener.class) +public class Appointment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "appointment_id") + private Integer appointmentId; + + @Column(name = "customer_id", nullable = false) + private Integer customerId; + + @Column(name = "vehicle_id", nullable = false) + private Integer vehicleId; + + @Enumerated(EnumType.STRING) + @Column(name = "service_type", nullable = false) + private ServiceType serviceType; + + @Column(name = "appointment_time", nullable = false) + private LocalDateTime appointmentTime; + + @Column(name = "contact_phone", nullable = false, length = 20) + private String contactPhone; + + @Column(name = "description", columnDefinition = "TEXT") + private String description; + + @Enumerated(EnumType.STRING) + @Column(name = "status") + private AppointmentStatus status = AppointmentStatus.pending; + + @Column(name = "order_id") + private Integer orderId; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; + + public enum ServiceType { + maintenance, repair, beauty, insurance + } + + public enum AppointmentStatus { + pending, confirmed, completed, cancelled + } +} diff --git a/backend/src/main/java/com/carmaintenance/entity/Customer.java b/backend/src/main/java/com/carmaintenance/entity/Customer.java new file mode 100644 index 0000000..6c03f07 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/Customer.java @@ -0,0 +1,67 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 客户信息实体 + */ +@Data +@Entity +@Table(name = "customers") +@EntityListeners(AuditingEntityListener.class) +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "customer_id") + private Integer customerId; + + @Column(name = "user_id", nullable = false) + private Integer userId; + + @Column(name = "customer_no", unique = true, length = 50) + private String customerNo; + + @Column(name = "id_card", length = 18) + private String idCard; + + @Column(name = "address", length = 200) + private String address; + + @Enumerated(EnumType.STRING) + @Column(name = "gender") + private Gender gender; + + @Column(name = "birth_date") + private LocalDate birthDate; + + @Enumerated(EnumType.STRING) + @Column(name = "membership_level") + private MembershipLevel membershipLevel = MembershipLevel.normal; + + @Column(name = "points") + private Integer points = 0; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; + + public enum Gender { + male, female, other + } + + public enum MembershipLevel { + normal, silver, gold, platinum + } +} diff --git a/backend/src/main/java/com/carmaintenance/entity/PartsInventory.java b/backend/src/main/java/com/carmaintenance/entity/PartsInventory.java new file mode 100644 index 0000000..730cba5 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/PartsInventory.java @@ -0,0 +1,72 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 配件库存实体 + */ +@Data +@Entity +@Table(name = "parts_inventory") +@EntityListeners(AuditingEntityListener.class) +public class PartsInventory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "part_id") + private Integer partId; + + @Column(name = "part_no", nullable = false, unique = true, length = 50) + private String partNo; + + @Column(name = "part_name", nullable = false, length = 100) + private String partName; + + @Column(name = "category", length = 50) + private String category; + + @Column(name = "brand", length = 50) + private String brand; + + @Column(name = "model", length = 100) + private String model; + + @Column(name = "unit", length = 20) + private String unit = "个"; + + @Column(name = "unit_price", nullable = false, precision = 10, scale = 2) + private BigDecimal unitPrice; + + @Column(name = "stock_quantity") + private Integer stockQuantity = 0; + + @Column(name = "min_stock") + private Integer minStock = 10; + + @Column(name = "supplier", length = 100) + private String supplier; + + @Column(name = "warehouse_location", length = 50) + private String warehouseLocation; + + @Column(name = "status") + private Integer status = 1; + + @Column(name = "remark", columnDefinition = "TEXT") + private String remark; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/carmaintenance/entity/ServiceItem.java b/backend/src/main/java/com/carmaintenance/entity/ServiceItem.java new file mode 100644 index 0000000..007fce9 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/ServiceItem.java @@ -0,0 +1,54 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 服务项目实体 + */ +@Data +@Entity +@Table(name = "service_items") +@EntityListeners(AuditingEntityListener.class) +public class ServiceItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "item_id") + private Integer itemId; + + @Column(name = "item_code", nullable = false, unique = true, length = 50) + private String itemCode; + + @Column(name = "item_name", nullable = false, length = 100) + private String itemName; + + @Column(name = "category", length = 50) + private String category; + + @Column(name = "standard_price", nullable = false, precision = 10, scale = 2) + private BigDecimal standardPrice; + + @Column(name = "duration") + private Integer duration; + + @Column(name = "description", columnDefinition = "TEXT") + private String description; + + @Column(name = "status") + private Integer status = 1; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/backend/src/main/java/com/carmaintenance/entity/ServiceOrder.java b/backend/src/main/java/com/carmaintenance/entity/ServiceOrder.java new file mode 100644 index 0000000..963312d --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/ServiceOrder.java @@ -0,0 +1,105 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 维保工单实体 + */ +@Data +@Entity +@Table(name = "service_orders") +@EntityListeners(AuditingEntityListener.class) +public class ServiceOrder { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "order_id") + private Integer orderId; + + @Column(name = "order_no", nullable = false, unique = true, length = 50) + private String orderNo; + + @Column(name = "vehicle_id", nullable = false) + private Integer vehicleId; + + @Column(name = "customer_id", nullable = false) + private Integer customerId; + + @Enumerated(EnumType.STRING) + @Column(name = "service_type", nullable = false) + private ServiceType serviceType; + + @Column(name = "appointment_time") + private LocalDateTime appointmentTime; + + @Column(name = "arrival_time") + private LocalDateTime arrivalTime; + + @Column(name = "start_time") + private LocalDateTime startTime; + + @Column(name = "complete_time") + private LocalDateTime completeTime; + + @Column(name = "staff_id") + private Integer staffId; + + @Column(name = "current_mileage", precision = 10, scale = 2) + private BigDecimal currentMileage; + + @Column(name = "fault_description", columnDefinition = "TEXT") + private String faultDescription; + + @Column(name = "diagnosis_result", columnDefinition = "TEXT") + private String diagnosisResult; + + @Column(name = "service_items", columnDefinition = "TEXT") + private String serviceItems; + + @Column(name = "parts_cost", precision = 10, scale = 2) + private BigDecimal partsCost = BigDecimal.ZERO; + + @Column(name = "labor_cost", precision = 10, scale = 2) + private BigDecimal laborCost = BigDecimal.ZERO; + + @Column(name = "total_cost", precision = 10, scale = 2) + private BigDecimal totalCost = BigDecimal.ZERO; + + @Enumerated(EnumType.STRING) + @Column(name = "status") + private OrderStatus status = OrderStatus.pending; + + @Enumerated(EnumType.STRING) + @Column(name = "payment_status") + private PaymentStatus paymentStatus = PaymentStatus.unpaid; + + @Column(name = "remark", columnDefinition = "TEXT") + private String remark; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; + + public enum ServiceType { + maintenance, repair, beauty, insurance + } + + public enum OrderStatus { + pending, appointed, in_progress, completed, cancelled + } + + public enum PaymentStatus { + unpaid, paid, refunded + } +} diff --git a/backend/src/main/java/com/carmaintenance/entity/User.java b/backend/src/main/java/com/carmaintenance/entity/User.java new file mode 100644 index 0000000..ff1cb9e --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/User.java @@ -0,0 +1,58 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 用户实体 + */ +@Data +@Entity +@Table(name = "users") +@EntityListeners(AuditingEntityListener.class) +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_id") + private Integer userId; + + @Column(name = "username", nullable = false, unique = true, length = 50) + private String username; + + @Column(name = "password", nullable = false) + private String password; + + @Column(name = "real_name", nullable = false, length = 50) + private String realName; + + @Column(name = "phone", nullable = false, length = 20) + private String phone; + + @Column(name = "email", length = 100) + private String email; + + @Enumerated(EnumType.STRING) + @Column(name = "role", nullable = false) + private UserRole role; + + @Column(name = "status") + private Integer status = 1; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; + + public enum UserRole { + admin, staff, customer + } +} diff --git a/backend/src/main/java/com/carmaintenance/entity/Vehicle.java b/backend/src/main/java/com/carmaintenance/entity/Vehicle.java new file mode 100644 index 0000000..fc4df35 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/entity/Vehicle.java @@ -0,0 +1,75 @@ +package com.carmaintenance.entity; + +import lombok.Data; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 车辆档案实体 + */ +@Data +@Entity +@Table(name = "vehicles") +@EntityListeners(AuditingEntityListener.class) +public class Vehicle { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "vehicle_id") + private Integer vehicleId; + + @Column(name = "customer_id", nullable = false) + private Integer customerId; + + @Column(name = "license_plate", nullable = false, unique = true, length = 20) + private String licensePlate; + + @Column(name = "brand", nullable = false, length = 50) + private String brand; + + @Column(name = "model", nullable = false, length = 50) + private String model; + + @Column(name = "color", length = 20) + private String color; + + @Column(name = "vin", unique = true, length = 17) + private String vin; + + @Column(name = "engine_no", length = 50) + private String engineNo; + + @Column(name = "purchase_date") + private LocalDate purchaseDate; + + @Column(name = "mileage", precision = 10, scale = 2) + private BigDecimal mileage = BigDecimal.ZERO; + + @Column(name = "last_maintenance_date") + private LocalDate lastMaintenanceDate; + + @Column(name = "next_maintenance_date") + private LocalDate nextMaintenanceDate; + + @Enumerated(EnumType.STRING) + @Column(name = "status") + private VehicleStatus status = VehicleStatus.normal; + + @CreatedDate + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + @LastModifiedDate + @Column(name = "update_time") + private LocalDateTime updateTime; + + public enum VehicleStatus { + normal, in_service, completed + } +} diff --git a/backend/src/main/java/com/carmaintenance/repository/AppointmentRepository.java b/backend/src/main/java/com/carmaintenance/repository/AppointmentRepository.java new file mode 100644 index 0000000..95f9740 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/AppointmentRepository.java @@ -0,0 +1,20 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.Appointment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +/** + * 预约记录Repository + */ +@Repository +public interface AppointmentRepository extends JpaRepository { + + List findByCustomerId(Integer customerId); + + List findByVehicleId(Integer vehicleId); + + List findByStatus(Appointment.AppointmentStatus status); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/CustomerRepository.java b/backend/src/main/java/com/carmaintenance/repository/CustomerRepository.java new file mode 100644 index 0000000..6c1d560 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/CustomerRepository.java @@ -0,0 +1,18 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.Customer; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * 客户Repository + */ +@Repository +public interface CustomerRepository extends JpaRepository { + + Optional findByUserId(Integer userId); + + Optional findByCustomerNo(String customerNo); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/PartsInventoryRepository.java b/backend/src/main/java/com/carmaintenance/repository/PartsInventoryRepository.java new file mode 100644 index 0000000..dc590e2 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/PartsInventoryRepository.java @@ -0,0 +1,21 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.PartsInventory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 配件库存Repository + */ +@Repository +public interface PartsInventoryRepository extends JpaRepository { + + Optional findByPartNo(String partNo); + + List findByCategory(String category); + + List findByStatus(Integer status); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/ServiceItemRepository.java b/backend/src/main/java/com/carmaintenance/repository/ServiceItemRepository.java new file mode 100644 index 0000000..2b00f33 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/ServiceItemRepository.java @@ -0,0 +1,21 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.ServiceItem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 服务项目Repository + */ +@Repository +public interface ServiceItemRepository extends JpaRepository { + + Optional findByItemCode(String itemCode); + + List findByCategory(String category); + + List findByStatus(Integer status); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/ServiceOrderRepository.java b/backend/src/main/java/com/carmaintenance/repository/ServiceOrderRepository.java new file mode 100644 index 0000000..6ced3c7 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/ServiceOrderRepository.java @@ -0,0 +1,25 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.ServiceOrder; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 维保工单Repository + */ +@Repository +public interface ServiceOrderRepository extends JpaRepository { + + Optional findByOrderNo(String orderNo); + + List findByCustomerId(Integer customerId); + + List findByVehicleId(Integer vehicleId); + + List findByStaffId(Integer staffId); + + List findByStatus(ServiceOrder.OrderStatus status); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/UserRepository.java b/backend/src/main/java/com/carmaintenance/repository/UserRepository.java new file mode 100644 index 0000000..76c7c5c --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/UserRepository.java @@ -0,0 +1,25 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 用户Repository + */ +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); + + Optional findByUsernameAndPassword(String username, String password); + + List findByRole(User.UserRole role); + + boolean existsByUsername(String username); + + boolean existsByPhone(String phone); +} diff --git a/backend/src/main/java/com/carmaintenance/repository/VehicleRepository.java b/backend/src/main/java/com/carmaintenance/repository/VehicleRepository.java new file mode 100644 index 0000000..3b292f4 --- /dev/null +++ b/backend/src/main/java/com/carmaintenance/repository/VehicleRepository.java @@ -0,0 +1,21 @@ +package com.carmaintenance.repository; + +import com.carmaintenance.entity.Vehicle; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 车辆Repository + */ +@Repository +public interface VehicleRepository extends JpaRepository { + + List findByCustomerId(Integer customerId); + + Optional findByLicensePlate(String licensePlate); + + Optional findByVin(String vin); +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties new file mode 100644 index 0000000..b81269e --- /dev/null +++ b/backend/src/main/resources/application.properties @@ -0,0 +1,43 @@ +# 应用配置 +spring.application.name=car-maintenance-system +server.port=8080 +server.servlet.context-path=/api + +# 数据库配置 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/car_maintenance_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true +spring.datasource.username=root +spring.datasource.password=123456 + +# JPA配置 +spring.jpa.database=mysql +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.properties.hibernate.format_sql=true + +# 日志配置 +logging.level.root=INFO +logging.level.com.carmaintenance=DEBUG +logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n +logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + +# 文件上传配置 +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB + +# Jackson配置 +spring.jackson.date-format=yyyy-MM-dd HH:mm:ss +spring.jackson.time-zone=GMT+8 +spring.jackson.serialization.write-dates-as-timestamps=false + +# JWT配置 +jwt.secret=carMaintenanceSystemSecretKey2025 +jwt.expiration=86400000 + +# CORS跨域配置 +cors.allowed-origins=http://localhost:3000,http://localhost:5500,http://127.0.0.1:5500 +cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS +cors.allowed-headers=* +cors.allow-credentials=true diff --git a/database/data.sql b/database/data.sql new file mode 100644 index 0000000..3dbfb00 --- /dev/null +++ b/database/data.sql @@ -0,0 +1,79 @@ +-- 车管家4S店车辆维保管理系统 - 初始数据 +-- 注意:密码使用明文存储仅用于演示,实际应使用加密(如BCrypt) + +USE car_maintenance_db; + +-- 1. 插入用户数据 +INSERT INTO users (username, password, real_name, phone, email, role, status) VALUES +('admin', '123456', '系统管理员', '13800138000', 'admin@carmaintenance.com', 'admin', 1), +('staff001', '123456', '张维修', '13800138001', 'zhang@carmaintenance.com', 'staff', 1), +('staff002', '123456', '李技师', '13800138002', 'li@carmaintenance.com', 'staff', 1), +('staff003', '123456', '王顾问', '13800138003', 'wang@carmaintenance.com', 'staff', 1), +('customer001', '123456', '赵客户', '13900139001', 'zhao@example.com', 'customer', 1), +('customer002', '123456', '钱客户', '13900139002', 'qian@example.com', 'customer', 1), +('customer003', '123456', '孙客户', '13900139003', 'sun@example.com', 'customer', 1); + +-- 2. 插入客户信息 +INSERT INTO customers (user_id, customer_no, id_card, address, gender, birth_date, membership_level, points) VALUES +(5, 'C20250001', '110101199001011234', '北京市朝阳区建国路100号', 'male', '1990-01-01', 'gold', 5000), +(6, 'C20250002', '110101198505055678', '北京市海淀区中关村大街1号', 'female', '1985-05-05', 'silver', 2000), +(7, 'C20250003', '110101199207079012', '北京市东城区王府井大街50号', 'male', '1992-07-07', 'normal', 500); + +-- 3. 插入车辆档案 +INSERT INTO vehicles (customer_id, license_plate, brand, model, color, vin, engine_no, purchase_date, mileage, last_maintenance_date, next_maintenance_date, status) VALUES +(1, '京A12345', '奔驰', 'C200L', '黑色', 'LBVCU31J7DR123456', 'M274920123456', '2022-03-15', 25000.00, '2024-12-01', '2025-06-01', 'normal'), +(1, '京B88888', '宝马', 'X5', '白色', 'WBAJW5105BVZ12345', 'N20B20A1234567', '2021-06-20', 35000.00, '2024-11-15', '2025-05-15', 'normal'), +(2, '京C99999', '奥迪', 'A6L', '银色', 'LFVBA28K5D3123456', 'EA888123456789', '2023-01-10', 15000.00, '2024-12-20', '2025-06-20', 'normal'), +(3, '京D66666', '丰田', '凯美瑞', '蓝色', 'LVGBM53E5CG123456', '2AR-FE123456', '2022-08-05', 20000.00, '2024-10-10', '2025-04-10', 'normal'); + +-- 4. 插入服务项目 +INSERT INTO service_items (item_code, item_name, category, standard_price, duration, description, status) VALUES +('M001', '小保养', '保养维护', 500.00, 60, '更换机油、机滤,常规检查', 1), +('M002', '大保养', '保养维护', 1200.00, 120, '更换机油、三滤,全面检查', 1), +('M003', '刹车系统检查', '检测诊断', 200.00, 30, '检查刹车片、刹车盘、制动液', 1), +('R001', '更换刹车片', '维修更换', 800.00, 90, '更换前/后刹车片', 1), +('R002', '更换电瓶', '维修更换', 600.00, 30, '更换蓄电池', 1), +('R003', '空调系统维修', '维修更换', 1500.00, 180, '空调检测、加氟、维修', 1), +('B001', '精致洗车', '美容服务', 80.00, 30, '外观清洗、内饰简单清理', 1), +('B002', '深度美容', '美容服务', 500.00, 180, '全车深度清洁、打蜡、内饰护理', 1), +('I001', '车辆年检代办', '保险代理', 300.00, 30, '代办车辆年检服务', 1), +('I002', '保险续保', '保险代理', 0.00, 30, '车辆保险续保服务', 1); + +-- 5. 插入配件库存 +INSERT INTO parts_inventory (part_no, part_name, category, brand, model, unit, unit_price, stock_quantity, min_stock, supplier, warehouse_location, status) VALUES +('P001', '机油滤清器', '滤清器', '曼牌', '通用型', '个', 45.00, 100, 20, '北京汽配公司', 'A-01', 1), +('P002', '空气滤清器', '滤清器', '曼牌', '通用型', '个', 60.00, 80, 15, '北京汽配公司', 'A-02', 1), +('P003', '空调滤清器', '滤清器', '博世', '通用型', '个', 80.00, 60, 15, '博世配件公司', 'A-03', 1), +('P004', '汽油滤清器', '滤清器', '马勒', '通用型', '个', 120.00, 50, 10, '马勒配件公司', 'A-04', 1), +('P005', '全合成机油5W-30', '润滑油', '美孚', '4L装', '桶', 380.00, 200, 30, '美孚授权经销商', 'B-01', 1), +('P006', '刹车片(前)', '制动系统', '博世', '奔驰C级专用', '套', 650.00, 30, 5, '博世配件公司', 'C-01', 1), +('P007', '刹车片(后)', '制动系统', '博世', '奔驰C级专用', '套', 550.00, 30, 5, '博世配件公司', 'C-02', 1), +('P008', '蓄电池12V60AH', '电气系统', '瓦尔塔', '通用型', '个', 500.00, 40, 8, '瓦尔塔授权商', 'D-01', 1), +('P009', '火花塞', '点火系统', 'NGK', '通用型', '个', 80.00, 100, 20, 'NGK配件公司', 'E-01', 1), +('P010', '雨刷片', '易损件', '博世', '通用型', '对', 120.00, 80, 15, '博世配件公司', 'F-01', 1); + +-- 6. 插入预约记录 +INSERT INTO appointments (customer_id, vehicle_id, service_type, appointment_time, contact_phone, description, status) VALUES +(1, 1, 'maintenance', '2025-01-10 09:00:00', '13900139001', '车辆到5000公里,需要小保养', 'confirmed'), +(2, 3, 'repair', '2025-01-12 14:00:00', '13900139002', '空调不制冷,需要检修', 'pending'), +(3, 4, 'beauty', '2025-01-15 10:00:00', '13900139003', '准备年检,需要深度清洗', 'pending'); + +-- 7. 插入维保工单 +INSERT INTO service_orders (order_no, vehicle_id, customer_id, service_type, appointment_time, arrival_time, start_time, complete_time, staff_id, current_mileage, fault_description, diagnosis_result, service_items, parts_cost, labor_cost, total_cost, status, payment_status, remark) VALUES +('SO202501070001', 1, 1, 'maintenance', '2025-01-07 09:00:00', '2025-01-07 09:15:00', '2025-01-07 09:30:00', '2025-01-07 11:00:00', 2, 24500.00, '常规保养', '车辆状况良好,已完成小保养', '[{"code":"M001","name":"小保养","price":500}]', 545.00, 500.00, 1045.00, 'completed', 'paid', '客户满意'), +('SO202501070002', 3, 2, 'repair', '2025-01-07 14:00:00', '2025-01-07 14:10:00', '2025-01-07 14:30:00', NULL, 3, 15200.00, '发动机异响', '检查发现需要更换机油和机滤', '[{"code":"M001","name":"小保养","price":500}]', 425.00, 500.00, 925.00, 'in_progress', 'unpaid', '正在处理中'), +('SO202412200003', 2, 1, 'maintenance', '2024-12-20 10:00:00', '2024-12-20 10:20:00', '2024-12-20 10:30:00', '2024-12-20 13:00:00', 2, 34500.00, '大保养', '已完成大保养,更换三滤和机油', '[{"code":"M002","name":"大保养","price":1200}]', 1085.00, 1200.00, 2285.00, 'completed', 'paid', '服务优质'); + +-- 8. 插入配件使用记录 +INSERT INTO parts_usage (order_id, part_id, quantity, unit_price, total_price) VALUES +(1, 1, 1, 45.00, 45.00), +(1, 5, 1, 380.00, 380.00), +(1, 10, 1, 120.00, 120.00), +(2, 1, 1, 45.00, 45.00), +(2, 5, 1, 380.00, 380.00), +(3, 1, 1, 45.00, 45.00), +(3, 2, 1, 60.00, 60.00), +(3, 3, 1, 80.00, 80.00), +(3, 4, 1, 120.00, 120.00), +(3, 5, 2, 380.00, 760.00), +(3, 9, 4, 20.00, 80.00); diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..98370ce --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,187 @@ +-- 车管家4S店车辆维保管理系统数据库设计 +-- Database: car_maintenance_db + +CREATE DATABASE IF NOT EXISTS car_maintenance_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE car_maintenance_db; + +-- 1. 用户表 (users) +CREATE TABLE IF NOT EXISTS users ( + user_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', + username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', + password VARCHAR(255) NOT NULL COMMENT '密码(加密)', + real_name VARCHAR(50) NOT NULL COMMENT '真实姓名', + phone VARCHAR(20) NOT NULL COMMENT '联系电话', + email VARCHAR(100) COMMENT '电子邮箱', + role ENUM('admin', 'staff', 'customer') NOT NULL COMMENT '角色:管理员/工作人员/客户', + status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_username (username), + INDEX idx_role (role), + INDEX idx_phone (phone) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +-- 2. 客户信息表 (customers) +CREATE TABLE IF NOT EXISTS customers ( + customer_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户ID', + user_id INT NOT NULL COMMENT '关联用户ID', + customer_no VARCHAR(50) UNIQUE COMMENT '客户编号', + id_card VARCHAR(18) COMMENT '身份证号', + address VARCHAR(200) COMMENT '联系地址', + gender ENUM('male', 'female', 'other') COMMENT '性别', + birth_date DATE COMMENT '出生日期', + membership_level ENUM('normal', 'silver', 'gold', 'platinum') DEFAULT 'normal' COMMENT '会员等级', + points INT DEFAULT 0 COMMENT '积分', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE, + INDEX idx_customer_no (customer_no), + INDEX idx_membership (membership_level) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户信息表'; + +-- 3. 车辆档案表 (vehicles) +CREATE TABLE IF NOT EXISTS vehicles ( + vehicle_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '车辆ID', + customer_id INT NOT NULL COMMENT '车主客户ID', + license_plate VARCHAR(20) NOT NULL UNIQUE COMMENT '车牌号', + brand VARCHAR(50) NOT NULL COMMENT '品牌', + model VARCHAR(50) NOT NULL COMMENT '型号', + color VARCHAR(20) COMMENT '颜色', + vin VARCHAR(17) UNIQUE COMMENT '车架号', + engine_no VARCHAR(50) COMMENT '发动机号', + purchase_date DATE COMMENT '购买日期', + mileage DECIMAL(10,2) DEFAULT 0 COMMENT '当前里程(公里)', + last_maintenance_date DATE COMMENT '上次保养日期', + next_maintenance_date DATE COMMENT '下次保养日期', + status ENUM('normal', 'in_service', 'completed') DEFAULT 'normal' COMMENT '状态', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE, + INDEX idx_license_plate (license_plate), + INDEX idx_customer (customer_id), + INDEX idx_vin (vin) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆档案表'; + +-- 4. 维保工单表 (service_orders) +CREATE TABLE IF NOT EXISTS service_orders ( + order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '工单ID', + order_no VARCHAR(50) NOT NULL UNIQUE COMMENT '工单编号', + vehicle_id INT NOT NULL COMMENT '车辆ID', + customer_id INT NOT NULL COMMENT '客户ID', + service_type ENUM('maintenance', 'repair', 'beauty', 'insurance') NOT NULL COMMENT '服务类型', + appointment_time DATETIME COMMENT '预约时间', + arrival_time DATETIME COMMENT '到店时间', + start_time DATETIME COMMENT '开始时间', + complete_time DATETIME COMMENT '完成时间', + staff_id INT COMMENT '负责员工ID', + current_mileage DECIMAL(10,2) COMMENT '当前里程', + fault_description TEXT COMMENT '故障描述', + diagnosis_result TEXT COMMENT '诊断结果', + service_items TEXT COMMENT '服务项目(JSON格式)', + parts_cost DECIMAL(10,2) DEFAULT 0 COMMENT '配件费用', + labor_cost DECIMAL(10,2) DEFAULT 0 COMMENT '工时费用', + total_cost DECIMAL(10,2) DEFAULT 0 COMMENT '总费用', + status ENUM('pending', 'appointed', 'in_progress', 'completed', 'cancelled') DEFAULT 'pending' COMMENT '工单状态', + payment_status ENUM('unpaid', 'paid', 'refunded') DEFAULT 'unpaid' COMMENT '支付状态', + remark TEXT COMMENT '备注', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + FOREIGN KEY (vehicle_id) REFERENCES vehicles(vehicle_id) ON DELETE CASCADE, + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE, + FOREIGN KEY (staff_id) REFERENCES users(user_id) ON DELETE SET NULL, + INDEX idx_order_no (order_no), + INDEX idx_vehicle (vehicle_id), + INDEX idx_customer (customer_id), + INDEX idx_status (status), + INDEX idx_appointment (appointment_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维保工单表'; + +-- 5. 配件库存表 (parts_inventory) +CREATE TABLE IF NOT EXISTS parts_inventory ( + part_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '配件ID', + part_no VARCHAR(50) NOT NULL UNIQUE COMMENT '配件编号', + part_name VARCHAR(100) NOT NULL COMMENT '配件名称', + category VARCHAR(50) COMMENT '配件类别', + brand VARCHAR(50) COMMENT '品牌', + model VARCHAR(100) COMMENT '适用车型', + unit VARCHAR(20) DEFAULT '个' COMMENT '单位', + unit_price DECIMAL(10,2) NOT NULL COMMENT '单价', + stock_quantity INT DEFAULT 0 COMMENT '库存数量', + min_stock INT DEFAULT 10 COMMENT '最小库存预警', + supplier VARCHAR(100) COMMENT '供应商', + warehouse_location VARCHAR(50) COMMENT '仓库位置', + status TINYINT DEFAULT 1 COMMENT '状态:1-正常,0-停用', + remark TEXT COMMENT '备注', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_part_no (part_no), + INDEX idx_category (category), + INDEX idx_stock (stock_quantity) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配件库存表'; + +-- 6. 配件使用记录表 (parts_usage) +CREATE TABLE IF NOT EXISTS parts_usage ( + usage_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '使用记录ID', + order_id INT NOT NULL COMMENT '工单ID', + part_id INT NOT NULL COMMENT '配件ID', + quantity INT NOT NULL COMMENT '使用数量', + unit_price DECIMAL(10,2) NOT NULL COMMENT '单价', + total_price DECIMAL(10,2) NOT NULL COMMENT '总价', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + FOREIGN KEY (order_id) REFERENCES service_orders(order_id) ON DELETE CASCADE, + FOREIGN KEY (part_id) REFERENCES parts_inventory(part_id) ON DELETE CASCADE, + INDEX idx_order (order_id), + INDEX idx_part (part_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配件使用记录表'; + +-- 7. 预约记录表 (appointments) +CREATE TABLE IF NOT EXISTS appointments ( + appointment_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '预约ID', + customer_id INT NOT NULL COMMENT '客户ID', + vehicle_id INT NOT NULL COMMENT '车辆ID', + service_type ENUM('maintenance', 'repair', 'beauty', 'insurance') NOT NULL COMMENT '服务类型', + appointment_time DATETIME NOT NULL COMMENT '预约时间', + contact_phone VARCHAR(20) NOT NULL COMMENT '联系电话', + description TEXT COMMENT '预约说明', + status ENUM('pending', 'confirmed', 'completed', 'cancelled') DEFAULT 'pending' COMMENT '预约状态', + order_id INT COMMENT '关联工单ID', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE, + FOREIGN KEY (vehicle_id) REFERENCES vehicles(vehicle_id) ON DELETE CASCADE, + FOREIGN KEY (order_id) REFERENCES service_orders(order_id) ON DELETE SET NULL, + INDEX idx_customer (customer_id), + INDEX idx_appointment_time (appointment_time), + INDEX idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表'; + +-- 8. 服务项目表 (service_items) +CREATE TABLE IF NOT EXISTS service_items ( + item_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '服务项目ID', + item_code VARCHAR(50) NOT NULL UNIQUE COMMENT '项目编码', + item_name VARCHAR(100) NOT NULL COMMENT '项目名称', + category VARCHAR(50) COMMENT '项目类别', + standard_price DECIMAL(10,2) NOT NULL COMMENT '标准价格', + duration INT COMMENT '标准工时(分钟)', + description TEXT COMMENT '项目描述', + status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-停用', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_item_code (item_code), + INDEX idx_category (category) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务项目表'; + +-- 9. 系统日志表 (system_logs) +CREATE TABLE IF NOT EXISTS system_logs ( + log_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '日志ID', + user_id INT COMMENT '用户ID', + operation VARCHAR(100) NOT NULL COMMENT '操作类型', + module VARCHAR(50) COMMENT '模块名称', + method VARCHAR(200) COMMENT '方法名', + params TEXT COMMENT '请求参数', + ip_address VARCHAR(50) COMMENT 'IP地址', + create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + INDEX idx_user (user_id), + INDEX idx_create_time (create_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表'; diff --git a/frontend/admin/dashboard.html b/frontend/admin/dashboard.html new file mode 100644 index 0000000..97f5196 --- /dev/null +++ b/frontend/admin/dashboard.html @@ -0,0 +1,300 @@ + + + + + + 管理员仪表板 - 车管家4S店车辆维保管理系统 + + + + +
+ + + + +
+ +
+
+

管理员仪表板

+
+
+ + +
+
+ + +
+
+
+
👥
+
+

0

+

用户总数

+
+
+
+
🚗
+
+

0

+

车辆总数

+
+
+
+
📋
+
+

0

+

工单总数

+
+
+
+
🔧
+
+

0

+

库存预警

+
+
+
+ +
+
+

最近工单

+
+
+ + + + + + + + + + + + + +
工单编号服务类型车牌号状态创建时间
加载中...
+
+
+
+ + + + + + + + + + + + + + + +
+
+ + + + + + diff --git a/frontend/css/common.css b/frontend/css/common.css new file mode 100644 index 0000000..d7c1d5c --- /dev/null +++ b/frontend/css/common.css @@ -0,0 +1,377 @@ +/* 通用样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif; + font-size: 14px; + color: #333; + background-color: #f5f5f5; + line-height: 1.6; +} + +/* 容器 */ +.container { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +/* 卡片样式 */ +.card { + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + padding: 20px; + margin-bottom: 20px; +} + +.card-header { + border-bottom: 2px solid #1890ff; + padding-bottom: 10px; + margin-bottom: 20px; +} + +.card-header h2 { + font-size: 18px; + font-weight: 600; + color: #1890ff; +} + +.card-body { + padding: 10px 0; +} + +/* 按钮样式 */ +.btn { + display: inline-block; + padding: 8px 16px; + font-size: 14px; + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s; + text-decoration: none; + text-align: center; +} + +.btn-primary { + background-color: #1890ff; + color: #fff; +} + +.btn-primary:hover { + background-color: #40a9ff; +} + +.btn-success { + background-color: #52c41a; + color: #fff; +} + +.btn-success:hover { + background-color: #73d13d; +} + +.btn-danger { + background-color: #f5222d; + color: #fff; +} + +.btn-danger:hover { + background-color: #ff4d4f; +} + +.btn-warning { + background-color: #faad14; + color: #fff; +} + +.btn-warning:hover { + background-color: #ffc53d; +} + +.btn-info { + background-color: #13c2c2; + color: #fff; +} + +.btn-info:hover { + background-color: #36cfc9; +} + +.btn-secondary { + background-color: #d9d9d9; + color: #333; +} + +.btn-secondary:hover { + background-color: #bfbfbf; +} + +/* 表单样式 */ +.form-group { + margin-bottom: 15px; +} + +.form-group label { + display: block; + margin-bottom: 5px; + font-weight: 500; + color: #555; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 14px; + transition: border-color 0.3s; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: none; + border-color: #1890ff; +} + +.form-group textarea { + resize: vertical; + min-height: 80px; +} + +.form-row { + display: flex; + gap: 15px; +} + +.form-row .form-group { + flex: 1; +} + +/* 表格样式 */ +.table { + width: 100%; + border-collapse: collapse; + background: #fff; +} + +.table th, +.table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid #e8e8e8; +} + +.table th { + background-color: #fafafa; + font-weight: 600; + color: #333; +} + +.table tr:hover { + background-color: #f5f5f5; +} + +.table-actions { + display: flex; + gap: 8px; +} + +.table-actions button { + padding: 4px 12px; + font-size: 12px; +} + +/* 状态标签 */ +.badge { + display: inline-block; + padding: 2px 8px; + font-size: 12px; + border-radius: 4px; + font-weight: 500; +} + +.badge-success { + background-color: #f6ffed; + color: #52c41a; + border: 1px solid #b7eb8f; +} + +.badge-warning { + background-color: #fffbe6; + color: #faad14; + border: 1px solid #ffe58f; +} + +.badge-danger { + background-color: #fff1f0; + color: #f5222d; + border: 1px solid #ffa39e; +} + +.badge-info { + background-color: #e6f7ff; + color: #1890ff; + border: 1px solid #91d5ff; +} + +.badge-secondary { + background-color: #fafafa; + color: #595959; + border: 1px solid #d9d9d9; +} + +/* 模态框 */ +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + overflow-y: auto; +} + +.modal.active { + display: flex; + align-items: center; + justify-content: center; +} + +.modal-content { + background: #fff; + border-radius: 8px; + width: 90%; + max-width: 600px; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.modal-header { + padding: 16px 20px; + border-bottom: 1px solid #e8e8e8; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + font-size: 16px; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #999; +} + +.modal-close:hover { + color: #333; +} + +.modal-body { + padding: 20px; +} + +.modal-footer { + padding: 12px 20px; + border-top: 1px solid #e8e8e8; + text-align: right; + display: flex; + justify-content: flex-end; + gap: 10px; +} + +/* 警告框 */ +.alert { + padding: 12px 16px; + border-radius: 4px; + margin-bottom: 15px; +} + +.alert-success { + background-color: #f6ffed; + color: #52c41a; + border: 1px solid #b7eb8f; +} + +.alert-error { + background-color: #fff1f0; + color: #f5222d; + border: 1px solid #ffa39e; +} + +.alert-warning { + background-color: #fffbe6; + color: #faad14; + border: 1px solid #ffe58f; +} + +.alert-info { + background-color: #e6f7ff; + color: #1890ff; + border: 1px solid #91d5ff; +} + +/* 加载动画 */ +.loading { + display: inline-block; + width: 20px; + height: 20px; + border: 3px solid #f3f3f3; + border-top: 3px solid #1890ff; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* 空状态 */ +.empty-state { + text-align: center; + padding: 40px; + color: #999; +} + +.empty-state img { + width: 120px; + margin-bottom: 20px; + opacity: 0.5; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .container { + padding: 10px; + } + + .form-row { + flex-direction: column; + } + + .table { + font-size: 12px; + } + + .table th, + .table td { + padding: 8px; + } +} diff --git a/frontend/css/dashboard.css b/frontend/css/dashboard.css new file mode 100644 index 0000000..fb6ca76 --- /dev/null +++ b/frontend/css/dashboard.css @@ -0,0 +1,286 @@ +/* 仪表板通用样式 */ +.dashboard-container { + display: flex; + min-height: 100vh; + background-color: #f0f2f5; +} + +/* 侧边栏 */ +.sidebar { + width: 250px; + background: linear-gradient(180deg, #1890ff 0%, #0050b3 100%); + color: #fff; + position: fixed; + height: 100vh; + overflow-y: auto; + box-shadow: 2px 0 8px rgba(0,0,0,0.1); +} + +.sidebar-header { + padding: 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.sidebar-header h2 { + font-size: 18px; + margin-bottom: 5px; +} + +.sidebar-header p { + font-size: 12px; + opacity: 0.8; +} + +.sidebar-menu { + padding: 20px 0; +} + +.menu-item { + padding: 12px 20px; + cursor: pointer; + transition: all 0.3s; + display: flex; + align-items: center; + gap: 10px; + color: rgba(255, 255, 255, 0.85); + text-decoration: none; +} + +.menu-item:hover { + background-color: rgba(255, 255, 255, 0.1); + color: #fff; +} + +.menu-item.active { + background-color: rgba(255, 255, 255, 0.2); + color: #fff; + border-left: 3px solid #fff; +} + +.menu-icon { + font-size: 18px; +} + +/* 主内容区 */ +.main-content { + flex: 1; + margin-left: 250px; + padding: 20px; +} + +/* 顶部导航 */ +.top-nav { + background: #fff; + padding: 15px 20px; + border-radius: 8px; + margin-bottom: 20px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + display: flex; + justify-content: space-between; + align-items: center; +} + +.top-nav-left h1 { + font-size: 20px; + color: #333; +} + +.top-nav-right { + display: flex; + align-items: center; + gap: 20px; +} + +.user-info { + display: flex; + align-items: center; + gap: 10px; +} + +.user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; +} + +.user-name { + font-size: 14px; + color: #333; +} + +.btn-logout { + padding: 6px 16px; + background-color: #ff4d4f; + color: #fff; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 13px; +} + +.btn-logout:hover { + background-color: #ff7875; +} + +/* 统计卡片 */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-bottom: 20px; +} + +.stat-card { + background: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + display: flex; + align-items: center; + gap: 15px; + transition: transform 0.3s; +} + +.stat-card:hover { + transform: translateY(-5px); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +.stat-icon { + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 24px; + color: #fff; +} + +.stat-icon.blue { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.stat-icon.green { + background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%); +} + +.stat-icon.orange { + background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); +} + +.stat-icon.red { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.stat-info h3 { + font-size: 28px; + font-weight: 600; + margin-bottom: 5px; + color: #333; +} + +.stat-info p { + font-size: 13px; + color: #666; +} + +/* 工具栏 */ +.toolbar { + background: #fff; + padding: 15px 20px; + border-radius: 8px; + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); +} + +.toolbar-left { + display: flex; + gap: 10px; +} + +.search-box { + position: relative; +} + +.search-box input { + padding: 8px 35px 8px 12px; + border: 1px solid #d9d9d9; + border-radius: 4px; + width: 250px; +} + +.search-box button { + position: absolute; + right: 0; + top: 0; + padding: 8px 12px; + background: none; + border: none; + cursor: pointer; + color: #666; +} + +/* 内容卡片 */ +.content-card { + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + overflow: hidden; +} + +.content-header { + padding: 15px 20px; + border-bottom: 1px solid #e8e8e8; + display: flex; + justify-content: space-between; + align-items: center; +} + +.content-header h2 { + font-size: 16px; + font-weight: 600; +} + +.content-body { + padding: 20px; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .sidebar { + width: 60px; + } + + .sidebar-header h2, + .sidebar-header p, + .menu-item span { + display: none; + } + + .main-content { + margin-left: 60px; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .toolbar { + flex-direction: column; + gap: 10px; + } + + .search-box input { + width: 100%; + } +} diff --git a/frontend/css/login.css b/frontend/css/login.css new file mode 100644 index 0000000..578497b --- /dev/null +++ b/frontend/css/login.css @@ -0,0 +1,152 @@ +/* 登录页面样式 */ +body { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} + +.login-container { + width: 100%; + max-width: 450px; + padding: 20px; +} + +.login-box { + background: #fff; + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +.login-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + padding: 30px 20px; + text-align: center; +} + +.login-header h1 { + font-size: 24px; + margin-bottom: 8px; + font-weight: 600; +} + +.login-header p { + font-size: 12px; + opacity: 0.9; + font-weight: 300; +} + +.login-form { + padding: 30px; +} + +.login-form .form-group { + margin-bottom: 20px; +} + +.login-form .form-group label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: #333; +} + +.login-form input, +.login-form select { + width: 100%; + padding: 12px 15px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + transition: all 0.3s; +} + +.login-form input:focus, +.login-form select:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.btn-login { + width: 100%; + padding: 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + border: none; + border-radius: 6px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + margin-top: 10px; +} + +.btn-login:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); +} + +.btn-login:active { + transform: translateY(0); +} + +.login-footer { + margin-top: 20px; + text-align: center; + display: flex; + justify-content: space-between; +} + +.login-footer a { + color: #667eea; + text-decoration: none; + font-size: 13px; + transition: color 0.3s; +} + +.login-footer a:hover { + color: #764ba2; + text-decoration: underline; +} + +.demo-accounts { + background: #f8f9fa; + padding: 15px 30px; + border-top: 1px solid #e9ecef; +} + +.demo-accounts p { + font-size: 12px; + color: #666; + margin-bottom: 8px; + font-weight: 600; +} + +.demo-accounts ul { + list-style: none; +} + +.demo-accounts li { + font-size: 11px; + color: #888; + padding: 3px 0; +} + +/* 响应式设计 */ +@media (max-width: 480px) { + .login-container { + padding: 10px; + } + + .login-header h1 { + font-size: 20px; + } + + .login-form { + padding: 20px; + } +} diff --git a/frontend/customer/dashboard.html b/frontend/customer/dashboard.html new file mode 100644 index 0000000..3de6926 --- /dev/null +++ b/frontend/customer/dashboard.html @@ -0,0 +1,361 @@ + + + + + + 客户中心 - 车管家4S店车辆维保管理系统 + + + + +
+ + +
+
+
+

客户中心

+
+
+ + +
+
+ +
+
+
+

我的车辆

+
+
+
+

加载中...

+
+
+
+
+ + + + + + +
+
+ + + + + + diff --git a/frontend/js/admin-dashboard.js b/frontend/js/admin-dashboard.js new file mode 100644 index 0000000..8e768bf --- /dev/null +++ b/frontend/js/admin-dashboard.js @@ -0,0 +1,468 @@ +// 管理员仪表板JavaScript + +// 检查登录状态和权限 +if (!utils.checkAuth() || !utils.hasRole('admin')) { + window.location.href = '../login.html'; +} + +// 页面加载时初始化 +window.addEventListener('DOMContentLoaded', () => { + initializeDashboard(); + loadOverviewData(); +}); + +// 初始化仪表板 +function initializeDashboard() { + const user = utils.getCurrentUser(); + if (user) { + document.getElementById('userName').textContent = user.realName; + document.getElementById('userAvatar').textContent = user.realName.charAt(0); + } +} + +// 切换显示区域 +function showSection(sectionName) { + // 隐藏所有区域 + const sections = document.querySelectorAll('.section'); + sections.forEach(section => section.style.display = 'none'); + + // 移除所有菜单项的活动状态 + const menuItems = document.querySelectorAll('.menu-item'); + menuItems.forEach(item => item.classList.remove('active')); + + // 显示选中的区域 + document.getElementById(`${sectionName}-section`).style.display = 'block'; + + // 设置对应菜单项为活动状态 + event.currentTarget.classList.add('active'); + + // 加载对应数据 + switch(sectionName) { + case 'overview': + loadOverviewData(); + break; + case 'users': + loadUsers(); + break; + case 'vehicles': + loadVehicles(); + break; + case 'orders': + loadOrders(); + break; + case 'parts': + loadParts(); + break; + case 'appointments': + loadAppointments(); + break; + } +} + +// 加载概览数据 +async function loadOverviewData() { + try { + // 加载统计数据 + const [usersRes, vehiclesRes, ordersRes, partsRes] = await Promise.all([ + api.get(API_ENDPOINTS.USERS), + api.get(API_ENDPOINTS.VEHICLES), + api.get(API_ENDPOINTS.ORDERS), + api.get(API_ENDPOINTS.PARTS_LOW_STOCK) + ]); + + document.getElementById('totalUsers').textContent = usersRes.data?.length || 0; + document.getElementById('totalVehicles').textContent = vehiclesRes.data?.length || 0; + document.getElementById('totalOrders').textContent = ordersRes.data?.length || 0; + document.getElementById('lowStockParts').textContent = partsRes.data?.length || 0; + + // 加载最近工单 + if (ordersRes.data && ordersRes.data.length > 0) { + const recentOrders = ordersRes.data.slice(0, 5); + displayRecentOrders(recentOrders); + } + } catch (error) { + console.error('加载概览数据失败:', error); + } +} + +// 显示最近工单 +async function displayRecentOrders(orders) { + const tbody = document.getElementById('recentOrdersBody'); + if (orders.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + // 获取所有车辆信息 + const vehiclesRes = await api.get(API_ENDPOINTS.VEHICLES); + const vehicles = vehiclesRes.data || []; + + tbody.innerHTML = orders.map(order => { + const vehicle = vehicles.find(v => v.vehicleId === order.vehicleId); + return ` + + ${order.orderNo} + ${utils.getServiceTypeText(order.serviceType)} + ${vehicle ? vehicle.licensePlate : '-'} + ${utils.getStatusBadge(order.status)} + ${utils.formatDateTime(order.createTime)} + + `; + }).join(''); +} + +// 加载用户列表 +async function loadUsers() { + try { + const response = await api.get(API_ENDPOINTS.USERS); + if (response.code === 200 && response.data) { + displayUsers(response.data); + } + } catch (error) { + console.error('加载用户列表失败:', error); + utils.showError('加载用户列表失败'); + } +} + +// 显示用户列表 +function displayUsers(users) { + const tbody = document.getElementById('usersTableBody'); + if (users.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + tbody.innerHTML = users.map(user => ` + + ${user.userId} + ${user.username} + ${user.realName} + ${user.phone} + ${utils.getRoleText(user.role)} + ${user.status === 1 ? '启用' : '禁用'} + + + + + + + `).join(''); +} + +// 加载车辆列表 +async function loadVehicles() { + try { + const response = await api.get(API_ENDPOINTS.VEHICLES); + if (response.code === 200 && response.data) { + displayVehicles(response.data); + } + } catch (error) { + console.error('加载车辆列表失败:', error); + utils.showError('加载车辆列表失败'); + } +} + +// 显示车辆列表 +function displayVehicles(vehicles) { + const tbody = document.getElementById('vehiclesTableBody'); + if (vehicles.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + tbody.innerHTML = vehicles.map(vehicle => ` + + ${vehicle.licensePlate} + ${vehicle.brand} ${vehicle.model} + ${vehicle.color || '-'} + ${vehicle.mileage || 0} 公里 + ${utils.getStatusBadge(vehicle.status)} + + + + + + + `).join(''); +} + +// 加载工单列表 +async function loadOrders(status = '') { + try { + const url = status ? API_ENDPOINTS.ORDERS_BY_STATUS(status) : API_ENDPOINTS.ORDERS; + const response = await api.get(url); + if (response.code === 200 && response.data) { + displayOrders(response.data); + } + } catch (error) { + console.error('加载工单列表失败:', error); + utils.showError('加载工单列表失败'); + } +} + +// 显示工单列表 +async function displayOrders(orders) { + const tbody = document.getElementById('ordersTableBody'); + if (orders.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + const vehiclesRes = await api.get(API_ENDPOINTS.VEHICLES); + const vehicles = vehiclesRes.data || []; + + tbody.innerHTML = orders.map(order => { + const vehicle = vehicles.find(v => v.vehicleId === order.vehicleId); + return ` + + ${order.orderNo} + ${utils.getServiceTypeText(order.serviceType)} + ${vehicle ? vehicle.licensePlate : '-'} + ¥${order.totalCost || 0} + ${utils.getStatusBadge(order.status)} + ${utils.formatDateTime(order.createTime)} + + + + + + + `; + }).join(''); +} + +// 加载配件列表 +async function loadParts() { + try { + const response = await api.get(API_ENDPOINTS.PARTS); + if (response.code === 200 && response.data) { + displayParts(response.data); + } + } catch (error) { + console.error('加载配件列表失败:', error); + utils.showError('加载配件列表失败'); + } +} + +// 显示配件列表 +function displayParts(parts) { + const tbody = document.getElementById('partsTableBody'); + if (parts.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + tbody.innerHTML = parts.map(part => { + const isLowStock = part.stockQuantity <= part.minStock; + return ` + + ${part.partNo} + ${part.partName} + ${part.category || '-'} + ${part.stockQuantity} ${part.unit}${isLowStock ? ' 预警' : ''} + ¥${part.unitPrice} + ${part.status === 1 ? '正常' : '停用'} + + + + + + + `; + }).join(''); +} + +// 加载预约列表 +async function loadAppointments(status = '') { + try { + const url = status ? API_ENDPOINTS.APPOINTMENTS_BY_STATUS(status) : API_ENDPOINTS.APPOINTMENTS; + const response = await api.get(url); + if (response.code === 200 && response.data) { + displayAppointments(response.data); + } + } catch (error) { + console.error('加载预约列表失败:', error); + utils.showError('加载预约列表失败'); + } +} + +// 显示预约列表 +async function displayAppointments(appointments) { + const tbody = document.getElementById('appointmentsTableBody'); + if (appointments.length === 0) { + tbody.innerHTML = '暂无数据'; + return; + } + + const vehiclesRes = await api.get(API_ENDPOINTS.VEHICLES); + const vehicles = vehiclesRes.data || []; + + tbody.innerHTML = appointments.map(appointment => { + const vehicle = vehicles.find(v => v.vehicleId === appointment.vehicleId); + return ` + + ${appointment.appointmentId} + ${utils.getServiceTypeText(appointment.serviceType)} + ${vehicle ? vehicle.licensePlate : '-'} + ${utils.formatDateTime(appointment.appointmentTime)} + ${appointment.contactPhone} + ${utils.getStatusBadge(appointment.status)} + + + + + + `; + }).join(''); +} + +// 过滤工单 +function filterOrders() { + const status = document.getElementById('orderStatusFilter').value; + loadOrders(status); +} + +// 过滤预约 +function filterAppointments() { + const status = document.getElementById('appointmentStatusFilter').value; + loadAppointments(status); +} + +// 搜索用户 +function searchUsers() { + const keyword = document.getElementById('searchUser').value.toLowerCase(); + // 实现搜索逻辑 +} + +// 搜索车辆 +function searchVehicles() { + const keyword = document.getElementById('searchVehicle').value.toLowerCase(); + // 实现搜索逻辑 +} + +// 搜索配件 +function searchParts() { + const keyword = document.getElementById('searchPart').value.toLowerCase(); + // 实现搜索逻辑 +} + +// 显示低库存配件 +async function showLowStockParts() { + try { + const response = await api.get(API_ENDPOINTS.PARTS_LOW_STOCK); + if (response.code === 200 && response.data) { + displayParts(response.data); + utils.showSuccess(`找到 ${response.data.length} 个库存预警配件`); + } + } catch (error) { + console.error('加载库存预警失败:', error); + utils.showError('加载库存预警失败'); + } +} + +// 占位函数 - 实际项目中需要实现完整功能 +function showAddUserModal() { alert('添加用户功能'); } +function viewUser(id) { alert('查看用户: ' + id); } +function editUser(id) { alert('编辑用户: ' + id); } +async function deleteUser(id) { + if (utils.confirm('确定要删除此用户吗?')) { + try { + const response = await api.delete(API_ENDPOINTS.USER_BY_ID(id)); + if (response.code === 200) { + utils.showSuccess('删除成功'); + loadUsers(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('删除失败'); + } + } +} + +function showAddVehicleModal() { alert('添加车辆功能'); } +function viewVehicle(id) { alert('查看车辆: ' + id); } +function editVehicle(id) { alert('编辑车辆: ' + id); } +async function deleteVehicle(id) { + if (utils.confirm('确定要删除此车辆吗?')) { + try { + const response = await api.delete(API_ENDPOINTS.VEHICLE_BY_ID(id)); + if (response.code === 200) { + utils.showSuccess('删除成功'); + loadVehicles(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('删除失败'); + } + } +} + +function showAddOrderModal() { alert('创建工单功能'); } +function viewOrder(id) { alert('查看工单: ' + id); } +function editOrder(id) { alert('编辑工单: ' + id); } +async function deleteOrder(id) { + if (utils.confirm('确定要删除此工单吗?')) { + try { + const response = await api.delete(API_ENDPOINTS.ORDER_BY_ID(id)); + if (response.code === 200) { + utils.showSuccess('删除成功'); + loadOrders(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('删除失败'); + } + } +} + +function showAddPartModal() { alert('添加配件功能'); } +function viewPart(id) { alert('查看配件: ' + id); } +function editPart(id) { alert('编辑配件: ' + id); } +async function deletePart(id) { + if (utils.confirm('确定要删除此配件吗?')) { + try { + const response = await api.delete(API_ENDPOINTS.PART_BY_ID(id)); + if (response.code === 200) { + utils.showSuccess('删除成功'); + loadParts(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('删除失败'); + } + } +} + +async function confirmAppointment(id) { + try { + const response = await api.put(API_ENDPOINTS.APPOINTMENT_BY_ID(id), { status: 'confirmed' }); + if (response.code === 200) { + utils.showSuccess('预约已确认'); + loadAppointments(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('操作失败'); + } +} + +async function cancelAppointment(id) { + if (utils.confirm('确定要取消此预约吗?')) { + try { + const response = await api.put(API_ENDPOINTS.CANCEL_APPOINTMENT(id)); + if (response.code === 200) { + utils.showSuccess('预约已取消'); + loadAppointments(); + } else { + utils.showError(response.message); + } + } catch (error) { + utils.showError('操作失败'); + } + } +} diff --git a/frontend/js/api.js b/frontend/js/api.js new file mode 100644 index 0000000..d01f12a --- /dev/null +++ b/frontend/js/api.js @@ -0,0 +1,194 @@ +// API请求工具类 +class API { + constructor() { + this.baseURL = API_CONFIG.BASE_URL; + this.timeout = API_CONFIG.TIMEOUT; + } + + // 获取请求头 + getHeaders() { + const headers = { + 'Content-Type': 'application/json' + }; + + const token = localStorage.getItem(STORAGE_KEYS.TOKEN); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + return headers; + } + + // 通用请求方法 + async request(url, options = {}) { + const config = { + method: options.method || 'GET', + headers: this.getHeaders(), + ...options + }; + + if (options.body && typeof options.body === 'object') { + config.body = JSON.stringify(options.body); + } + + try { + const response = await fetch(this.baseURL + url, config); + const data = await response.json(); + + if (data.code === 401) { + this.handleUnauthorized(); + throw new Error('未授权,请重新登录'); + } + + return data; + } catch (error) { + console.error('API请求错误:', error); + throw error; + } + } + + // GET请求 + async get(url) { + return this.request(url, { method: 'GET' }); + } + + // POST请求 + async post(url, body) { + return this.request(url, { method: 'POST', body }); + } + + // PUT请求 + async put(url, body) { + return this.request(url, { method: 'PUT', body }); + } + + // DELETE请求 + async delete(url) { + return this.request(url, { method: 'DELETE' }); + } + + // 处理未授权情况 + handleUnauthorized() { + localStorage.removeItem(STORAGE_KEYS.TOKEN); + localStorage.removeItem(STORAGE_KEYS.USER_INFO); + window.location.href = 'login.html'; + } +} + +// 创建API实例 +const api = new API(); + +// 工具函数 +const utils = { + // 显示提示消息 + showMessage(message, type = 'info') { + alert(message); + }, + + // 显示成功消息 + showSuccess(message) { + this.showMessage(message, 'success'); + }, + + // 显示错误消息 + showError(message) { + this.showMessage(message, 'error'); + }, + + // 确认对话框 + confirm(message) { + return window.confirm(message); + }, + + // 格式化日期 + formatDate(dateString) { + if (!dateString) return '-'; + const date = new Date(dateString); + return date.toLocaleDateString('zh-CN'); + }, + + // 格式化日期时间 + formatDateTime(dateString) { + if (!dateString) return '-'; + const date = new Date(dateString); + return date.toLocaleString('zh-CN'); + }, + + // 获取当前用户信息 + getCurrentUser() { + const userStr = localStorage.getItem(STORAGE_KEYS.USER_INFO); + return userStr ? JSON.parse(userStr) : null; + }, + + // 检查用户角色 + hasRole(role) { + const user = this.getCurrentUser(); + return user && user.role === role; + }, + + // 退出登录 + logout() { + if (this.confirm('确定要退出登录吗?')) { + localStorage.removeItem(STORAGE_KEYS.TOKEN); + localStorage.removeItem(STORAGE_KEYS.USER_INFO); + window.location.href = 'login.html'; + } + }, + + // 检查登录状态 + checkAuth() { + const token = localStorage.getItem(STORAGE_KEYS.TOKEN); + if (!token) { + window.location.href = 'login.html'; + return false; + } + return true; + }, + + // 获取状态标签HTML + getStatusBadge(status, type) { + const badges = { + // 工单状态 + pending: '待处理', + appointed: '已预约', + in_progress: '进行中', + completed: '已完成', + cancelled: '已取消', + + // 支付状态 + unpaid: '未支付', + paid: '已支付', + refunded: '已退款', + + // 预约状态 + confirmed: '已确认', + + // 车辆状态 + normal: '正常', + in_service: '维修中' + }; + + return badges[status] || `${status}`; + }, + + // 获取服务类型文本 + getServiceTypeText(type) { + const types = { + maintenance: '保养维护', + repair: '维修服务', + beauty: '美容服务', + insurance: '保险代理' + }; + return types[type] || type; + }, + + // 获取用户角色文本 + getRoleText(role) { + const roles = { + admin: '管理员', + staff: '工作人员', + customer: '客户' + }; + return roles[role] || role; + } +}; diff --git a/frontend/js/config.js b/frontend/js/config.js new file mode 100644 index 0000000..3019a25 --- /dev/null +++ b/frontend/js/config.js @@ -0,0 +1,52 @@ +// API配置 +const API_CONFIG = { + BASE_URL: 'http://localhost:8080/api', + TIMEOUT: 30000 +}; + +// API端点 +const API_ENDPOINTS = { + // 认证相关 + LOGIN: '/auth/login', + LOGOUT: '/auth/logout', + REGISTER: '/auth/register', + + // 用户管理 + USERS: '/users', + USER_BY_ID: (id) => `/users/${id}`, + USERS_BY_ROLE: (role) => `/users/role/${role}`, + CHANGE_PASSWORD: (id) => `/users/${id}/password`, + + // 车辆管理 + VEHICLES: '/vehicles', + VEHICLE_BY_ID: (id) => `/vehicles/${id}`, + VEHICLES_BY_CUSTOMER: (customerId) => `/vehicles/customer/${customerId}`, + VEHICLE_BY_PLATE: (plate) => `/vehicles/plate/${plate}`, + + // 工单管理 + ORDERS: '/orders', + ORDER_BY_ID: (id) => `/orders/${id}`, + ORDERS_BY_CUSTOMER: (customerId) => `/orders/customer/${customerId}`, + ORDERS_BY_VEHICLE: (vehicleId) => `/orders/vehicle/${vehicleId}`, + ORDERS_BY_STATUS: (status) => `/orders/status/${status}`, + + // 配件管理 + PARTS: '/parts', + PART_BY_ID: (id) => `/parts/${id}`, + PARTS_BY_CATEGORY: (category) => `/parts/category/${category}`, + PARTS_LOW_STOCK: '/parts/low-stock', + + // 预约管理 + APPOINTMENTS: '/appointments', + APPOINTMENT_BY_ID: (id) => `/appointments/${id}`, + APPOINTMENTS_BY_CUSTOMER: (customerId) => `/appointments/customer/${customerId}`, + APPOINTMENTS_BY_STATUS: (status) => `/appointments/status/${status}`, + CANCEL_APPOINTMENT: (id) => `/appointments/${id}/cancel` +}; + +// 本地存储键名 +const STORAGE_KEYS = { + TOKEN: 'car_maintenance_token', + USER_INFO: 'car_maintenance_user', + REMEMBER_ME: 'car_maintenance_remember' +}; diff --git a/frontend/js/login.js b/frontend/js/login.js new file mode 100644 index 0000000..8ac9fa0 --- /dev/null +++ b/frontend/js/login.js @@ -0,0 +1,102 @@ +// 登录页面JavaScript + +// 页面加载时检查是否已登录 +window.addEventListener('DOMContentLoaded', () => { + const token = localStorage.getItem(STORAGE_KEYS.TOKEN); + if (token) { + const user = utils.getCurrentUser(); + if (user) { + redirectToDashboard(user.role); + } + } + + // 回车键登录 + document.getElementById('password').addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + handleLogin(); + } + }); +}); + +// 处理登录 +async function handleLogin() { + const username = document.getElementById('username').value.trim(); + const password = document.getElementById('password').value.trim(); + const role = document.getElementById('role').value; + + // 验证输入 + if (!username) { + utils.showError('请输入用户名'); + return; + } + + if (!password) { + utils.showError('请输入密码'); + return; + } + + // 禁用登录按钮 + const loginBtn = document.querySelector('.btn-login'); + const originalText = loginBtn.textContent; + loginBtn.disabled = true; + loginBtn.textContent = '登录中...'; + + try { + // 调用登录API + const response = await api.post(API_ENDPOINTS.LOGIN, { + username: username, + password: password + }); + + if (response.code === 200) { + const { token, userInfo } = response.data; + + // 验证角色 + if (userInfo.role !== role) { + utils.showError('登录角色不匹配,请选择正确的角色'); + loginBtn.disabled = false; + loginBtn.textContent = originalText; + return; + } + + // 保存登录信息 + localStorage.setItem(STORAGE_KEYS.TOKEN, token); + localStorage.setItem(STORAGE_KEYS.USER_INFO, JSON.stringify(userInfo)); + + utils.showSuccess('登录成功!'); + + // 延迟跳转以显示成功消息 + setTimeout(() => { + redirectToDashboard(userInfo.role); + }, 500); + + } else { + utils.showError(response.message || '登录失败'); + loginBtn.disabled = false; + loginBtn.textContent = originalText; + } + + } catch (error) { + console.error('登录错误:', error); + utils.showError('登录失败,请检查网络连接'); + loginBtn.disabled = false; + loginBtn.textContent = originalText; + } +} + +// 根据角色跳转到对应的仪表板 +function redirectToDashboard(role) { + switch (role) { + case 'admin': + window.location.href = 'admin/dashboard.html'; + break; + case 'staff': + window.location.href = 'staff/dashboard.html'; + break; + case 'customer': + window.location.href = 'customer/dashboard.html'; + break; + default: + utils.showError('未知的用户角色'); + } +} diff --git a/frontend/login.html b/frontend/login.html new file mode 100644 index 0000000..9b70249 --- /dev/null +++ b/frontend/login.html @@ -0,0 +1,61 @@ + + + + + + 车管家4S店车辆维保管理系统 - 登录 + + + + + + + + + + + diff --git a/frontend/staff/dashboard.html b/frontend/staff/dashboard.html new file mode 100644 index 0000000..c8cf050 --- /dev/null +++ b/frontend/staff/dashboard.html @@ -0,0 +1,270 @@ + + + + + + 工作人员仪表板 - 车管家4S店车辆维保管理系统 + + + + +
+ + +
+
+
+

工作人员仪表板

+
+
+ + +
+
+ +
+
+
+
📋
+
+

0

+

我的工单

+
+
+
+
+
+

0

+

进行中

+
+
+
+
+
+

0

+

已完成

+
+
+
+ +
+
+

待处理工单

+
+
+ + + + + + + + + + + + + + +
工单编号服务类型车牌号状态预约时间操作
暂无待处理工单
+
+
+
+ + + + + + +
+
+ + + + + + diff --git a/git-push.bat b/git-push.bat new file mode 100644 index 0000000..da78f39 --- /dev/null +++ b/git-push.bat @@ -0,0 +1,44 @@ +@echo off +echo ================================ +echo Git Repository Setup and Push +echo ================================ + +REM 1. Initialize Git repository +echo. +echo [1/6] Initializing Git repository... +git init + +REM 2. Configure Git user info +echo. +echo [2/6] Configuring Git user info... +git config user.name "wangziqi" +git config user.email "wangziqi@example.com" + +REM 3. Add all files +echo. +echo [3/6] Adding all files to staging area... +git add . + +REM 4. Create initial commit +echo. +echo [4/6] Creating initial commit... +git commit -m "Initial commit: Car Maintenance Management System" -m "Author: Yang Lu" -m "School: Liaoning Institute of Science and Technology" -m "Major: Computer Science and Technology" -m "Class: BZ246" -m "" -m "Tech Stack:" -m "- Backend: Spring Boot 2.7.18 + JPA + MySQL" -m "- Frontend: HTML5 + CSS3 + JavaScript" -m "" -m "Features:" -m "- User Management (Admin/Staff/Customer roles)" -m "- Vehicle Archive Management" -m "- Service Order Management" -m "- Parts Inventory Management" -m "- Online Appointment Service" -m "- Data Statistics and Analysis" -m "" -m "Generated with Claude Code" -m "Co-Authored-By: Claude Sonnet 4.5 " + +REM 5. Add remote repository +echo. +echo [5/6] Adding remote repository... +git remote add origin http://wangziqi:qq5211314@154.36.185.171:3003/car-maintenance-system.git + +REM 6. Push to remote repository +echo. +echo [6/6] Pushing to remote repository... +git push -u origin master + +echo. +echo ================================ +echo Git repository push completed! +echo ================================ +echo. +echo Remote repository: http://154.36.185.171:3003/car-maintenance-system.git +echo. +pause diff --git a/git-push.sh b/git-push.sh new file mode 100644 index 0000000..7d8500a --- /dev/null +++ b/git-push.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# 车管家4S店车辆维保管理系统 - Git初始化和推送脚本 + +echo "================================" +echo "Git仓库初始化和推送脚本" +echo "================================" + +# 1. 初始化Git仓库 +echo "" +echo "[1/6] 初始化Git仓库..." +git init + +# 2. 配置Git用户信息(可选,如果已配置可跳过) +echo "" +echo "[2/6] 配置Git用户信息..." +git config user.name "wangziqi" +git config user.email "wangziqi@example.com" + +# 3. 添加所有文件 +echo "" +echo "[3/6] 添加所有文件到暂存区..." +git add . + +# 4. 创建初始提交 +echo "" +echo "[4/6] 创建初始提交..." +git commit -m "Initial commit: 车管家4S店车辆维保管理系统 + +项目信息: +- 作者: 杨璐 +- 学校: 辽宁科技学院 +- 专业: 计算机科学与技术 +- 班级: 计BZ246 + +技术栈: +- 后端: Spring Boot 2.7.18 + JPA + MySQL +- 前端: HTML5 + CSS3 + JavaScript + +主要功能: +- 用户管理(管理员/工作人员/客户三种角色) +- 车辆档案管理 +- 维保工单管理 +- 配件库存管理 +- 在线预约服务 +- 数据统计分析 + +Generated with Claude Code +Co-Authored-By: Claude Sonnet 4.5 " + +# 5. 添加远程仓库 +echo "" +echo "[5/6] 添加远程仓库..." +git remote add origin http://wangziqi:qq5211314@154.36.185.171:3003/car-maintenance-system.git + +# 6. 推送到远程仓库 +echo "" +echo "[6/6] 推送到远程仓库..." +git push -u origin master + +echo "" +echo "================================" +echo "Git仓库推送完成!" +echo "================================" +echo "" +echo "远程仓库地址: http://154.36.185.171:3003/car-maintenance-system.git" +echo ""