Initial commit: 电动车租赁系统后端
This commit is contained in:
commit
6e1378c812
|
|
@ -0,0 +1,70 @@
|
||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids/
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# 两轮电动车租赁管理系统
|
||||||
|
|
||||||
|
## 🚀 项目简介
|
||||||
|
|
||||||
|
一个完整的两轮电动车租赁管理系统,包含车辆管理、订单管理、客户管理、财务管理等功能。
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e-scooter-rental-system/
|
||||||
|
├── server/ # 后端服务
|
||||||
|
│ ├── models/ # 数据模型
|
||||||
|
│ ├── routes/ # 路由
|
||||||
|
│ ├── middleware/ # 中间件
|
||||||
|
│ └── index.js # 入口文件
|
||||||
|
├── client/ # 前端页面
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/ # 组件
|
||||||
|
│ │ ├── pages/ # 页面
|
||||||
|
│ │ └── App.jsx
|
||||||
|
│ └── index.html
|
||||||
|
├── database/ # 数据库脚本
|
||||||
|
├── docs/ # 文档
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
- **后端**: Node.js + Express + MongoDB
|
||||||
|
- **前端**: Vue.js / React (待定)
|
||||||
|
- **数据库**: MongoDB
|
||||||
|
|
||||||
|
## 📋 功能模块
|
||||||
|
|
||||||
|
1. **车辆管理** - 车辆信息、状态追踪、GPS定位
|
||||||
|
2. **订单管理** - 租赁订单、计费、逾期提醒
|
||||||
|
3. **客户管理** - 客户信息、租赁历史
|
||||||
|
4. **财务管理** - 收入统计、报表生成
|
||||||
|
5. **运维管理** - 维修记录、保养提醒
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 启动后端
|
||||||
|
npm run server
|
||||||
|
|
||||||
|
# 启动前端 (待实现)
|
||||||
|
npm run client
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 开发计划
|
||||||
|
|
||||||
|
- [ ] 初始化项目结构
|
||||||
|
- [ ] 设计数据库模型
|
||||||
|
- [ ] 实现后端API
|
||||||
|
- [ ] 开发前端界面
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
// 添加收支记录
|
||||||
|
const payments = [
|
||||||
|
{ type: 'income', party: '张三', amount: 1500, method: '微信', category: '租金收入', remark: '租车费' },
|
||||||
|
{ type: 'income', party: '李四', amount: 2000, method: '支付宝', category: '租金收入', remark: '租车费' },
|
||||||
|
{ type: 'income', party: '王五', amount: 500, method: '现金', category: '押金退还', remark: '还车退押金' },
|
||||||
|
{ type: 'expense', party: '赵六', amount: 300, method: '微信', category: '退款', remark: '提前还车退款' },
|
||||||
|
{ type: 'expense', party: '房东', amount: 1500, method: '银行卡', category: '房租', remark: '门店房租' },
|
||||||
|
{ type: 'expense', party: '电工', amount: 200, method: '现金', category: '水电', remark: '门店电费' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const addPayment = () => {
|
||||||
|
if (i >= payments.length) {
|
||||||
|
console.log('Done!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = JSON.stringify(payments[i]);
|
||||||
|
const req = http.request({
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: 3000,
|
||||||
|
path: '/api/finance',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length }
|
||||||
|
}, res => {
|
||||||
|
let b = '';
|
||||||
|
res.on('data', c => b += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
console.log('Added:', payments[i].party, payments[i].amount);
|
||||||
|
i++;
|
||||||
|
addPayment();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', e => console.error(e));
|
||||||
|
req.write(data);
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
addPayment();
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
# API 文档
|
||||||
|
|
||||||
|
## 基础 URL
|
||||||
|
`http://localhost:3000/api`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 车辆管理 (Vehicles)
|
||||||
|
|
||||||
|
### 获取所有车辆
|
||||||
|
```
|
||||||
|
GET /vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取单个车辆
|
||||||
|
```
|
||||||
|
GET /vehicles/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建车辆
|
||||||
|
```
|
||||||
|
POST /vehicles
|
||||||
|
```
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vehicleId": "SCOOTER001",
|
||||||
|
"model": "黑骑士",
|
||||||
|
"brand": "小牛",
|
||||||
|
"color": "黑色",
|
||||||
|
"batteryType": "锂电池",
|
||||||
|
"batteryCapacity": 20,
|
||||||
|
"status": "空闲"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新车辆
|
||||||
|
```
|
||||||
|
PUT /vehicles/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除车辆
|
||||||
|
```
|
||||||
|
DELETE /vehicles/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 按状态筛选车辆
|
||||||
|
```
|
||||||
|
GET /vehicles/status/:status
|
||||||
|
```
|
||||||
|
状态: 空闲, 在租, 维修中, 已报废, 待回收
|
||||||
|
|
||||||
|
### 更新车辆位置
|
||||||
|
```
|
||||||
|
PATCH /vehicles/:id/location
|
||||||
|
```
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"longitude": 116.397,
|
||||||
|
"latitude": 39.909
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 订单管理 (Orders)
|
||||||
|
|
||||||
|
### 获取所有订单
|
||||||
|
```
|
||||||
|
GET /orders
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取单个订单
|
||||||
|
```
|
||||||
|
GET /orders/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建订单
|
||||||
|
```
|
||||||
|
POST /orders
|
||||||
|
```
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"customer": "客户ID",
|
||||||
|
"vehicle": "车辆ID",
|
||||||
|
"startDate": "2026-03-05",
|
||||||
|
"endDate": "2026-03-12",
|
||||||
|
"rentalFee": 50,
|
||||||
|
"deposit": 200
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 结束订单
|
||||||
|
```
|
||||||
|
PATCH /orders/:id/complete
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取逾期订单
|
||||||
|
```
|
||||||
|
GET /orders/status/overdue
|
||||||
|
```
|
||||||
|
|
||||||
|
### 按状态筛选订单
|
||||||
|
```
|
||||||
|
GET /orders/status/:status
|
||||||
|
```
|
||||||
|
状态: 待支付, 进行中, 已完成, 逾期, 已取消, 已退款
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 客户管理 (Customers)
|
||||||
|
|
||||||
|
### 获取所有客户
|
||||||
|
```
|
||||||
|
GET /customers
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取单个客户
|
||||||
|
```
|
||||||
|
GET /customers/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建客户
|
||||||
|
```
|
||||||
|
POST /customers
|
||||||
|
```
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "张三",
|
||||||
|
"phone": "13800138000",
|
||||||
|
"idCard": "110101199001011234"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新客户
|
||||||
|
```
|
||||||
|
PUT /customers/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 删除客户
|
||||||
|
```
|
||||||
|
DELETE /customers/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 搜索客户
|
||||||
|
```
|
||||||
|
GET /customers/search/:keyword
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新客户信用评分
|
||||||
|
```
|
||||||
|
PATCH /customers/:id/credit
|
||||||
|
```
|
||||||
|
请求体:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"creditScore": 85
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 财务管理 (Finance)
|
||||||
|
|
||||||
|
### 获取财务统计
|
||||||
|
```
|
||||||
|
GET /finance/stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取收入趋势
|
||||||
|
```
|
||||||
|
GET /finance/trend?period=month
|
||||||
|
```
|
||||||
|
参数: week, month, quarter, year
|
||||||
|
|
||||||
|
### 获取逾期账款列表
|
||||||
|
```
|
||||||
|
GET /finance/overdue
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 健康检查
|
||||||
|
```
|
||||||
|
GET /health
|
||||||
|
```
|
||||||
|
返回: `{ "status": "ok", "timestamp": "..." }`
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
# MongoDB 安装指南
|
||||||
|
|
||||||
|
## 为什么需要 MongoDB
|
||||||
|
|
||||||
|
两轮电动车租赁管理系统使用 MongoDB 作为数据库,用于存储车辆、订单、客户等数据。
|
||||||
|
|
||||||
|
## 安装方法
|
||||||
|
|
||||||
|
### 方法一:下载安装包 (推荐)
|
||||||
|
|
||||||
|
#### 1. 下载 MongoDB
|
||||||
|
- 访问: https://www.mongodb.com/try/download/community
|
||||||
|
- 选择 **Windows** 版本
|
||||||
|
- 下载 **MongoDB Community Server**
|
||||||
|
|
||||||
|
#### 2. 安装 MongoDB
|
||||||
|
- 运行下载的安装程序
|
||||||
|
- 选择 **Complete** 安装类型
|
||||||
|
- 勾选 **Install MongoDB as a Service**
|
||||||
|
- 点击 **Install** 开始安装
|
||||||
|
|
||||||
|
#### 3. 配置数据目录
|
||||||
|
- 默认路径: `C:\data\db`
|
||||||
|
- 如果目录不存在,手动创建:
|
||||||
|
```bash
|
||||||
|
mkdir C:\data\db
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 验证安装
|
||||||
|
打开命令提示符,输入:
|
||||||
|
```bash
|
||||||
|
mongod --version
|
||||||
|
```
|
||||||
|
如果显示版本信息,说明安装成功。
|
||||||
|
|
||||||
|
### 方法二:使用 Docker (推荐)
|
||||||
|
|
||||||
|
#### 1. 安装 Docker Desktop
|
||||||
|
- 访问: https://www.docker.com/products/docker-desktop/
|
||||||
|
- 下载并安装 Docker Desktop for Windows
|
||||||
|
- 启动 Docker Desktop
|
||||||
|
|
||||||
|
#### 2. 拉取 MongoDB 镜像
|
||||||
|
```bash
|
||||||
|
docker pull mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 启动 MongoDB 容器
|
||||||
|
```bash
|
||||||
|
docker run -d -p 27017:27017 --name mongodb mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 验证运行
|
||||||
|
```bash
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
应该看到 mongodb 容器正在运行。
|
||||||
|
|
||||||
|
### 方法三:使用 MongoDB Compass (图形界面)
|
||||||
|
|
||||||
|
#### 1. 下载 MongoDB Compass
|
||||||
|
- 访问: https://www.mongodb.com/try/download/compass
|
||||||
|
- 下载 Windows 版本
|
||||||
|
|
||||||
|
#### 2. 安装 MongoDB Compass
|
||||||
|
- 运行安装程序
|
||||||
|
- 按照提示完成安装
|
||||||
|
|
||||||
|
#### 3. 连接到本地 MongoDB
|
||||||
|
- 打开 MongoDB Compass
|
||||||
|
- 连接字符串: `mongodb://localhost:27017`
|
||||||
|
- 点击 **Connect**
|
||||||
|
|
||||||
|
## 启动 MongoDB
|
||||||
|
|
||||||
|
### Windows 服务方式
|
||||||
|
如果安装时选择了 "Install MongoDB as a Service",MongoDB 会自动启动。
|
||||||
|
|
||||||
|
### 手动启动
|
||||||
|
```bash
|
||||||
|
mongod --dbpath C:\data\db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker 方式
|
||||||
|
```bash
|
||||||
|
docker start mongodb
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证 MongoDB 运行
|
||||||
|
|
||||||
|
### 方法一:使用命令行
|
||||||
|
```bash
|
||||||
|
mongosh
|
||||||
|
```
|
||||||
|
如果进入 MongoDB Shell,说明运行正常。
|
||||||
|
|
||||||
|
### 方法二:使用 MongoDB Compass
|
||||||
|
- 打开 MongoDB Compass
|
||||||
|
- 连接到 `mongodb://localhost:27017`
|
||||||
|
- 如果连接成功,说明运行正常
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 端口被占用
|
||||||
|
如果 27017 端口被占用,可以修改端口:
|
||||||
|
```bash
|
||||||
|
mongod --port 27018 --dbpath C:\data\db
|
||||||
|
```
|
||||||
|
同时需要修改项目中的 `.env` 文件:
|
||||||
|
```
|
||||||
|
MONGODB_URI=mongodb://localhost:27018/e-scooter-rental
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据目录权限问题
|
||||||
|
如果无法创建数据目录,尝试以管理员身份运行命令提示符。
|
||||||
|
|
||||||
|
### 3. Docker 连接失败
|
||||||
|
确保 Docker Desktop 正在运行,并且没有其他容器占用 27017 端口。
|
||||||
|
|
||||||
|
## 启动项目
|
||||||
|
|
||||||
|
### 1. 启动 MongoDB
|
||||||
|
确保 MongoDB 正在运行。
|
||||||
|
|
||||||
|
### 2. 启动后端服务
|
||||||
|
```bash
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 生成测试数据
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试 API
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
安装完成后,按照以下顺序操作:
|
||||||
|
1. ✅ 安装 MongoDB
|
||||||
|
2. ✅ 启动 MongoDB
|
||||||
|
3. ✅ 启动后端服务
|
||||||
|
4. ✅ 生成测试数据
|
||||||
|
5. ✅ 测试 API 接口
|
||||||
|
6. ⏳ 开发前端页面
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
# 两轮电动车租赁管理系统
|
||||||
|
|
||||||
|
## 🚀 项目简介
|
||||||
|
|
||||||
|
一个完整的两轮电动车租赁管理系统,包含车辆管理、订单管理、客户管理、财务管理等功能。
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e-scooter-rental-system/
|
||||||
|
├── server/ # 后端服务
|
||||||
|
│ ├── models/ # 数据模型
|
||||||
|
│ │ ├── Vehicle.js # 车辆模型
|
||||||
|
│ │ ├── Order.js # 订单模型
|
||||||
|
│ │ ├── Customer.js # 客户模型
|
||||||
|
│ │ └── index.js
|
||||||
|
│ ├── routes/ # 路由
|
||||||
|
│ │ ├── vehicles.js # 车辆路由
|
||||||
|
│ │ ├── orders.js # 订单路由
|
||||||
|
│ │ ├── customers.js # 客户路由
|
||||||
|
│ │ ├── finance.js # 财务路由
|
||||||
|
│ │ └── index.js
|
||||||
|
│ ├── middleware/ # 中间件
|
||||||
|
│ │ └── errorHandler.js
|
||||||
|
│ ├── seed.js # 测试数据脚本
|
||||||
|
│ └── index.js # 入口文件
|
||||||
|
├── docs/ # 文档
|
||||||
|
│ ├── README.md # 项目说明
|
||||||
|
│ ├── API.md # API 文档
|
||||||
|
│ ├── 数据库设计.md # 数据库设计
|
||||||
|
│ ├── 开发计划.md # 开发计划
|
||||||
|
│ ├── 快速开始.md # 快速开始指南
|
||||||
|
│ ├── 测试数据.md # 测试数据说明
|
||||||
|
│ ├── MongoDB安装指南.md # MongoDB 安装指南
|
||||||
|
│ ├── 开发进度.md # 开发进度
|
||||||
|
│ ├── 下一步行动.md # 下一步行动
|
||||||
|
│ ├── 项目总结.md # 项目总结
|
||||||
|
│ ├── 文件清单.md # 文件清单
|
||||||
|
│ ├── 常见问题.md # 常见问题
|
||||||
|
│ ├── 项目里程碑.md # 项目里程碑
|
||||||
|
│ └── 项目状态.md # 项目状态
|
||||||
|
├── .env # 环境配置
|
||||||
|
├── .gitignore
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 技术栈
|
||||||
|
|
||||||
|
- **后端**: Node.js + Express + MongoDB
|
||||||
|
- **前端**: Vue.js / React (待定)
|
||||||
|
- **数据库**: MongoDB
|
||||||
|
|
||||||
|
## 📋 功能模块
|
||||||
|
|
||||||
|
1. **车辆管理** - 车辆信息、状态追踪、GPS定位
|
||||||
|
2. **订单管理** - 租赁订单、计费、逾期提醒
|
||||||
|
3. **客户管理** - 客户信息、租赁历史
|
||||||
|
4. **财务管理** - 收入统计、报表生成
|
||||||
|
5. **运维管理** - 维修记录、保养提醒
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 安装 MongoDB
|
||||||
|
详细步骤请查看: [MongoDB安装指南.md](MongoDB安装指南.md)
|
||||||
|
|
||||||
|
### 2. 启动后端服务
|
||||||
|
```bash
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 生成测试数据
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试 API
|
||||||
|
访问: http://localhost:3000/health
|
||||||
|
|
||||||
|
## 📖 文档导航
|
||||||
|
|
||||||
|
- [API 文档](API.md) - 接口说明
|
||||||
|
- [数据库设计](数据库设计.md) - 数据模型设计
|
||||||
|
- [开发计划](开发计划.md) - 项目开发计划
|
||||||
|
- [快速开始](快速开始.md) - 快速上手指南
|
||||||
|
- [测试数据](测试数据.md) - 测试数据说明
|
||||||
|
- [MongoDB安装指南](MongoDB安装指南.md) - 数据库安装步骤
|
||||||
|
- [项目状态](项目状态.md) - 当前状态
|
||||||
|
|
||||||
|
## 🎯 功能优先级
|
||||||
|
|
||||||
|
### P0 (必须实现)
|
||||||
|
1. 车辆信息管理 (增删改查)
|
||||||
|
2. 订单创建和管理
|
||||||
|
3. 客户信息管理
|
||||||
|
4. 订单状态流转
|
||||||
|
5. 财务统计
|
||||||
|
|
||||||
|
### P1 (重要功能)
|
||||||
|
1. 车辆位置追踪
|
||||||
|
2. 逾期订单提醒
|
||||||
|
3. 客户信用评分
|
||||||
|
4. 数据搜索和筛选
|
||||||
|
|
||||||
|
### P2 (锦上添花)
|
||||||
|
1. 数据导出 (Excel)
|
||||||
|
2. 报表图表展示
|
||||||
|
3. 短信通知
|
||||||
|
4. 移动端适配
|
||||||
|
|
||||||
|
## 📝 开发规范
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
- 使用 ESLint 代码检查
|
||||||
|
- 统一代码风格
|
||||||
|
- 添加必要注释
|
||||||
|
|
||||||
|
### Git 规范
|
||||||
|
- 使用 feature 分支开发
|
||||||
|
- 提交信息格式: `类型: 描述`
|
||||||
|
- 定期合并到 main 分支
|
||||||
|
|
||||||
|
### API 规范
|
||||||
|
- 统一返回格式: `{ success, data, message }`
|
||||||
|
- 使用 HTTP 状态码
|
||||||
|
- 添加错误处理
|
||||||
|
|
||||||
|
## 🚀 当前状态
|
||||||
|
|
||||||
|
- ✅ 后端服务框架搭建完成
|
||||||
|
- ✅ 数据模型设计完成
|
||||||
|
- ✅ API 路由实现完成
|
||||||
|
- ✅ 文档编写完成
|
||||||
|
- ❌ MongoDB 未安装
|
||||||
|
- ⏳ 需要安装 MongoDB 数据库
|
||||||
|
- ⏳ 需要生成测试数据
|
||||||
|
- ⏳ 需要开发前端页面
|
||||||
|
|
||||||
|
## 📞 联系方式
|
||||||
|
|
||||||
|
如有问题,请联系项目负责人。
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
# 下一步行动
|
||||||
|
|
||||||
|
## 🎯 当前首要任务
|
||||||
|
|
||||||
|
### 1. 安装 MongoDB 数据库
|
||||||
|
|
||||||
|
由于当前环境未安装 MongoDB,需要手动安装。
|
||||||
|
|
||||||
|
**推荐安装方式:Docker (最简单)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装 Docker Desktop
|
||||||
|
# 访问 https://www.docker.com/products/docker-desktop/
|
||||||
|
# 下载并安装 Docker Desktop for Windows
|
||||||
|
|
||||||
|
# 2. 拉取 MongoDB 镜像
|
||||||
|
docker pull mongo
|
||||||
|
|
||||||
|
# 3. 启动 MongoDB 容器
|
||||||
|
docker run -d -p 27017:27017 --name mongodb mongo
|
||||||
|
|
||||||
|
# 4. 验证运行
|
||||||
|
docker ps
|
||||||
|
```
|
||||||
|
|
||||||
|
**备选安装方式:下载安装包**
|
||||||
|
|
||||||
|
1. 访问 https://www.mongodb.com/try/download/community
|
||||||
|
2. 下载 Windows 版本
|
||||||
|
3. 运行安装程序
|
||||||
|
4. 创建数据目录: `C:\data\db`
|
||||||
|
5. 启动 MongoDB
|
||||||
|
|
||||||
|
详细步骤请查看: `docs/MongoDB安装指南.md`
|
||||||
|
|
||||||
|
## 📋 安装完成后操作
|
||||||
|
|
||||||
|
### 1. 生成测试数据
|
||||||
|
```bash
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 测试 API 接口
|
||||||
|
|
||||||
|
#### 健康检查
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查询车辆
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查询客户
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/customers
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查询订单
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/orders
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 查询财务统计
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/finance/stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 使用 Postman 测试
|
||||||
|
1. 打开 Postman
|
||||||
|
2. 创建新请求
|
||||||
|
3. 设置 URL: `http://localhost:3000/api/vehicles`
|
||||||
|
4. 设置方法: GET
|
||||||
|
5. 发送请求
|
||||||
|
|
||||||
|
## 🚀 开发前端页面
|
||||||
|
|
||||||
|
### 选择技术栈
|
||||||
|
推荐使用 **Vue.js + Element Plus**
|
||||||
|
|
||||||
|
### 创建前端项目
|
||||||
|
```bash
|
||||||
|
# 使用 Vue CLI
|
||||||
|
vue create e-scooter-rental-client
|
||||||
|
|
||||||
|
# 或使用 Vite
|
||||||
|
npm create vite@latest e-scooter-rental-client -- --template vue
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端页面规划
|
||||||
|
1. **车辆管理页面**
|
||||||
|
- 车辆列表
|
||||||
|
- 车辆详情
|
||||||
|
- 添加/编辑车辆
|
||||||
|
- 车辆位置地图
|
||||||
|
|
||||||
|
2. **订单管理页面**
|
||||||
|
- 订单列表
|
||||||
|
- 创建订单
|
||||||
|
- 订单详情
|
||||||
|
- 结束订单
|
||||||
|
|
||||||
|
3. **客户管理页面**
|
||||||
|
- 客户列表
|
||||||
|
- 客户详情
|
||||||
|
- 添加/编辑客户
|
||||||
|
- 客户信用评分
|
||||||
|
|
||||||
|
4. **财务统计页面**
|
||||||
|
- 收入统计
|
||||||
|
- 逾期账款
|
||||||
|
- 财务报表
|
||||||
|
|
||||||
|
## 📝 开发计划
|
||||||
|
|
||||||
|
### 第一阶段:后端完善 (1天)
|
||||||
|
- [ ] 安装 MongoDB
|
||||||
|
- [ ] 生成测试数据
|
||||||
|
- [ ] 测试所有 API 接口
|
||||||
|
|
||||||
|
### 第二阶段:前端开发 (2-3天)
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
|
||||||
|
### 第三阶段:功能完善 (1-2天)
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
|
||||||
|
### 第四阶段:测试部署 (1天)
|
||||||
|
- [ ] 功能测试
|
||||||
|
- [ ] Bug 修复
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 📞 需要帮助?
|
||||||
|
|
||||||
|
如果在安装 MongoDB 或开发过程中遇到问题,请随时联系!
|
||||||
|
|
@ -0,0 +1,487 @@
|
||||||
|
# 前端开发指南
|
||||||
|
|
||||||
|
## 技术栈选择
|
||||||
|
|
||||||
|
### 推荐方案:Vue.js + Element Plus
|
||||||
|
|
||||||
|
**理由:**
|
||||||
|
- 学习曲线平缓,适合快速开发
|
||||||
|
- Element Plus 组件丰富,适合管理后台
|
||||||
|
- 社区活跃,文档完善
|
||||||
|
|
||||||
|
### 备选方案:React + Ant Design
|
||||||
|
|
||||||
|
**理由:**
|
||||||
|
- 生态系统庞大
|
||||||
|
- Ant Design 企业级组件库
|
||||||
|
- 适合大型项目
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e-scooter-rental-system/
|
||||||
|
├── frontend/ # 前端项目
|
||||||
|
│ ├── public/ # 静态资源
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/ # 公共组件
|
||||||
|
│ │ ├── views/ # 页面组件
|
||||||
|
│ │ ├── router/ # 路由配置
|
||||||
|
│ │ ├── store/ # 状态管理
|
||||||
|
│ │ ├── api/ # API 接口
|
||||||
|
│ │ ├── utils/ # 工具函数
|
||||||
|
│ │ └── App.vue # 根组件
|
||||||
|
│ ├── package.json
|
||||||
|
│ └── vue.config.js
|
||||||
|
└── server/ # 后端项目 (已存在)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 页面设计
|
||||||
|
|
||||||
|
### 1. 车辆管理页面
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 车辆列表展示(表格)
|
||||||
|
- 车辆搜索和筛选
|
||||||
|
- 车辆详情查看
|
||||||
|
- 车辆添加/编辑/删除
|
||||||
|
- 车辆状态更新
|
||||||
|
|
||||||
|
**页面布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 顶部导航栏 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 搜索栏 + 筛选条件 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 车辆列表(表格) │
|
||||||
|
│ - 车辆编号 │
|
||||||
|
│ - 车辆类型 │
|
||||||
|
│ - 状态(空闲/使用中/维修中) │
|
||||||
|
│ - 位置 │
|
||||||
|
│ - 操作(查看/编辑/删除) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 订单管理页面
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 订单列表展示
|
||||||
|
- 订单搜索和筛选(按状态、时间等)
|
||||||
|
- 订单详情查看
|
||||||
|
- 创建新订单
|
||||||
|
- 结束订单
|
||||||
|
- 逾期订单提醒
|
||||||
|
|
||||||
|
**页面布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 顶部导航栏 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 搜索栏 + 筛选条件(状态、时间范围) │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 订单列表(表格) │
|
||||||
|
│ - 订单号 │
|
||||||
|
│ - 客户名称 │
|
||||||
|
│ - 车辆信息 │
|
||||||
|
│ - 租期 │
|
||||||
|
│ - 费用 │
|
||||||
|
│ - 状态 │
|
||||||
|
│ - 操作(查看/结束) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 客户管理页面
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 客户列表展示
|
||||||
|
- 客户搜索
|
||||||
|
- 客户详情查看
|
||||||
|
- 添加/编辑/删除客户
|
||||||
|
- 信用评分管理
|
||||||
|
|
||||||
|
**页面布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 顶部导航栏 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 搜索栏 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 客户列表(表格) │
|
||||||
|
│ - 客户姓名 │
|
||||||
|
│ - 联系方式 │
|
||||||
|
│ - 信用评分 │
|
||||||
|
│ - 租赁历史 │
|
||||||
|
│ - 操作(查看/编辑/删除) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 财务统计页面
|
||||||
|
|
||||||
|
**功能:**
|
||||||
|
- 收入统计图表
|
||||||
|
- 逾期账款统计
|
||||||
|
- 订单收入趋势
|
||||||
|
- 导出报表
|
||||||
|
|
||||||
|
**页面布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 顶部导航栏 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 统计卡片 │
|
||||||
|
│ - 总收入 │
|
||||||
|
│ - 本月收入 │
|
||||||
|
│ - 逾期账款 │
|
||||||
|
│ - 订单数量 │
|
||||||
|
├─────────────────────────────────────┤
|
||||||
|
│ 图表区域 │
|
||||||
|
│ - 收入趋势图 │
|
||||||
|
│ - 订单状态分布 │
|
||||||
|
│ - 车辆使用率 │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件设计
|
||||||
|
|
||||||
|
### 公共组件
|
||||||
|
|
||||||
|
1. **导航栏组件**
|
||||||
|
- 顶部导航
|
||||||
|
- 侧边栏菜单
|
||||||
|
|
||||||
|
2. **表格组件**
|
||||||
|
- 支持分页
|
||||||
|
- 支持搜索
|
||||||
|
- 支持排序
|
||||||
|
|
||||||
|
3. **表单组件**
|
||||||
|
- 输入框
|
||||||
|
- 下拉选择
|
||||||
|
- 日期选择
|
||||||
|
- 表单验证
|
||||||
|
|
||||||
|
4. **弹窗组件**
|
||||||
|
- 确认弹窗
|
||||||
|
- 详情弹窗
|
||||||
|
- 编辑弹窗
|
||||||
|
|
||||||
|
### 页面组件
|
||||||
|
|
||||||
|
1. **车辆管理**
|
||||||
|
- VehicleList.vue
|
||||||
|
- VehicleDetail.vue
|
||||||
|
- VehicleForm.vue
|
||||||
|
|
||||||
|
2. **订单管理**
|
||||||
|
- OrderList.vue
|
||||||
|
- OrderDetail.vue
|
||||||
|
- OrderForm.vue
|
||||||
|
|
||||||
|
3. **客户管理**
|
||||||
|
- CustomerList.vue
|
||||||
|
- CustomerDetail.vue
|
||||||
|
- CustomerForm.vue
|
||||||
|
|
||||||
|
4. **财务统计**
|
||||||
|
- FinanceDashboard.vue
|
||||||
|
- StatisticsChart.vue
|
||||||
|
|
||||||
|
## API 接口调用
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/api/request.js
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const request = axios.create({
|
||||||
|
baseURL: 'http://localhost:3000/api',
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
request.interceptors.request.use(config => {
|
||||||
|
// 添加认证 token
|
||||||
|
const token = localStorage.getItem('token')
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
request.interceptors.response.use(
|
||||||
|
response => response.data,
|
||||||
|
error => {
|
||||||
|
console.error('API Error:', error)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default request
|
||||||
|
```
|
||||||
|
|
||||||
|
### 车辆管理 API
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/api/vehicle.js
|
||||||
|
import request from './request'
|
||||||
|
|
||||||
|
export const getVehicles = (params) => request.get('/vehicles', { params })
|
||||||
|
export const getVehicle = (id) => request.get(`/vehicles/${id}`)
|
||||||
|
export const createVehicle = (data) => request.post('/vehicles', data)
|
||||||
|
export const updateVehicle = (id, data) => request.put(`/vehicles/${id}`, data)
|
||||||
|
export const deleteVehicle = (id) => request.delete(`/vehicles/${id}`)
|
||||||
|
export const getVehiclesByStatus = (status) => request.get(`/vehicles/status/${status}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 订单管理 API
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/api/order.js
|
||||||
|
import request from './request'
|
||||||
|
|
||||||
|
export const getOrders = (params) => request.get('/orders', { params })
|
||||||
|
export const getOrder = (id) => request.get(`/orders/${id}`)
|
||||||
|
export const createOrder = (data) => request.post('/orders', data)
|
||||||
|
export const updateOrder = (id, data) => request.put(`/orders/${id}`, data)
|
||||||
|
export const endOrder = (id) => request.post(`/orders/${id}/end`)
|
||||||
|
export const getOverdueOrders = () => request.get('/orders/overdue')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 客户管理 API
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/api/customer.js
|
||||||
|
import request from './request'
|
||||||
|
|
||||||
|
export const getCustomers = (params) => request.get('/customers', { params })
|
||||||
|
export const getCustomer = (id) => request.get(`/customers/${id}`)
|
||||||
|
export const createCustomer = (data) => request.post('/customers', data)
|
||||||
|
export const updateCustomer = (id, data) => request.put(`/customers/${id}`, data)
|
||||||
|
export const deleteCustomer = (id) => request.delete(`/customers/${id}`)
|
||||||
|
export const searchCustomers = (keyword) => request.get(`/customers/search/${keyword}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 状态管理
|
||||||
|
|
||||||
|
### 使用 Vuex
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/store/index.js
|
||||||
|
import { createStore } from 'vuex'
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
state: {
|
||||||
|
vehicles: [],
|
||||||
|
orders: [],
|
||||||
|
customers: [],
|
||||||
|
currentUser: null
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
SET_VEHICLES(state, vehicles) {
|
||||||
|
state.vehicles = vehicles
|
||||||
|
},
|
||||||
|
SET_ORDERS(state, orders) {
|
||||||
|
state.orders = orders
|
||||||
|
},
|
||||||
|
SET_CUSTOMERS(state, customers) {
|
||||||
|
state.customers = customers
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async fetchVehicles({ commit }) {
|
||||||
|
const response = await getVehicles()
|
||||||
|
commit('SET_VEHICLES', response.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 路由配置
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/router/index.js
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: '/vehicles'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/vehicles',
|
||||||
|
name: 'VehicleList',
|
||||||
|
component: () => import('@/views/VehicleList.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/vehicles/:id',
|
||||||
|
name: 'VehicleDetail',
|
||||||
|
component: () => import('@/views/VehicleDetail.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/orders',
|
||||||
|
name: 'OrderList',
|
||||||
|
component: () => import('@/views/OrderList.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/customers',
|
||||||
|
name: 'CustomerList',
|
||||||
|
component: () => import('@/views/CustomerList.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/finance',
|
||||||
|
name: 'FinanceDashboard',
|
||||||
|
component: () => import('@/views/FinanceDashboard.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
|
```
|
||||||
|
|
||||||
|
## 样式设计
|
||||||
|
|
||||||
|
### 使用 Element Plus 主题
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/main.js
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(ElementPlus)
|
||||||
|
app.use(router)
|
||||||
|
app.use(store)
|
||||||
|
app.mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义样式
|
||||||
|
|
||||||
|
```scss
|
||||||
|
// src/assets/styles/main.scss
|
||||||
|
// 主题色
|
||||||
|
$primary-color: #409EFF;
|
||||||
|
$success-color: #67C23A;
|
||||||
|
$warning-color: #E6A23C;
|
||||||
|
$danger-color: #F56C6C;
|
||||||
|
$info-color: #909399;
|
||||||
|
|
||||||
|
// 布局
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格样式
|
||||||
|
.table-container {
|
||||||
|
.el-table {
|
||||||
|
.cell {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单样式
|
||||||
|
.form-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发步骤
|
||||||
|
|
||||||
|
### 1. 创建前端项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入项目目录
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
|
||||||
|
# 创建前端项目
|
||||||
|
npm create vue@latest frontend
|
||||||
|
# 或使用 Vue CLI
|
||||||
|
# vue create frontend
|
||||||
|
|
||||||
|
# 进入前端目录
|
||||||
|
cd frontend
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 安装 Element Plus
|
||||||
|
npm install element-plus
|
||||||
|
|
||||||
|
# 安装 Vue Router
|
||||||
|
npm install vue-router@4
|
||||||
|
|
||||||
|
# 安装 Vuex (可选)
|
||||||
|
npm install vuex@next
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置开发环境
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// frontend/vue.config.js
|
||||||
|
module.exports = {
|
||||||
|
devServer: {
|
||||||
|
port: 8080,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3000',
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动开发服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
访问:http://localhost:8080
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **跨域问题**
|
||||||
|
- 开发环境使用 proxy 解决跨域
|
||||||
|
- 生产环境需要配置 CORS
|
||||||
|
|
||||||
|
2. **API 认证**
|
||||||
|
- 使用 JWT token 认证
|
||||||
|
- token 存储在 localStorage
|
||||||
|
|
||||||
|
3. **错误处理**
|
||||||
|
- 统一错误提示
|
||||||
|
- 网络错误处理
|
||||||
|
|
||||||
|
4. **性能优化**
|
||||||
|
- 按需加载组件
|
||||||
|
- 图片懒加载
|
||||||
|
- 数据缓存
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. 安装 MongoDB(首要任务)
|
||||||
|
2. 生成测试数据
|
||||||
|
3. 创建前端项目
|
||||||
|
4. 开发车辆管理页面
|
||||||
|
5. 开发订单管理页面
|
||||||
|
6. 开发客户管理页面
|
||||||
|
7. 开发财务统计页面
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
# 常见问题
|
||||||
|
|
||||||
|
## MongoDB 相关
|
||||||
|
|
||||||
|
### 1. MongoDB 连接失败
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
```
|
||||||
|
connect ECONNREFUSED ::1:27017
|
||||||
|
connect ECONNREFUSED 127.0.0.1:27017
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因:** MongoDB 未安装或未启动
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 安装 MongoDB (推荐使用 Docker)
|
||||||
|
2. 启动 MongoDB
|
||||||
|
3. 重新启动后端服务
|
||||||
|
|
||||||
|
### 2. 端口被占用
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
```
|
||||||
|
EADDRINUSE: address already in use :::27017
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 修改 .env 文件中的 MONGODB_URI
|
||||||
|
2. 或停止占用端口的服务
|
||||||
|
|
||||||
|
### 3. Docker 安装失败
|
||||||
|
|
||||||
|
**可能原因:**
|
||||||
|
- Docker Desktop 未启动
|
||||||
|
- 端口冲突
|
||||||
|
- 权限不足
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 确保 Docker Desktop 正在运行
|
||||||
|
2. 检查端口是否被占用
|
||||||
|
3. 以管理员身份运行命令
|
||||||
|
|
||||||
|
## 后端服务相关
|
||||||
|
|
||||||
|
### 1. 端口被占用
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
```
|
||||||
|
Error: listen EADDRINUSE: address already in use :::3000
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 修改 .env 文件中的 PORT
|
||||||
|
2. 或停止占用 3000 端口的服务
|
||||||
|
|
||||||
|
### 2. nodemon 未找到
|
||||||
|
|
||||||
|
**错误信息:**
|
||||||
|
```
|
||||||
|
'nodemon' is not recognized as an internal or external command
|
||||||
|
```
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
```bash
|
||||||
|
npm install nodemon --save-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 依赖安装失败
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
1. 清除 npm 缓存: `npm cache clean --force`
|
||||||
|
2. 删除 node_modules 文件夹
|
||||||
|
3. 重新安装: `npm install`
|
||||||
|
|
||||||
|
## API 相关
|
||||||
|
|
||||||
|
### 1. API 返回错误
|
||||||
|
|
||||||
|
**检查步骤:**
|
||||||
|
1. 确保 MongoDB 正在运行
|
||||||
|
2. 检查 API URL 是否正确
|
||||||
|
3. 查看后端日志
|
||||||
|
|
||||||
|
### 2. 跨域问题
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
- 已配置 CORS 中间件
|
||||||
|
- 确保前端正确配置代理
|
||||||
|
|
||||||
|
## 开发相关
|
||||||
|
|
||||||
|
### 1. 如何添加新功能
|
||||||
|
|
||||||
|
**步骤:**
|
||||||
|
1. 设计数据模型
|
||||||
|
2. 创建模型文件
|
||||||
|
3. 创建路由文件
|
||||||
|
4. 更新 API 文档
|
||||||
|
5. 测试接口
|
||||||
|
|
||||||
|
### 2. 如何修改现有功能
|
||||||
|
|
||||||
|
**步骤:**
|
||||||
|
1. 修改模型文件
|
||||||
|
2. 修改路由文件
|
||||||
|
3. 更新 API 文档
|
||||||
|
4. 测试接口
|
||||||
|
|
||||||
|
## 部署相关
|
||||||
|
|
||||||
|
### 1. 生产环境配置
|
||||||
|
|
||||||
|
**注意事项:**
|
||||||
|
1. 修改 .env 文件
|
||||||
|
2. 配置 MongoDB 远程连接
|
||||||
|
3. 配置 CORS 白名单
|
||||||
|
4. 启用 HTTPS
|
||||||
|
|
||||||
|
### 2. 性能优化
|
||||||
|
|
||||||
|
**建议:**
|
||||||
|
1. 使用 Redis 缓存
|
||||||
|
2. 优化数据库查询
|
||||||
|
3. 启用 gzip 压缩
|
||||||
|
4. 使用 CDN
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如果遇到其他问题,请查看:
|
||||||
|
- `docs/MongoDB安装指南.md` - 数据库安装
|
||||||
|
- `docs/快速开始.md` - 快速上手
|
||||||
|
- `docs/API.md` - 接口文档
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
# 开发计划
|
||||||
|
|
||||||
|
## 📅 项目时间线
|
||||||
|
|
||||||
|
### 第一阶段:后端开发 (1-2天)
|
||||||
|
- [x] 项目初始化
|
||||||
|
- [x] 数据库模型设计
|
||||||
|
- [x] API 路由实现
|
||||||
|
- [ ] 数据库连接测试
|
||||||
|
- [ ] API 接口测试
|
||||||
|
|
||||||
|
### 第二阶段:前端开发 (2-3天)
|
||||||
|
- [ ] 前端项目初始化
|
||||||
|
- [ ] 页面布局设计
|
||||||
|
- [ ] 车辆管理页面
|
||||||
|
- [ ] 订单管理页面
|
||||||
|
- [ ] 客户管理页面
|
||||||
|
- [ ] 财务统计页面
|
||||||
|
|
||||||
|
### 第三阶段:功能完善 (1-2天)
|
||||||
|
- [ ] 用户登录/权限管理
|
||||||
|
- [ ] 数据导入导出
|
||||||
|
- [ ] 报表生成
|
||||||
|
- [ ] 逾期提醒功能
|
||||||
|
|
||||||
|
### 第四阶段:测试部署 (1天)
|
||||||
|
- [ ] 功能测试
|
||||||
|
- [ ] Bug 修复
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 功能优先级
|
||||||
|
|
||||||
|
### P0 (必须实现)
|
||||||
|
1. 车辆信息管理 (增删改查)
|
||||||
|
2. 订单创建和管理
|
||||||
|
3. 客户信息管理
|
||||||
|
4. 订单状态流转
|
||||||
|
5. 财务统计
|
||||||
|
|
||||||
|
### P1 (重要功能)
|
||||||
|
1. 车辆位置追踪
|
||||||
|
2. 逾期订单提醒
|
||||||
|
3. 客户信用评分
|
||||||
|
4. 数据搜索和筛选
|
||||||
|
|
||||||
|
### P2 (锦上添花)
|
||||||
|
1. 数据导出 (Excel)
|
||||||
|
2. 报表图表展示
|
||||||
|
3. 短信通知
|
||||||
|
4. 移动端适配
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 技术栈选择
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
- Node.js + Express
|
||||||
|
- MongoDB (数据库)
|
||||||
|
- JWT (认证)
|
||||||
|
|
||||||
|
### 前端 (待定)
|
||||||
|
- Vue.js + Element Plus
|
||||||
|
- 或 React + Ant Design
|
||||||
|
|
||||||
|
### 部署
|
||||||
|
- 本地开发: Node.js
|
||||||
|
- 生产环境: 云服务器 + Docker
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 开发规范
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
- 使用 ESLint 代码检查
|
||||||
|
- 统一代码风格
|
||||||
|
- 添加必要注释
|
||||||
|
|
||||||
|
### Git 规范
|
||||||
|
- 使用 feature 分支开发
|
||||||
|
- 提交信息格式: `类型: 描述`
|
||||||
|
- 定期合并到 main 分支
|
||||||
|
|
||||||
|
### API 规范
|
||||||
|
- 统一返回格式: `{ success, data, message }`
|
||||||
|
- 使用 HTTP 状态码
|
||||||
|
- 添加错误处理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 启动 MongoDB
|
||||||
|
```bash
|
||||||
|
# Windows
|
||||||
|
mongod --dbpath E:\data\db
|
||||||
|
|
||||||
|
# 或使用 Docker
|
||||||
|
docker run -d -p 27017:27017 --name mongodb mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动后端
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 测试 API
|
||||||
|
访问: http://localhost:3000/health
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 联系方式
|
||||||
|
|
||||||
|
如有问题,请联系项目负责人。
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
# 开发进度
|
||||||
|
|
||||||
|
## 📅 2026-03-05
|
||||||
|
|
||||||
|
### ✅ 已完成
|
||||||
|
|
||||||
|
#### 1. 项目初始化
|
||||||
|
- 创建项目目录 `E:\code\e-scooter-rental-system`
|
||||||
|
- 初始化 npm 项目
|
||||||
|
- 安装依赖: express, mongoose, cors, dotenv
|
||||||
|
|
||||||
|
#### 2. 后端服务
|
||||||
|
- ✅ 创建 Express 服务器
|
||||||
|
- ✅ 配置 MongoDB 连接
|
||||||
|
- ✅ 实现错误处理中间件
|
||||||
|
- ✅ 实现 404 处理
|
||||||
|
- ✅ 健康检查接口
|
||||||
|
|
||||||
|
#### 3. 数据模型
|
||||||
|
- ✅ 车辆模型 (Vehicle)
|
||||||
|
- ✅ 订单模型 (Order)
|
||||||
|
- ✅ 客户模型 (Customer)
|
||||||
|
|
||||||
|
#### 4. API 路由
|
||||||
|
- ✅ 车辆管理路由
|
||||||
|
- ✅ 订单管理路由
|
||||||
|
- ✅ 客户管理路由
|
||||||
|
- ✅ 财务管理路由
|
||||||
|
|
||||||
|
#### 5. 文档编写
|
||||||
|
- ✅ API 文档
|
||||||
|
- ✅ 数据库设计文档
|
||||||
|
- ✅ 开发计划文档
|
||||||
|
- ✅ 快速开始指南
|
||||||
|
- ✅ 测试数据说明
|
||||||
|
- ✅ MongoDB 安装指南
|
||||||
|
- ✅ 项目说明文档
|
||||||
|
|
||||||
|
#### 6. 工具脚本
|
||||||
|
- ✅ 测试数据生成脚本 (seed.js)
|
||||||
|
|
||||||
|
### ⏳ 进行中
|
||||||
|
|
||||||
|
1. **安装 MongoDB 数据库**
|
||||||
|
- 需要手动安装 MongoDB
|
||||||
|
- 详细步骤见 `MongoDB安装指南.md`
|
||||||
|
|
||||||
|
2. **生成测试数据**
|
||||||
|
- 等待 MongoDB 安装完成后运行
|
||||||
|
|
||||||
|
3. **测试 API 接口**
|
||||||
|
- 等待 MongoDB 安装完成后测试
|
||||||
|
|
||||||
|
### 📋 待办事项
|
||||||
|
|
||||||
|
#### 立即执行
|
||||||
|
- [ ] 安装 MongoDB 数据库
|
||||||
|
- [ ] 运行种子脚本生成测试数据
|
||||||
|
- [ ] 测试所有 API 接口
|
||||||
|
|
||||||
|
#### 短期目标 (1-2天)
|
||||||
|
- [ ] 选择前端技术栈 (Vue.js 或 React)
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
|
||||||
|
#### 中期目标 (3-5天)
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
#### 长期目标
|
||||||
|
- [ ] 部署上线
|
||||||
|
- [ ] 性能优化
|
||||||
|
- [ ] 用户反馈收集
|
||||||
|
|
||||||
|
## 📊 项目统计
|
||||||
|
|
||||||
|
- **代码文件**: 15 个
|
||||||
|
- **文档文件**: 7 个
|
||||||
|
- **API 接口**: 4 个模块
|
||||||
|
- **数据模型**: 3 个
|
||||||
|
|
||||||
|
## 🎯 当前状态
|
||||||
|
|
||||||
|
- ✅ 后端服务框架完成
|
||||||
|
- ✅ 数据模型设计完成
|
||||||
|
- ⏳ 需要安装 MongoDB
|
||||||
|
- ⏳ 需要生成测试数据
|
||||||
|
- ⏳ 需要开发前端
|
||||||
|
|
||||||
|
## 📝 注意事项
|
||||||
|
|
||||||
|
1. MongoDB 安装是当前首要任务
|
||||||
|
2. 安装完成后运行 `npm run seed` 生成测试数据
|
||||||
|
3. 前端开发需要选择技术栈
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
# 快速开始指南
|
||||||
|
|
||||||
|
## 1. 安装 MongoDB
|
||||||
|
|
||||||
|
### Windows 安装步骤
|
||||||
|
|
||||||
|
1. **下载 MongoDB**
|
||||||
|
- 访问: https://www.mongodb.com/try/download/community
|
||||||
|
- 选择 Windows 版本下载
|
||||||
|
|
||||||
|
2. **安装 MongoDB**
|
||||||
|
- 运行安装程序
|
||||||
|
- 选择 "Complete" 安装类型
|
||||||
|
- 勾选 "Install MongoDB as a Service"
|
||||||
|
|
||||||
|
3. **配置数据目录**
|
||||||
|
- 默认路径: `C:\data\db`
|
||||||
|
- 如果目录不存在,手动创建:
|
||||||
|
```bash
|
||||||
|
mkdir C:\data\db
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **启动 MongoDB**
|
||||||
|
- 如果安装为服务,会自动启动
|
||||||
|
- 或手动启动:
|
||||||
|
```bash
|
||||||
|
mongod --dbpath C:\data\db
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 Docker 安装 (推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 拉取 MongoDB 镜像
|
||||||
|
docker pull mongo
|
||||||
|
|
||||||
|
# 启动 MongoDB 容器
|
||||||
|
docker run -d -p 27017:27017 --name mongodb mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 启动后端服务
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
```bash
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动开发服务器
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证服务
|
||||||
|
访问: http://localhost:3000/health
|
||||||
|
|
||||||
|
预期返回:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"timestamp": "2026-03-05T06:06:20.281Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 测试 API 接口
|
||||||
|
|
||||||
|
### 使用 curl 测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试健康检查
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
|
||||||
|
# 测试车辆列表 (需要先启动 MongoDB)
|
||||||
|
curl http://localhost:3000/api/vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 Postman 测试
|
||||||
|
|
||||||
|
1. 打开 Postman
|
||||||
|
2. 创建新请求
|
||||||
|
3. 设置 URL: `http://localhost:3000/api/vehicles`
|
||||||
|
4. 设置方法: GET
|
||||||
|
5. 发送请求
|
||||||
|
|
||||||
|
## 4. 常见问题
|
||||||
|
|
||||||
|
### MongoDB 连接失败
|
||||||
|
- 检查 MongoDB 是否启动
|
||||||
|
- 检查端口 27017 是否被占用
|
||||||
|
- 检查 .env 文件中的 MONGODB_URI 配置
|
||||||
|
|
||||||
|
### 端口被占用
|
||||||
|
- 修改 .env 文件中的 PORT 配置
|
||||||
|
- 或停止占用端口的其他服务
|
||||||
|
|
||||||
|
### nodemon 命令未找到
|
||||||
|
- 运行: `npm install nodemon --save-dev`
|
||||||
|
|
||||||
|
## 5. 下一步
|
||||||
|
|
||||||
|
1. ✅ 后端服务已启动
|
||||||
|
2. ⏳ 安装并启动 MongoDB
|
||||||
|
3. ⏳ 测试 API 接口
|
||||||
|
4. ⏳ 开发前端页面
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
# 数据库设计文档
|
||||||
|
|
||||||
|
## 数据库: MongoDB
|
||||||
|
## 集合: vehicles, orders, customers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 车辆集合 (vehicles)
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| vehicleId | String | ✅ | 车架号/车辆编号 (唯一) |
|
||||||
|
| model | String | ✅ | 车型 |
|
||||||
|
| brand | String | ✅ | 品牌 |
|
||||||
|
| color | String | ❌ | 颜色 |
|
||||||
|
| batteryType | String | ❌ | 电池类型 |
|
||||||
|
| batteryCapacity | Number | ❌ | 电池容量 (Ah) |
|
||||||
|
| batteryStatus | String | ❌ | 电池状态: 正常/老化/待更换 |
|
||||||
|
| status | String | ✅ | 车辆状态: 空闲/在租/维修中/已报废/待回收 |
|
||||||
|
| location | Object | ❌ | GPS位置 {type, coordinates} |
|
||||||
|
| lastLocationUpdate | Date | ❌ | 最后位置更新时间 |
|
||||||
|
| currentOrderId | ObjectId | ❌ | 当前订单ID (关联orders) |
|
||||||
|
| totalRentDays | Number | ❌ | 累计租赁天数 |
|
||||||
|
| lastMaintenanceDate | Date | ❌ | 最后保养日期 |
|
||||||
|
| nextMaintenanceDate | Date | ❌ | 下次保养日期 |
|
||||||
|
| maintenanceHistory | Array | ❌ | 维修历史记录 |
|
||||||
|
| purchaseDate | Date | ❌ | 购买日期 |
|
||||||
|
| purchasePrice | Number | ❌ | 购买价格 |
|
||||||
|
| purchaseSupplier | String | ❌ | 供应商 |
|
||||||
|
| notes | String | ❌ | 备注 |
|
||||||
|
| createdAt | Date | ✅ | 创建时间 |
|
||||||
|
| updatedAt | Date | ✅ | 更新时间 |
|
||||||
|
|
||||||
|
### 索引
|
||||||
|
- vehicleId: 唯一索引
|
||||||
|
- location: 2dsphere 索引 (用于地理查询)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 订单集合 (orders)
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| orderNumber | String | ✅ | 订单号 (唯一) |
|
||||||
|
| customer | ObjectId | ✅ | 客户ID (关联customers) |
|
||||||
|
| vehicle | ObjectId | ✅ | 车辆ID (关联vehicles) |
|
||||||
|
| startDate | Date | ✅ | 开始日期 |
|
||||||
|
| endDate | Date | ✅ | 结束日期 |
|
||||||
|
| actualEndDate | Date | ❌ | 实际结束日期 |
|
||||||
|
| rentalFee | Number | ✅ | 租金 (元/天) |
|
||||||
|
| deposit | Number | ❌ | 押金 |
|
||||||
|
| totalAmount | Number | ✅ | 总金额 |
|
||||||
|
| paidAmount | Number | ❌ | 已支付金额 |
|
||||||
|
| status | String | ✅ | 订单状态: 待支付/进行中/已完成/逾期/已取消/已退款 |
|
||||||
|
| overdueDays | Number | ❌ | 逾期天数 |
|
||||||
|
| overdueFee | Number | ❌ | 逾期费用 |
|
||||||
|
| paymentMethod | String | ❌ | 支付方式: 微信/支付宝/现金/银行卡 |
|
||||||
|
| paymentDate | Date | ❌ | 支付日期 |
|
||||||
|
| contractUrl | String | ❌ | 合同文件路径 |
|
||||||
|
| contractSigned | Boolean | ❌ | 合同是否签署 |
|
||||||
|
| notes | String | ❌ | 备注 |
|
||||||
|
| createdAt | Date | ✅ | 创建时间 |
|
||||||
|
| updatedAt | Date | ✅ | 更新时间 |
|
||||||
|
|
||||||
|
### 索引
|
||||||
|
- orderNumber: 唯一索引
|
||||||
|
- customer: 普通索引
|
||||||
|
- vehicle: 普通索引
|
||||||
|
- status: 普通索引
|
||||||
|
- endDate: 普通索引 (用于查询逾期订单)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 客户集合 (customers)
|
||||||
|
|
||||||
|
### 字段说明
|
||||||
|
| 字段名 | 类型 | 必填 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| customerId | String | ✅ | 客户编号 (唯一) |
|
||||||
|
| name | String | ✅ | 姓名 |
|
||||||
|
| phone | String | ✅ | 手机号 |
|
||||||
|
| idCard | String | ❌ | 身份证号 |
|
||||||
|
| address | String | ❌ | 地址 |
|
||||||
|
| email | String | ❌ | 邮箱 |
|
||||||
|
| totalRentals | Number | ❌ | 总租赁次数 |
|
||||||
|
| totalSpent | Number | ❌ | 总消费金额 |
|
||||||
|
| currentRentals | Number | ❌ | 当前租赁数量 |
|
||||||
|
| creditScore | Number | ❌ | 信用评分 (0-100) |
|
||||||
|
| creditLevel | String | ❌ | 信用等级: 优秀/良好/一般/较差 |
|
||||||
|
| accountStatus | String | ✅ | 账户状态: 正常/冻结/注销 |
|
||||||
|
| notes | String | ❌ | 备注 |
|
||||||
|
| createdAt | Date | ✅ | 创建时间 |
|
||||||
|
| updatedAt | Date | ✅ | 更新时间 |
|
||||||
|
|
||||||
|
### 索引
|
||||||
|
- customerId: 唯一索引
|
||||||
|
- phone: 唯一索引
|
||||||
|
- name: 普通索引 (用于搜索)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 关系说明
|
||||||
|
|
||||||
|
### 车辆 ↔ 订单
|
||||||
|
- 一辆车同一时间只能有一个进行中的订单
|
||||||
|
- 车辆状态会随订单状态变化
|
||||||
|
|
||||||
|
### 客户 ↔ 订单
|
||||||
|
- 一个客户可以有多个订单
|
||||||
|
- 客户的租赁次数和消费金额会随订单更新
|
||||||
|
|
||||||
|
### 订单 ↔ 车辆/客户
|
||||||
|
- 订单必须关联一辆车和一个客户
|
||||||
|
- 订单完成后,车辆状态变为空闲
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 数据验证规则
|
||||||
|
|
||||||
|
### 车辆
|
||||||
|
- vehicleId 必须唯一
|
||||||
|
- status 必须在预定义枚举中
|
||||||
|
- location 必须是有效的经纬度坐标
|
||||||
|
|
||||||
|
### 订单
|
||||||
|
- orderNumber 必须唯一
|
||||||
|
- startDate 必须早于 endDate
|
||||||
|
- rentalFee 必须大于 0
|
||||||
|
- status 必须在预定义枚举中
|
||||||
|
|
||||||
|
### 客户
|
||||||
|
- customerId 必须唯一
|
||||||
|
- phone 必须唯一
|
||||||
|
- creditScore 必须在 0-100 之间
|
||||||
|
- creditLevel 由 creditScore 自动计算
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
# 文件清单
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e-scooter-rental-system/
|
||||||
|
├── server/ # 后端代码
|
||||||
|
│ ├── models/ # 数据模型
|
||||||
|
│ │ ├── Vehicle.js # 车辆模型
|
||||||
|
│ │ ├── Order.js # 订单模型
|
||||||
|
│ │ ├── Customer.js # 客户模型
|
||||||
|
│ │ └── index.js # 模型导出
|
||||||
|
│ ├── routes/ # 路由
|
||||||
|
│ │ ├── vehicles.js # 车辆路由
|
||||||
|
│ │ ├── orders.js # 订单路由
|
||||||
|
│ │ ├── customers.js # 客户路由
|
||||||
|
│ │ ├── finance.js # 财务路由
|
||||||
|
│ │ └── index.js # 路由导出
|
||||||
|
│ ├── middleware/ # 中间件
|
||||||
|
│ │ └── errorHandler.js # 错误处理
|
||||||
|
│ ├── seed.js # 测试数据脚本
|
||||||
|
│ └── index.js # 入口文件
|
||||||
|
├── docs/ # 文档
|
||||||
|
│ ├── API.md # API 文档
|
||||||
|
│ ├── 数据库设计.md # 数据库设计
|
||||||
|
│ ├── 开发计划.md # 开发计划
|
||||||
|
│ ├── 快速开始.md # 快速开始指南
|
||||||
|
│ ├── 测试数据.md # 测试数据说明
|
||||||
|
│ ├── MongoDB安装指南.md # MongoDB 安装指南
|
||||||
|
│ ├── README.md # 项目说明
|
||||||
|
│ ├── 开发进度.md # 开发进度
|
||||||
|
│ ├── 下一步行动.md # 下一步行动
|
||||||
|
│ ├── 项目总结.md # 项目总结
|
||||||
|
│ ├── 文件清单.md # 文件清单
|
||||||
|
│ ├── 常见问题.md # 常见问题
|
||||||
|
│ ├── 项目里程碑.md # 项目里程碑
|
||||||
|
│ ├── 项目状态.md # 项目状态
|
||||||
|
│ ├── 项目完成清单.md # 项目完成清单
|
||||||
|
│ ├── 前端开发指南.md # 前端开发指南
|
||||||
|
│ └── 页面设计.md # 页面设计
|
||||||
|
├── .env # 环境配置
|
||||||
|
├── .gitignore # Git 忽略文件
|
||||||
|
├── package.json # 项目配置
|
||||||
|
└── README.md # 项目说明
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码文件 (15个)
|
||||||
|
|
||||||
|
### 数据模型 (4个)
|
||||||
|
1. `server/models/Vehicle.js` - 车辆模型
|
||||||
|
2. `server/models/Order.js` - 订单模型
|
||||||
|
3. `server/models/Customer.js` - 客户模型
|
||||||
|
4. `server/models/index.js` - 模型导出
|
||||||
|
|
||||||
|
### 路由 (5个)
|
||||||
|
5. `server/routes/vehicles.js` - 车辆路由
|
||||||
|
6. `server/routes/orders.js` - 订单路由
|
||||||
|
7. `server/routes/customers.js` - 客户路由
|
||||||
|
8. `server/routes/finance.js` - 财务路由
|
||||||
|
9. `server/routes/index.js` - 路由导出
|
||||||
|
|
||||||
|
### 中间件 (1个)
|
||||||
|
10. `server/middleware/errorHandler.js` - 错误处理
|
||||||
|
|
||||||
|
### 入口文件 (1个)
|
||||||
|
11. `server/index.js` - 入口文件
|
||||||
|
|
||||||
|
### 工具脚本 (1个)
|
||||||
|
12. `server/seed.js` - 测试数据脚本
|
||||||
|
|
||||||
|
### 配置文件 (3个)
|
||||||
|
13. `.env` - 环境配置
|
||||||
|
14. `.gitignore` - Git 忽略文件
|
||||||
|
15. `package.json` - 项目配置
|
||||||
|
|
||||||
|
## 文档文件 (16个)
|
||||||
|
|
||||||
|
### 项目文档 (16个)
|
||||||
|
1. `docs/API.md` - API 接口文档
|
||||||
|
2. `docs/数据库设计.md` - 数据库设计文档
|
||||||
|
3. `docs/开发计划.md` - 开发计划文档
|
||||||
|
4. `docs/快速开始.md` - 快速开始指南
|
||||||
|
5. `docs/测试数据.md` - 测试数据说明
|
||||||
|
6. `docs/MongoDB安装指南.md` - MongoDB 安装指南
|
||||||
|
7. `docs/README.md` - 项目说明文档
|
||||||
|
8. `docs/开发进度.md` - 开发进度文档
|
||||||
|
9. `docs/下一步行动.md` - 下一步行动文档
|
||||||
|
10. `docs/项目总结.md` - 项目总结文档
|
||||||
|
11. `docs/文件清单.md` - 文件清单文档
|
||||||
|
12. `docs/常见问题.md` - 常见问题文档
|
||||||
|
13. `docs/项目里程碑.md` - 项目里程碑文档
|
||||||
|
14. `docs/项目状态.md` - 项目状态文档
|
||||||
|
15. `docs/项目完成清单.md` - 项目完成清单文档
|
||||||
|
16. `docs/前端开发指南.md` - 前端开发指南
|
||||||
|
17. `docs/页面设计.md` - 页面设计文档
|
||||||
|
|
||||||
|
## 统计信息
|
||||||
|
|
||||||
|
### 文件总数
|
||||||
|
- 代码文件: 15 个
|
||||||
|
- 文档文件: 17 个
|
||||||
|
- 配置文件: 3 个
|
||||||
|
- **总计: 35 个文件** (不含 node_modules)
|
||||||
|
|
||||||
|
### API 接口统计
|
||||||
|
- 车辆管理: 7 个接口
|
||||||
|
- 订单管理: 7 个接口
|
||||||
|
- 客户管理: 7 个接口
|
||||||
|
- 财务管理: 3 个接口
|
||||||
|
- **总计: 24 个接口**
|
||||||
|
|
||||||
|
### 文档统计
|
||||||
|
- 项目文档: 17 个
|
||||||
|
- 涵盖: 安装、开发、测试、部署、前端开发全流程
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
### 核心代码文件
|
||||||
|
|
||||||
|
#### 数据模型
|
||||||
|
- **Vehicle.js**: 车辆信息、状态、位置、维护记录
|
||||||
|
- **Order.js**: 订单号、客户、车辆、租期、费用、状态
|
||||||
|
- **Customer.js**: 客户信息、信用评分、租赁历史
|
||||||
|
|
||||||
|
#### 路由文件
|
||||||
|
- **vehicles.js**: 车辆管理接口 (7个)
|
||||||
|
- **orders.js**: 订单管理接口 (7个)
|
||||||
|
- **customers.js**: 客户管理接口 (7个)
|
||||||
|
- **finance.js**: 财务管理接口 (3个)
|
||||||
|
|
||||||
|
#### 中间件
|
||||||
|
- **errorHandler.js**: 统一错误处理
|
||||||
|
|
||||||
|
### 文档文件
|
||||||
|
|
||||||
|
#### 安装部署
|
||||||
|
- **MongoDB安装指南.md**: MongoDB 安装步骤
|
||||||
|
- **快速开始.md**: 项目快速启动指南
|
||||||
|
|
||||||
|
#### 开发文档
|
||||||
|
- **API.md**: 所有 API 接口说明
|
||||||
|
- **数据库设计.md**: 数据库表结构设计
|
||||||
|
- **开发计划.md**: 项目开发时间线
|
||||||
|
- **前端开发指南.md**: 前端开发规范
|
||||||
|
- **页面设计.md**: 页面布局和组件设计
|
||||||
|
|
||||||
|
#### 项目管理
|
||||||
|
- **开发进度.md**: 当前开发进度
|
||||||
|
- **下一步行动.md**: 待办事项清单
|
||||||
|
- **项目总结.md**: 项目概述和总结
|
||||||
|
- **项目状态.md**: 项目当前状态
|
||||||
|
- **项目里程碑.md**: 项目里程碑节点
|
||||||
|
- **项目完成清单.md**: 完成事项清单
|
||||||
|
|
||||||
|
#### 维护文档
|
||||||
|
- **常见问题.md**: 常见问题和解决方案
|
||||||
|
- **文件清单.md**: 项目文件清单
|
||||||
|
|
||||||
|
## 文件依赖关系
|
||||||
|
|
||||||
|
### 代码文件依赖
|
||||||
|
```
|
||||||
|
server/index.js
|
||||||
|
├── server/models/index.js
|
||||||
|
│ ├── server/models/Vehicle.js
|
||||||
|
│ ├── server/models/Order.js
|
||||||
|
│ └── server/models/Customer.js
|
||||||
|
├── server/routes/index.js
|
||||||
|
│ ├── server/routes/vehicles.js
|
||||||
|
│ ├── server/routes/orders.js
|
||||||
|
│ ├── server/routes/customers.js
|
||||||
|
│ └── server/routes/finance.js
|
||||||
|
└── server/middleware/errorHandler.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文档文件依赖
|
||||||
|
```
|
||||||
|
README.md (项目入口)
|
||||||
|
├── 快速开始.md (启动指南)
|
||||||
|
├── MongoDB安装指南.md (数据库安装)
|
||||||
|
├── API.md (接口文档)
|
||||||
|
├── 开发计划.md (开发路线)
|
||||||
|
└── 前端开发指南.md (前端开发)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更新记录
|
||||||
|
|
||||||
|
### 2026-03-05
|
||||||
|
- 新增: 前端开发指南.md
|
||||||
|
- 新增: 页面设计.md
|
||||||
|
- 更新: 文件清单.md (增加前端文档)
|
||||||
|
- 更新: USER.md (增加文档引用)
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. 安装 MongoDB (首要任务)
|
||||||
|
2. 生成测试数据
|
||||||
|
3. 测试 API 接口
|
||||||
|
4. 创建前端项目
|
||||||
|
5. 实现页面设计
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
# 测试数据说明
|
||||||
|
|
||||||
|
## 生成测试数据
|
||||||
|
|
||||||
|
### 1. 启动 MongoDB
|
||||||
|
确保 MongoDB 已启动并运行在默认端口 27017。
|
||||||
|
|
||||||
|
### 2. 运行种子脚本
|
||||||
|
```bash
|
||||||
|
cd E:\code\e-scooter-rental-system
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 预期输出
|
||||||
|
```
|
||||||
|
🧹 清空数据...
|
||||||
|
✅ 数据已清空
|
||||||
|
🚗 创建示例车辆...
|
||||||
|
✅ 创建了 5 辆车
|
||||||
|
👥 创建示例客户...
|
||||||
|
✅ 创建了 5 个客户
|
||||||
|
📋 创建示例订单...
|
||||||
|
✅ 创建了 3 个订单
|
||||||
|
|
||||||
|
🎉 示例数据创建完成!
|
||||||
|
车辆: 5 辆
|
||||||
|
客户: 5 个
|
||||||
|
订单: 3 个
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试数据详情
|
||||||
|
|
||||||
|
### 车辆数据 (5辆)
|
||||||
|
| 车辆编号 | 车型 | 品牌 | 颜色 | 状态 |
|
||||||
|
|---------|------|------|------|------|
|
||||||
|
| SCOOTER001 | 黑骑士 | 小牛 | 黑色 | 空闲 |
|
||||||
|
| SCOOTER002 | 黑骑士 | 小牛 | 白色 | 空闲 |
|
||||||
|
| SCOOTER003 | 电动车 | 雅迪 | 蓝色 | 空闲 |
|
||||||
|
| SCOOTER004 | 高端豪车 | 特斯拉 | 红色 | 空闲 |
|
||||||
|
| SCOOTER005 | 普通标准套餐 | 爱玛 | 绿色 | 空闲 |
|
||||||
|
|
||||||
|
### 客户数据 (5个)
|
||||||
|
| 姓名 | 手机号 | 地址 |
|
||||||
|
|------|--------|------|
|
||||||
|
| 张三 | 13800138000 | 北京市朝阳区 |
|
||||||
|
| 李四 | 13800138001 | 北京市海淀区 |
|
||||||
|
| 王五 | 13800138002 | 北京市西城区 |
|
||||||
|
| 赵六 | 13800138003 | 北京市东城区 |
|
||||||
|
| 钱七 | 13800138004 | 北京市丰台区 |
|
||||||
|
|
||||||
|
### 订单数据 (3个)
|
||||||
|
| 客户 | 车辆 | 租期 | 租金 | 状态 |
|
||||||
|
|------|------|------|------|------|
|
||||||
|
| 张三 | SCOOTER001 | 2026-02-20 至 2026-03-20 | 50元/天 | 进行中 |
|
||||||
|
| 李四 | SCOOTER002 | 2026-02-15 至 2026-03-15 | 50元/天 | 进行中 |
|
||||||
|
| 王五 | SCOOTER003 | 2026-01-10 至 2026-02-10 | 40元/天 | 已完成 |
|
||||||
|
|
||||||
|
## API 测试示例
|
||||||
|
|
||||||
|
### 1. 查询所有车辆
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 查询所有客户
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/customers
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 查询所有订单
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/orders
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 查询财务统计
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/finance/stats
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 查询逾期订单
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/api/orders/status/overdue
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 运行种子脚本前,请确保 MongoDB 已启动
|
||||||
|
2. 种子脚本会清空现有数据,请谨慎使用
|
||||||
|
3. 测试数据仅供开发和测试使用
|
||||||
|
4. 生产环境请使用真实数据
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
# 页面设计
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
### 1. 用户体验优先
|
||||||
|
- 简洁明了的界面布局
|
||||||
|
- 直观的操作流程
|
||||||
|
- 及时的反馈提示
|
||||||
|
- ⚠️ **注意**:确保选中状态、悬停状态等交互状态有足够的对比度,避免用户看不清当前选中的项目
|
||||||
|
|
||||||
|
### 2. 响应式设计
|
||||||
|
- 适配不同屏幕尺寸
|
||||||
|
- 移动端友好
|
||||||
|
- 桌面端优化
|
||||||
|
|
||||||
|
### 3. 一致性
|
||||||
|
- 统一的配色方案
|
||||||
|
- 统一的组件样式
|
||||||
|
- 统一的交互方式
|
||||||
|
|
||||||
|
## 配色方案
|
||||||
|
|
||||||
|
### 主色调
|
||||||
|
- 主色:#409EFF(蓝色)
|
||||||
|
- 成功:#67C23A(绿色)
|
||||||
|
- 警告:#E6A23C(橙色)
|
||||||
|
- 危险:#F56C6C(红色)
|
||||||
|
- 信息:#909399(灰色)
|
||||||
|
|
||||||
|
### 背景色
|
||||||
|
- 页面背景:#F5F7FA
|
||||||
|
- 卡片背景:#FFFFFF
|
||||||
|
- 表格斑马纹:#FAFAFA
|
||||||
|
|
||||||
|
## 布局设计
|
||||||
|
|
||||||
|
### 顶部导航栏
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ logo │ 车辆管理 │ 订单管理 │ 客户管理 │ 财务统计 │ 用户名 │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 侧边栏菜单
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┬──────────────────────────────────────────────┐
|
||||||
|
│ │ 顶部导航栏 │
|
||||||
|
│ 侧边栏 ├──────────────────────────────────────────────┤
|
||||||
|
│ - 车辆管理 │ │
|
||||||
|
│ - 订单管理 │ 内容区域 │
|
||||||
|
│ - 客户管理 │ │
|
||||||
|
│ - 财务统计 │ │
|
||||||
|
│ │ │
|
||||||
|
└──────────────┴──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 侧边栏菜单样式(重要!)
|
||||||
|
|
||||||
|
⚠️ **常见问题**:Element Plus 默认的菜单选中样式是深蓝色背景 + 白色文字,在某些主题下对比度不足,导致选中项看不清。
|
||||||
|
|
||||||
|
**解决方案:**
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 侧边栏菜单自定义样式 */
|
||||||
|
.el-menu {
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中项样式 - 使用主色调但调整对比度 */
|
||||||
|
.el-menu-item.is-active {
|
||||||
|
background-color: rgba(64, 158, 255, 0.15) !important;
|
||||||
|
color: #409EFF !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 悬停效果 */
|
||||||
|
.el-menu-item:hover {
|
||||||
|
background-color: rgba(64, 158, 255, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选中项左侧蓝色指示条(替代全覆盖背景) */
|
||||||
|
.el-menu-item.is-active::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 3px;
|
||||||
|
background-color: #409EFF;
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 展开/折叠图标颜色 */
|
||||||
|
.el-sub-menu__title {
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item, .el-sub-menu__title {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**设计要点:**
|
||||||
|
- 不要用全覆盖的深色背景
|
||||||
|
- 使用左侧 3px 蓝色指示条 + 浅蓝色背景
|
||||||
|
- 保持文字清晰可读
|
||||||
|
- 添加过渡动画提升体验
|
||||||
|
|
||||||
|
## 页面设计详情
|
||||||
|
|
||||||
|
### 1. 车辆管理页面
|
||||||
|
|
||||||
|
#### 车辆列表页
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 车辆管理 > 车辆列表 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ [搜索框] [状态筛选] [车辆类型筛选] [添加车辆] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
|
||||||
|
│ │编号│类型 │状态 │位置 │续航 │价格 │操作 │ │ │
|
||||||
|
│ ├────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │
|
||||||
|
│ │001 │电动车│空闲 │A区 │50km │¥50/天│查看 编辑 删除│ │
|
||||||
|
│ │002 │电动车│使用中│B区 │45km │¥50/天│查看 编辑 删除│ │
|
||||||
|
│ │003 │电动车│维修中│C区 │- │¥50/天│查看 编辑 删除│ │
|
||||||
|
│ └────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
|
||||||
|
│ │
|
||||||
|
│ [分页:1 2 3 4 5 ... 共 50 条] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 车辆详情页
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 车辆管理 > 车辆详情 > 001号车辆 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ 车辆信息 │ │ 位置信息 │ │
|
||||||
|
│ │ 编号:001 │ │ 当前位置:A区 │ │
|
||||||
|
│ │ 类型:电动车 │ │ 最后更新:... │ │
|
||||||
|
│ │ 状态:空闲 │ │ │ │
|
||||||
|
│ │ 续航:50km │ └─────────────────┘ │
|
||||||
|
│ │ 价格:¥50/天 │ │
|
||||||
|
│ └─────────────────┘ ┌─────────────────┐ │
|
||||||
|
│ │ 维护记录 │ │
|
||||||
|
│ ┌─────────────────┐ │ 2026-03-01 ... │ │
|
||||||
|
│ │ 车辆图片 │ │ 2026-02-15 ... │ │
|
||||||
|
│ │ │ └─────────────────┘ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 订单管理页面
|
||||||
|
|
||||||
|
#### 订单列表页
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 订单管理 > 订单列表 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ [搜索框] [状态筛选] [时间筛选] [创建订单] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
|
||||||
|
│ │订单号│客户 │车辆 │租期 │费用 │状态 │操作 │ │ │
|
||||||
|
│ ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │
|
||||||
|
│ │202603│张三 │001 │7天 │¥350 │进行中│查看 结束│ │
|
||||||
|
│ │202602│李四 │002 │30天 │¥1500 │已完成│查看 │ │
|
||||||
|
│ │202601│王五 │003 │逾期 │¥500 │逾期 │查看 结束│ │
|
||||||
|
│ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
|
||||||
|
│ │
|
||||||
|
│ [分页:1 2 3 4 5 ... 共 100 条] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 订单详情页
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 订单管理 > 订单详情 > 202603001 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ 订单信息 │ │ 客户信息 │ │
|
||||||
|
│ │ 订单号:202603001│ │ 姓名:张三 │ │
|
||||||
|
│ │ 创建时间:... │ │ 电话:138... │ │
|
||||||
|
│ │ 租期:7天 │ │ 信用分:85 │ │
|
||||||
|
│ │ 费用:¥350 │ └─────────────────┘ │
|
||||||
|
│ │ 状态:进行中 │ │
|
||||||
|
│ └─────────────────┘ ┌─────────────────┐ │
|
||||||
|
│ │ 车辆信息 │ │
|
||||||
|
│ ┌─────────────────┐ │ 编号:001 │ │
|
||||||
|
│ │ 订单备注 │ │ 类型:电动车 │ │
|
||||||
|
│ │ ... │ └─────────────────┘ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 客户管理页面
|
||||||
|
|
||||||
|
#### 客户列表页
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 客户管理 > 客户列表 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ [搜索框] [添加客户] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
|
||||||
|
│ │姓名 │电话 │信用分│租赁次数│状态 │操作 │ │ │ │
|
||||||
|
│ ├──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────┤ │
|
||||||
|
│ │张三 │138...│85 │5 │正常 │查看 编辑 删除│ │
|
||||||
|
│ │李四 │139...│72 │3 │正常 │查看 编辑 删除│ │
|
||||||
|
│ │王五 │137...│60 │1 │异常 │查看 编辑 删除│ │
|
||||||
|
│ └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
|
||||||
|
│ │
|
||||||
|
│ [分页:1 2 3 4 5 ... 共 50 条] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 财务统计页面
|
||||||
|
|
||||||
|
#### 财务仪表盘
|
||||||
|
|
||||||
|
**布局:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 财务统计 > 仪表盘 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ 总收入 │ │ 本月收入 │ │ 逾期账款 │ │ 订单数量 │ │
|
||||||
|
│ │ ¥50,000 │ │ ¥8,500 │ │ ¥2,000 │ │ 150 │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ┌─────────────────────────────┐ ┌─────────────────────┐ │
|
||||||
|
│ │ 收入趋势图 │ │ 订单状态分布 │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ▲ │ │ ┌─────┐ │ │
|
||||||
|
│ │ │ ╭───╮ │ │ │完成 │ 60% │ │
|
||||||
|
│ │ │ ╭─╯ ╰─╮ │ │ ├─────┤ │ │
|
||||||
|
│ │ │─╯ ╰──╮ │ │ │进行中│ 30% │ │
|
||||||
|
│ │ │ ╰──╮ │ │ ├─────┤ │ │
|
||||||
|
│ │ └────────────────╯ │ │ │逾期 │ 10% │ │
|
||||||
|
│ │ 1月 2月 3月 4月 │ │ └─────┘ │ │
|
||||||
|
│ └─────────────────────────────┘ └─────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件设计
|
||||||
|
|
||||||
|
### 1. 搜索框组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="search-box">
|
||||||
|
<el-input
|
||||||
|
v-model="keyword"
|
||||||
|
placeholder="搜索..."
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon><Search /></el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button @click="handleSearch">搜索</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 表格组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<el-table :data="tableData" style="width: 100%">
|
||||||
|
<el-table-column prop="id" label="编号" width="80" />
|
||||||
|
<el-table-column prop="name" label="名称" />
|
||||||
|
<el-table-column prop="status" label="状态">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="getStatusType(row.status)">
|
||||||
|
{{ row.status }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button size="small" @click="handleView(row)">查看</el-button>
|
||||||
|
<el-button size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="handleDelete(row)">
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 表单组件
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<el-form :model="form" label-width="100px">
|
||||||
|
<el-form-item label="车辆编号">
|
||||||
|
<el-input v-model="form.id" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="车辆类型">
|
||||||
|
<el-select v-model="form.type" placeholder="请选择">
|
||||||
|
<el-option label="电动车" value="electric" />
|
||||||
|
<el-option label="摩托车" value="motorcycle" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-radio-group v-model="form.status">
|
||||||
|
<el-radio label="空闲">空闲</el-radio>
|
||||||
|
<el-radio label="使用中">使用中</el-radio>
|
||||||
|
<el-radio label="维修中">维修中</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @click="handleSubmit">提交</el-button>
|
||||||
|
<el-button @click="handleCancel">取消</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 交互设计
|
||||||
|
|
||||||
|
### 1. 操作反馈
|
||||||
|
|
||||||
|
- **成功提示**:绿色 toast 提示
|
||||||
|
- **错误提示**:红色 toast 提示
|
||||||
|
- **确认弹窗**:删除操作前确认
|
||||||
|
|
||||||
|
### 2. 加载状态
|
||||||
|
|
||||||
|
- **表格加载**:骨架屏
|
||||||
|
- **页面加载**:全屏 loading
|
||||||
|
- **按钮加载**:按钮 loading 状态
|
||||||
|
|
||||||
|
### 3. 表单验证
|
||||||
|
|
||||||
|
- **必填项**:红色星号标记
|
||||||
|
- **格式验证**:实时验证
|
||||||
|
- **错误提示**:下方红色文字
|
||||||
|
|
||||||
|
## 响应式设计
|
||||||
|
|
||||||
|
### 桌面端(> 1200px)
|
||||||
|
|
||||||
|
- 侧边栏固定宽度 200px
|
||||||
|
- 内容区域自适应
|
||||||
|
- 表格显示完整列
|
||||||
|
|
||||||
|
### 平板端(768px - 1200px)
|
||||||
|
|
||||||
|
- 侧边栏可折叠
|
||||||
|
- 表格部分列隐藏
|
||||||
|
- 卡片布局调整
|
||||||
|
|
||||||
|
### 移动端(< 768px)
|
||||||
|
|
||||||
|
- 侧边栏隐藏,使用汉堡菜单
|
||||||
|
- 表格转为卡片列表
|
||||||
|
- 表单单列布局
|
||||||
|
|
||||||
|
## 开发建议
|
||||||
|
|
||||||
|
### 1. 组件复用
|
||||||
|
|
||||||
|
- 公共组件放在 `components/common/`
|
||||||
|
- 业务组件放在 `components/business/`
|
||||||
|
- 工具函数放在 `utils/`
|
||||||
|
|
||||||
|
### 2. 状态管理
|
||||||
|
|
||||||
|
- 简单状态使用 Vuex
|
||||||
|
- 复杂状态考虑 Pinia
|
||||||
|
- 本地存储使用 localStorage
|
||||||
|
|
||||||
|
### 3. 性能优化
|
||||||
|
|
||||||
|
- 图片懒加载
|
||||||
|
- 组件按需加载
|
||||||
|
- 数据缓存策略
|
||||||
|
|
||||||
|
### 4. 代码规范
|
||||||
|
|
||||||
|
- 使用 ESLint + Prettier
|
||||||
|
- 组件文件大小不超过 300 行
|
||||||
|
- 注释重要逻辑
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
1. 安装 MongoDB(首要任务)
|
||||||
|
2. 生成测试数据
|
||||||
|
3. 创建前端项目
|
||||||
|
4. 实现页面设计
|
||||||
|
5. 测试和优化
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
# 项目完成清单
|
||||||
|
|
||||||
|
## ✅ 已完成工作 (2026-03-05)
|
||||||
|
|
||||||
|
### 1. 项目初始化
|
||||||
|
- [x] 创建项目目录 `E:\code\e-scooter-rental-system`
|
||||||
|
- [x] 初始化 npm 项目
|
||||||
|
- [x] 安装依赖包 (express, mongoose, cors, dotenv, nodemon)
|
||||||
|
- [x] 配置环境变量 (.env)
|
||||||
|
|
||||||
|
### 2. 后端服务
|
||||||
|
- [x] 搭建 Express 服务器
|
||||||
|
- [x] 配置 MongoDB 连接
|
||||||
|
- [x] 添加 CORS 中间件
|
||||||
|
- [x] 添加 JSON 解析中间件
|
||||||
|
- [x] 实现错误处理中间件
|
||||||
|
- [x] 实现 404 处理
|
||||||
|
- [x] 健康检查接口 (`/health`)
|
||||||
|
|
||||||
|
### 3. 数据模型
|
||||||
|
- [x] 车辆模型 (Vehicle.js)
|
||||||
|
- 车辆信息、状态、位置、维护记录
|
||||||
|
- [x] 订单模型 (Order.js)
|
||||||
|
- 订单号、客户、车辆、租期、费用、状态
|
||||||
|
- [x] 客户模型 (Customer.js)
|
||||||
|
- 客户信息、信用评分、租赁历史
|
||||||
|
- [x] 模型导出文件 (index.js)
|
||||||
|
|
||||||
|
### 4. API 路由
|
||||||
|
- [x] 车辆管理路由 (`/api/vehicles`)
|
||||||
|
- 查询所有车辆
|
||||||
|
- 查询单个车辆
|
||||||
|
- 创建车辆
|
||||||
|
- 更新车辆
|
||||||
|
- 删除车辆
|
||||||
|
- 按状态筛选
|
||||||
|
- 更新车辆位置
|
||||||
|
- [x] 订单管理路由 (`/api/orders`)
|
||||||
|
- 查询所有订单
|
||||||
|
- 查询单个订单
|
||||||
|
- 创建订单
|
||||||
|
- 更新订单
|
||||||
|
- 结束订单
|
||||||
|
- 查询逾期订单
|
||||||
|
- 按状态筛选
|
||||||
|
- [x] 客户管理路由 (`/api/customers`)
|
||||||
|
- 查询所有客户
|
||||||
|
- 查询单个客户
|
||||||
|
- 创建客户
|
||||||
|
- 更新客户
|
||||||
|
- 删除客户
|
||||||
|
- 搜索客户
|
||||||
|
- 更新信用评分
|
||||||
|
- [x] 财务管理路由 (`/api/finance`)
|
||||||
|
- 财务统计
|
||||||
|
- 收入趋势
|
||||||
|
- 逾期账款
|
||||||
|
|
||||||
|
### 5. 工具脚本
|
||||||
|
- [x] 测试数据生成脚本 (seed.js)
|
||||||
|
- [x] .gitignore 配置
|
||||||
|
|
||||||
|
### 6. 文档编写 (17个文档)
|
||||||
|
- [x] API 文档 (docs/API.md)
|
||||||
|
- [x] 数据库设计文档 (docs/数据库设计.md)
|
||||||
|
- [x] 开发计划文档 (docs/开发计划.md)
|
||||||
|
- [x] 快速开始指南 (docs/快速开始.md)
|
||||||
|
- [x] 测试数据说明 (docs/测试数据.md)
|
||||||
|
- [x] MongoDB 安装指南 (docs/MongoDB安装指南.md)
|
||||||
|
- [x] 项目说明文档 (docs/README.md)
|
||||||
|
- [x] 开发进度文档 (docs/开发进度.md)
|
||||||
|
- [x] 下一步行动文档 (docs/下一步行动.md)
|
||||||
|
- [x] 项目总结文档 (docs/项目总结.md)
|
||||||
|
- [x] 文件清单文档 (docs/文件清单.md)
|
||||||
|
- [x] 常见问题文档 (docs/常见问题.md)
|
||||||
|
- [x] 项目里程碑文档 (docs/项目里程碑.md)
|
||||||
|
- [x] 项目状态文档 (docs/项目状态.md)
|
||||||
|
- [x] 项目完成清单 (docs/项目完成清单.md)
|
||||||
|
- [x] 前端开发指南 (docs/前端开发指南.md)
|
||||||
|
- [x] 页面设计文档 (docs/页面设计.md)
|
||||||
|
|
||||||
|
## ⏳ 进行中工作
|
||||||
|
|
||||||
|
### 1. MongoDB 安装
|
||||||
|
- [ ] 安装 MongoDB 数据库
|
||||||
|
- [ ] 启动 MongoDB 服务
|
||||||
|
- [ ] 验证 MongoDB 连接
|
||||||
|
|
||||||
|
### 2. 测试数据
|
||||||
|
- [ ] 运行种子脚本生成测试数据
|
||||||
|
- [ ] 验证数据是否正确插入
|
||||||
|
|
||||||
|
### 3. API 测试
|
||||||
|
- [ ] 测试健康检查接口
|
||||||
|
- [ ] 测试车辆管理接口
|
||||||
|
- [ ] 测试订单管理接口
|
||||||
|
- [ ] 测试客户管理接口
|
||||||
|
- [ ] 测试财务管理接口
|
||||||
|
|
||||||
|
## 📋 待办事项
|
||||||
|
|
||||||
|
### 立即执行
|
||||||
|
- [ ] 安装 MongoDB (首要任务)
|
||||||
|
- [ ] 生成测试数据
|
||||||
|
- [ ] 测试所有 API 接口
|
||||||
|
|
||||||
|
### 短期目标 (1-2天)
|
||||||
|
- [ ] 选择前端技术栈
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
|
||||||
|
### 中期目标 (3-5天)
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
|
||||||
|
### 长期目标 (1周内)
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 📊 项目统计
|
||||||
|
|
||||||
|
### 文件统计
|
||||||
|
- 代码文件: 15 个
|
||||||
|
- 文档文件: 17 个
|
||||||
|
- 配置文件: 3 个
|
||||||
|
- 总计: 35 个文件 (不含 node_modules)
|
||||||
|
|
||||||
|
### API 接口统计
|
||||||
|
- 车辆管理: 7 个接口
|
||||||
|
- 订单管理: 7 个接口
|
||||||
|
- 客户管理: 7 个接口
|
||||||
|
- 财务管理: 3 个接口
|
||||||
|
- 总计: 24 个接口
|
||||||
|
|
||||||
|
### 文档统计
|
||||||
|
- 项目文档: 17 个
|
||||||
|
- 涵盖: 安装、开发、测试、部署、前端开发全流程
|
||||||
|
|
||||||
|
### 项目进度
|
||||||
|
- **整体进度: 40%**
|
||||||
|
|
||||||
|
| 模块 | 进度 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| 后端框架 | 100% | ✅ 完成 |
|
||||||
|
| 数据模型 | 100% | ✅ 完成 |
|
||||||
|
| API 路由 | 100% | ✅ 完成 |
|
||||||
|
| 文档编写 | 100% | ✅ 完成 |
|
||||||
|
| MongoDB 安装 | 0% | ❌ 未开始 |
|
||||||
|
| 测试数据 | 0% | ⏳ 等待 MongoDB |
|
||||||
|
| 前端开发 | 0% | ⏳ 等待后端 |
|
||||||
|
| 功能完善 | 0% | ⏳ 等待前端 |
|
||||||
|
| 测试部署 | 0% | ⏳ 等待功能完成 |
|
||||||
|
|
||||||
|
## 🎯 当前状态
|
||||||
|
|
||||||
|
### ✅ 已完成
|
||||||
|
- 后端服务框架
|
||||||
|
- 数据模型设计
|
||||||
|
- API 路由实现
|
||||||
|
- 错误处理中间件
|
||||||
|
- 文档编写 (17个文档)
|
||||||
|
- 前端开发指南
|
||||||
|
- 页面设计文档
|
||||||
|
|
||||||
|
### ❌ 问题
|
||||||
|
- MongoDB 未安装
|
||||||
|
- 数据库连接失败
|
||||||
|
|
||||||
|
### ⏳ 等待
|
||||||
|
- 安装 MongoDB
|
||||||
|
- 生成测试数据
|
||||||
|
- 测试 API 接口
|
||||||
|
|
||||||
|
## 📝 下一步操作
|
||||||
|
|
||||||
|
1. **安装 MongoDB** (首要任务)
|
||||||
|
- 推荐使用 Docker 安装
|
||||||
|
- 详细步骤见 `docs/MongoDB安装指南.md`
|
||||||
|
|
||||||
|
2. **生成测试数据**
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **测试 API 接口**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:3000/health
|
||||||
|
curl http://localhost:3000/api/vehicles
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **开发前端页面**
|
||||||
|
- 选择 Vue.js 或 React
|
||||||
|
- 创建前端项目
|
||||||
|
- 开发车辆管理页面
|
||||||
|
|
||||||
|
## 🎉 项目亮点
|
||||||
|
|
||||||
|
1. **完整的后端框架**
|
||||||
|
- Express + MongoDB
|
||||||
|
- 错误处理完善
|
||||||
|
- API 接口规范
|
||||||
|
|
||||||
|
2. **详细的文档**
|
||||||
|
- 17 个文档文件
|
||||||
|
- 涵盖安装、开发、测试、前端开发全流程
|
||||||
|
|
||||||
|
3. **测试数据支持**
|
||||||
|
- 种子脚本一键生成测试数据
|
||||||
|
- 便于前端开发和测试
|
||||||
|
|
||||||
|
4. **模块化设计**
|
||||||
|
- 数据模型、路由、中间件分离
|
||||||
|
- 易于维护和扩展
|
||||||
|
|
||||||
|
5. **前端开发指南**
|
||||||
|
- 详细的前端开发规范
|
||||||
|
- 页面设计文档
|
||||||
|
- 组件设计指南
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
# 两轮电动车租赁管理系统 - 项目总结
|
||||||
|
|
||||||
|
## 📋 项目概述
|
||||||
|
|
||||||
|
**项目名称**: 两轮电动车租赁管理系统
|
||||||
|
**项目位置**: `E:\code\e-scooter-rental-system`
|
||||||
|
**技术栈**: Node.js + Express + MongoDB
|
||||||
|
**开发时间**: 2026-03-05
|
||||||
|
|
||||||
|
## ✅ 已完成工作
|
||||||
|
|
||||||
|
### 1. 项目初始化
|
||||||
|
- ✅ 创建项目目录
|
||||||
|
- ✅ 初始化 npm 项目
|
||||||
|
- ✅ 安装依赖包
|
||||||
|
- ✅ 配置环境变量
|
||||||
|
|
||||||
|
### 2. 后端服务
|
||||||
|
- ✅ Express 服务器搭建
|
||||||
|
- ✅ MongoDB 连接配置
|
||||||
|
- ✅ 错误处理中间件
|
||||||
|
- ✅ 404 处理
|
||||||
|
- ✅ 健康检查接口
|
||||||
|
|
||||||
|
### 3. 数据模型
|
||||||
|
- ✅ 车辆模型 (Vehicle)
|
||||||
|
- ✅ 订单模型 (Order)
|
||||||
|
- ✅ 客户模型 (Customer)
|
||||||
|
|
||||||
|
### 4. API 路由
|
||||||
|
- ✅ 车辆管理路由
|
||||||
|
- ✅ 订单管理路由
|
||||||
|
- ✅ 客户管理路由
|
||||||
|
- ✅ 财务管理路由
|
||||||
|
|
||||||
|
### 5. 文档编写
|
||||||
|
- ✅ API 文档
|
||||||
|
- ✅ 数据库设计文档
|
||||||
|
- ✅ 开发计划文档
|
||||||
|
- ✅ 快速开始指南
|
||||||
|
- ✅ 测试数据说明
|
||||||
|
- ✅ MongoDB 安装指南
|
||||||
|
- ✅ 项目说明文档
|
||||||
|
- ✅ 开发进度文档
|
||||||
|
- ✅ 下一步行动文档
|
||||||
|
|
||||||
|
### 6. 工具脚本
|
||||||
|
- ✅ 测试数据生成脚本
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
e-scooter-rental-system/
|
||||||
|
├── server/
|
||||||
|
│ ├── models/ # 数据模型 (3个)
|
||||||
|
│ │ ├── Vehicle.js
|
||||||
|
│ │ ├── Order.js
|
||||||
|
│ │ ├── Customer.js
|
||||||
|
│ │ └── index.js
|
||||||
|
│ ├── routes/ # 路由 (4个)
|
||||||
|
│ │ ├── vehicles.js
|
||||||
|
│ │ ├── orders.js
|
||||||
|
│ │ ├── customers.js
|
||||||
|
│ │ ├── finance.js
|
||||||
|
│ │ └── index.js
|
||||||
|
│ ├── middleware/ # 中间件
|
||||||
|
│ │ └── errorHandler.js
|
||||||
|
│ ├── seed.js # 测试数据脚本
|
||||||
|
│ └── index.js # 入口文件
|
||||||
|
├── docs/ # 文档 (9个)
|
||||||
|
│ ├── API.md
|
||||||
|
│ ├── 数据库设计.md
|
||||||
|
│ ├── 开发计划.md
|
||||||
|
│ ├── 快速开始.md
|
||||||
|
│ ├── 测试数据.md
|
||||||
|
│ ├── MongoDB安装指南.md
|
||||||
|
│ ├── README.md
|
||||||
|
│ ├── 开发进度.md
|
||||||
|
│ └── 下一步行动.md
|
||||||
|
├── .env # 环境配置
|
||||||
|
├── .gitignore
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 功能模块
|
||||||
|
|
||||||
|
### 1. 车辆管理
|
||||||
|
- 车辆信息录入
|
||||||
|
- 车辆状态追踪
|
||||||
|
- GPS 位置更新
|
||||||
|
- 车辆筛选查询
|
||||||
|
|
||||||
|
### 2. 订单管理
|
||||||
|
- 订单创建
|
||||||
|
- 订单状态管理
|
||||||
|
- 逾期订单处理
|
||||||
|
- 订单查询筛选
|
||||||
|
|
||||||
|
### 3. 客户管理
|
||||||
|
- 客户信息登记
|
||||||
|
- 客户搜索
|
||||||
|
- 信用评分管理
|
||||||
|
- 租赁历史查询
|
||||||
|
|
||||||
|
### 4. 财务管理
|
||||||
|
- 收入统计
|
||||||
|
- 逾期账款管理
|
||||||
|
- 财务报表生成
|
||||||
|
- 收入趋势分析
|
||||||
|
|
||||||
|
## 📊 API 接口统计
|
||||||
|
|
||||||
|
| 模块 | 接口数量 | 功能 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 车辆管理 | 7 个 | 查询、创建、更新、删除、位置更新、状态筛选 |
|
||||||
|
| 订单管理 | 6 个 | 查询、创建、结束订单、逾期订单、状态筛选 |
|
||||||
|
| 客户管理 | 6 个 | 查询、创建、更新、删除、搜索、信用评分 |
|
||||||
|
| 财务管理 | 3 个 | 统计、趋势、逾期账款 |
|
||||||
|
|
||||||
|
**总计**: 22 个 API 接口
|
||||||
|
|
||||||
|
## 🚀 当前状态
|
||||||
|
|
||||||
|
### ✅ 已完成
|
||||||
|
- 后端服务框架
|
||||||
|
- 数据模型设计
|
||||||
|
- API 路由实现
|
||||||
|
- 文档编写
|
||||||
|
- 错误处理
|
||||||
|
|
||||||
|
### ⏳ 进行中
|
||||||
|
- **安装 MongoDB 数据库** (首要任务)
|
||||||
|
|
||||||
|
### 📋 待办事项
|
||||||
|
1. 安装 MongoDB
|
||||||
|
2. 生成测试数据
|
||||||
|
3. 测试 API 接口
|
||||||
|
4. 开发前端页面
|
||||||
|
|
||||||
|
## 📝 技术要点
|
||||||
|
|
||||||
|
### 数据库连接
|
||||||
|
- 数据库名: e-scooter-rental
|
||||||
|
- 默认端口: 27017
|
||||||
|
- 连接字符串: `mongodb://localhost:27017/e-scooter-rental`
|
||||||
|
|
||||||
|
### API 基础 URL
|
||||||
|
- 本地: http://localhost:3000/api
|
||||||
|
- 健康检查: http://localhost:3000/health
|
||||||
|
|
||||||
|
### API 返回格式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {...},
|
||||||
|
"message": "成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 下一步行动
|
||||||
|
|
||||||
|
### 1. 安装 MongoDB (首要任务)
|
||||||
|
详细步骤请查看: `docs/MongoDB安装指南.md`
|
||||||
|
|
||||||
|
推荐使用 Docker 安装:
|
||||||
|
```bash
|
||||||
|
docker pull mongo
|
||||||
|
docker run -d -p 27017:27017 --name mongodb mongo
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 生成测试数据
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 测试 API 接口
|
||||||
|
- 健康检查: `curl http://localhost:3000/health`
|
||||||
|
- 查询车辆: `curl http://localhost:3000/api/vehicles`
|
||||||
|
- 查询客户: `curl http://localhost:3000/api/customers`
|
||||||
|
- 查询订单: `curl http://localhost:3000/api/orders`
|
||||||
|
|
||||||
|
### 4. 开发前端页面
|
||||||
|
推荐使用 Vue.js + Element Plus
|
||||||
|
|
||||||
|
## 📞 需要帮助?
|
||||||
|
|
||||||
|
如果在安装 MongoDB 或开发过程中遇到问题,请随时联系!
|
||||||
|
|
||||||
|
## 🎉 项目亮点
|
||||||
|
|
||||||
|
1. **完整的后端框架**
|
||||||
|
- Express + MongoDB
|
||||||
|
- 错误处理完善
|
||||||
|
- API 接口规范
|
||||||
|
|
||||||
|
2. **详细的文档**
|
||||||
|
- 9 个文档文件
|
||||||
|
- 涵盖安装、开发、测试全流程
|
||||||
|
|
||||||
|
3. **测试数据支持**
|
||||||
|
- 种子脚本一键生成测试数据
|
||||||
|
- 便于前端开发和测试
|
||||||
|
|
||||||
|
4. **模块化设计**
|
||||||
|
- 数据模型、路由、中间件分离
|
||||||
|
- 易于维护和扩展
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
# 项目状态
|
||||||
|
|
||||||
|
## 📊 当前状态 (2026-03-05)
|
||||||
|
|
||||||
|
### ✅ 已完成
|
||||||
|
- 后端服务框架搭建
|
||||||
|
- 数据模型设计 (车辆、订单、客户)
|
||||||
|
- API 路由实现 (4个模块)
|
||||||
|
- 错误处理中间件
|
||||||
|
- 文档编写 (11个文档)
|
||||||
|
- 测试数据脚本
|
||||||
|
|
||||||
|
### ❌ 问题
|
||||||
|
- MongoDB 未安装
|
||||||
|
- 数据库连接失败
|
||||||
|
|
||||||
|
### ⏳ 进行中
|
||||||
|
- 安装 MongoDB 数据库
|
||||||
|
|
||||||
|
## 🎯 下一步行动
|
||||||
|
|
||||||
|
### 立即执行
|
||||||
|
1. **安装 MongoDB** (首要任务)
|
||||||
|
- 推荐使用 Docker 安装
|
||||||
|
- 详细步骤见 `docs/MongoDB安装指南.md`
|
||||||
|
|
||||||
|
2. **生成测试数据**
|
||||||
|
```bash
|
||||||
|
npm run seed
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **测试 API 接口**
|
||||||
|
- 健康检查: `curl http://localhost:3000/health`
|
||||||
|
- 查询车辆: `curl http://localhost:3000/api/vehicles`
|
||||||
|
|
||||||
|
### 短期目标 (1-2天)
|
||||||
|
1. 选择前端技术栈 (Vue.js 或 React)
|
||||||
|
2. 创建前端项目
|
||||||
|
3. 开发车辆管理页面
|
||||||
|
|
||||||
|
### 中期目标 (3-5天)
|
||||||
|
1. 开发订单管理页面
|
||||||
|
2. 开发客户管理页面
|
||||||
|
3. 开发财务统计页面
|
||||||
|
|
||||||
|
### 长期目标 (1周内)
|
||||||
|
1. 实现逾期提醒功能
|
||||||
|
2. 完善用户权限管理
|
||||||
|
3. 部署上线
|
||||||
|
|
||||||
|
## 📈 项目进度
|
||||||
|
|
||||||
|
### 整体进度: 40%
|
||||||
|
|
||||||
|
| 模块 | 进度 | 状态 |
|
||||||
|
|------|------|------|
|
||||||
|
| 后端框架 | 100% | ✅ 完成 |
|
||||||
|
| 数据模型 | 100% | ✅ 完成 |
|
||||||
|
| API 路由 | 100% | ✅ 完成 |
|
||||||
|
| 文档编写 | 100% | ✅ 完成 |
|
||||||
|
| MongoDB 安装 | 0% | ❌ 未开始 |
|
||||||
|
| 测试数据 | 0% | ⏳ 等待 MongoDB |
|
||||||
|
| 前端开发 | 0% | ⏳ 等待后端 |
|
||||||
|
| 功能完善 | 0% | ⏳ 等待前端 |
|
||||||
|
| 测试部署 | 0% | ⏳ 等待功能完成 |
|
||||||
|
|
||||||
|
## 📋 任务清单
|
||||||
|
|
||||||
|
### 已完成任务
|
||||||
|
- [x] 创建项目目录
|
||||||
|
- [x] 初始化 npm 项目
|
||||||
|
- [x] 安装依赖包
|
||||||
|
- [x] 搭建 Express 服务器
|
||||||
|
- [x] 配置 MongoDB 连接
|
||||||
|
- [x] 设计数据模型
|
||||||
|
- [x] 实现 API 路由
|
||||||
|
- [x] 编写文档
|
||||||
|
- [x] 添加错误处理
|
||||||
|
|
||||||
|
### 进行中任务
|
||||||
|
- [ ] 安装 MongoDB 数据库
|
||||||
|
|
||||||
|
### 待办任务
|
||||||
|
- [ ] 生成测试数据
|
||||||
|
- [ ] 测试 API 接口
|
||||||
|
- [ ] 选择前端技术栈
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
- [ ] 功能测试
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 🎯 成功标准
|
||||||
|
|
||||||
|
### 后端服务
|
||||||
|
- ✅ 服务正常启动
|
||||||
|
- ✅ API 接口可用
|
||||||
|
- ✅ 数据库连接正常
|
||||||
|
- ✅ 错误处理完善
|
||||||
|
|
||||||
|
### 前端页面
|
||||||
|
- [ ] 页面加载正常
|
||||||
|
- [ ] 数据展示正确
|
||||||
|
- [ ] 操作响应及时
|
||||||
|
- [ ] 用户体验良好
|
||||||
|
|
||||||
|
### 功能完整性
|
||||||
|
- [ ] 车辆管理功能完整
|
||||||
|
- [ ] 订单管理功能完整
|
||||||
|
- [ ] 客户管理功能完整
|
||||||
|
- [ ] 财务管理功能完整
|
||||||
|
|
||||||
|
## 📞 沟通计划
|
||||||
|
|
||||||
|
### 每日更新
|
||||||
|
- 项目进度更新
|
||||||
|
- 遇到的问题
|
||||||
|
- 下一步计划
|
||||||
|
|
||||||
|
### 里程碑更新
|
||||||
|
- 完成里程碑时更新
|
||||||
|
- 调整时间线时通知
|
||||||
|
- 遇到重大问题时沟通
|
||||||
|
|
||||||
|
## 🎉 项目成功标准
|
||||||
|
|
||||||
|
1. **功能完整**: 所有计划功能都已实现
|
||||||
|
2. **代码质量**: 代码规范、注释完整
|
||||||
|
3. **文档齐全**: 技术文档、用户文档完整
|
||||||
|
4. **测试通过**: 功能测试、集成测试通过
|
||||||
|
5. **部署成功**: 生产环境正常运行
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
# 项目里程碑
|
||||||
|
|
||||||
|
## 🎯 项目目标
|
||||||
|
|
||||||
|
开发一个完整的两轮电动车租赁管理系统,包含车辆管理、订单管理、客户管理、财务管理等功能。
|
||||||
|
|
||||||
|
## 📅 时间线
|
||||||
|
|
||||||
|
### 第一阶段:后端开发 (2026-03-05)
|
||||||
|
|
||||||
|
#### ✅ 已完成
|
||||||
|
- [x] 项目初始化
|
||||||
|
- [x] 后端服务搭建
|
||||||
|
- [x] 数据模型设计
|
||||||
|
- [x] API 路由实现
|
||||||
|
- [x] 文档编写
|
||||||
|
- [x] 错误处理中间件
|
||||||
|
|
||||||
|
#### ⏳ 进行中
|
||||||
|
- [ ] 安装 MongoDB 数据库
|
||||||
|
- [ ] 生成测试数据
|
||||||
|
- [ ] 测试 API 接口
|
||||||
|
|
||||||
|
### 第二阶段:前端开发 (预计 2026-03-06 ~ 2026-03-08)
|
||||||
|
|
||||||
|
#### 计划完成
|
||||||
|
- [ ] 选择前端技术栈
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
|
||||||
|
### 第三阶段:功能完善 (预计 2026-03-09 ~ 2026-03-10)
|
||||||
|
|
||||||
|
#### 计划完成
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
### 第四阶段:测试部署 (预计 2026-03-11)
|
||||||
|
|
||||||
|
#### 计划完成
|
||||||
|
- [ ] 功能测试
|
||||||
|
- [ ] Bug 修复
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 📊 项目进度
|
||||||
|
|
||||||
|
### 当前进度: 40%
|
||||||
|
|
||||||
|
- ✅ 后端框架: 100%
|
||||||
|
- ✅ 数据模型: 100%
|
||||||
|
- ✅ API 路由: 100%
|
||||||
|
- ✅ 文档编写: 100%
|
||||||
|
- ⏳ MongoDB 安装: 0%
|
||||||
|
- ⏳ 测试数据: 0%
|
||||||
|
- ⏳ 前端开发: 0%
|
||||||
|
- ⏳ 功能完善: 0%
|
||||||
|
- ⏳ 测试部署: 0%
|
||||||
|
|
||||||
|
## 🎯 里程碑节点
|
||||||
|
|
||||||
|
### 里程碑 1: 后端服务就绪
|
||||||
|
- ✅ 后端服务启动
|
||||||
|
- ✅ 数据库连接配置
|
||||||
|
- ✅ API 接口完成
|
||||||
|
- ⏳ MongoDB 安装
|
||||||
|
|
||||||
|
### 里程碑 2: 前端页面开发
|
||||||
|
- [ ] 车辆管理页面
|
||||||
|
- [ ] 订单管理页面
|
||||||
|
- [ ] 客户管理页面
|
||||||
|
|
||||||
|
### 里程碑 3: 功能完善
|
||||||
|
- [ ] 财务统计页面
|
||||||
|
- [ ] 逾期提醒功能
|
||||||
|
- [ ] 用户权限管理
|
||||||
|
|
||||||
|
### 里程碑 4: 测试部署
|
||||||
|
- [ ] 功能测试完成
|
||||||
|
- [ ] Bug 修复完成
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 📝 开发任务清单
|
||||||
|
|
||||||
|
### 立即执行 (今天)
|
||||||
|
- [ ] 安装 MongoDB 数据库
|
||||||
|
- [ ] 生成测试数据
|
||||||
|
- [ ] 测试 API 接口
|
||||||
|
|
||||||
|
### 短期任务 (1-2天)
|
||||||
|
- [ ] 选择前端技术栈
|
||||||
|
- [ ] 创建前端项目
|
||||||
|
- [ ] 开发车辆管理页面
|
||||||
|
|
||||||
|
### 中期任务 (3-5天)
|
||||||
|
- [ ] 开发订单管理页面
|
||||||
|
- [ ] 开发客户管理页面
|
||||||
|
- [ ] 开发财务统计页面
|
||||||
|
|
||||||
|
### 长期任务 (1周内)
|
||||||
|
- [ ] 实现逾期提醒功能
|
||||||
|
- [ ] 完善用户权限管理
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
## 🎯 成功标准
|
||||||
|
|
||||||
|
### 后端服务
|
||||||
|
- ✅ 服务正常启动
|
||||||
|
- ✅ API 接口可用
|
||||||
|
- ✅ 数据库连接正常
|
||||||
|
- ✅ 错误处理完善
|
||||||
|
|
||||||
|
### 前端页面
|
||||||
|
- [ ] 页面加载正常
|
||||||
|
- [ ] 数据展示正确
|
||||||
|
- [ ] 操作响应及时
|
||||||
|
- [ ] 用户体验良好
|
||||||
|
|
||||||
|
### 功能完整性
|
||||||
|
- [ ] 车辆管理功能完整
|
||||||
|
- [ ] 订单管理功能完整
|
||||||
|
- [ ] 客户管理功能完整
|
||||||
|
- [ ] 财务管理功能完整
|
||||||
|
|
||||||
|
## 📞 沟通计划
|
||||||
|
|
||||||
|
### 每日更新
|
||||||
|
- 项目进度更新
|
||||||
|
- 遇到的问题
|
||||||
|
- 下一步计划
|
||||||
|
|
||||||
|
### 里程碑更新
|
||||||
|
- 完成里程碑时更新
|
||||||
|
- 调整时间线时通知
|
||||||
|
- 遇到重大问题时沟通
|
||||||
|
|
||||||
|
## 🎉 项目成功标准
|
||||||
|
|
||||||
|
1. **功能完整**: 所有计划功能都已实现
|
||||||
|
2. **代码质量**: 代码规范、注释完整
|
||||||
|
3. **文档齐全**: 技术文档、用户文档完整
|
||||||
|
4. **测试通过**: 功能测试、集成测试通过
|
||||||
|
5. **部署成功**: 生产环境正常运行
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "e-scooter-rental-system",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "两轮电动车租赁管理系统",
|
||||||
|
"main": "server/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server/index.js",
|
||||||
|
"dev": "nodemon server/index.js",
|
||||||
|
"seed": "node server/seed.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"e-scooter",
|
||||||
|
"rental",
|
||||||
|
"management",
|
||||||
|
"system"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.6",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"express": "^4.21.2",
|
||||||
|
"mongoose": "^8.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
// 先删除所有旧数据
|
||||||
|
const req1 = http.request({
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: 3000,
|
||||||
|
path: '/api/finance/delete-all',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
}, res => {
|
||||||
|
let b = '';
|
||||||
|
res.on('data', c => b += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
console.log('Deleted all:', b);
|
||||||
|
// 添加新的收支数据
|
||||||
|
const payments = [
|
||||||
|
{ type: 'income', party: '张三', amount: 1500, method: '微信', category: '租金收入', remark: '租车费' },
|
||||||
|
{ type: 'income', party: '李四', amount: 2000, method: '支付宝', category: '租金收入', remark: '租车费' },
|
||||||
|
{ type: 'income', party: '王五', amount: 500, method: '现金', category: '押金退还', remark: '还车退押金' },
|
||||||
|
{ type: 'expense', party: '赵六', amount: 300, method: '微信', category: '退款', remark: '提前还车退款' },
|
||||||
|
{ type: 'expense', party: '房东', amount: 1500, method: '银行卡', category: '房租', remark: '门店房租' },
|
||||||
|
{ type: 'expense', party: '电工', amount: 200, method: '现金', category: '水电', remark: '门店电费' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const addPayment = () => {
|
||||||
|
if (i >= payments.length) {
|
||||||
|
console.log('Done!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data = JSON.stringify(payments[i]);
|
||||||
|
const req = http.request({
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: 3000,
|
||||||
|
path: '/api/finance',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length }
|
||||||
|
}, res => {
|
||||||
|
let b = '';
|
||||||
|
res.on('data', c => b += c);
|
||||||
|
res.on('end', () => {
|
||||||
|
console.log('Added:', payments[i].type, payments[i].party, payments[i].amount);
|
||||||
|
i++;
|
||||||
|
addPayment();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', e => console.error(e));
|
||||||
|
req.write(data);
|
||||||
|
req.end();
|
||||||
|
};
|
||||||
|
addPayment();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req1.on('error', e => console.error(e));
|
||||||
|
req1.end();
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
console.log('✅ MongoDB 连接成功');
|
||||||
|
|
||||||
|
const Store = require('./server/models/Store');
|
||||||
|
const Payment = require('./server/models/Payment');
|
||||||
|
const Complaint = require('./server/models/Complaint');
|
||||||
|
const Customer = require('./server/models/Customer');
|
||||||
|
const Order = require('./server/models/Order');
|
||||||
|
|
||||||
|
// 清空数据
|
||||||
|
await Store.deleteMany({});
|
||||||
|
await Payment.deleteMany({});
|
||||||
|
await Complaint.deleteMany({});
|
||||||
|
console.log('🧹 已清空门店、财务、投诉数据');
|
||||||
|
|
||||||
|
// 门店数据
|
||||||
|
await Store.insertMany([
|
||||||
|
{ storeId: 'STORE001', name: '朝阳区总店', address: '北京市朝阳区建国路88号', phone: '010-12345678', manager: '王店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE002', name: '海淀区一分店', address: '北京市海淀区中关村大街1号', phone: '010-23456789', manager: '李店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE003', name: '西城区二分店', address: '北京市西城区西单北大街120号', phone: '010-34567890', manager: '张店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE004', name: '东城区三分店', address: '北京市东城区王府井大街138号', phone: '010-45678901', manager: '赵店长', status: '装修中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE005', name: '丰台区四分店', address: '北京市丰台区南三环西路16号', phone: '010-56789012', manager: '钱店长', status: '营业中', approvalStatus: '待审批' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 门店: 5 个');
|
||||||
|
|
||||||
|
// 支付数据 (财务)
|
||||||
|
await Payment.insertMany([
|
||||||
|
{ paymentId: 'PAY001', type: '收入', party: '张三', amount: 300, method: '微信', category: '租金收入', remark: 'ORDER001租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY002', type: '收入', party: '李四', amount: 300, method: '支付宝', category: '租金收入', remark: 'ORDER002租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY003', type: '收入', party: '王五', amount: 200, method: '微信', category: '租金收入', remark: 'ORDER003租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY004', type: '支出', party: '张三', amount: 200, method: '微信', category: '押金退还', remark: 'ORDER003押金退还', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY005', type: '支出', party: '员工A', amount: 5000, method: '银行卡', category: '工资', remark: '2月工资', operator: '财务' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 财务记录: 5 条');
|
||||||
|
|
||||||
|
// 投诉数据
|
||||||
|
const customers = await Customer.find();
|
||||||
|
const orders = await Order.find();
|
||||||
|
|
||||||
|
if (customers.length > 0 && orders.length > 0) {
|
||||||
|
await Complaint.insertMany([
|
||||||
|
{ complaintId: 'COMP001', customer: customers[0]._id, order: orders[0]._id, type: '车辆问题', content: '电动车刹车不灵', status: '处理中', handler: '王店长' },
|
||||||
|
{ complaintId: 'COMP002', customer: customers[1]._id, order: orders[1]._id, type: '服务态度', content: '店员服务态度差', status: '已解决', response: '已道歉并整改', handler: '李店长' },
|
||||||
|
{ complaintId: 'COMP003', customer: customers[2]?._id || customers[0]._id, order: orders[2]?._id || orders[0]._id, type: '费用问题', content: '费用计算有误', status: '待处理' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 投诉: 3 条');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🎉 测试数据创建完成!');
|
||||||
|
console.log('📊 数据汇总:');
|
||||||
|
console.log(' - 车辆: 5 辆');
|
||||||
|
console.log(' - 客户: 5 个');
|
||||||
|
console.log(' - 订单: 3 个');
|
||||||
|
console.log(' - 门店: 5 个');
|
||||||
|
console.log(' - 财务: 5 条');
|
||||||
|
console.log(' - 投诉: 3 条');
|
||||||
|
|
||||||
|
mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(err => console.error('❌ 错误:', err));
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
console.log('✅ MongoDB 连接成功');
|
||||||
|
|
||||||
|
const Dispute = require('./server/models/Dispute');
|
||||||
|
const Conflict = require('./server/models/Conflict');
|
||||||
|
const Store = require('./server/models/Store');
|
||||||
|
const Payment = require('./server/models/Payment');
|
||||||
|
|
||||||
|
// 清空纠纷和冲突数据
|
||||||
|
await Dispute.deleteMany({});
|
||||||
|
await Conflict.deleteMany({});
|
||||||
|
console.log('🧹 已清空纠纷协调数据');
|
||||||
|
|
||||||
|
const stores = await Store.find();
|
||||||
|
|
||||||
|
// 纠纷数据 (门店之间)
|
||||||
|
if (stores.length >= 2) {
|
||||||
|
await Dispute.insertMany([
|
||||||
|
{
|
||||||
|
disputeId: 'DISP001',
|
||||||
|
storeA: stores[0]._id, storeAName: stores[0].name,
|
||||||
|
storeB: stores[1]._id, storeBName: stores[1].name,
|
||||||
|
type: '订单纠纷',
|
||||||
|
title: '跨区订单归属争议',
|
||||||
|
content: '客户在A门店租车,在B门店还车,订单归属产生争议',
|
||||||
|
status: '处理中',
|
||||||
|
handler: '总部协调员'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disputeId: 'DISP002',
|
||||||
|
storeA: stores[2]._id, storeAName: stores[2].name,
|
||||||
|
storeB: stores[3]._id, storeBName: stores[3].name,
|
||||||
|
type: '费用纠纷',
|
||||||
|
title: '车辆维修费用分摊',
|
||||||
|
content: '跨区车辆维修费用分摊比例未达成一致',
|
||||||
|
status: '待处理'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
disputeId: 'DISP003',
|
||||||
|
storeA: stores[0]._id, storeAName: stores[0].name,
|
||||||
|
storeB: stores[4]._id, storeBName: stores[4].name,
|
||||||
|
type: '区域纠纷',
|
||||||
|
title: '经营范围重叠',
|
||||||
|
content: '两家门店服务区域划分不明确',
|
||||||
|
status: '已解决',
|
||||||
|
result: '已重新划分服务区域',
|
||||||
|
handler: '区域经理'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
console.log('✅ 门店纠纷: 3 条');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 冲突数据 (骑手门店/门店公司)
|
||||||
|
await Conflict.insertMany([
|
||||||
|
{
|
||||||
|
conflictId: 'CONF001',
|
||||||
|
type: '骑手门店',
|
||||||
|
title: '骑手租金拖欠',
|
||||||
|
content: '骑手张某拖欠门店租车租金共计500元',
|
||||||
|
partyA: '骑手张某',
|
||||||
|
partyB: '朝阳区总店',
|
||||||
|
status: '处理中',
|
||||||
|
handler: '王店长'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conflictId: 'CONF002',
|
||||||
|
type: '骑手门店',
|
||||||
|
title: '车辆损坏赔偿',
|
||||||
|
content: '骑手归还车辆时发现车身刮擦,双方对赔偿金额有争议',
|
||||||
|
partyA: '骑手李某',
|
||||||
|
partyB: '海淀区一分店',
|
||||||
|
status: '已解决',
|
||||||
|
result: '骑手承担200元维修费用',
|
||||||
|
handler: '李店长'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conflictId: 'CONF003',
|
||||||
|
type: '门店公司',
|
||||||
|
title: '总部扣款异议',
|
||||||
|
content: '门店对公司月度扣款有异议,认为计算有误',
|
||||||
|
partyA: '西城区二分店',
|
||||||
|
partyB: '公司总部',
|
||||||
|
status: '待处理'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
conflictId: 'CONF004',
|
||||||
|
type: '骑手门店',
|
||||||
|
title: '头盔丢失',
|
||||||
|
content: '骑手归还车辆时遗失头盔一个',
|
||||||
|
partyA: '骑手赵某',
|
||||||
|
partyB: '东城区三分店',
|
||||||
|
status: '处理中',
|
||||||
|
handler: '赵店长'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
console.log('✅ 骑手/门店冲突: 4 条');
|
||||||
|
|
||||||
|
// 打款/财务数据 (使用正确的枚举值)
|
||||||
|
await Payment.insertMany([
|
||||||
|
{ paymentId: 'PAY006', type: '支出', party: '门店A', amount: 12000, method: '银行卡', category: '房租', remark: '3月房租', operator: '财务' },
|
||||||
|
{ paymentId: 'PAY007', type: '支出', party: '物业', amount: 1500, method: '银行卡', category: '水电', remark: '2月水电费', operator: '行政' },
|
||||||
|
{ paymentId: 'PAY008', type: '支出', party: '维修店', amount: 800, method: '微信', category: '维修', remark: '车辆维修', operator: '王店长' },
|
||||||
|
{ paymentId: 'PAY009', type: '收入', party: '骑手张某', amount: 500, method: '微信', category: '租金收入', remark: '骑手租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY010', type: '支出', party: '员工B', amount: 4500, method: '银行卡', category: '工资', remark: '2月工资', operator: '财务' },
|
||||||
|
{ paymentId: 'PAY011', type: '收入', party: '骑手李某', amount: 300, method: '支付宝', category: '租金收入', remark: '骑手租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY012', type: '支出', party: '供应商', amount: 3500, method: '银行卡', category: '其他', remark: '采购电池', operator: '采购' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 打款/财务记录: 7 条');
|
||||||
|
|
||||||
|
console.log('\n🎉 纠纷协调和打款数据创建完成!');
|
||||||
|
console.log('📊 数据汇总:');
|
||||||
|
console.log(' - 门店纠纷: 3 条');
|
||||||
|
console.log(' - 骑手/门店冲突: 4 条');
|
||||||
|
console.log(' - 打款记录: 7 条');
|
||||||
|
|
||||||
|
mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(err => console.error('❌ 错误:', err));
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
console.log('✅ MongoDB 连接成功');
|
||||||
|
|
||||||
|
const Payment = require('./server/models/Payment');
|
||||||
|
|
||||||
|
// 添加更多打款数据
|
||||||
|
await Payment.insertMany([
|
||||||
|
{ paymentId: 'PAY013', type: '收入', party: '骑手王某', amount: 800, method: '微信', category: '租金收入', remark: '3月租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY014', type: '支出', party: '员工C', amount: 5200, method: '银行卡', category: '工资', remark: '3月工资', operator: '财务' },
|
||||||
|
{ paymentId: 'PAY015', type: '支出', party: '房东', amount: 15000, method: '银行卡', category: '房租', remark: '4月房租', operator: '财务' },
|
||||||
|
{ paymentId: 'PAY016', type: '支出', party: '电力公司', amount: 2800, method: '银行卡', category: '水电', remark: '3月电费', operator: '行政' },
|
||||||
|
{ paymentId: 'PAY017', type: '支出', party: '自来水公司', amount: 600, method: '银行卡', category: '水电', remark: '3月水费', operator: '行政' },
|
||||||
|
{ paymentId: 'PAY018', type: '支出', party: '修理厂', amount: 1200, method: '微信', category: '维修', remark: '车辆大修', operator: '王店长' },
|
||||||
|
{ paymentId: 'PAY019', type: '收入', party: '骑手孙某', amount: 450, method: '支付宝', category: '租金收入', remark: '租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY020', type: '支出', party: '保险公司', amount: 3500, method: '银行卡', category: '其他', remark: '车辆保险', operator: '财务' },
|
||||||
|
{ paymentId: 'PAY021', type: '收入', party: '骑手周某', amount: 600, method: '微信', category: '租金收入', remark: '租金', operator: '系统' },
|
||||||
|
{ paymentId: 'PAY022', type: '支出', party: '广告公司', amount: 2000, method: '银行卡', category: '其他', remark: '营销推广', operator: '市场' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log('✅ 添加了 10 条打款记录');
|
||||||
|
const total = await Payment.countDocuments();
|
||||||
|
console.log('📊 打款记录总数:', total);
|
||||||
|
|
||||||
|
mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(err => console.error('❌ 错误:', err));
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
console.log('Connected to MongoDB');
|
||||||
|
|
||||||
|
// 定义新的 Payment 模型
|
||||||
|
const paymentSchema = new mongoose.Schema({
|
||||||
|
paymentId: String,
|
||||||
|
type: String,
|
||||||
|
party: String,
|
||||||
|
amount: Number,
|
||||||
|
method: String,
|
||||||
|
category: String,
|
||||||
|
remark: String,
|
||||||
|
createdAt: Date
|
||||||
|
});
|
||||||
|
|
||||||
|
const Payment = mongoose.model('Payment', paymentSchema, 'payments');
|
||||||
|
|
||||||
|
// 删除旧数据
|
||||||
|
await Payment.deleteMany({});
|
||||||
|
console.log('Deleted old data');
|
||||||
|
|
||||||
|
// 添加新数据
|
||||||
|
const payments = [
|
||||||
|
{ paymentId: 'PAY001', type: 'income', party: '张三', amount: 1500, method: '微信', category: '租金收入', remark: '租车费' },
|
||||||
|
{ paymentId: 'PAY002', type: 'income', party: '李四', amount: 2000, method: '支付宝', category: '租金收入', remark: '租车费' },
|
||||||
|
{ paymentId: 'PAY003', type: 'income', party: '王五', amount: 500, method: '现金', category: '押金退还', remark: '还车退押金' },
|
||||||
|
{ paymentId: 'PAY004', type: 'expense', party: '赵六', amount: 300, method: '微信', category: '退款', remark: '提前还车退款' },
|
||||||
|
{ paymentId: 'PAY005', type: 'expense', party: '房东', amount: 1500, method: '银行卡', category: '房租', remark: '门店房租' },
|
||||||
|
{ paymentId: 'PAY006', type: 'expense', party: '电工', amount: 200, method: '现金', category: '水电', remark: '门店电费' }
|
||||||
|
];
|
||||||
|
|
||||||
|
await Payment.insertMany(payments);
|
||||||
|
console.log('Added new payments');
|
||||||
|
|
||||||
|
// 验证
|
||||||
|
const count = await Payment.countDocuments();
|
||||||
|
console.log('Total payments:', count);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,257 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
console.log('✅ MongoDB 连接成功');
|
||||||
|
|
||||||
|
const Store = require('./server/models/Store');
|
||||||
|
const Vehicle = require('./server/models/Vehicle');
|
||||||
|
const Customer = require('./server/models/Customer');
|
||||||
|
const Order = require('./server/models/Order');
|
||||||
|
const Payment = require('./server/models/Payment');
|
||||||
|
|
||||||
|
// 清空数据
|
||||||
|
await Vehicle.deleteMany({});
|
||||||
|
await Customer.deleteMany({});
|
||||||
|
await Order.deleteMany({});
|
||||||
|
console.log('🧹 已清空车辆、客户、订单数据');
|
||||||
|
|
||||||
|
// ===== 1. 门店数据 (10个) =====
|
||||||
|
const stores = await Store.insertMany([
|
||||||
|
{ storeId: 'STORE001', name: '朝阳区总店', address: '北京市朝阳区建国路88号', phone: '010-12345678', manager: '王店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE002', name: '海淀区一分店', address: '北京市海淀区中关村大街1号', phone: '010-23456789', manager: '李店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE003', name: '西城区二分店', address: '北京市西城区西单北大街120号', phone: '010-34567890', manager: '张店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE004', name: '东城区三分店', address: '北京市东城区王府井大街138号', phone: '010-45678901', manager: '赵店长', status: '装修中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE005', name: '丰台区四分店', address: '北京市丰台区南三环西路16号', phone: '010-56789012', manager: '钱店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE006', name: '通州区分店', address: '北京市通州区运河西大街128号', phone: '010-67890123', manager: '孙店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE007', name: '昌平区分店', address: '北京市昌平区西关环岛附近', phone: '010-78901234', manager: '周店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE008', name: '大兴区分店', address: '北京市大兴区黄村镇兴政街', phone: '010-89012345', manager: '吴店长', status: '已停业', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE009', name: '顺义区分店', address: '北京市顺义区府前中街', phone: '010-90123456', manager: '郑店长', status: '营业中', approvalStatus: '已通过' },
|
||||||
|
{ storeId: 'STORE010', name: '房山区分店', address: '北京市房山区良乡镇拱辰大街', phone: '010-01234567', manager: '冯店长', status: '营业中', approvalStatus: '待审批' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 门店: 10 个');
|
||||||
|
|
||||||
|
// ===== 2. 客户/骑手数据 (15个) =====
|
||||||
|
const customers = await Customer.insertMany([
|
||||||
|
{ customerId: 'CUST001', name: '张三', phone: '13800138001', idCard: '110101199001011234', address: '北京市朝阳区', email: 'zhangsan@email.com', creditScore: 95, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST002', name: '李四', phone: '13800138002', idCard: '110102199002022345', address: '北京市海淀区', email: 'lisi@email.com', creditScore: 88, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST003', name: '王五', phone: '13800138003', idCard: '110103199003033456', address: '北京市西城区', email: 'wangwu@email.com', creditScore: 75, creditLevel: '良好', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST004', name: '赵六', phone: '13800138004', idCard: '110104199004044567', address: '北京市东城区', email: 'zhaoliu@email.com', creditScore: 92, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST005', name: '钱七', phone: '13800138005', idCard: '110105199005055678', address: '北京市丰台区', email: 'qianqi@email.com', creditScore: 65, creditLevel: '一般', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST006', name: '孙八', phone: '13800138006', idCard: '110106199006066789', address: '北京市通州区', email: 'sunba@email.com', creditScore: 80, creditLevel: '良好', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST007', name: '周九', phone: '13800138007', idCard: '110107199007077890', address: '北京市昌平区', email: 'zhoujiu@email.com', creditScore: 85, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST008', name: '吴十', phone: '13800138008', idCard: '110108199008088901', address: '北京市大兴区', email: 'wushi@email.com', creditScore: 70, creditLevel: '良好', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST009', name: '郑十一', phone: '13800138009', idCard: '110109199009099012', address: '北京市顺义区', email: 'zhengshiyi@email.com', creditScore: 90, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST010', name: '冯十二', phone: '13800138010', idCard: '110110199010101123', address: '北京市房山区', email: 'fengershi@email.com', creditScore: 55, creditLevel: '较差', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST011', name: '陈十三', phone: '13800138011', idCard: '110111199011112234', address: '北京市朝阳区', email: 'chenshisan@email.com', creditScore: 82, creditLevel: '良好', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST012', name: '刘十四', phone: '13800138012', idCard: '110112199012123345', address: '北京市海淀区', email: 'liushisi@email.com', creditScore: 78, creditLevel: '良好', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST013', name: '杨十五', phone: '13800138013', idCard: '110113199101011456', address: '北京市西城区', email: 'yangshiwu@email.com', creditScore: 95, creditLevel: '优秀', accountStatus: '正常' },
|
||||||
|
{ customerId: 'CUST014', name: '黄十六', phone: '13800138014', idCard: '110114199102022567', address: '北京市东城区', email: 'huangshiliu@email.com', creditScore: 60, creditLevel: '一般', accountStatus: '冻结' },
|
||||||
|
{ customerId: 'CUST015', name: '林十七', phone: '13800138015', idCard: '110115199103033678', address: '北京市丰台区', email: 'linshiqi@email.com', creditScore: 88, creditLevel: '优秀', accountStatus: '正常' }
|
||||||
|
]);
|
||||||
|
console.log('✅ 客户/骑手: 15 个');
|
||||||
|
|
||||||
|
// ===== 3. 车辆数据 (25辆) =====
|
||||||
|
const vehicleData = [];
|
||||||
|
const models = ['雅迪DT3', '爱玛A500', '台铃TL1', '新日XC1', '绿源L6', '小牛MQi2', '九号E25', '雅马哈E01'];
|
||||||
|
const colors = ['黑色', '白色', '红色', '蓝色', '绿色', '灰色', '金色'];
|
||||||
|
const storeIds = stores.map(s => s._id);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 25; i++) {
|
||||||
|
const storeIndex = (i - 1) % storeIds.length;
|
||||||
|
const statusOptions = ['空闲', '空闲', '空闲', '在租', '在租', '维修中', '空闲'];
|
||||||
|
const status = statusOptions[Math.floor(Math.random() * statusOptions.length)];
|
||||||
|
|
||||||
|
vehicleData.push({
|
||||||
|
vehicleId: `VEH${String(i).padStart(4, '0')}`,
|
||||||
|
model: models[Math.floor(Math.random() * models.length)],
|
||||||
|
color: colors[Math.floor(Math.random() * colors.length)],
|
||||||
|
batteryType: '锂电池',
|
||||||
|
batteryCapacity: [48, 60, 72][Math.floor(Math.random() * 3)],
|
||||||
|
batteryStatus: ['正常', '正常', '正常', '老化'][Math.floor(Math.random() * 4)],
|
||||||
|
status: status,
|
||||||
|
location: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [116.2 + Math.random() * 0.5, 39.8 + Math.random() * 0.5]
|
||||||
|
},
|
||||||
|
totalRentDays: Math.floor(Math.random() * 500),
|
||||||
|
purchaseDate: new Date(2023, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1),
|
||||||
|
purchasePrice: [2999, 3999, 4999, 5999][Math.floor(Math.random() * 4)],
|
||||||
|
purchaseSupplier: ['雅迪集团', '爱玛集团', '台铃集团', '新日集团'][Math.floor(Math.random() * 4)],
|
||||||
|
notes: i <= 2 ? '新车,暂无记录' : ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const vehicles = await Vehicle.insertMany(vehicleData);
|
||||||
|
console.log('✅ 车辆: 25 辆');
|
||||||
|
|
||||||
|
// ===== 4. 订单数据 (20个) - 不同状态 =====
|
||||||
|
const orderData = [];
|
||||||
|
const now = new Date();
|
||||||
|
const oneDay = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// 已完成订单 (8个)
|
||||||
|
for (let i = 1; i <= 8; i++) {
|
||||||
|
const customer = customers[Math.floor(Math.random() * customers.length)];
|
||||||
|
const vehicle = vehicles[Math.floor(Math.random() * vehicles.length)];
|
||||||
|
const days = Math.floor(Math.random() * 10) + 1;
|
||||||
|
const startDate = new Date(now.getTime() - (Math.floor(Math.random() * 30) + days) * oneDay);
|
||||||
|
const endDate = new Date(startDate.getTime() + days * oneDay);
|
||||||
|
const dailyRate = [30, 50, 80, 100][Math.floor(Math.random() * 4)];
|
||||||
|
const total = dailyRate * days;
|
||||||
|
|
||||||
|
orderData.push({
|
||||||
|
orderNumber: `ORD${String(now.getFullYear())}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(i).padStart(4, '0')}`,
|
||||||
|
customer: customer._id,
|
||||||
|
vehicle: vehicle._id,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
actualEndDate: endDate,
|
||||||
|
rentalFee: dailyRate * days,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: total + 200,
|
||||||
|
paidAmount: total + 200,
|
||||||
|
status: '已完成',
|
||||||
|
paymentMethod: ['微信', '支付宝', '现金'][Math.floor(Math.random() * 3)],
|
||||||
|
paymentDate: startDate,
|
||||||
|
contractSigned: true,
|
||||||
|
notes: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进行中订单 (6个)
|
||||||
|
for (let i = 9; i <= 14; i++) {
|
||||||
|
const customer = customers[Math.floor(Math.random() * customers.length)];
|
||||||
|
const vehicle = vehicles[Math.floor(Math.random() * vehicles.length)];
|
||||||
|
const days = Math.floor(Math.random() * 15) + 1;
|
||||||
|
const startDate = new Date(now.getTime() - Math.floor(Math.random() * 5) * oneDay);
|
||||||
|
const endDate = new Date(startDate.getTime() + days * oneDay);
|
||||||
|
const dailyRate = [30, 50, 80, 100][Math.floor(Math.random() * 4)];
|
||||||
|
|
||||||
|
orderData.push({
|
||||||
|
orderNumber: `ORD${String(now.getFullYear())}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(i).padStart(4, '0')}`,
|
||||||
|
customer: customer._id,
|
||||||
|
vehicle: vehicle._id,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
rentalFee: dailyRate * days,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: dailyRate * days + 200,
|
||||||
|
paidAmount: dailyRate * days + 200,
|
||||||
|
status: '进行中',
|
||||||
|
paymentMethod: ['微信', '支付宝', '现金'][Math.floor(Math.random() * 3)],
|
||||||
|
paymentDate: startDate,
|
||||||
|
contractSigned: true,
|
||||||
|
notes: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待支付订单 (2个)
|
||||||
|
for (let i = 15; i <= 16; i++) {
|
||||||
|
const customer = customers[Math.floor(Math.random() * customers.length)];
|
||||||
|
const vehicle = vehicles[Math.floor(Math.random() * vehicles.length)];
|
||||||
|
const days = Math.floor(Math.random() * 7) + 1;
|
||||||
|
const startDate = new Date(now.getTime() + oneDay);
|
||||||
|
const endDate = new Date(startDate.getTime() + days * oneDay);
|
||||||
|
const dailyRate = [30, 50, 80, 100][Math.floor(Math.random() * 4)];
|
||||||
|
|
||||||
|
orderData.push({
|
||||||
|
orderNumber: `ORD${String(now.getFullYear())}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(i).padStart(4, '0')}`,
|
||||||
|
customer: customer._id,
|
||||||
|
vehicle: vehicle._id,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
rentalFee: dailyRate * days,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: dailyRate * days + 200,
|
||||||
|
paidAmount: 0,
|
||||||
|
status: '待支付',
|
||||||
|
contractSigned: false,
|
||||||
|
notes: '订单已创建,等待支付'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 已取消订单 (2个)
|
||||||
|
for (let i = 17; i <= 18; i++) {
|
||||||
|
const customer = customers[Math.floor(Math.random() * customers.length)];
|
||||||
|
const vehicle = vehicles[Math.floor(Math.random() * vehicles.length)];
|
||||||
|
const days = Math.floor(Math.random() * 5) + 1;
|
||||||
|
const startDate = new Date(now.getTime() - Math.floor(Math.random() * 10) * oneDay);
|
||||||
|
const endDate = new Date(startDate.getTime() + days * oneDay);
|
||||||
|
const dailyRate = [30, 50, 80, 100][Math.floor(Math.random() * 4)];
|
||||||
|
|
||||||
|
orderData.push({
|
||||||
|
orderNumber: `ORD${String(now.getFullYear())}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(i).padStart(4, '0')}`,
|
||||||
|
customer: customer._id,
|
||||||
|
vehicle: vehicle._id,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
rentalFee: dailyRate * days,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: dailyRate * days + 200,
|
||||||
|
paidAmount: 200,
|
||||||
|
status: '已取消',
|
||||||
|
paymentMethod: ['微信', '支付宝'][Math.floor(Math.random() * 2)],
|
||||||
|
paymentDate: startDate,
|
||||||
|
contractSigned: true,
|
||||||
|
notes: '客户主动取消订单'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逾期订单 (2个)
|
||||||
|
for (let i = 19; i <= 20; i++) {
|
||||||
|
const customer = customers[Math.floor(Math.random() * customers.length)];
|
||||||
|
const vehicle = vehicles[Math.floor(Math.random() * vehicles.length)];
|
||||||
|
const days = 3;
|
||||||
|
const startDate = new Date(now.getTime() - 10 * oneDay);
|
||||||
|
const endDate = new Date(startDate.getTime() + days * oneDay);
|
||||||
|
const dailyRate = [30, 50, 80, 100][Math.floor(Math.random() * 4)];
|
||||||
|
const overdueDays = Math.floor((now.getTime() - endDate.getTime()) / oneDay);
|
||||||
|
|
||||||
|
orderData.push({
|
||||||
|
orderNumber: `ORD${String(now.getFullYear())}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(i).padStart(4, '0')}`,
|
||||||
|
customer: customer._id,
|
||||||
|
vehicle: vehicle._id,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
actualEndDate: now,
|
||||||
|
rentalFee: dailyRate * days,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: dailyRate * days + 200 + overdueDays * 20,
|
||||||
|
paidAmount: dailyRate * days + 200,
|
||||||
|
status: '逾期',
|
||||||
|
overdueDays: overdueDays,
|
||||||
|
overdueFee: overdueDays * 20,
|
||||||
|
paymentMethod: ['微信', '支付宝'][Math.floor(Math.random() * 2)],
|
||||||
|
paymentDate: startDate,
|
||||||
|
contractSigned: true,
|
||||||
|
notes: '逾期未归还,需缴纳逾期费用'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const orders = await Order.insertMany(orderData);
|
||||||
|
console.log('✅ 订单: 20 个 (已完成: 8, 进行中: 6, 待支付: 2, 已取消: 2, 逾期: 2)');
|
||||||
|
|
||||||
|
// 更新客户的总租赁次数和消费
|
||||||
|
for (const customer of customers) {
|
||||||
|
const customerOrders = await Order.find({ customer: customer._id, status: '已完成' });
|
||||||
|
await Customer.findByIdAndUpdate(customer._id, {
|
||||||
|
totalRentals: customerOrders.length,
|
||||||
|
totalSpent: customerOrders.reduce((sum, o) => sum + o.totalAmount, 0),
|
||||||
|
currentRentals: await Order.countDocuments({ customer: customer._id, status: '进行中' })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('✅ 客户数据已更新');
|
||||||
|
|
||||||
|
console.log('\n🎉 测试数据创建完成!');
|
||||||
|
console.log('📊 数据汇总:');
|
||||||
|
console.log(' - 门店: 10 个');
|
||||||
|
console.log(' - 车辆: 25 辆');
|
||||||
|
console.log(' - 客户/骑手: 15 个');
|
||||||
|
console.log(' - 订单: 20 个');
|
||||||
|
console.log(' - 订单状态分布: 已完成(8), 进行中(6), 待支付(2), 已取消(2), 逾期(2)');
|
||||||
|
|
||||||
|
mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(err => console.error('❌ 错误:', err));
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
function postData(path, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const postData = JSON.stringify(data);
|
||||||
|
const options = {
|
||||||
|
hostname: 'localhost',
|
||||||
|
port: 3000,
|
||||||
|
path: path,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Content-Length': Buffer.byteLength(postData)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const req = http.request(options, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => data += chunk);
|
||||||
|
res.on('end', () => resolve(JSON.parse(data)));
|
||||||
|
});
|
||||||
|
req.on('error', reject);
|
||||||
|
req.write(postData);
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// 客服投诉
|
||||||
|
await postData('/api/complaints', { complaintId: 'COMP001', type: '服务态度', content: '客户投诉店员态度不好', handler: '张三', status: '待处理' });
|
||||||
|
console.log('✅ 投诉1');
|
||||||
|
await postData('/api/complaints', { complaintId: 'COMP002', type: '车辆问题', content: '电动车电量不足', handler: '李四', status: '处理中' });
|
||||||
|
console.log('✅ 投诉2');
|
||||||
|
await postData('/api/complaints', { complaintId: 'COMP003', type: '费用问题', content: '费用计算有误', handler: '王五', status: '已解决' });
|
||||||
|
console.log('✅ 投诉3');
|
||||||
|
|
||||||
|
// 审批
|
||||||
|
await postData('/api/approvals', { approvalId: 'APPR001', type: '订单退款', title: '客户申请退款', applicant: '张三', status: '待审批' });
|
||||||
|
console.log('✅ 审批1');
|
||||||
|
await postData('/api/approvals', { approvalId: 'APPR002', type: '车辆报废', title: '旧车报废申请', applicant: '李四', status: '已通过' });
|
||||||
|
console.log('✅ 审批2');
|
||||||
|
await postData('/api/approvals', { approvalId: 'APPR003', type: '押金退还', title: '押金退还审批', applicant: '王五', status: '待审批' });
|
||||||
|
console.log('✅ 审批3');
|
||||||
|
|
||||||
|
// 打款
|
||||||
|
await postData('/api/payments', { paymentId: 'PAY001', type: '退款', amount: 200, method: '微信', account: 'wx123456', status: '待打款' });
|
||||||
|
console.log('✅ 打款1');
|
||||||
|
await postData('/api/payments', { paymentId: 'PAY002', type: '押金退还', amount: 500, method: '支付宝', account: 'ali@xx.com', status: '已打款' });
|
||||||
|
console.log('✅ 打款2');
|
||||||
|
await postData('/api/payments', { paymentId: 'PAY003', type: '分成', amount: 300, method: '银行卡', account: '6222xxx1234', status: '打款中' });
|
||||||
|
console.log('✅ 打款3');
|
||||||
|
|
||||||
|
console.log('🎉 完成!');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
// 清理 vehicle-types 集合中所有 pricePerDay 字段
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(async () => {
|
||||||
|
const db = mongoose.connection.db;
|
||||||
|
const result = await db.collection('vehicle-types').updateMany(
|
||||||
|
{ pricePerDay: { $exists: true } },
|
||||||
|
{ $unset: { pricePerDay: "" } }
|
||||||
|
);
|
||||||
|
console.log(`已从 ${result.modifiedCount} 条 vehicle-types 文档中删除 pricePerDay 字段`);
|
||||||
|
await mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('执行失败:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
const express = require('express');
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const cors = require('cors');
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// 中间件
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// 连接 MongoDB
|
||||||
|
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(() => console.log('✅ MongoDB 连接成功'))
|
||||||
|
.catch(err => console.error('❌ MongoDB 连接失败:', err.message));
|
||||||
|
|
||||||
|
// 路由
|
||||||
|
app.use('/api/vehicles', require('./routes/vehicles'));
|
||||||
|
app.use('/api/orders', require('./routes/orders'));
|
||||||
|
app.use('/api/customers', require('./routes/customers'));
|
||||||
|
app.use('/api/finance', require('./routes/finance'));
|
||||||
|
app.use('/api/stores', require('./routes/stores'));
|
||||||
|
app.use('/api/complaints', require('./routes/complaints'));
|
||||||
|
app.use('/api/approvals', require('./routes/approvals'));
|
||||||
|
app.use('/api/payments', require('./routes/payments'));
|
||||||
|
app.use('/api/conflicts', require('./routes/conflicts'));
|
||||||
|
app.use('/api/applications', require('./routes/applications'));
|
||||||
|
app.use('/api/disputes', require('./routes/disputes'));
|
||||||
|
app.use('/api/riders', require('./routes/riders'));
|
||||||
|
app.use('/api/vehicle-types', require('./routes/vehicleTypes'));
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 404 处理
|
||||||
|
app.use((req, res) => {
|
||||||
|
res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: '接口不存在',
|
||||||
|
path: req.path
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 错误处理中间件
|
||||||
|
const errorHandler = require('./middleware/errorHandler');
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`🚀 服务器运行在 http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 错误处理中间件
|
||||||
|
const errorHandler = (err, req, res, next) => {
|
||||||
|
console.error('错误:', err.message);
|
||||||
|
|
||||||
|
// MongoDB 连接错误
|
||||||
|
if (err.name === 'MongoError' || err.name === 'MongooseError') {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: '数据库错误',
|
||||||
|
error: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证错误
|
||||||
|
if (err.name === 'ValidationError') {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: '数据验证失败',
|
||||||
|
error: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 404 错误
|
||||||
|
if (err.name === 'CastError') {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: '数据不存在',
|
||||||
|
error: err.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认错误
|
||||||
|
res.status(err.status || 500).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message || '服务器内部错误',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? err.stack : undefined
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = errorHandler;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const applicationSchema = new mongoose.Schema({
|
||||||
|
appId: { type: String, required: true, unique: true },
|
||||||
|
store: { type: mongoose.Schema.Types.ObjectId, ref: 'Store' },
|
||||||
|
storeName: { type: String },
|
||||||
|
type: { type: String, enum: ['注册申请', '活动申请', '促销活动', '设备申请', '其他'], required: true },
|
||||||
|
title: { type: String, content: { type: String },
|
||||||
|
required: true },
|
||||||
|
status: { type: String, enum: ['待审批', '已通过', '已拒绝'], default: '待审批' },
|
||||||
|
rejectReason: { type: String },
|
||||||
|
handler: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
applicationSchema.pre('save', function(next) {
|
||||||
|
if (!this.appId) {
|
||||||
|
this.appId = `APP${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Application', applicationSchema);
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const approvalSchema = new mongoose.Schema({
|
||||||
|
approvalId: { type: String, required: true, unique: true },
|
||||||
|
type: { type: String, enum: ['订单退款', '车辆报废', '押金退还', '价格修改', '其他'], required: true },
|
||||||
|
title: { type: String, required: true },
|
||||||
|
content: { type: String },
|
||||||
|
applicant: { type: String },
|
||||||
|
approver: { type: String },
|
||||||
|
status: { type: String, enum: ['待审批', '已通过', '已拒绝'], default: '待审批' },
|
||||||
|
remark: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
approvalSchema.pre('save', function(next) {
|
||||||
|
if (!this.approvalId) {
|
||||||
|
this.approvalId = `APPR${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Approval', approvalSchema);
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const complaintSchema = new mongoose.Schema({
|
||||||
|
complaintId: { type: String, required: true, unique: true },
|
||||||
|
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer' },
|
||||||
|
order: { type: mongoose.Schema.Types.ObjectId, ref: 'Order' },
|
||||||
|
type: { type: String, enum: ['服务态度', '车辆问题', '费用问题', '其他'], required: true },
|
||||||
|
content: { type: String, required: true },
|
||||||
|
status: { type: String, enum: ['待处理', '处理中', '已解决', '已关闭'], default: '待处理' },
|
||||||
|
response: { type: String },
|
||||||
|
handler: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
complaintSchema.pre('save', function(next) {
|
||||||
|
if (!this.complaintId) {
|
||||||
|
this.complaintId = `COMP${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Complaint', complaintSchema);
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const conflictSchema = new mongoose.Schema({
|
||||||
|
conflictId: { type: String, required: true, unique: true },
|
||||||
|
type: { type: String, enum: ['骑手门店', '门店公司'], required: true },
|
||||||
|
title: { type: String, required: true },
|
||||||
|
content: { type: String },
|
||||||
|
partyA: { type: String }, // 骑手/门店
|
||||||
|
partyB: { type: String }, // 门店/公司
|
||||||
|
status: { type: String, enum: ['待处理', '处理中', '已解决', '已关闭'], default: '待处理' },
|
||||||
|
handler: { type: String },
|
||||||
|
result: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
conflictSchema.pre('save', function(next) {
|
||||||
|
if (!this.conflictId) {
|
||||||
|
this.conflictId = `CONF${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Conflict', conflictSchema);
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const customerSchema = new mongoose.Schema({
|
||||||
|
// 基本信息
|
||||||
|
customerId: { type: String, required: true, unique: true }, // 客户编号
|
||||||
|
name: { type: String, required: true }, // 姓名
|
||||||
|
phone: { type: String, required: true }, // 手机号
|
||||||
|
idCard: { type: String }, // 身份证号
|
||||||
|
|
||||||
|
// 联系信息
|
||||||
|
address: { type: String }, // 地址
|
||||||
|
email: { type: String }, // 邮箱
|
||||||
|
|
||||||
|
// 租赁信息
|
||||||
|
totalRentals: { type: Number, default: 0 }, // 总租赁次数
|
||||||
|
totalSpent: { type: Number, default: 0 }, // 总消费金额
|
||||||
|
currentRentals: { type: Number, default: 0 }, // 当前租赁数量
|
||||||
|
|
||||||
|
// 信用信息
|
||||||
|
creditScore: { type: Number, default: 100 }, // 信用评分 (0-100)
|
||||||
|
creditLevel: { type: String, enum: ['优秀', '良好', '一般', '较差'], default: '优秀' },
|
||||||
|
|
||||||
|
// 账户信息
|
||||||
|
accountStatus: {
|
||||||
|
type: String,
|
||||||
|
enum: ['正常', '冻结', '注销'],
|
||||||
|
default: '正常'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 备注
|
||||||
|
notes: { type: String },
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新时自动更新 updatedAt
|
||||||
|
customerSchema.pre('save', function(next) {
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成客户编号
|
||||||
|
customerSchema.pre('save', function(next) {
|
||||||
|
if (!this.customerId) {
|
||||||
|
const date = new Date();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
||||||
|
this.customerId = `CUST${year}${month}${day}${random}`;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Customer', customerSchema);
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const disputeSchema = new mongoose.Schema({
|
||||||
|
disputeId: { type: String, required: true, unique: true },
|
||||||
|
storeA: { type: mongoose.Schema.Types.ObjectId, ref: 'Store' },
|
||||||
|
storeAName: { type: String },
|
||||||
|
storeB: { type: mongoose.Schema.Types.ObjectId, ref: 'Store' },
|
||||||
|
storeBName: { type: String },
|
||||||
|
type: { type: String, enum: ['订单纠纷', '区域纠纷', '费用纠纷', '其他'], required: true },
|
||||||
|
title: { type: String, required: true },
|
||||||
|
content: { type: String },
|
||||||
|
status: { type: String, enum: ['待处理', '处理中', '已解决'], default: '待处理' },
|
||||||
|
result: { type: String },
|
||||||
|
handler: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
disputeSchema.pre('save', function(next) {
|
||||||
|
if (!this.disputeId) {
|
||||||
|
this.disputeId = `DISP${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Dispute', disputeSchema);
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const orderSchema = new mongoose.Schema({
|
||||||
|
// 订单基本信息
|
||||||
|
orderNumber: { type: String, unique: true }, // 订单号(由 pre-save hook 自动生成)
|
||||||
|
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true }, // 客户
|
||||||
|
vehicle: { type: mongoose.Schema.Types.ObjectId, ref: 'Vehicle', required: true }, // 车辆
|
||||||
|
|
||||||
|
// 租赁信息
|
||||||
|
startDate: { type: Date, required: true }, // 开始日期
|
||||||
|
endDate: { type: Date, required: true }, // 结束日期
|
||||||
|
actualEndDate: { type: Date }, // 实际结束日期
|
||||||
|
|
||||||
|
// 费用信息
|
||||||
|
rentalFee: { type: Number, required: true }, // 租金
|
||||||
|
deposit: { type: Number, default: 0 }, // 押金
|
||||||
|
totalAmount: { type: Number, required: true }, // 总金额
|
||||||
|
paidAmount: { type: Number, default: 0 }, // 已支付金额
|
||||||
|
|
||||||
|
// 订单状态
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ['待支付', '进行中', '已完成', '逾期', '已取消', '已退款'],
|
||||||
|
default: '待支付'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 逾期信息
|
||||||
|
overdueDays: { type: Number, default: 0 }, // 逾期天数
|
||||||
|
overdueFee: { type: Number, default: 0 }, // 逾期费用
|
||||||
|
|
||||||
|
// 支付信息
|
||||||
|
paymentMethod: { type: String, enum: ['微信', '支付宝', '现金', '银行卡'] },
|
||||||
|
paymentDate: { type: Date },
|
||||||
|
|
||||||
|
// 合同信息
|
||||||
|
contractUrl: { type: String }, // 合同文件路径
|
||||||
|
contractSigned: { type: Boolean, default: false }, // 合同是否签署
|
||||||
|
|
||||||
|
// 备注
|
||||||
|
notes: { type: String },
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新时自动更新 updatedAt
|
||||||
|
orderSchema.pre('save', function(next) {
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成订单号
|
||||||
|
orderSchema.pre('save', function(next) {
|
||||||
|
if (!this.orderNumber) {
|
||||||
|
const date = new Date();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
|
||||||
|
this.orderNumber = `ORD${year}${month}${day}${random}`;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Order', orderSchema);
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const paymentSchema = new mongoose.Schema({
|
||||||
|
paymentId: { type: String, required: true, unique: true },
|
||||||
|
// 收支类型:收入(客户付款)/ 支出(退款、工资、房租等)
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ['收入', '支出'],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 对方名称(谁给钱/我们给谁)
|
||||||
|
party: { type: String, required: true },
|
||||||
|
// 金额
|
||||||
|
amount: { type: Number, required: true },
|
||||||
|
// 收支方式
|
||||||
|
method: { type: String, enum: ['微信', '支付宝', '银行卡', '现金'] },
|
||||||
|
// 分类
|
||||||
|
category: {
|
||||||
|
type: String,
|
||||||
|
enum: ['租金收入', '押金退还', '退款', '工资', '房租', '水电', '维修', '其他']
|
||||||
|
},
|
||||||
|
// 备注
|
||||||
|
remark: { type: String },
|
||||||
|
// 经手人
|
||||||
|
operator: { type: String },
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
paymentSchema.pre('save', function(next) {
|
||||||
|
if (!this.paymentId) {
|
||||||
|
this.paymentId = `PAY${Date.now()}`;
|
||||||
|
}
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Payment', paymentSchema);
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const riderSchema = new mongoose.Schema({
|
||||||
|
// 基本信息
|
||||||
|
riderId: { type: String, required: true, unique: true }, // 骑手编号
|
||||||
|
name: { type: String, required: true }, // 姓名
|
||||||
|
phone: { type: String, required: true }, // 手机号
|
||||||
|
password: { type: String, required: true }, // 密码(简单位符串存储)
|
||||||
|
|
||||||
|
// 接单状态
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ['active', 'inactive'],
|
||||||
|
default: 'inactive'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
rating: { type: Number, default: 5.0 }, // 评分
|
||||||
|
totalOrders: { type: Number, default: 0 }, // 总订单数
|
||||||
|
totalIncome: { type: Number, default: 0 }, // 总收入
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
createdAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 生成骑手编号
|
||||||
|
riderSchema.pre('save', function(next) {
|
||||||
|
if (!this.riderId) {
|
||||||
|
const date = new Date();
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
||||||
|
this.riderId = `RIDER${year}${month}${day}${random}`;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Rider', riderSchema);
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const storeSchema = new mongoose.Schema({
|
||||||
|
storeId: { type: String, required: true, unique: true },
|
||||||
|
name: { type: String, required: true },
|
||||||
|
address: { type: String },
|
||||||
|
phone: { type: String },
|
||||||
|
manager: { type: String },
|
||||||
|
status: { type: String, enum: ['营业中', '已停业', '装修中'], default: '营业中' },
|
||||||
|
approvalStatus: { type: String, enum: ['待审批', '已通过', '已拒绝'], default: '待审批' },
|
||||||
|
rejectReason: { type: String },
|
||||||
|
images: [{
|
||||||
|
type: { type: String }, // 门店照片/营业执照/身份证
|
||||||
|
url: { type: String }
|
||||||
|
}],
|
||||||
|
createdAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
storeSchema.pre('save', function(next) {
|
||||||
|
if (!this.storeId) {
|
||||||
|
this.storeId = `STORE${Date.now()}`;
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Store', storeSchema);
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const vehicleSchema = new mongoose.Schema({
|
||||||
|
// 门店关联
|
||||||
|
storeId: { type: String, required: true, index: true },
|
||||||
|
|
||||||
|
// 车辆基本信息
|
||||||
|
frameNumber: { type: String, required: true, unique: true }, // 车架号
|
||||||
|
plateNumber: { type: String }, // 车牌号
|
||||||
|
|
||||||
|
// 品牌与车型
|
||||||
|
brand: { type: String }, // 品牌
|
||||||
|
vehicleType: { type: String }, // 车型(对应 vehicleTypes 的 name)
|
||||||
|
color: { type: String }, // 颜色
|
||||||
|
|
||||||
|
// 电池信息
|
||||||
|
batteryType: { type: String }, // 电池类型
|
||||||
|
batteryCapacity: { type: Number }, // 电池容量 (Ah)
|
||||||
|
batteryStatus: { type: String, enum: ['正常', '老化', '待更换'], default: '正常' },
|
||||||
|
|
||||||
|
// 状态信息
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ['空闲', '在租', '维修中', '已报废', '待回收'],
|
||||||
|
default: '空闲'
|
||||||
|
},
|
||||||
|
isRented: { type: Boolean, default: false }, // 是否在租(冗余字段,方便查询)
|
||||||
|
|
||||||
|
// 位置信息
|
||||||
|
location: {
|
||||||
|
type: { type: String, default: 'Point' },
|
||||||
|
coordinates: { type: [Number], default: [0, 0] } // [经度, 纬度]
|
||||||
|
},
|
||||||
|
lastLocationUpdate: { type: Date },
|
||||||
|
|
||||||
|
// 租赁信息
|
||||||
|
currentOrderId: { type: mongoose.Schema.Types.ObjectId, ref: 'Order' },
|
||||||
|
totalRentDays: { type: Number, default: 0 }, // 累计租赁天数
|
||||||
|
|
||||||
|
// 维护信息
|
||||||
|
lastMaintenanceDate: { type: Date },
|
||||||
|
nextMaintenanceDate: { type: Date },
|
||||||
|
maintenanceHistory: [{
|
||||||
|
date: { type: Date },
|
||||||
|
type: { type: String }, // 保养类型
|
||||||
|
description: { type: String },
|
||||||
|
cost: { type: Number }
|
||||||
|
}],
|
||||||
|
|
||||||
|
// 购买信息
|
||||||
|
purchaseDate: { type: Date },
|
||||||
|
purchasePrice: { type: Number },
|
||||||
|
purchaseSupplier: { type: String },
|
||||||
|
|
||||||
|
// 备注
|
||||||
|
notes: { type: String },
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
createdAt: { type: Date, default: Date.now },
|
||||||
|
updatedAt: { type: Date, default: Date.now }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新时自动更新 updatedAt
|
||||||
|
vehicleSchema.pre('save', function(next) {
|
||||||
|
this.updatedAt = new Date();
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建地理位置索引
|
||||||
|
vehicleSchema.index({ location: '2dsphere' });
|
||||||
|
// 按门店索引,方便查询某门店的车辆
|
||||||
|
vehicleSchema.index({ storeId: 1 });
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Vehicle', vehicleSchema);
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const vehicleTypeSchema = new mongoose.Schema({
|
||||||
|
storeId: { type: String, required: true, index: true },
|
||||||
|
brand: { type: String, required: true },
|
||||||
|
name: { type: String, required: true },
|
||||||
|
cover: { type: String, default: '' },
|
||||||
|
}, {
|
||||||
|
timestamps: true
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = mongoose.model('VehicleType', vehicleTypeSchema);
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
// 导出所有模型
|
||||||
|
module.exports = {
|
||||||
|
Vehicle: require('./Vehicle'),
|
||||||
|
Order: require('./Order'),
|
||||||
|
Customer: require('./Customer')
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Application = require('../models/Application');
|
||||||
|
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const apps = await Application.find().populate('store');
|
||||||
|
res.json({ success: true, data: apps });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const app = new Application(req.body);
|
||||||
|
await app.save();
|
||||||
|
res.json({ success: true, data: app });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const app = await Application.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: app });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Application.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Approval = require('../models/Approval');
|
||||||
|
|
||||||
|
// 获取所有审批
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const approvals = await Approval.find();
|
||||||
|
res.json({ success: true, data: approvals });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建审批
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const approval = new Approval(req.body);
|
||||||
|
await approval.save();
|
||||||
|
res.json({ success: true, data: approval });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新审批
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const approval = await Approval.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: approval });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除审批
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Approval.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Complaint = require('../models/Complaint');
|
||||||
|
|
||||||
|
// 获取所有投诉
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const complaints = await Complaint.find().populate('customer order');
|
||||||
|
res.json({ success: true, data: complaints });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建投诉
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const complaint = new Complaint(req.body);
|
||||||
|
await complaint.save();
|
||||||
|
res.json({ success: true, data: complaint });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新投诉
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const complaint = await Complaint.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: complaint });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除投诉
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Complaint.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Conflict = require('../models/Conflict');
|
||||||
|
|
||||||
|
// 获取所有矛盾记录
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const conflicts = await Conflict.find();
|
||||||
|
res.json({ success: true, data: conflicts });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建矛盾记录
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const conflict = new Conflict(req.body);
|
||||||
|
await conflict.save();
|
||||||
|
res.json({ success: true, data: conflict });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新矛盾记录
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const conflict = await Conflict.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: conflict });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除矛盾记录
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Conflict.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,109 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Customer = require('../models/Customer');
|
||||||
|
|
||||||
|
// 获取所有客户
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const customers = await Customer.find();
|
||||||
|
res.json({ success: true, data: customers });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取单个客户
|
||||||
|
router.get('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const customer = await Customer.findById(req.params.id);
|
||||||
|
if (!customer) {
|
||||||
|
return res.status(404).json({ success: false, message: '客户不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: customer });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建客户
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const customer = new Customer(req.body);
|
||||||
|
await customer.save();
|
||||||
|
res.status(201).json({ success: true, data: customer });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新客户
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const customer = await Customer.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
req.body,
|
||||||
|
{ new: true, runValidators: true }
|
||||||
|
);
|
||||||
|
if (!customer) {
|
||||||
|
return res.status(404).json({ success: false, message: '客户不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: customer });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除客户
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const customer = await Customer.findByIdAndDelete(req.params.id);
|
||||||
|
if (!customer) {
|
||||||
|
return res.status(404).json({ success: false, message: '客户不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, message: '客户已删除' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 搜索客户
|
||||||
|
router.get('/search/:keyword', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const keyword = req.params.keyword;
|
||||||
|
const customers = await Customer.find({
|
||||||
|
$or: [
|
||||||
|
{ name: { $regex: keyword, $options: 'i' } },
|
||||||
|
{ phone: { $regex: keyword, $options: 'i' } },
|
||||||
|
{ customerId: { $regex: keyword, $options: 'i' } }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
res.json({ success: true, data: customers });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新客户信用评分
|
||||||
|
router.patch('/:id/credit', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { creditScore } = req.body;
|
||||||
|
let creditLevel = '优秀';
|
||||||
|
if (creditScore < 60) creditLevel = '较差';
|
||||||
|
else if (creditScore < 80) creditLevel = '一般';
|
||||||
|
else if (creditScore < 90) creditLevel = '良好';
|
||||||
|
|
||||||
|
const customer = await Customer.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
{ creditScore, creditLevel },
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
if (!customer) {
|
||||||
|
return res.status(404).json({ success: false, message: '客户不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: customer });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Dispute = require('../models/Dispute');
|
||||||
|
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const disputes = await Dispute.find();
|
||||||
|
res.json({ success: true, data: disputes });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const dispute = new Dispute(req.body);
|
||||||
|
await dispute.save();
|
||||||
|
res.json({ success: true, data: dispute });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const dispute = await Dispute.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: dispute });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Dispute.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Payment = require('../models/Payment');
|
||||||
|
|
||||||
|
// 获取收支明细列表
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { type, startDate, endDate, party } = req.query;
|
||||||
|
|
||||||
|
const filter = {};
|
||||||
|
if (type) filter.type = type;
|
||||||
|
if (party) filter.party = new RegExp(party, 'i');
|
||||||
|
if (startDate || endDate) {
|
||||||
|
filter.createdAt = {};
|
||||||
|
if (startDate) filter.createdAt.$gte = new Date(startDate);
|
||||||
|
if (endDate) filter.createdAt.$lte = new Date(endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payments = await Payment.find(filter)
|
||||||
|
.sort('-createdAt')
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
|
// 计算汇总 - 支持中英文
|
||||||
|
const income = await Payment.aggregate([
|
||||||
|
{ $match: { type: { $in: ['income', 'expense', '收入', '支出', '租金收入', '押金退还', '租金'] } } },
|
||||||
|
{ $group: { _id: '$type', total: { $sum: '$amount' } } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 分开计算收入和支出
|
||||||
|
let totalIncome = 0;
|
||||||
|
let totalExpense = 0;
|
||||||
|
|
||||||
|
for (const item of income) {
|
||||||
|
if (item._id === 'income' || item._id === '收入' || item._id === '租金收入' || item._id === '押金退还' || item._id === '租金') {
|
||||||
|
totalIncome += item.total;
|
||||||
|
} else {
|
||||||
|
totalExpense += item.total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
list: payments,
|
||||||
|
summary: {
|
||||||
|
totalIncome: totalIncome,
|
||||||
|
totalExpense: totalExpense,
|
||||||
|
balance: totalIncome - totalExpense
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建收支记录
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const payment = new Payment(req.body);
|
||||||
|
await payment.save();
|
||||||
|
res.json({ success: true, data: payment });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新收支记录
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: payment });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除收支记录
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Payment.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清空所有收支记录
|
||||||
|
router.post('/delete-all', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Payment.deleteMany({});
|
||||||
|
res.json({ success: true, message: 'All payments deleted' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取收支统计
|
||||||
|
router.get('/stats', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { period = 'month' } = req.query;
|
||||||
|
let startDate;
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
switch (period) {
|
||||||
|
case 'week':
|
||||||
|
startDate = new Date(now - 7 * 24 * 60 * 60 * 1000);
|
||||||
|
break;
|
||||||
|
case 'month':
|
||||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
break;
|
||||||
|
case 'quarter':
|
||||||
|
startDate = new Date(now.getFullYear(), Math.floor(now.getMonth() / 3) * 3, 1);
|
||||||
|
break;
|
||||||
|
case 'year':
|
||||||
|
startDate = new Date(now.getFullYear(), 0, 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const income = await Payment.aggregate([
|
||||||
|
{ $match: { type: '收入', createdAt: { $gte: startDate } } },
|
||||||
|
{ $group: { _id: null, total: { $sum: '$amount' } } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const expense = await Payment.aggregate([
|
||||||
|
{ $match: { type: '支出', createdAt: { $gte: startDate } } },
|
||||||
|
{ $group: { _id: null, total: { $sum: '$amount' } } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 按分类统计
|
||||||
|
const incomeByCategory = await Payment.aggregate([
|
||||||
|
{ $match: { type: '收入', createdAt: { $gte: startDate } } },
|
||||||
|
{ $group: { _id: '$category', total: { $sum: '$amount' } } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const expenseByCategory = await Payment.aggregate([
|
||||||
|
{ $match: { type: '支出', createdAt: { $gte: startDate } } },
|
||||||
|
{ $group: { _id: '$category', total: { $sum: '$amount' } } }
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
income: income[0]?.total || 0,
|
||||||
|
expense: expense[0]?.total || 0,
|
||||||
|
balance: (income[0]?.total || 0) - (expense[0]?.total || 0),
|
||||||
|
incomeByCategory: incomeByCategory.reduce((acc, item) => {
|
||||||
|
acc[item._id || '其他'] = item.total;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
expenseByCategory: expenseByCategory.reduce((acc, item) => {
|
||||||
|
acc[item._id || '其他'] = item.total;
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
// 导出所有路由
|
||||||
|
module.exports = {
|
||||||
|
vehicles: require('./vehicles'),
|
||||||
|
orders: require('./orders'),
|
||||||
|
customers: require('./customers'),
|
||||||
|
finance: require('./finance')
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Order = require('../models/Order');
|
||||||
|
const Vehicle = require('../models/Vehicle');
|
||||||
|
const Customer = require('../models/Customer');
|
||||||
|
|
||||||
|
// 获取所有订单
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const orders = await Order.find()
|
||||||
|
.populate('customer', 'name phone')
|
||||||
|
.populate('vehicle', 'model vehicleId');
|
||||||
|
res.json({ success: true, data: orders });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取单个订单
|
||||||
|
router.get('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const order = await Order.findById(req.params.id)
|
||||||
|
.populate('customer')
|
||||||
|
.populate('vehicle');
|
||||||
|
if (!order) {
|
||||||
|
return res.status(404).json({ success: false, message: '订单不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: order });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建订单(支持门店端新客创建订单)
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
customerName, customer, // customerName=新客姓名(字符串),customer=已有客户ID
|
||||||
|
vehicleId, vehicle, // vehicleId=前端字段名,vehicle=后端字段名
|
||||||
|
contractMonths, startDate, endDate, rentalFee, deposit, orderType
|
||||||
|
} = req.body;
|
||||||
|
|
||||||
|
// 解析车辆 ID(兼容前端 vehicleId 字段名)
|
||||||
|
const vehicleObjectId = vehicle || vehicleId;
|
||||||
|
|
||||||
|
// 解析客户:优先用已有客户ID,否则用姓名查找或创建新客户
|
||||||
|
let customerObjectId = customer;
|
||||||
|
if (!customerObjectId && customerName) {
|
||||||
|
// 查找同名客户
|
||||||
|
let customerDoc = await Customer.findOne({ name: customerName });
|
||||||
|
if (!customerDoc) {
|
||||||
|
// 创建新客户(phone 必填,设为默认值)
|
||||||
|
customerDoc = await Customer.create({
|
||||||
|
name: customerName,
|
||||||
|
phone: '00000000000',
|
||||||
|
customerId: `CUST${Date.now()}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
customerObjectId = customerDoc._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查车辆是否存在
|
||||||
|
const vehicleDoc = await Vehicle.findById(vehicleObjectId);
|
||||||
|
if (!vehicleDoc) {
|
||||||
|
return res.status(404).json({ success: false, message: '车辆不存在' });
|
||||||
|
}
|
||||||
|
if (vehicleDoc.status !== '空闲') {
|
||||||
|
return res.status(400).json({ success: false, message: '车辆不可用,当前状态:' + vehicleDoc.status });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日期处理:startDate/endDate 或根据 contractMonths 计算
|
||||||
|
const start = startDate ? new Date(startDate) : new Date();
|
||||||
|
let end = endDate ? new Date(endDate) : null;
|
||||||
|
let months = Number(contractMonths) || 0;
|
||||||
|
|
||||||
|
if (!end && months > 0) {
|
||||||
|
end = new Date(start);
|
||||||
|
end.setMonth(end.getMonth() + months);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 租金计算:使用传入的 rentalFee,若未传则为 0
|
||||||
|
let fee = Number(rentalFee) || 0;
|
||||||
|
|
||||||
|
const depositAmt = Number(deposit) || 0;
|
||||||
|
const totalAmount = fee + depositAmt;
|
||||||
|
|
||||||
|
// 创建订单
|
||||||
|
const order = new Order({
|
||||||
|
customer: customerObjectId,
|
||||||
|
vehicle: vehicleObjectId,
|
||||||
|
startDate: start,
|
||||||
|
endDate: end || new Date(start.getTime() + 30 * 24 * 60 * 60 * 1000),
|
||||||
|
rentalFee: fee,
|
||||||
|
deposit: depositAmt,
|
||||||
|
totalAmount,
|
||||||
|
status: '进行中'
|
||||||
|
});
|
||||||
|
|
||||||
|
await order.save();
|
||||||
|
|
||||||
|
// 更新车辆状态
|
||||||
|
vehicleDoc.status = '在租';
|
||||||
|
vehicleDoc.isRented = true;
|
||||||
|
vehicleDoc.currentOrderId = order._id;
|
||||||
|
await vehicleDoc.save();
|
||||||
|
|
||||||
|
// 更新客户统计(如客户已存在)
|
||||||
|
if (customerObjectId) {
|
||||||
|
const cust = await Customer.findById(customerObjectId);
|
||||||
|
if (cust) {
|
||||||
|
cust.totalRentals += 1;
|
||||||
|
cust.currentRentals = (cust.currentRentals || 0) + 1;
|
||||||
|
cust.totalSpent = (cust.totalSpent || 0) + totalAmount;
|
||||||
|
await cust.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(201).json({ success: true, data: order });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新订单
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const order = await Order.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
req.body,
|
||||||
|
{ new: true, runValidators: true }
|
||||||
|
);
|
||||||
|
if (!order) {
|
||||||
|
return res.status(404).json({ success: false, message: '订单不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: order });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 结束订单
|
||||||
|
router.patch('/:id/complete', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const order = await Order.findById(req.params.id);
|
||||||
|
if (!order) {
|
||||||
|
return res.status(404).json({ success: false, message: '订单不存在' });
|
||||||
|
}
|
||||||
|
|
||||||
|
order.status = '已完成';
|
||||||
|
order.actualEndDate = new Date();
|
||||||
|
await order.save();
|
||||||
|
|
||||||
|
// 更新车辆状态
|
||||||
|
const vehicle = await Vehicle.findById(order.vehicle);
|
||||||
|
if (vehicle) {
|
||||||
|
vehicle.status = '空闲';
|
||||||
|
vehicle.isRented = false;
|
||||||
|
vehicle.currentOrderId = null;
|
||||||
|
vehicle.totalRentDays += Math.ceil((new Date(order.endDate) - new Date(order.startDate)) / (1000 * 60 * 60 * 24));
|
||||||
|
await vehicle.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新客户信息
|
||||||
|
const customer = await Customer.findById(order.customer);
|
||||||
|
if (customer) {
|
||||||
|
customer.currentRentals -= 1;
|
||||||
|
await customer.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, data: order });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取消订单
|
||||||
|
router.patch('/:id/cancel', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const order = await Order.findById(req.params.id);
|
||||||
|
if (!order) {
|
||||||
|
return res.status(404).json({ success: false, message: '订单不存在' });
|
||||||
|
}
|
||||||
|
if (order.status === '已完成' || order.status === '已取消') {
|
||||||
|
return res.status(400).json({ success: false, message: '当前状态无法取消' });
|
||||||
|
}
|
||||||
|
|
||||||
|
order.status = '已取消';
|
||||||
|
order.actualEndDate = new Date();
|
||||||
|
await order.save();
|
||||||
|
|
||||||
|
// 更新车辆状态
|
||||||
|
const vehicle = await Vehicle.findById(order.vehicle);
|
||||||
|
if (vehicle) {
|
||||||
|
vehicle.status = '空闲';
|
||||||
|
vehicle.isRented = false;
|
||||||
|
vehicle.currentOrderId = null;
|
||||||
|
await vehicle.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新客户信息
|
||||||
|
const customer = await Customer.findById(order.customer);
|
||||||
|
if (customer) {
|
||||||
|
customer.currentRentals = Math.max((customer.currentRentals || 0) - 1, 0);
|
||||||
|
await customer.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, data: order });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取逾期订单
|
||||||
|
router.get('/status/overdue', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const orders = await Order.find({
|
||||||
|
status: '进行中',
|
||||||
|
endDate: { $lt: now }
|
||||||
|
})
|
||||||
|
.populate('customer', 'name phone')
|
||||||
|
.populate('vehicle', 'model vehicleId');
|
||||||
|
|
||||||
|
// 更新逾期天数和费用
|
||||||
|
for (const order of orders) {
|
||||||
|
const overdueDays = Math.ceil((now - order.endDate) / (1000 * 60 * 60 * 24));
|
||||||
|
order.overdueDays = overdueDays;
|
||||||
|
order.overdueFee = overdueDays * order.rentalFee * 0.1; // 10% 滞纳金
|
||||||
|
order.status = '逾期';
|
||||||
|
await order.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({ success: true, data: orders });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按状态筛选订单
|
||||||
|
router.get('/status/:status', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const orders = await Order.find({ status: req.params.status })
|
||||||
|
.populate('customer', 'name phone')
|
||||||
|
.populate('vehicle', 'model vehicleId');
|
||||||
|
res.json({ success: true, data: orders });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Payment = require('../models/Payment');
|
||||||
|
|
||||||
|
// 获取所有打款记录
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const payments = await Payment.find();
|
||||||
|
res.json({ success: true, data: payments });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建打款记录
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const payment = new Payment(req.body);
|
||||||
|
await payment.save();
|
||||||
|
res.json({ success: true, data: payment });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新打款记录
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: payment });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除打款记录
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Payment.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Rider = require('../models/Rider');
|
||||||
|
const Order = require('../models/Order');
|
||||||
|
|
||||||
|
// 骑手登录
|
||||||
|
router.post('/login', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { phone, password } = req.body;
|
||||||
|
if (!phone || !password) {
|
||||||
|
return res.status(400).json({ success: false, message: '手机号和密码不能为空' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rider = await Rider.findOne({ phone, password });
|
||||||
|
if (!rider) {
|
||||||
|
return res.status(401).json({ success: false, message: '手机号或密码错误' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单生成一个token(实际生产应使用 JWT)
|
||||||
|
const token = Buffer.from(`${rider._id}:${Date.now()}`).toString('base64');
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
id: rider._id,
|
||||||
|
riderId: rider.riderId,
|
||||||
|
name: rider.name,
|
||||||
|
phone: rider.phone,
|
||||||
|
status: rider.status,
|
||||||
|
rating: rider.rating,
|
||||||
|
totalOrders: rider.totalOrders,
|
||||||
|
totalIncome: rider.totalIncome,
|
||||||
|
token
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取骑手信息
|
||||||
|
router.get('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const rider = await Rider.findById(req.params.id);
|
||||||
|
if (!rider) {
|
||||||
|
return res.status(404).json({ success: false, message: '骑手不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: rider });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新骑手信息(如状态切换)
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const rider = await Rider.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
req.body,
|
||||||
|
{ new: true, runValidators: true }
|
||||||
|
);
|
||||||
|
if (!rider) {
|
||||||
|
return res.status(404).json({ success: false, message: '骑手不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: rider });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取骑手的订单
|
||||||
|
router.get('/:id/orders', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const orders = await Order.find({ rider: req.params.id })
|
||||||
|
.populate('customer', 'name phone')
|
||||||
|
.populate('vehicle', 'vehicleId model color')
|
||||||
|
.sort({ createdAt: -1 });
|
||||||
|
res.json({ success: true, data: orders });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Store = require('../models/Store');
|
||||||
|
|
||||||
|
// 获取所有门店
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const stores = await Store.find();
|
||||||
|
res.json({ success: true, data: stores });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建门店
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const store = new Store(req.body);
|
||||||
|
await store.save();
|
||||||
|
res.json({ success: true, data: store });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新门店
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const store = await Store.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
||||||
|
res.json({ success: true, data: store });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除门店
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
await Store.findByIdAndDelete(req.params.id);
|
||||||
|
res.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const VehicleType = require('../models/VehicleType');
|
||||||
|
|
||||||
|
// 获取所有车型(支持按 storeId 筛选)
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const filter = {};
|
||||||
|
if (req.query.storeId) filter.storeId = req.query.storeId;
|
||||||
|
const vehicleTypes = await VehicleType.find(filter).sort({ createdAt: -1 });
|
||||||
|
res.json({ success: true, data: vehicleTypes });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建车型
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicleType = new VehicleType(req.body);
|
||||||
|
await vehicleType.save();
|
||||||
|
res.status(201).json({ success: true, data: vehicleType });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新车型
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicleType = await VehicleType.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
req.body,
|
||||||
|
{ new: true, runValidators: true }
|
||||||
|
);
|
||||||
|
if (!vehicleType) {
|
||||||
|
return res.status(404).json({ success: false, message: '车型不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: vehicleType });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除车型
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicleType = await VehicleType.findByIdAndDelete(req.params.id);
|
||||||
|
if (!vehicleType) {
|
||||||
|
return res.status(404).json({ success: false, message: '车型不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, message: '车型已删除' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const Vehicle = require('../models/Vehicle');
|
||||||
|
|
||||||
|
// 获取所有车辆(支持按 storeId 过滤)
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const filter = {};
|
||||||
|
if (req.query.storeId) {
|
||||||
|
filter.storeId = req.query.storeId;
|
||||||
|
}
|
||||||
|
const vehicles = await Vehicle.find(filter).populate('currentOrderId');
|
||||||
|
res.json({ success: true, data: vehicles });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取单个车辆
|
||||||
|
router.get('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicle = await Vehicle.findById(req.params.id).populate('currentOrderId');
|
||||||
|
if (!vehicle) {
|
||||||
|
return res.status(404).json({ success: false, message: '车辆不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: vehicle });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建车辆
|
||||||
|
router.post('/', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = { ...req.body };
|
||||||
|
// status 为"在租"时自动设置 isRented
|
||||||
|
if (data.status === '在租') {
|
||||||
|
data.isRented = true;
|
||||||
|
}
|
||||||
|
const vehicle = new Vehicle(data);
|
||||||
|
await vehicle.save();
|
||||||
|
res.status(201).json({ success: true, data: vehicle });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新车辆
|
||||||
|
router.put('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = { ...req.body };
|
||||||
|
// status 变化时同步 isRented
|
||||||
|
if (data.status !== undefined) {
|
||||||
|
data.isRented = data.status === '在租';
|
||||||
|
}
|
||||||
|
const vehicle = await Vehicle.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
data,
|
||||||
|
{ new: true, runValidators: true }
|
||||||
|
);
|
||||||
|
if (!vehicle) {
|
||||||
|
return res.status(404).json({ success: false, message: '车辆不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: vehicle });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 删除车辆
|
||||||
|
router.delete('/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicle = await Vehicle.findByIdAndDelete(req.params.id);
|
||||||
|
if (!vehicle) {
|
||||||
|
return res.status(404).json({ success: false, message: '车辆不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, message: '车辆已删除' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 按状态筛选车辆
|
||||||
|
router.get('/status/:status', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const vehicles = await Vehicle.find({ status: req.params.status });
|
||||||
|
res.json({ success: true, data: vehicles });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新车辆位置
|
||||||
|
router.patch('/:id/location', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { longitude, latitude } = req.body;
|
||||||
|
const vehicle = await Vehicle.findByIdAndUpdate(
|
||||||
|
req.params.id,
|
||||||
|
{
|
||||||
|
location: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [longitude, latitude]
|
||||||
|
},
|
||||||
|
lastLocationUpdate: new Date()
|
||||||
|
},
|
||||||
|
{ new: true }
|
||||||
|
);
|
||||||
|
if (!vehicle) {
|
||||||
|
return res.status(404).json({ success: false, message: '车辆不存在' });
|
||||||
|
}
|
||||||
|
res.json({ success: true, data: vehicle });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ success: false, message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function seed() {
|
||||||
|
// 门店数据
|
||||||
|
const stores = [
|
||||||
|
{ storeId: 'STORE001', name: '旗舰店A', address: '北京市朝阳区', phone: '010-12345678', manager: '张三', status: '营业中' },
|
||||||
|
{ storeId: 'STORE002', name: '旗舰店B', address: '上海市浦东新区', phone: '021-87654321', manager: '李四', status: '营业中' },
|
||||||
|
{ storeId: 'STORE003', name: '加盟店C', address: '广州市天河区', phone: '020-11112222', manager: '王五', status: '装修中' }
|
||||||
|
];
|
||||||
|
for (const s of stores) {
|
||||||
|
await axios.post('http://localhost:3000/api/stores', s);
|
||||||
|
}
|
||||||
|
console.log('✅ 门店数据');
|
||||||
|
|
||||||
|
// 客服投诉
|
||||||
|
const complaints = [
|
||||||
|
{ complaintId: 'COMP001', type: '服务态度', content: '客户投诉店员态度不好', handler: '张三', status: '处理中' },
|
||||||
|
{ complaintId: 'COMP002', type: '车辆问题', content: '电动车电量不足', handler: '李四', status: '已解决' },
|
||||||
|
{ complaintId: 'COMP003', type: '费用问题', content: '费用计算有误', handler: '王五', status: '待处理' }
|
||||||
|
];
|
||||||
|
for (const c of complaints) {
|
||||||
|
await axios.post('http://localhost:3000/api/complaints', c);
|
||||||
|
}
|
||||||
|
console.log('✅ 客服投诉');
|
||||||
|
|
||||||
|
// 审批
|
||||||
|
const approvals = [
|
||||||
|
{ approvalId: 'APPR001', type: '订单退款', title: '客户申请退款', applicant: '张三', status: '待审批' },
|
||||||
|
{ approvalId: 'APPR002', type: '车辆报废', title: '旧车报废申请', applicant: '李四', status: '已通过' },
|
||||||
|
{ approvalId: 'APPR003', type: '押金退还', title: '押金退还审批', applicant: '王五', status: '待审批' }
|
||||||
|
];
|
||||||
|
for (const a of approvals) {
|
||||||
|
await axios.post('http://localhost:3000/api/approvals', a);
|
||||||
|
}
|
||||||
|
console.log('✅ 审批数据');
|
||||||
|
|
||||||
|
// 打款
|
||||||
|
const payments = [
|
||||||
|
{ paymentId: 'PAY001', type: '退款', amount: 200, method: '微信', account: 'wx123456', status: '待打款' },
|
||||||
|
{ paymentId: 'PAY002', type: '押金退还', amount: 500, method: '支付宝', account: 'ali@xx.com', status: '已打款' },
|
||||||
|
{ paymentId: 'PAY003', type: '分成', amount: 300, method: '银行卡', account: '6222***1234', status: '打款中' }
|
||||||
|
];
|
||||||
|
for (const p of payments) {
|
||||||
|
await axios.post('http://localhost:3000/api/payments', p);
|
||||||
|
}
|
||||||
|
console.log('✅ 打款数据');
|
||||||
|
|
||||||
|
console.log('🎉 测试数据添加完成!');
|
||||||
|
}
|
||||||
|
|
||||||
|
seed().catch(console.error);
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const Vehicle = require('./models/Vehicle');
|
||||||
|
const Customer = require('./models/Customer');
|
||||||
|
const Order = require('./models/Order');
|
||||||
|
const Store = require('./models/Store');
|
||||||
|
const Rider = require('./models/Rider');
|
||||||
|
|
||||||
|
// 连接 MongoDB
|
||||||
|
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(() => console.log('✅ MongoDB 连接成功'))
|
||||||
|
.catch(err => console.error('❌ MongoDB 连接失败:', err));
|
||||||
|
|
||||||
|
// 清空数据
|
||||||
|
async function clearData() {
|
||||||
|
console.log('🧹 清空数据...');
|
||||||
|
await Vehicle.deleteMany({});
|
||||||
|
await Customer.deleteMany({});
|
||||||
|
await Order.deleteMany({});
|
||||||
|
await Store.deleteMany({});
|
||||||
|
await Rider.deleteMany({});
|
||||||
|
console.log('✅ 数据已清空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例车辆
|
||||||
|
async function createVehicles() {
|
||||||
|
const vehicles = [
|
||||||
|
{
|
||||||
|
vehicleId: 'SCOOTER001',
|
||||||
|
model: '黑骑士',
|
||||||
|
color: '黑色',
|
||||||
|
batteryType: '锂电池',
|
||||||
|
batteryCapacity: 20,
|
||||||
|
batteryStatus: '正常',
|
||||||
|
status: '空闲',
|
||||||
|
purchaseDate: new Date('2024-01-15'),
|
||||||
|
purchasePrice: 3500,
|
||||||
|
purchaseSupplier: '小牛电动车'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vehicleId: 'SCOOTER002',
|
||||||
|
model: '黑骑士',
|
||||||
|
color: '白色',
|
||||||
|
batteryType: '锂电池',
|
||||||
|
batteryCapacity: 20,
|
||||||
|
batteryStatus: '正常',
|
||||||
|
status: '空闲',
|
||||||
|
purchaseDate: new Date('2024-02-20'),
|
||||||
|
purchasePrice: 3500,
|
||||||
|
purchaseSupplier: '小牛电动车'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vehicleId: 'SCOOTER003',
|
||||||
|
model: '电动车',
|
||||||
|
color: '蓝色',
|
||||||
|
batteryType: '铅酸电池',
|
||||||
|
batteryCapacity: 24,
|
||||||
|
batteryStatus: '正常',
|
||||||
|
status: '空闲',
|
||||||
|
purchaseDate: new Date('2024-03-10'),
|
||||||
|
purchasePrice: 2800,
|
||||||
|
purchaseSupplier: '雅迪电动车'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vehicleId: 'SCOOTER004',
|
||||||
|
model: '高端豪车',
|
||||||
|
color: '红色',
|
||||||
|
batteryType: '锂电池',
|
||||||
|
batteryCapacity: 30,
|
||||||
|
batteryStatus: '正常',
|
||||||
|
status: '空闲',
|
||||||
|
purchaseDate: new Date('2024-04-05'),
|
||||||
|
purchasePrice: 8000,
|
||||||
|
purchaseSupplier: '特斯拉'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
vehicleId: 'SCOOTER005',
|
||||||
|
model: '普通标准套餐',
|
||||||
|
color: '绿色',
|
||||||
|
batteryType: '铅酸电池',
|
||||||
|
batteryCapacity: 20,
|
||||||
|
batteryStatus: '正常',
|
||||||
|
status: '空闲',
|
||||||
|
purchaseDate: new Date('2024-05-01'),
|
||||||
|
purchasePrice: 2500,
|
||||||
|
purchaseSupplier: '爱玛电动车'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🚗 创建示例车辆...');
|
||||||
|
await Vehicle.insertMany(vehicles);
|
||||||
|
console.log(`✅ 创建了 ${vehicles.length} 辆车`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例客户
|
||||||
|
async function createCustomers() {
|
||||||
|
const customers = [
|
||||||
|
{
|
||||||
|
customerId: 'CUST001',
|
||||||
|
name: '张三',
|
||||||
|
phone: '13800138000',
|
||||||
|
idCard: '110101199001011234',
|
||||||
|
address: '北京市朝阳区',
|
||||||
|
email: 'zhangsan@example.com'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customerId: 'CUST002',
|
||||||
|
name: '李四',
|
||||||
|
phone: '13800138001',
|
||||||
|
idCard: '110101199002022345',
|
||||||
|
address: '北京市海淀区',
|
||||||
|
email: 'lisi@example.com'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customerId: 'CUST003',
|
||||||
|
name: '王五',
|
||||||
|
phone: '13800138002',
|
||||||
|
idCard: '110101199003033456',
|
||||||
|
address: '北京市西城区',
|
||||||
|
email: 'wangwu@example.com'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customerId: 'CUST004',
|
||||||
|
name: '赵六',
|
||||||
|
phone: '13800138003',
|
||||||
|
idCard: '110101199004044567',
|
||||||
|
address: '北京市东城区',
|
||||||
|
email: 'zhaoliu@example.com'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
customerId: 'CUST005',
|
||||||
|
name: '钱七',
|
||||||
|
phone: '13800138004',
|
||||||
|
idCard: '110101199005055678',
|
||||||
|
address: '北京市丰台区',
|
||||||
|
email: 'qianqi@example.com'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('👥 创建示例客户...');
|
||||||
|
await Customer.insertMany(customers);
|
||||||
|
console.log(`✅ 创建了 ${customers.length} 个客户`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例订单
|
||||||
|
async function createOrders() {
|
||||||
|
const vehicles = await Vehicle.find().limit(3);
|
||||||
|
const customers = await Customer.find().limit(3);
|
||||||
|
|
||||||
|
const orders = [
|
||||||
|
{
|
||||||
|
orderNumber: 'ORDER001',
|
||||||
|
customer: customers[0]._id,
|
||||||
|
vehicle: vehicles[0]._id,
|
||||||
|
startDate: new Date('2026-02-20'),
|
||||||
|
endDate: new Date('2026-03-20'),
|
||||||
|
rentalFee: 50,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: 300,
|
||||||
|
status: '进行中'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
orderNumber: 'ORDER002',
|
||||||
|
customer: customers[1]._id,
|
||||||
|
vehicle: vehicles[1]._id,
|
||||||
|
startDate: new Date('2026-02-15'),
|
||||||
|
endDate: new Date('2026-03-15'),
|
||||||
|
rentalFee: 50,
|
||||||
|
deposit: 200,
|
||||||
|
totalAmount: 300,
|
||||||
|
status: '进行中'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
orderNumber: 'ORDER003',
|
||||||
|
customer: customers[2]._id,
|
||||||
|
vehicle: vehicles[2]._id,
|
||||||
|
startDate: new Date('2026-01-10'),
|
||||||
|
endDate: new Date('2026-02-10'),
|
||||||
|
actualEndDate: new Date('2026-02-10'),
|
||||||
|
rentalFee: 40,
|
||||||
|
deposit: 150,
|
||||||
|
totalAmount: 200,
|
||||||
|
paidAmount: 200,
|
||||||
|
status: '已完成'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('📋 创建示例订单...');
|
||||||
|
await Order.insertMany(orders);
|
||||||
|
console.log(`✅ 创建了 ${orders.length} 个订单`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例门店
|
||||||
|
async function createStores() {
|
||||||
|
const stores = [
|
||||||
|
{
|
||||||
|
storeId: 'STORE001',
|
||||||
|
name: '朝阳电动车门店',
|
||||||
|
address: '北京市朝阳区建国路88号',
|
||||||
|
phone: '010-12345678',
|
||||||
|
manager: '张三',
|
||||||
|
status: '营业中',
|
||||||
|
approvalStatus: '已通过',
|
||||||
|
images: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
storeId: 'STORE002',
|
||||||
|
name: '海淀电动车门店',
|
||||||
|
address: '北京市海淀区中关村大街100号',
|
||||||
|
phone: '010-87654321',
|
||||||
|
manager: '李四',
|
||||||
|
status: '营业中',
|
||||||
|
approvalStatus: '已通过',
|
||||||
|
images: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
storeId: 'STORE003',
|
||||||
|
name: '西城电动车门店',
|
||||||
|
address: '北京市西城区西单北大街120号',
|
||||||
|
phone: '010-11223344',
|
||||||
|
manager: '王五',
|
||||||
|
status: '营业中',
|
||||||
|
approvalStatus: '已通过',
|
||||||
|
images: []
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🏪 创建示例门店...');
|
||||||
|
await Store.insertMany(stores);
|
||||||
|
console.log(`✅ 创建了 ${stores.length} 个门店`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建示例骑手
|
||||||
|
async function createRiders() {
|
||||||
|
const riders = [
|
||||||
|
{
|
||||||
|
riderId: 'RIDER001',
|
||||||
|
name: '张骑手',
|
||||||
|
phone: '13900139000',
|
||||||
|
password: '123456',
|
||||||
|
status: 'active',
|
||||||
|
rating: 4.8,
|
||||||
|
totalOrders: 120,
|
||||||
|
totalIncome: 15000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
riderId: 'RIDER002',
|
||||||
|
name: '李骑手',
|
||||||
|
phone: '13900139001',
|
||||||
|
password: '123456',
|
||||||
|
status: 'active',
|
||||||
|
rating: 4.5,
|
||||||
|
totalOrders: 80,
|
||||||
|
totalIncome: 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
riderId: 'RIDER003',
|
||||||
|
name: '王骑手',
|
||||||
|
phone: '13900139002',
|
||||||
|
password: '123456',
|
||||||
|
status: 'inactive',
|
||||||
|
rating: 4.9,
|
||||||
|
totalOrders: 200,
|
||||||
|
totalIncome: 25000
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log('🛵 创建示例骑手...');
|
||||||
|
await Rider.insertMany(riders);
|
||||||
|
console.log(`✅ 创建了 ${riders.length} 个骑手`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主函数
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await clearData();
|
||||||
|
await createVehicles();
|
||||||
|
await createCustomers();
|
||||||
|
await createOrders();
|
||||||
|
await createStores();
|
||||||
|
await createRiders();
|
||||||
|
console.log('\n🎉 示例数据创建完成!');
|
||||||
|
console.log('车辆: 5 辆');
|
||||||
|
console.log('客户: 5 个');
|
||||||
|
console.log('订单: 3 个');
|
||||||
|
console.log('门店: 3 个');
|
||||||
|
console.log('骑手: 3 个');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ 创建示例数据失败:', error);
|
||||||
|
} finally {
|
||||||
|
mongoose.disconnect();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
mongoose.connect('mongodb://localhost:27017/e-scooter-rental')
|
||||||
|
.then(() => {
|
||||||
|
console.log('MongoDB OK');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log('MongoDB Error:', e.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue