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:
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal 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
238
QUICKSTART.md
Normal 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文件
|
||||
|
||||
### 问题4:Maven依赖下载失败
|
||||
|
||||
**解决方案**:
|
||||
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
489
README.md
Normal 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
113
backend/pom.xml
Normal 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>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("删除成功");
|
||||
}
|
||||
}
|
||||
@@ -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("退出成功");
|
||||
}
|
||||
}
|
||||
@@ -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("删除成功");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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("密码修改成功");
|
||||
}
|
||||
}
|
||||
@@ -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("删除成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.carmaintenance.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
54
backend/src/main/java/com/carmaintenance/dto/Result.java
Normal file
54
backend/src/main/java/com/carmaintenance/dto/Result.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
58
backend/src/main/java/com/carmaintenance/entity/User.java
Normal file
58
backend/src/main/java/com/carmaintenance/entity/User.java
Normal 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
|
||||
}
|
||||
}
|
||||
75
backend/src/main/java/com/carmaintenance/entity/Vehicle.java
Normal file
75
backend/src/main/java/com/carmaintenance/entity/Vehicle.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
43
backend/src/main/resources/application.properties
Normal file
43
backend/src/main/resources/application.properties
Normal 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
79
database/data.sql
Normal 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
187
database/schema.sql
Normal 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='系统日志表';
|
||||
300
frontend/admin/dashboard.html
Normal file
300
frontend/admin/dashboard.html
Normal 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
377
frontend/css/common.css
Normal 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
286
frontend/css/dashboard.css
Normal 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
152
frontend/css/login.css
Normal 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;
|
||||
}
|
||||
}
|
||||
361
frontend/customer/dashboard.html
Normal file
361
frontend/customer/dashboard.html
Normal 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>
|
||||
468
frontend/js/admin-dashboard.js
Normal file
468
frontend/js/admin-dashboard.js
Normal 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
194
frontend/js/api.js
Normal 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
52
frontend/js/config.js
Normal 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
102
frontend/js/login.js
Normal 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
61
frontend/login.html
Normal 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>
|
||||
270
frontend/staff/dashboard.html
Normal file
270
frontend/staff/dashboard.html
Normal 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
44
git-push.bat
Normal 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
67
git-push.sh
Normal 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 ""
|
||||
Reference in New Issue
Block a user