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 <noreply@anthropic.com>
This commit is contained in:
wangziqi
2026-01-07 14:28:50 +08:00
commit cfae122685
45 changed files with 5447 additions and 0 deletions

56
.gitignore vendored Normal file
View File

@@ -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

238
QUICKSTART.md Normal file
View File

@@ -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 <PID>
```
### 问题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文件
### 问题4Maven依赖下载失败
**解决方案**
1. 配置Maven国内镜像阿里云
编辑 `~/.m2/settings.xml`,添加:
```xml
<mirrors>
<mirror>
<id>aliyun</id>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
```
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店车辆维保管理系统
辽宁科技学院 - 计算机科学与技术专业
作者:杨璐
指导教师:刘慧宇

489
README.md Normal file
View File

@@ -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)
- 完成系统架构设计
- 实现用户认证功能
- 实现车辆档案管理
- 实现维保工单管理
- 实现配件库存管理
- 实现预约管理功能
- 完成前端页面设计
- 完成数据库设计
---
## 联系方式
如有问题或建议,请联系:
- 邮箱: 学生邮箱
- 学校: 辽宁科技学院
- 院系: 电子与信息工程学院
---
**祝使用愉快!**

113
backend/pom.xml Normal file
View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.carmaintenance</groupId>
<artifactId>car-maintenance-system</artifactId>
<version>1.0.0</version>
<name>车管家4S店车辆维保管理系统</name>
<description>基于Spring Boot的车辆维保管理系统后端服务</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- Apache Commons Lang -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Spring Boot DevTools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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<List<Appointment>> getAllAppointments() {
List<Appointment> appointments = appointmentRepository.findAll();
return Result.success(appointments);
}
/**
* 根据ID获取预约
*/
@GetMapping("/{id}")
public Result<Appointment> 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<List<Appointment>> getAppointmentsByCustomerId(@PathVariable Integer customerId) {
List<Appointment> appointments = appointmentRepository.findByCustomerId(customerId);
return Result.success(appointments);
}
/**
* 根据状态获取预约列表
*/
@GetMapping("/status/{status}")
public Result<List<Appointment>> getAppointmentsByStatus(@PathVariable String status) {
try {
Appointment.AppointmentStatus appointmentStatus = Appointment.AppointmentStatus.valueOf(status);
List<Appointment> appointments = appointmentRepository.findByStatus(appointmentStatus);
return Result.success(appointments);
} catch (IllegalArgumentException e) {
return Result.error("状态参数错误");
}
}
/**
* 创建预约
*/
@PostMapping
public Result<Appointment> createAppointment(@RequestBody Appointment appointment) {
appointment.setStatus(Appointment.AppointmentStatus.pending);
Appointment savedAppointment = appointmentRepository.save(appointment);
return Result.success("预约成功", savedAppointment);
}
/**
* 更新预约
*/
@PutMapping("/{id}")
public Result<Appointment> 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<Appointment> 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<Void> deleteAppointment(@PathVariable Integer id) {
if (!appointmentRepository.existsById(id)) {
return Result.notFound("预约不存在");
}
appointmentRepository.deleteById(id);
return Result.success("删除成功");
}
}

View File

@@ -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<LoginResponse> 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<User> 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<Void> logout() {
return Result.success("退出成功");
}
}

View File

@@ -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<List<PartsInventory>> getAllParts() {
List<PartsInventory> parts = partsInventoryRepository.findAll();
return Result.success(parts);
}
/**
* 根据ID获取配件
*/
@GetMapping("/{id}")
public Result<PartsInventory> 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<List<PartsInventory>> getPartsByCategory(@PathVariable String category) {
List<PartsInventory> parts = partsInventoryRepository.findByCategory(category);
return Result.success(parts);
}
/**
* 获取库存预警配件
*/
@GetMapping("/low-stock")
public Result<List<PartsInventory>> getLowStockParts() {
List<PartsInventory> allParts = partsInventoryRepository.findAll();
List<PartsInventory> lowStockParts = allParts.stream()
.filter(part -> part.getStockQuantity() <= part.getMinStock())
.collect(java.util.stream.Collectors.toList());
return Result.success(lowStockParts);
}
/**
* 创建配件
*/
@PostMapping
public Result<PartsInventory> 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<PartsInventory> 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<Void> deletePart(@PathVariable Integer id) {
if (!partsInventoryRepository.existsById(id)) {
return Result.notFound("配件不存在");
}
partsInventoryRepository.deleteById(id);
return Result.success("删除成功");
}
}

View File

@@ -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<List<ServiceOrder>> getAllOrders() {
List<ServiceOrder> orders = serviceOrderRepository.findAll();
return Result.success(orders);
}
/**
* 根据ID获取工单
*/
@GetMapping("/{id}")
public Result<ServiceOrder> 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<List<ServiceOrder>> getOrdersByCustomerId(@PathVariable Integer customerId) {
List<ServiceOrder> orders = serviceOrderRepository.findByCustomerId(customerId);
return Result.success(orders);
}
/**
* 根据车辆ID获取工单列表
*/
@GetMapping("/vehicle/{vehicleId}")
public Result<List<ServiceOrder>> getOrdersByVehicleId(@PathVariable Integer vehicleId) {
List<ServiceOrder> orders = serviceOrderRepository.findByVehicleId(vehicleId);
return Result.success(orders);
}
/**
* 根据状态获取工单列表
*/
@GetMapping("/status/{status}")
public Result<List<ServiceOrder>> getOrdersByStatus(@PathVariable String status) {
try {
ServiceOrder.OrderStatus orderStatus = ServiceOrder.OrderStatus.valueOf(status);
List<ServiceOrder> orders = serviceOrderRepository.findByStatus(orderStatus);
return Result.success(orders);
} catch (IllegalArgumentException e) {
return Result.error("状态参数错误");
}
}
/**
* 创建工单
*/
@PostMapping
public Result<ServiceOrder> 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<ServiceOrder> 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<Void> 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);
}
}

View File

@@ -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<List<User>> getAllUsers() {
List<User> users = userRepository.findAll();
users.forEach(user -> user.setPassword(null));
return Result.success(users);
}
/**
* 根据ID获取用户
*/
@GetMapping("/{id}")
public Result<User> 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<List<User>> getUsersByRole(@PathVariable String role) {
try {
User.UserRole userRole = User.UserRole.valueOf(role);
List<User> users = userRepository.findByRole(userRole);
users.forEach(user -> user.setPassword(null));
return Result.success(users);
} catch (IllegalArgumentException e) {
return Result.error("角色参数错误");
}
}
/**
* 创建用户
*/
@PostMapping
public Result<User> 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<User> 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<Void> deleteUser(@PathVariable Integer id) {
if (!userRepository.existsById(id)) {
return Result.notFound("用户不存在");
}
userRepository.deleteById(id);
return Result.success("删除成功");
}
/**
* 修改密码
*/
@PutMapping("/{id}/password")
public Result<Void> 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("密码修改成功");
}
}

View File

@@ -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<List<Vehicle>> getAllVehicles() {
List<Vehicle> vehicles = vehicleRepository.findAll();
return Result.success(vehicles);
}
/**
* 根据ID获取车辆
*/
@GetMapping("/{id}")
public Result<Vehicle> 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<List<Vehicle>> getVehiclesByCustomerId(@PathVariable Integer customerId) {
List<Vehicle> vehicles = vehicleRepository.findByCustomerId(customerId);
return Result.success(vehicles);
}
/**
* 根据车牌号查询车辆
*/
@GetMapping("/plate/{licensePlate}")
public Result<Vehicle> getVehicleByPlate(@PathVariable String licensePlate) {
Vehicle vehicle = vehicleRepository.findByLicensePlate(licensePlate).orElse(null);
if (vehicle == null) {
return Result.notFound("车辆不存在");
}
return Result.success(vehicle);
}
/**
* 创建车辆档案
*/
@PostMapping
public Result<Vehicle> 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<Vehicle> 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<Void> deleteVehicle(@PathVariable Integer id) {
if (!vehicleRepository.existsById(id)) {
return Result.notFound("车辆不存在");
}
vehicleRepository.deleteById(id);
return Result.success("删除成功");
}
}

View File

@@ -0,0 +1,12 @@
package com.carmaintenance.dto;
import lombok.Data;
/**
* 登录请求DTO
*/
@Data
public class LoginRequest {
private String username;
private String password;
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,54 @@
package com.carmaintenance.dto;
import lombok.Data;
/**
* 统一API响应结果
*/
@Data
public class Result<T> {
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 <T> Result<T> success() {
return new Result<>(200, "操作成功", null);
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static <T> Result<T> success(String message, T data) {
return new Result<>(200, message, data);
}
public static <T> Result<T> error(String message) {
return new Result<>(500, message, null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
public static <T> Result<T> unauthorized(String message) {
return new Result<>(401, message, null);
}
public static <T> Result<T> forbidden(String message) {
return new Result<>(403, message, null);
}
public static <T> Result<T> notFound(String message) {
return new Result<>(404, message, null);
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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<Appointment, Integer> {
List<Appointment> findByCustomerId(Integer customerId);
List<Appointment> findByVehicleId(Integer vehicleId);
List<Appointment> findByStatus(Appointment.AppointmentStatus status);
}

View File

@@ -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<Customer, Integer> {
Optional<Customer> findByUserId(Integer userId);
Optional<Customer> findByCustomerNo(String customerNo);
}

View File

@@ -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<PartsInventory, Integer> {
Optional<PartsInventory> findByPartNo(String partNo);
List<PartsInventory> findByCategory(String category);
List<PartsInventory> findByStatus(Integer status);
}

View File

@@ -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<ServiceItem, Integer> {
Optional<ServiceItem> findByItemCode(String itemCode);
List<ServiceItem> findByCategory(String category);
List<ServiceItem> findByStatus(Integer status);
}

View File

@@ -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<ServiceOrder, Integer> {
Optional<ServiceOrder> findByOrderNo(String orderNo);
List<ServiceOrder> findByCustomerId(Integer customerId);
List<ServiceOrder> findByVehicleId(Integer vehicleId);
List<ServiceOrder> findByStaffId(Integer staffId);
List<ServiceOrder> findByStatus(ServiceOrder.OrderStatus status);
}

View File

@@ -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<User, Integer> {
Optional<User> findByUsername(String username);
Optional<User> findByUsernameAndPassword(String username, String password);
List<User> findByRole(User.UserRole role);
boolean existsByUsername(String username);
boolean existsByPhone(String phone);
}

View File

@@ -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<Vehicle, Integer> {
List<Vehicle> findByCustomerId(Integer customerId);
Optional<Vehicle> findByLicensePlate(String licensePlate);
Optional<Vehicle> findByVin(String vin);
}

View File

@@ -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

79
database/data.sql Normal file
View File

@@ -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);

187
database/schema.sql Normal file
View File

@@ -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='系统日志表';

View File

@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理员仪表板 - 车管家4S店车辆维保管理系统</title>
<link rel="stylesheet" href="../css/common.css">
<link rel="stylesheet" href="../css/dashboard.css">
</head>
<body>
<div class="dashboard-container">
<!-- 侧边栏 -->
<div class="sidebar">
<div class="sidebar-header">
<h2>车管家系统</h2>
<p>管理员控制台</p>
</div>
<div class="sidebar-menu">
<div class="menu-item active" onclick="showSection('overview')">
<span class="menu-icon">📊</span>
<span>系统概览</span>
</div>
<div class="menu-item" onclick="showSection('users')">
<span class="menu-icon">👥</span>
<span>用户管理</span>
</div>
<div class="menu-item" onclick="showSection('vehicles')">
<span class="menu-icon">🚗</span>
<span>车辆管理</span>
</div>
<div class="menu-item" onclick="showSection('orders')">
<span class="menu-icon">📋</span>
<span>工单管理</span>
</div>
<div class="menu-item" onclick="showSection('parts')">
<span class="menu-icon">🔧</span>
<span>配件管理</span>
</div>
<div class="menu-item" onclick="showSection('appointments')">
<span class="menu-icon">📅</span>
<span>预约管理</span>
</div>
</div>
</div>
<!-- 主内容区 -->
<div class="main-content">
<!-- 顶部导航 -->
<div class="top-nav">
<div class="top-nav-left">
<h1>管理员仪表板</h1>
</div>
<div class="top-nav-right">
<div class="user-info">
<div class="user-avatar" id="userAvatar">A</div>
<span class="user-name" id="userName">管理员</span>
</div>
<button class="btn-logout" onclick="utils.logout()">退出登录</button>
</div>
</div>
<!-- 系统概览 -->
<div id="overview-section" class="section">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon blue">👥</div>
<div class="stat-info">
<h3 id="totalUsers">0</h3>
<p>用户总数</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green">🚗</div>
<div class="stat-info">
<h3 id="totalVehicles">0</h3>
<p>车辆总数</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon orange">📋</div>
<div class="stat-info">
<h3 id="totalOrders">0</h3>
<p>工单总数</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon red">🔧</div>
<div class="stat-info">
<h3 id="lowStockParts">0</h3>
<p>库存预警</p>
</div>
</div>
</div>
<div class="content-card">
<div class="content-header">
<h2>最近工单</h2>
</div>
<div class="content-body">
<table class="table" id="recentOrdersTable">
<thead>
<tr>
<th>工单编号</th>
<th>服务类型</th>
<th>车牌号</th>
<th>状态</th>
<th>创建时间</th>
</tr>
</thead>
<tbody id="recentOrdersBody">
<tr><td colspan="5" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 用户管理 -->
<div id="users-section" class="section" style="display: none;">
<div class="toolbar">
<div class="toolbar-left">
<button class="btn btn-primary" onclick="showAddUserModal()">添加用户</button>
</div>
<div class="search-box">
<input type="text" id="searchUser" placeholder="搜索用户..." onkeyup="searchUsers()">
<button>🔍</button>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>真实姓名</th>
<th>手机号</th>
<th>角色</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="usersTableBody">
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 车辆管理 -->
<div id="vehicles-section" class="section" style="display: none;">
<div class="toolbar">
<div class="toolbar-left">
<button class="btn btn-primary" onclick="showAddVehicleModal()">添加车辆</button>
</div>
<div class="search-box">
<input type="text" id="searchVehicle" placeholder="搜索车辆..." onkeyup="searchVehicles()">
<button>🔍</button>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>车牌号</th>
<th>品牌型号</th>
<th>颜色</th>
<th>里程数</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="vehiclesTableBody">
<tr><td colspan="6" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 工单管理 -->
<div id="orders-section" class="section" style="display: none;">
<div class="toolbar">
<div class="toolbar-left">
<button class="btn btn-primary" onclick="showAddOrderModal()">创建工单</button>
<select id="orderStatusFilter" onchange="filterOrders()">
<option value="">全部状态</option>
<option value="pending">待处理</option>
<option value="appointed">已预约</option>
<option value="in_progress">进行中</option>
<option value="completed">已完成</option>
<option value="cancelled">已取消</option>
</select>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>工单编号</th>
<th>服务类型</th>
<th>车牌号</th>
<th>总费用</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="ordersTableBody">
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 配件管理 -->
<div id="parts-section" class="section" style="display: none;">
<div class="toolbar">
<div class="toolbar-left">
<button class="btn btn-primary" onclick="showAddPartModal()">添加配件</button>
<button class="btn btn-warning" onclick="showLowStockParts()">库存预警</button>
</div>
<div class="search-box">
<input type="text" id="searchPart" placeholder="搜索配件..." onkeyup="searchParts()">
<button>🔍</button>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>配件编号</th>
<th>配件名称</th>
<th>类别</th>
<th>库存数量</th>
<th>单价</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="partsTableBody">
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 预约管理 -->
<div id="appointments-section" class="section" style="display: none;">
<div class="toolbar">
<div class="toolbar-left">
<select id="appointmentStatusFilter" onchange="filterAppointments()">
<option value="">全部状态</option>
<option value="pending">待确认</option>
<option value="confirmed">已确认</option>
<option value="completed">已完成</option>
<option value="cancelled">已取消</option>
</select>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>预约ID</th>
<th>服务类型</th>
<th>车牌号</th>
<th>预约时间</th>
<th>联系电话</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="appointmentsTableBody">
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script src="../js/config.js"></script>
<script src="../js/api.js"></script>
<script src="../js/admin-dashboard.js"></script>
</body>
</html>

377
frontend/css/common.css Normal file
View File

@@ -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;
}
}

286
frontend/css/dashboard.css Normal file
View File

@@ -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%;
}
}

152
frontend/css/login.css Normal file
View File

@@ -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;
}
}

View File

@@ -0,0 +1,361 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>客户中心 - 车管家4S店车辆维保管理系统</title>
<link rel="stylesheet" href="../css/common.css">
<link rel="stylesheet" href="../css/dashboard.css">
</head>
<body>
<div class="dashboard-container">
<div class="sidebar">
<div class="sidebar-header">
<h2>车管家系统</h2>
<p>客户中心</p>
</div>
<div class="sidebar-menu">
<div class="menu-item active" onclick="showSection('myvehicles')">
<span class="menu-icon">🚗</span>
<span>我的车辆</span>
</div>
<div class="menu-item" onclick="showSection('myorders')">
<span class="menu-icon">📋</span>
<span>维保记录</span>
</div>
<div class="menu-item" onclick="showSection('appointments')">
<span class="menu-icon">📅</span>
<span>我的预约</span>
</div>
<div class="menu-item" onclick="showSection('newappointment')">
<span class="menu-icon"></span>
<span>在线预约</span>
</div>
</div>
</div>
<div class="main-content">
<div class="top-nav">
<div class="top-nav-left">
<h1>客户中心</h1>
</div>
<div class="top-nav-right">
<div class="user-info">
<div class="user-avatar" id="userAvatar">C</div>
<span class="user-name" id="userName">客户</span>
</div>
<button class="btn-logout" onclick="utils.logout()">退出登录</button>
</div>
</div>
<div id="myvehicles-section" class="section">
<div class="content-card">
<div class="content-header">
<h2>我的车辆</h2>
</div>
<div class="content-body">
<div id="vehiclesGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px;">
<p class="empty-state">加载中...</p>
</div>
</div>
</div>
</div>
<div id="myorders-section" class="section" style="display: none;">
<div class="content-card">
<div class="content-header">
<h2>维保记录</h2>
</div>
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>工单编号</th>
<th>服务类型</th>
<th>车牌号</th>
<th>费用</th>
<th>状态</th>
<th>创建时间</th>
</tr>
</thead>
<tbody id="ordersBody">
<tr><td colspan="6" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="appointments-section" class="section" style="display: none;">
<div class="content-card">
<div class="content-header">
<h2>我的预约</h2>
</div>
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>服务类型</th>
<th>车牌号</th>
<th>预约时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="appointmentsBody">
<tr><td colspan="5" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="newappointment-section" class="section" style="display: none;">
<div class="content-card">
<div class="content-header">
<h2>在线预约服务</h2>
</div>
<div class="content-body">
<form id="appointmentForm" style="max-width:600px;">
<div class="form-group">
<label>选择车辆</label>
<select id="vehicleSelect" required>
<option value="">请选择车辆</option>
</select>
</div>
<div class="form-group">
<label>服务类型</label>
<select id="serviceType" required>
<option value="maintenance">保养维护</option>
<option value="repair">维修服务</option>
<option value="beauty">美容服务</option>
<option value="insurance">保险代理</option>
</select>
</div>
<div class="form-group">
<label>预约时间</label>
<input type="datetime-local" id="appointmentTime" required>
</div>
<div class="form-group">
<label>联系电话</label>
<input type="tel" id="contactPhone" required>
</div>
<div class="form-group">
<label>预约说明</label>
<textarea id="description" placeholder="请描述您的需求"></textarea>
</div>
<button type="submit" class="btn btn-primary">提交预约</button>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="../js/config.js"></script>
<script src="../js/api.js"></script>
<script>
if (!utils.checkAuth() || !utils.hasRole('customer')) {
window.location.href = '../login.html';
}
let currentCustomerId = null;
window.addEventListener('DOMContentLoaded', async () => {
const user = utils.getCurrentUser();
if (user) {
document.getElementById('userName').textContent = user.realName;
document.getElementById('userAvatar').textContent = user.realName.charAt(0);
document.getElementById('contactPhone').value = user.phone;
await loadCustomerData(user.userId);
}
});
async function loadCustomerData(userId) {
try {
const usersRes = await api.get(API_ENDPOINTS.USERS);
const customers = usersRes.data?.filter(u => u.role === 'customer') || [];
const currentUser = customers.find(c => c.userId === userId);
if (currentUser) {
currentCustomerId = currentUser.userId;
loadVehicles();
loadOrders();
loadAppointments();
loadVehicleOptions();
}
} catch (error) {
console.error('加载数据失败:', error);
}
}
async function loadVehicles() {
try {
const response = await api.get(API_ENDPOINTS.VEHICLES);
if (response.code === 200 && response.data) {
const myVehicles = response.data.filter(v => v.customerId === currentCustomerId);
displayVehicles(myVehicles);
}
} catch (error) {
console.error('加载车辆失败:', error);
}
}
function displayVehicles(vehicles) {
const grid = document.getElementById('vehiclesGrid');
if (vehicles.length === 0) {
grid.innerHTML = '<p class="empty-state">暂无车辆</p>';
return;
}
grid.innerHTML = vehicles.map(v => `
<div class="card">
<h3 style="color:#1890ff;">${v.licensePlate}</h3>
<p><strong>品牌:</strong> ${v.brand} ${v.model}</p>
<p><strong>颜色:</strong> ${v.color || '-'}</p>
<p><strong>里程:</strong> ${v.mileage || 0} 公里</p>
<p><strong>上次保养:</strong> ${utils.formatDate(v.lastMaintenanceDate)}</p>
<p><strong>下次保养:</strong> ${utils.formatDate(v.nextMaintenanceDate)}</p>
<p><strong>状态:</strong> ${utils.getStatusBadge(v.status)}</p>
</div>
`).join('');
}
async function loadOrders() {
try {
const [ordersRes, vehiclesRes] = await Promise.all([
api.get(API_ENDPOINTS.ORDERS),
api.get(API_ENDPOINTS.VEHICLES)
]);
if (ordersRes.code === 200 && ordersRes.data) {
const myOrders = ordersRes.data.filter(o => o.customerId === currentCustomerId);
const vehicles = vehiclesRes.data || [];
const tbody = document.getElementById('ordersBody');
if (myOrders.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-state">暂无维保记录</td></tr>';
return;
}
tbody.innerHTML = myOrders.map(o => {
const vehicle = vehicles.find(v => v.vehicleId === o.vehicleId);
return `
<tr>
<td>${o.orderNo}</td>
<td>${utils.getServiceTypeText(o.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>¥${o.totalCost || 0}</td>
<td>${utils.getStatusBadge(o.status)}</td>
<td>${utils.formatDateTime(o.createTime)}</td>
</tr>
`;
}).join('');
}
} catch (error) {
console.error('加载工单失败:', error);
}
}
async function loadAppointments() {
try {
const [appointmentsRes, vehiclesRes] = await Promise.all([
api.get(API_ENDPOINTS.APPOINTMENTS),
api.get(API_ENDPOINTS.VEHICLES)
]);
if (appointmentsRes.code === 200 && appointmentsRes.data) {
const myAppointments = appointmentsRes.data.filter(a => a.customerId === currentCustomerId);
const vehicles = vehiclesRes.data || [];
const tbody = document.getElementById('appointmentsBody');
if (myAppointments.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">暂无预约记录</td></tr>';
return;
}
tbody.innerHTML = myAppointments.map(a => {
const vehicle = vehicles.find(v => v.vehicleId === a.vehicleId);
return `
<tr>
<td>${utils.getServiceTypeText(a.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>${utils.formatDateTime(a.appointmentTime)}</td>
<td>${utils.getStatusBadge(a.status)}</td>
<td>
${a.status === 'pending' ? `<button class="btn btn-danger" onclick="cancelAppointment(${a.appointmentId})">取消</button>` : '-'}
</td>
</tr>
`;
}).join('');
}
} catch (error) {
console.error('加载预约失败:', error);
}
}
async function loadVehicleOptions() {
try {
const response = await api.get(API_ENDPOINTS.VEHICLES);
if (response.code === 200 && response.data) {
const myVehicles = response.data.filter(v => v.customerId === currentCustomerId);
const select = document.getElementById('vehicleSelect');
select.innerHTML = '<option value="">请选择车辆</option>' +
myVehicles.map(v => `<option value="${v.vehicleId}">${v.licensePlate} - ${v.brand} ${v.model}</option>`).join('');
}
} catch (error) {
console.error('加载车辆选项失败:', error);
}
}
document.getElementById('appointmentForm').addEventListener('submit', async (e) => {
e.preventDefault();
const data = {
customerId: currentCustomerId,
vehicleId: parseInt(document.getElementById('vehicleSelect').value),
serviceType: document.getElementById('serviceType').value,
appointmentTime: document.getElementById('appointmentTime').value,
contactPhone: document.getElementById('contactPhone').value,
description: document.getElementById('description').value
};
try {
const response = await api.post(API_ENDPOINTS.APPOINTMENTS, data);
if (response.code === 200) {
utils.showSuccess('预约成功!');
document.getElementById('appointmentForm').reset();
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('操作失败');
}
}
}
function showSection(name) {
document.querySelectorAll('.section').forEach(s => s.style.display = 'none');
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
event.currentTarget.classList.add('active');
document.getElementById(name + '-section').style.display = 'block';
}
</script>
</body>
</html>

View File

@@ -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 = '<tr><td colspan="5" class="empty-state">暂无数据</td></tr>';
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 `
<tr>
<td>${order.orderNo}</td>
<td>${utils.getServiceTypeText(order.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>${utils.getStatusBadge(order.status)}</td>
<td>${utils.formatDateTime(order.createTime)}</td>
</tr>
`;
}).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 = '<tr><td colspan="7" class="empty-state">暂无数据</td></tr>';
return;
}
tbody.innerHTML = users.map(user => `
<tr>
<td>${user.userId}</td>
<td>${user.username}</td>
<td>${user.realName}</td>
<td>${user.phone}</td>
<td>${utils.getRoleText(user.role)}</td>
<td>${user.status === 1 ? '<span class="badge badge-success">启用</span>' : '<span class="badge badge-secondary">禁用</span>'}</td>
<td class="table-actions">
<button class="btn btn-info" onclick="viewUser(${user.userId})">查看</button>
<button class="btn btn-warning" onclick="editUser(${user.userId})">编辑</button>
<button class="btn btn-danger" onclick="deleteUser(${user.userId})">删除</button>
</td>
</tr>
`).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 = '<tr><td colspan="6" class="empty-state">暂无数据</td></tr>';
return;
}
tbody.innerHTML = vehicles.map(vehicle => `
<tr>
<td>${vehicle.licensePlate}</td>
<td>${vehicle.brand} ${vehicle.model}</td>
<td>${vehicle.color || '-'}</td>
<td>${vehicle.mileage || 0} 公里</td>
<td>${utils.getStatusBadge(vehicle.status)}</td>
<td class="table-actions">
<button class="btn btn-info" onclick="viewVehicle(${vehicle.vehicleId})">查看</button>
<button class="btn btn-warning" onclick="editVehicle(${vehicle.vehicleId})">编辑</button>
<button class="btn btn-danger" onclick="deleteVehicle(${vehicle.vehicleId})">删除</button>
</td>
</tr>
`).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 = '<tr><td colspan="7" class="empty-state">暂无数据</td></tr>';
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 `
<tr>
<td>${order.orderNo}</td>
<td>${utils.getServiceTypeText(order.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>¥${order.totalCost || 0}</td>
<td>${utils.getStatusBadge(order.status)}</td>
<td>${utils.formatDateTime(order.createTime)}</td>
<td class="table-actions">
<button class="btn btn-info" onclick="viewOrder(${order.orderId})">查看</button>
<button class="btn btn-warning" onclick="editOrder(${order.orderId})">编辑</button>
<button class="btn btn-danger" onclick="deleteOrder(${order.orderId})">删除</button>
</td>
</tr>
`;
}).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 = '<tr><td colspan="7" class="empty-state">暂无数据</td></tr>';
return;
}
tbody.innerHTML = parts.map(part => {
const isLowStock = part.stockQuantity <= part.minStock;
return `
<tr ${isLowStock ? 'style="background-color: #fff1f0;"' : ''}>
<td>${part.partNo}</td>
<td>${part.partName}</td>
<td>${part.category || '-'}</td>
<td>${part.stockQuantity} ${part.unit}${isLowStock ? ' <span class="badge badge-danger">预警</span>' : ''}</td>
<td>¥${part.unitPrice}</td>
<td>${part.status === 1 ? '<span class="badge badge-success">正常</span>' : '<span class="badge badge-secondary">停用</span>'}</td>
<td class="table-actions">
<button class="btn btn-info" onclick="viewPart(${part.partId})">查看</button>
<button class="btn btn-warning" onclick="editPart(${part.partId})">编辑</button>
<button class="btn btn-danger" onclick="deletePart(${part.partId})">删除</button>
</td>
</tr>
`;
}).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 = '<tr><td colspan="7" class="empty-state">暂无数据</td></tr>';
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 `
<tr>
<td>${appointment.appointmentId}</td>
<td>${utils.getServiceTypeText(appointment.serviceType)}</td>
<td>${vehicle ? vehicle.licensePlate : '-'}</td>
<td>${utils.formatDateTime(appointment.appointmentTime)}</td>
<td>${appointment.contactPhone}</td>
<td>${utils.getStatusBadge(appointment.status)}</td>
<td class="table-actions">
<button class="btn btn-success" onclick="confirmAppointment(${appointment.appointmentId})">确认</button>
<button class="btn btn-danger" onclick="cancelAppointment(${appointment.appointmentId})">取消</button>
</td>
</tr>
`;
}).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('操作失败');
}
}
}

194
frontend/js/api.js Normal file
View File

@@ -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: '<span class="badge badge-info">待处理</span>',
appointed: '<span class="badge badge-info">已预约</span>',
in_progress: '<span class="badge badge-warning">进行中</span>',
completed: '<span class="badge badge-success">已完成</span>',
cancelled: '<span class="badge badge-secondary">已取消</span>',
// 支付状态
unpaid: '<span class="badge badge-danger">未支付</span>',
paid: '<span class="badge badge-success">已支付</span>',
refunded: '<span class="badge badge-secondary">已退款</span>',
// 预约状态
confirmed: '<span class="badge badge-success">已确认</span>',
// 车辆状态
normal: '<span class="badge badge-success">正常</span>',
in_service: '<span class="badge badge-warning">维修中</span>'
};
return badges[status] || `<span class="badge badge-secondary">${status}</span>`;
},
// 获取服务类型文本
getServiceTypeText(type) {
const types = {
maintenance: '保养维护',
repair: '维修服务',
beauty: '美容服务',
insurance: '保险代理'
};
return types[type] || type;
},
// 获取用户角色文本
getRoleText(role) {
const roles = {
admin: '管理员',
staff: '工作人员',
customer: '客户'
};
return roles[role] || role;
}
};

52
frontend/js/config.js Normal file
View File

@@ -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'
};

102
frontend/js/login.js Normal file
View File

@@ -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('未知的用户角色');
}
}

61
frontend/login.html Normal file
View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>车管家4S店车辆维保管理系统 - 登录</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/login.css">
</head>
<body>
<div class="login-container">
<div class="login-box">
<div class="login-header">
<h1>车管家4S店车辆维保管理系统</h1>
<p>Car Maintenance Management System</p>
</div>
<div class="login-form">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" placeholder="请输入用户名" autocomplete="off">
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" placeholder="请输入密码">
</div>
<div class="form-group">
<label for="role">登录角色</label>
<select id="role" name="role">
<option value="admin">管理员</option>
<option value="staff">工作人员</option>
<option value="customer">客户</option>
</select>
</div>
<button class="btn-login" onclick="handleLogin()">登录</button>
<div class="login-footer">
<a href="#" onclick="alert('请联系管理员重置密码')">忘记密码?</a>
<a href="#" onclick="alert('客户可自助注册,工作人员请联系管理员')">注册账号</a>
</div>
</div>
<div class="demo-accounts">
<p>演示账号:</p>
<ul>
<li>管理员: admin / 123456</li>
<li>工作人员: staff001 / 123456</li>
<li>客户: customer001 / 123456</li>
</ul>
</div>
</div>
</div>
<script src="js/config.js"></script>
<script src="js/api.js"></script>
<script src="js/login.js"></script>
</body>
</html>

View File

@@ -0,0 +1,270 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>工作人员仪表板 - 车管家4S店车辆维保管理系统</title>
<link rel="stylesheet" href="../css/common.css">
<link rel="stylesheet" href="../css/dashboard.css">
</head>
<body>
<div class="dashboard-container">
<div class="sidebar">
<div class="sidebar-header">
<h2>车管家系统</h2>
<p>工作人员控制台</p>
</div>
<div class="sidebar-menu">
<div class="menu-item active" onclick="showSection('overview')">
<span class="menu-icon">📊</span>
<span>工作概览</span>
</div>
<div class="menu-item" onclick="showSection('myorders')">
<span class="menu-icon">📋</span>
<span>我的工单</span>
</div>
<div class="menu-item" onclick="showSection('vehicles')">
<span class="menu-icon">🚗</span>
<span>车辆查询</span>
</div>
<div class="menu-item" onclick="showSection('parts')">
<span class="menu-icon">🔧</span>
<span>配件查询</span>
</div>
</div>
</div>
<div class="main-content">
<div class="top-nav">
<div class="top-nav-left">
<h1>工作人员仪表板</h1>
</div>
<div class="top-nav-right">
<div class="user-info">
<div class="user-avatar" id="userAvatar">S</div>
<span class="user-name" id="userName">工作人员</span>
</div>
<button class="btn-logout" onclick="utils.logout()">退出登录</button>
</div>
</div>
<div id="overview-section" class="section">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon blue">📋</div>
<div class="stat-info">
<h3 id="myOrdersCount">0</h3>
<p>我的工单</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon orange"></div>
<div class="stat-info">
<h3 id="inProgressCount">0</h3>
<p>进行中</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon green"></div>
<div class="stat-info">
<h3 id="completedCount">0</h3>
<p>已完成</p>
</div>
</div>
</div>
<div class="content-card">
<div class="content-header">
<h2>待处理工单</h2>
</div>
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>工单编号</th>
<th>服务类型</th>
<th>车牌号</th>
<th>状态</th>
<th>预约时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="pendingOrdersBody">
<tr><td colspan="6" class="empty-state">暂无待处理工单</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="myorders-section" class="section" style="display: none;">
<div class="content-card">
<div class="content-header">
<h2>我的工单列表</h2>
</div>
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>工单编号</th>
<th>服务类型</th>
<th>车牌号</th>
<th>客户姓名</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="myOrdersBody">
<tr><td colspan="7" class="empty-state">加载中...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<div id="vehicles-section" class="section" style="display: none;">
<div class="toolbar">
<div class="search-box">
<input type="text" id="searchVehicle" placeholder="输入车牌号查询...">
<button onclick="searchVehicle()">🔍</button>
</div>
</div>
<div class="content-card">
<div class="content-body">
<div id="vehicleResult"></div>
</div>
</div>
</div>
<div id="parts-section" class="section" style="display: none;">
<div class="toolbar">
<div class="search-box">
<input type="text" id="searchPart" placeholder="搜索配件...">
<button onclick="searchParts()">🔍</button>
</div>
</div>
<div class="content-card">
<div class="content-body">
<table class="table">
<thead>
<tr>
<th>配件编号</th>
<th>配件名称</th>
<th>类别</th>
<th>库存数量</th>
<th>单价</th>
</tr>
</thead>
<tbody id="partsBody">
<tr><td colspan="5" class="empty-state">请输入关键词搜索配件</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script src="../js/config.js"></script>
<script src="../js/api.js"></script>
<script>
if (!utils.checkAuth() || !utils.hasRole('staff')) {
window.location.href = '../login.html';
}
window.addEventListener('DOMContentLoaded', () => {
const user = utils.getCurrentUser();
if (user) {
document.getElementById('userName').textContent = user.realName;
document.getElementById('userAvatar').textContent = user.realName.charAt(0);
loadStaffData(user.userId);
}
});
function showSection(name) {
document.querySelectorAll('.section').forEach(s => s.style.display = 'none');
document.querySelectorAll('.menu-item').forEach(m => m.classList.remove('active'));
event.currentTarget.classList.add('active');
document.getElementById(name + '-section').style.display = 'block';
}
async function loadStaffData(staffId) {
try {
const response = await api.get(API_ENDPOINTS.ORDERS);
if (response.code === 200 && response.data) {
const myOrders = response.data.filter(o => o.staffId === staffId);
const inProgress = myOrders.filter(o => o.status === 'in_progress');
const completed = myOrders.filter(o => o.status === 'completed');
document.getElementById('myOrdersCount').textContent = myOrders.length;
document.getElementById('inProgressCount').textContent = inProgress.length;
document.getElementById('completedCount').textContent = completed.length;
}
} catch (error) {
console.error('加载数据失败:', error);
}
}
async function searchVehicle() {
const plate = document.getElementById('searchVehicle').value.trim();
if (!plate) {
utils.showError('请输入车牌号');
return;
}
try {
const response = await api.get(API_ENDPOINTS.VEHICLE_BY_PLATE(plate));
if (response.code === 200 && response.data) {
const v = response.data;
document.getElementById('vehicleResult').innerHTML = `
<div class="card">
<h3>车辆信息</h3>
<p><strong>车牌号:</strong> ${v.licensePlate}</p>
<p><strong>品牌型号:</strong> ${v.brand} ${v.model}</p>
<p><strong>颜色:</strong> ${v.color || '-'}</p>
<p><strong>车架号:</strong> ${v.vin || '-'}</p>
<p><strong>里程数:</strong> ${v.mileage || 0} 公里</p>
<p><strong>状态:</strong> ${utils.getStatusBadge(v.status)}</p>
</div>
`;
} else {
document.getElementById('vehicleResult').innerHTML = '<p class="empty-state">未找到该车辆</p>';
}
} catch (error) {
utils.showError('查询失败');
}
}
async function searchParts() {
try {
const response = await api.get(API_ENDPOINTS.PARTS);
if (response.code === 200 && response.data) {
const tbody = document.getElementById('partsBody');
const keyword = document.getElementById('searchPart').value.toLowerCase();
const filtered = response.data.filter(p =>
p.partName.toLowerCase().includes(keyword) ||
p.partNo.toLowerCase().includes(keyword)
);
if (filtered.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-state">未找到配件</td></tr>';
return;
}
tbody.innerHTML = filtered.map(p => `
<tr>
<td>${p.partNo}</td>
<td>${p.partName}</td>
<td>${p.category || '-'}</td>
<td>${p.stockQuantity} ${p.unit}</td>
<td>¥${p.unitPrice}</td>
</tr>
`).join('');
}
} catch (error) {
utils.showError('搜索失败');
}
}
</script>
</body>
</html>

44
git-push.bat Normal file
View File

@@ -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 <noreply@anthropic.com>"
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

67
git-push.sh Normal file
View File

@@ -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 <noreply@anthropic.com>"
# 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 ""