diff --git a/README.md b/README.md
index e805f8c..1ce5b5a 100644
--- a/README.md
+++ b/README.md
@@ -18,48 +18,72 @@
- **智能指派功能**: 将订单指派给合适的货运人员
- **订单状态跟踪**: 实时更新订单配送状态
-### 用户系统功能
+### 👤 用户系统功能
- **多角色支持**: 管理员、货运人员、游客等不同角色
-- **用户登录/登出**: 支持微信登录
-- **签到功能**: 支持每日签到记录和状态管理
-- **注册功能**: 游客用户注册为正式用户
-- **个人信息管理**: 查看和编辑个人信息
+- **微信登录/登出**: 支持微信授权登录和登出
+- **签到/签退功能**: 支持每日签到和签退状态管理
+- **用户注册**: 游客用户注册为正式用户
+- **用户状态管理**: 支持signed_in/signed_out/registered/unregistered四种状态
- **权限控制**: 不同角色有不同的操作权限
-### ⚙️ 系统设置功能
-- **数据模式切换**: 支持模拟数据和真实API两种模式
-- **配置管理**: 可以配置API服务器地址等参数
-- **调试信息**: 显示系统运行状态和调试信息
+### 👥 员工管理功能
+- **员工列表查看**: 管理员可以查看所有员工信息(姓名、手机号、角色、ID)
+- **员工搜索过滤**: 支持按姓名、手机号、角色搜索员工
+- **添加新员工**: 管理员可以添加新员工,包含姓名、手机号、角色信息
+- **删除员工**: 管理员可以删除不需要的员工
+- **员工信息验证**: 注册时验证员工信息是否存在
+- **角色管理**: 支持管理员和配送员两种角色分配
+
+### 📍 位置追踪功能
+- **实时位置更新**: 通过WebSocket实现货运人员实时位置追踪
+- **位置订阅机制**: 支持订阅特定货运人员的位置更新
+- **在线状态管理**: 实时显示货运人员在线/离线状态
+- **位置历史记录**: 记录货运人员位置历史轨迹
+
+### 🔧 系统管理功能
+- **API配置管理**: 可以配置API服务器地址等参数
+- **调试信息显示**: 显示系统运行状态和调试信息
+- **模块化架构**: 基于模块化设计,便于功能扩展和维护
## 技术架构
-### 双服务架构设计
-系统采用创新的双服务架构,支持两种运行模式:
+### 模块化架构设计
+系统采用现代化的模块化架构设计,专注于生产环境部署和功能扩展:
-1. **模拟数据模式 (Mock Mode)**
- - 使用本地模拟数据进行开发和测试
- - 不依赖后端服务,可以独立运行
- - 适合功能开发、单元测试、演示
+**模块化设计模式**
+- **主页面模块 (MainPageModule)**: 协调各个子模块,处理页面生命周期和事件分发
+- **登录模块 (LoginModule)**: 处理用户登录、授权、用户信息管理
+- **地图模块 (MapModule)**: 处理地图显示、定位、标记点管理
+- **订单模块 (OrderModule)**: 处理订单列表、指派、状态跟踪
+- **仓库模块 (WarehouseModule)**: 处理仓库信息展示和管理
+- **员工模块 (EmployeeModule)**: 处理员工管理、位置跟踪、交互
+- **位置模块 (LocationModule)**: 处理位置追踪和实时更新
+- **数据模块 (DataModule)**: 统一管理页面数据状态
-2. **真实API模式 (API Mode)**
- - 连接真实的后端RESTful API服务
- - 支持生产环境部署和联调测试
- - 数据实时更新,支持多用户协作
+**真实API模式 (API Mode)**
+- 连接真实的后端RESTful API服务
+- 支持生产环境部署和联调测试
+- 数据实时更新,支持多用户协作
+- 通过WebSocket实现实时位置追踪
+
+**注意**: 模拟数据模式已移除,系统现在仅支持真实API模式运行。
### 核心服务组件
-- **apiService.ts**: 封装所有后端API调用的基础服务
-- **userService.ts**: 用户认证和信息管理服务
-- **mapService.ts**: 地图相关功能和服务
+- **apiService.ts**: 封装所有后端API调用和WebSocket连接管理的基础服务
+- **userService.ts**: 用户认证和信息管理服务(包含签到/签退功能)
+- **mapService.ts**: 地图展示、路线规划、地点搜索等地图相关功能
- **orderService.ts**: 订单管理和指派服务
-- **deliveryPersonService.ts**: 配送人员管理和位置更新服务
+- **deliveryPersonService.ts**: 配送人员信息管理服务
- **warehouseService.ts**: 仓库和库存管理服务
+- **employeeService.ts**: 员工管理服务
+- **locationTrackingService.ts**: 位置追踪管理服务(负责实时位置更新、在线用户状态管理、WebSocket位置订阅)
## 后端接口需求
### 基础配置
- **API基础地址**: `http://localhost:8080`
-- **模拟模式**: 支持模拟数据和真实API两种模式切换
+- **WebSocket地址**: `ws://localhost:8080/ws/location`
### 用户认证模块
1. **微信登录** - `POST /user/wxlogin`
@@ -70,13 +94,38 @@
- 请求: `{ userId: number }`
- 响应: `{ success: boolean, userInfo: UserInfo, message?: string }`
-3. **用户注册** - `POST /user/register`
+3. **用户签退** - `POST /user/signout`
+ - 请求: `{ userId: number }`
+ - 响应: `{ success: boolean, message?: string }`
+
+4. **获取用户状态** - `GET /user/status`
+ - 响应: `{ status: 'signed_in' | 'signed_out' | 'registered' | 'unregistered'; lastSignInTime?: string; lastSignOutTime?: string }`
+
+5. **用户注册** - `POST /user/register`
- 请求: `{ name: string, phone: string }`
- 响应: `{ success: boolean, employeeInfo: EmployeeInfo, message?: string }`
-4. **获取用户信息** - `GET /user/info`
+6. **获取用户信息** - `GET /user/info`
- 响应: `UserInfo`
+7. **用户登出** - `POST /user/logout`
+ - 响应: `{ success: boolean, message?: string }`
+
+### 员工管理模块
+1. **获取员工列表** - `GET /employees`
+ - 响应: `EmployeeInfo[]`
+
+2. **添加员工** - `POST /employees`
+ - 请求: `{ name: string, phone: string, role: string }`
+ - 响应: `EmployeeInfo`
+
+3. **删除员工** - `DELETE /employees/{id}`
+ - 响应: `{ success: boolean, message?: string }`
+
+4. **更新员工信息** - `PUT /employees/{id}`
+ - 请求: `{ name?: string, phone?: string, role?: string }`
+ - 响应: `EmployeeInfo`
+
### 仓库管理模块
1. **获取仓库列表** - `GET /warehouses`
- 响应: `WarehouseInfo[]`
@@ -119,13 +168,7 @@
- 请求: `{ status: Order['status'] }`
- 响应: `{ success: boolean, message?: string }`
-### 实时位置服务
-1. **订阅位置更新** - `POST /locations/subscribe`
- - 请求: `{ deliveryPersonIds: number[] }`
- - 响应: `{ subscriptionId: string, expiresAt: number }`
-2. **取消订阅** - `POST /locations/unsubscribe`
- - 请求: `{ subscriptionId: string }`
### 统计和日志模块
1. **系统统计** - `GET /stats/system`
@@ -185,6 +228,19 @@
### 系统从服务器接收的消息数据
+#### WebSocket实时位置消息
+
+1. **位置更新消息**
+ ```json
+ {
+ "type": "updateLocation", // 消息类型
+ "deliveryPersonId": number, // 货运人员ID
+ "latitude": number, // 纬度
+ "longitude": number, // 经度
+ "timestamp": number // 时间戳
+ }
+ ```
+
#### 用户相关消息
1. **微信登录响应**
@@ -377,41 +433,167 @@
- **用户状态 (userStatus)**: 包括未登录、已登录、已签到等状态
- **角色权限**: 根据用户角色控制功能访问权限
-## 运货师傅上线与位置更新机制
+## WebSocket实时位置交互机制
-### 师傅上线流程
-1. **数据获取**: 通过 `deliveryPersonService.getDeliveryPersons()` 获取所有货运人员数据
-2. **列表更新**: 在 `deliveryPersonModule.loadDeliveryPersons()` 中调用服务获取数据
-3. **标记点生成**: 通过 `updateDeliveryPersonMarkers()` 方法将货运人员转换为地图标记点
-4. **地图更新**: 调用 `dataModule.updateMarkers()` 更新地图显示
+### WebSocket连接管理
-### 实时位置更新机制
-1. **订阅机制**: 通过 `deliveryPersonService.subscribeToRealTimeLocations()` 订阅实时位置更新
-2. **WebSocket连接**: 在真实环境中使用WebSocket接收实时位置数据
-3. **回调处理**: 位置更新通过 `handleRealTimeLocationUpdate()` 回调函数处理
-4. **单个标记更新**: 通过 `updateSingleDeliveryPersonMarker()` 方法更新特定货运人员的图形位置
+#### 连接建立流程
+1. **连接初始化**: 通过 `apiService.initLocationWebSocket()` 建立WebSocket连接
+2. **连接地址**: 使用配置的API地址,将HTTP协议替换为WebSocket协议(如 `ws://localhost:8080/ws/location`)
+3. **连接状态管理**: 自动处理连接建立、消息接收、连接关闭和错误重连
-### 数据刷新机制
-- **实时更新**: 当收到位置消息时,直接更新对应货运人员的标记点位置(经度、纬度)
-- **数据同步**: 位置更新会同步修改货运人员对象的 `currentLocation` 属性
-- **状态保持**: 货运人员的其他信息(姓名、状态、订单等)保持不变,只有位置信息更新
+#### 连接生命周期
+- **连接建立**: 调用 `wx.connectSocket()` 建立WebSocket连接
+- **连接保持**: 连接成功后保持活跃状态,接收实时位置更新
+- **自动重连**: 连接异常断开时,3秒后自动尝试重连(非正常关闭除外)
+- **连接关闭**: 调用 `apiService.closeLocationWebSocket()` 主动关闭连接
-### 关键代码位置
-- `deliveryPersonModule.ts` - 处理货运人员显示和位置更新
-- `deliveryPersonService.ts` - 提供货运人员数据服务
-- `dataModule.ts` - 管理地图数据和标记点
+### 消息交互协议
+
+#### 客户端发送消息类型
+
+1. **发送位置更新**
+```json
+{
+ "type": "updateLocation",
+ "userId": 123,
+ "latitude": 39.9042,
+ "longitude": 116.4074,
+ "timestamp": 1640995200000
+}
+```
+
+#### 服务端推送消息类型
+
+1. **位置更新消息**
+```json
+{
+ "type": "updateLocation",
+ "userId": 123,
+ "latitude": 39.9042,
+ "longitude": 116.4074,
+ "timestamp": 1640995200000
+}
+```
+
+2. **在线用户列表消息**
+```json
+{
+ "type": "onlineUserList",
+ "users": [
+ {
+ "userId": 123,
+ "name": "张三",
+ "latitude": 39.9042,
+ "longitude": 116.4074,
+ "lastUpdate": 1640995200000
+ }
+ ]
+}
+```
+
+
+
+### 位置更新处理流程
+
+#### 订阅机制
+1. **用户签到**: 用户签到成功后,服务器自动处理位置订阅逻辑
+2. **WebSocket连接**: 建立WebSocket连接后,服务器会自动推送位置更新
+3. **回调绑定**: 通过 `apiService.onLocationUpdate(callback)` 注册位置更新回调函数
+
+#### 实时位置处理
+1. **消息接收**: WebSocket接收到位置更新消息后,解析JSON数据
+2. **消息分发**: 调用所有注册的位置更新回调函数
+3. **地图更新**: 回调函数中更新对应货运人员的地图标记点位置
+4. **数据同步**: 同步更新货运人员对象的当前位置信息
+
+### 回调机制设计
+
+#### 多回调支持
+- **回调集合**: 使用 `Set` 数据结构管理多个位置更新回调函数
+- **动态注册**: 支持多个组件同时订阅位置更新
+- **自动清理**: 当所有回调都被移除时,自动关闭WebSocket连接
+
+#### 取消订阅机制
+- **取消订阅**: 调用返回的取消订阅函数移除特定回调
+- **连接管理**: 当没有活跃回调时,自动关闭WebSocket连接以节省资源
+
+### 错误处理与重连机制
+
+#### 连接错误处理
+- **连接失败**: 捕获连接异常并记录错误日志
+- **环境检测**: 自动检测当前环境是否支持WebSocket
+- **优雅降级**: 在不支持WebSocket的环境中使用模拟数据
+
+#### 自动重连策略
+- **异常检测**: 监控WebSocket连接状态
+- **智能重连**: 仅在非正常关闭时触发重连(排除主动关闭)
+- **重连间隔**: 3秒重连间隔,避免频繁重连
+
+### 真实环境部署
+
+#### 生产环境要求
+- **WebSocket支持**: 后端服务必须支持WebSocket协议
+- **实时通信**: 支持实时位置更新推送
+- **连接管理**: 支持连接保持和自动重连机制
+
+#### 部署注意事项
+- **HTTPS要求**: 生产环境必须使用HTTPS和WSS协议
+- **认证授权**: WebSocket连接需要身份验证
+- **性能优化**: 合理控制位置更新频率
+
+### 关键服务组件
+
+#### ApiService (核心WebSocket管理)
+- **连接管理**: WebSocket连接的建立、维护和关闭
+- **消息处理**: WebSocket消息的发送、接收和分发
+- **错误处理**: 连接异常和消息处理错误的统一处理
+
+#### DeliveryPersonService (货运人员服务)
+- **位置订阅**: 封装位置订阅和取消订阅接口
+- **实时更新**: 提供位置更新发送和接收功能
+- **服务集成**: 与地图服务和用户服务集成
+
+#### LocationTrackingService (位置追踪服务)
+- **状态管理**: 管理在线用户状态和位置信息
+- **位置分发**: 将位置更新分发给所有订阅者
+- **超时检测**: 检测用户离线状态和位置更新超时
+
+### 性能优化策略
+
+#### 连接复用
+- **单例模式**: 整个应用共享同一个WebSocket连接
+- **按需连接**: 仅在需要时建立连接,无订阅时自动关闭
+- **资源节约**: 避免多个组件创建重复的WebSocket连接
+
+#### 消息优化
+- **消息压缩**: 使用简洁的消息格式减少网络传输
+- **批量更新**: 支持批量位置更新消息处理
+- **频率控制**: 控制位置更新频率,避免过度频繁的更新
+
+### 安全考虑
+
+#### 认证授权
+- **Token验证**: WebSocket连接使用Bearer Token进行身份验证
+- **权限控制**: 仅允许授权用户订阅和发送位置更新
+- **数据隔离**: 确保用户只能访问自己有权限的位置数据
+
+#### 数据安全
+- **数据验证**: 对接收的WebSocket消息进行格式验证
+- **异常防护**: 防止恶意消息导致的系统异常
+- **日志记录**: 记录关键操作和异常情况
## 开发与测试
-### 开发模式(模拟数据)
-1. 在设置页面切换到"模拟数据"模式
-2. 系统使用本地模拟数据进行开发测试
-3. 所有功能都可以独立运行,不依赖后端
+### 开发环境要求
+1. 确保后端API服务已启动并运行在配置的API地址
+2. 系统会自动连接后端API进行数据交互
+3. 支持WebSocket实时位置更新功能
-### 联调模式(真实API)
-1. 在设置页面切换到"真实API"模式
-2. 确保后端服务运行在配置的API地址
-3. 系统会自动连接后端API进行数据交互
+### 联调测试
+1. 验证所有API接口调用正常
+2. 测试WebSocket连接和实时位置更新
+3. 检查位置追踪服务的正常运行
### 快速开始
@@ -429,9 +611,9 @@
- 导入项目目录
- 点击编译运行
-4. **切换数据模式**:
- - 进入"设置"页面
- - 切换数据模式进行测试
+4. **配置后端服务**:
+ - 确保后端API服务运行在 `http://localhost:8080`
+ - 确保WebSocket服务运行在 `ws://localhost:8080/ws/location`
## 项目结构
@@ -452,8 +634,9 @@ miniprogram/
│ ├── admin/ # 管理员页面
│ └── index/ # 首页(地图+订单)
├── services/ # 服务层
-│ ├── apiService.ts # API服务基础封装
-│ ├── deliveryPersonService.ts # 配送人员服务
+│ ├── apiService.ts # API服务基础封装(包含WebSocket连接管理)
+│ ├── deliveryPersonService.ts # 配送人员服务(包含位置订阅功能)
+│ ├── locationTrackingService.ts # 位置追踪服务(实时位置管理)
│ ├── mapService.ts # 地图服务
│ ├── orderService.ts # 订单服务
│ ├── userService.ts # 用户服务
@@ -470,21 +653,21 @@ miniprogram/
### 生产环境部署
1. 确保后端API服务已部署并正常运行
-2. 修改API服务地址配置
-3. 切换到"真实API"模式
+2. 修改API服务地址配置为生产环境地址
+3. 确保WebSocket服务正常运行
4. 提交审核发布
### 开发环境配置
1. 本地启动后端开发服务器
-2. 配置API地址
-3. 使用"真实API"模式进行联调测试
+2. 配置API地址为开发环境地址
+3. 确保WebSocket连接正常
## 注意事项
-1. **数据一致性**: 确保模拟数据格式与后端API返回格式一致
-2. **错误处理**: 两种模式下的错误处理机制需要保持一致
-3. **性能优化**: 真实API模式下注意网络请求的优化
-4. **安全性**: 生产环境务必使用HTTPS和合适的认证机制
+1. **API一致性**: 确保前端API调用与后端接口格式一致
+2. **错误处理**: 实现完善的网络错误和API错误处理机制
+3. **性能优化**: 注意网络请求的优化和WebSocket连接管理
+4. **安全性**: 生产环境务必使用HTTPS和WSS协议,实现合适的认证机制
## 联系方式
diff --git a/miniprogram/app.json b/miniprogram/app.json
index b825add..f46db04 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -2,7 +2,8 @@
"pages": [
"pages/index/index",
"pages/admin/admin",
- "pages/apply/apply"
+ "pages/apply/apply",
+ "pages/employee/employee"
],
"requiredPrivateInfos": [
"getLocation"
diff --git a/miniprogram/images/crown.png b/miniprogram/images/crown.png
new file mode 100644
index 0000000..f737be6
Binary files /dev/null and b/miniprogram/images/crown.png differ
diff --git a/miniprogram/images/truck.png b/miniprogram/images/truck.png
new file mode 100644
index 0000000..f737be6
Binary files /dev/null and b/miniprogram/images/truck.png differ
diff --git a/miniprogram/pages/admin/admin.ts b/miniprogram/pages/admin/admin.ts
index 3b00698..9f56d4d 100644
--- a/miniprogram/pages/admin/admin.ts
+++ b/miniprogram/pages/admin/admin.ts
@@ -42,7 +42,7 @@ Page({
userInfo: {
id: 1,
name: '管理员',
- role: 'ADMIN',
+ role: 'ADMIN' as any,
phone: '13800138000'
}
});
@@ -150,10 +150,11 @@ Page({
// 添加员工位置标记
deliveryPersons.forEach(person => {
+ // 使用默认坐标,实际位置由locationTrackingService统一管理
markers.push({
id: 20000 + person.id, // 避免ID冲突
- longitude: person.currentLocation.longitude,
- latitude: person.currentLocation.latitude,
+ longitude: 102.833722, // 默认经度
+ latitude: 24.880095, // 默认纬度
title: person.name,
iconPath: this.getStaffStatusIcon(person.status),
width: 32,
@@ -259,7 +260,7 @@ Page({
if (assignedPerson) {
this.setData({
- mapCenter: assignedPerson.currentLocation
+ mapCenter: { longitude: 102.833722, latitude: 24.880095 } // 默认坐标
});
// 绘制从起点到终点的路线
@@ -526,25 +527,19 @@ Page({
this.hideAddModal();
},
- // 切换底部页签
+ // 底部页签切换
switchMainTab(e: any) {
const tab = e.currentTarget.dataset.tab;
this.setData({
currentTab: tab
});
-
- // 根据不同的页签执行不同的逻辑
- switch (tab) {
- case 'main':
- // 管理中心,显示所有内容
- break;
- case 'orders':
- // 订单详情,可能需要调整布局
- break;
- case 'staff':
- // 员工管理,可能需要调整布局
- break;
- }
+ },
+
+ // 跳转到员工管理页面
+ goToEmployeeManagement() {
+ wx.navigateTo({
+ url: '/pages/employee/employee'
+ });
},
// 地图点击事件
diff --git a/miniprogram/pages/admin/admin.wxml b/miniprogram/pages/admin/admin.wxml
index 6091e7b..ad5f70c 100644
--- a/miniprogram/pages/admin/admin.wxml
+++ b/miniprogram/pages/admin/admin.wxml
@@ -52,6 +52,7 @@
diff --git a/miniprogram/pages/admin/admin.wxss b/miniprogram/pages/admin/admin.wxss
index d47124a..2d1544a 100644
--- a/miniprogram/pages/admin/admin.wxss
+++ b/miniprogram/pages/admin/admin.wxss
@@ -112,6 +112,20 @@
min-width: 160rpx;
}
+.manage-btn {
+ background-color: #1890ff;
+ color: white;
+ font-size: 28rpx;
+ padding: 10rpx 20rpx;
+ border-radius: 40rpx;
+ border: none;
+ min-width: 160rpx;
+}
+
+.manage-btn:active {
+ background-color: #096dd9;
+}
+
/* 订单标签页 */
.order-tabs {
display: flex;
diff --git a/miniprogram/pages/apply/apply.ts b/miniprogram/pages/apply/apply.ts
index 2af1d9c..1befc89 100644
--- a/miniprogram/pages/apply/apply.ts
+++ b/miniprogram/pages/apply/apply.ts
@@ -8,7 +8,6 @@ Page({
data: {
applyForm: {
name: '',
- idCard: '',
phone: ''
}
},
@@ -44,7 +43,7 @@ Page({
* 提交申请
*/
async onSubmit() {
- const { name, idCard, phone } = this.data.applyForm;
+ const { name, phone } = this.data.applyForm;
// 表单验证
if (!this.validateForm()) {
@@ -58,7 +57,7 @@ Page({
});
// 调用API提交申请
- const result = await this.submitApplication({ name, idCard, phone });
+ const result = await this.submitApplication({ name, phone });
if (result.success) {
wx.showToast({
@@ -115,9 +114,9 @@ Page({
* 表单验证
*/
validateForm(): boolean {
- const { name, idCard, phone } = this.data.applyForm;
+ const { name, phone } = this.data.applyForm;
- if (!name || !idCard || !phone) {
+ if (!name || !phone) {
wx.showToast({
title: '请填写完整信息',
icon: 'none',
@@ -126,16 +125,6 @@ Page({
return false;
}
- // 身份证号验证
- if (idCard.length !== 18) {
- wx.showToast({
- title: '请输入正确的身份证号',
- icon: 'none',
- duration: 2000
- });
- return false;
- }
-
// 手机号验证
if (!/^1[3-9]\d{9}$/.test(phone)) {
wx.showToast({
diff --git a/miniprogram/pages/apply/apply.wxml b/miniprogram/pages/apply/apply.wxml
index 717bbc2..4674231 100644
--- a/miniprogram/pages/apply/apply.wxml
+++ b/miniprogram/pages/apply/apply.wxml
@@ -25,21 +25,7 @@
-
-
- 身份证号
-
-
-
+
diff --git a/miniprogram/pages/employee/employee.json b/miniprogram/pages/employee/employee.json
new file mode 100644
index 0000000..ff97c31
--- /dev/null
+++ b/miniprogram/pages/employee/employee.json
@@ -0,0 +1,7 @@
+{
+ "usingComponents": {},
+ "navigationBarTitleText": "员工管理",
+ "navigationStyle": "custom",
+ "enablePullDownRefresh": true,
+ "backgroundColor": "#f5f5f5"
+}
\ No newline at end of file
diff --git a/miniprogram/pages/employee/employee.ts b/miniprogram/pages/employee/employee.ts
new file mode 100644
index 0000000..1449311
--- /dev/null
+++ b/miniprogram/pages/employee/employee.ts
@@ -0,0 +1,339 @@
+// 员工管理页面逻辑
+import { EmployeeInfo } from '../../types';
+import employeeService from '../../services/employeeService';
+import { Role, getRoleOptions } from '../../utils/roleUtils';
+
+Page({
+ data: {
+ // 员工列表
+ employees: [] as EmployeeInfo[],
+ filteredEmployees: [] as EmployeeInfo[],
+
+ // 页面状态
+ currentTab: 'list', // list: 列表页, add: 添加页
+ loading: false,
+
+ // 添加员工表单数据
+ addForm: {
+ name: '',
+ phone: '',
+ role: Role.DELIVERY_PERSON
+ },
+
+ // 错误信息
+ errorMessage: '',
+ successMessage: '',
+
+ // 搜索关键词
+ searchKeyword: '',
+
+ // 角色选项
+ roleOptions: getRoleOptions()
+ },
+
+ onLoad() {
+ // 页面加载时获取员工列表
+ this.loadEmployees();
+ },
+
+ onShow() {
+ // 页面显示时刷新数据
+ this.loadEmployees();
+ },
+
+ /**
+ * 加载员工列表
+ */
+ async loadEmployees() {
+ this.setData({
+ loading: true,
+ errorMessage: '',
+ successMessage: ''
+ });
+
+ try {
+ const employees = await employeeService.getEmployees();
+ // 获取过滤后的员工列表
+ const filteredEmployees = this.getFilteredEmployees(employees);
+
+ this.setData({
+ employees,
+ filteredEmployees,
+ loading: false
+ });
+ } catch (error) {
+ console.error('加载员工列表失败:', error);
+ this.setData({
+ loading: false,
+ errorMessage: '加载员工列表失败,请稍后重试'
+ });
+ }
+ },
+
+ /**
+ * 切换页面标签
+ */
+ switchTab(e: any) {
+ const tab = e.currentTarget.dataset.tab;
+ this.setData({
+ currentTab: tab,
+ errorMessage: '',
+ successMessage: ''
+ });
+
+ if (tab === 'list') {
+ this.loadEmployees();
+ }
+ },
+
+ /**
+ * 处理添加员工表单输入
+ */
+ onFormInput(e: any) {
+ const { field } = e.currentTarget.dataset;
+ const value = e.detail.value;
+
+ this.setData({
+ [`addForm.${field}`]: value,
+ errorMessage: '',
+ successMessage: ''
+ });
+ },
+
+ /**
+ * 处理角色选择
+ */
+ onRoleChange(e: any) {
+ const index = e.detail.value;
+ const selectedRole = this.data.roleOptions[index].value;
+ this.setData({
+ 'addForm.role': selectedRole
+ });
+ },
+
+ /**
+ * 验证表单数据
+ */
+ validateForm(): boolean {
+ const { name, phone, role } = this.data.addForm;
+
+ if (!name.trim()) {
+ this.setData({
+ errorMessage: '请输入员工姓名'
+ });
+ return false;
+ }
+
+ if (!phone.trim()) {
+ this.setData({
+ errorMessage: '请输入手机号'
+ });
+ return false;
+ }
+
+ // 简单的手机号格式验证
+ const phoneRegex = /^1[3-9]\d{9}$/;
+ if (!phoneRegex.test(phone)) {
+ this.setData({
+ errorMessage: '请输入正确的手机号格式'
+ });
+ return false;
+ }
+
+ if (!role) {
+ this.setData({
+ errorMessage: '请选择员工角色'
+ });
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * 提交添加员工表单
+ */
+ async submitAddForm() {
+ if (!this.validateForm()) {
+ return;
+ }
+
+ this.setData({
+ loading: true,
+ errorMessage: '',
+ successMessage: ''
+ });
+
+ try {
+ await employeeService.addEmployee(this.data.addForm);
+
+ this.setData({
+ loading: false,
+ successMessage: '员工添加成功',
+ addForm: {
+ name: '',
+ phone: '',
+ role: Role.DELIVERY_PERSON
+ }
+ });
+
+ // 添加成功后自动切换到列表页
+ setTimeout(() => {
+ this.setData({
+ currentTab: 'list'
+ });
+ this.loadEmployees();
+ }, 1500);
+ } catch (error) {
+ console.error('添加员工失败:', error);
+ this.setData({
+ loading: false,
+ errorMessage: (error as Error).message || '添加员工失败,请稍后重试'
+ });
+ }
+ },
+
+ /**
+ * 删除员工
+ */
+ async deleteEmployee(e: any) {
+ const employeeId = e.currentTarget.dataset.id;
+ const employee = this.data.employees.find(emp => emp.id === employeeId);
+
+ if (!employee) {
+ return;
+ }
+
+ wx.showModal({
+ title: '确认删除',
+ content: `确定要删除员工 ${employee.name} (${employee.phone}) 吗?此操作不可恢复。`,
+ confirmText: '删除',
+ confirmColor: '#ff4d4f',
+ cancelText: '取消',
+ success: async (res) => {
+ if (res.confirm) {
+ this.setData({
+ loading: true,
+ errorMessage: '',
+ successMessage: ''
+ });
+
+ try {
+ const result = await employeeService.deleteEmployee(employeeId);
+
+ if (result.success) {
+ this.setData({
+ loading: false,
+ successMessage: result.message || '员工删除成功'
+ });
+
+ // 重新加载员工列表
+ this.loadEmployees();
+ } else {
+ this.setData({
+ loading: false,
+ errorMessage: result.message || '删除员工失败'
+ });
+ }
+ } catch (error) {
+ console.error('删除员工失败:', error);
+ this.setData({
+ loading: false,
+ errorMessage: '删除员工失败,请稍后重试'
+ });
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * 搜索员工
+ */
+ onSearchInput(e: any) {
+ const keyword = e.detail.value;
+ this.setData({
+ searchKeyword: keyword
+ });
+
+ // 更新过滤后的员工列表
+ this.updateFilteredEmployees();
+ },
+
+ /**
+ * 更新过滤后的员工列表
+ */
+ updateFilteredEmployees() {
+ const { employees } = this.data;
+ const filteredEmployees = this.getFilteredEmployees(employees);
+ this.setData({
+ filteredEmployees
+ });
+ },
+
+ /**
+ * 获取过滤后的员工列表
+ */
+ getFilteredEmployees(employees?: EmployeeInfo[]): EmployeeInfo[] {
+ const { searchKeyword } = this.data;
+
+ // 如果employees为空,返回空数组
+ if (!employees || !Array.isArray(employees)) {
+ return [];
+ }
+
+ // 获取当前登录用户信息
+ const app = getApp();
+ const currentUser = app.globalData.userInfo;
+
+ // 过滤掉当前登录用户
+ let filteredEmployees = employees.filter(emp => {
+ // 如果没有当前用户信息,显示所有员工
+ if (!currentUser || !currentUser.id) {
+ return true;
+ }
+ // 过滤掉当前用户
+ return emp.id !== currentUser.id;
+ });
+
+ // 更严格的搜索关键词检查
+ if (!searchKeyword || typeof searchKeyword !== 'string' || !searchKeyword.trim()) {
+ return filteredEmployees;
+ }
+
+ const keyword = searchKeyword.toLowerCase();
+ return filteredEmployees.filter(emp =>
+ emp.name.toLowerCase().includes(keyword) ||
+ emp.phone.includes(keyword) ||
+ (emp.role || '').toLowerCase().includes(keyword)
+ );
+ },
+
+ /**
+ * 获取角色显示文本
+ */
+ getRoleText(role: string): string {
+ const roleMap: Record = {
+ 'DELIVERY_PERSON': '配送员',
+ 'ADMIN': '管理员'
+ };
+ return roleMap[role] || role;
+ },
+
+ /**
+ * 清空消息
+ */
+ clearMessages() {
+ this.setData({
+ errorMessage: '',
+ successMessage: ''
+ });
+ },
+
+ /**
+ * 返回上一页
+ */
+ goBack() {
+ wx.navigateBack();
+ }
+});
\ No newline at end of file
diff --git a/miniprogram/pages/employee/employee.wxml b/miniprogram/pages/employee/employee.wxml
new file mode 100644
index 0000000..192c928
--- /dev/null
+++ b/miniprogram/pages/employee/employee.wxml
@@ -0,0 +1,166 @@
+
+
+
+
+
+ ←
+ 员工管理
+
+
+ 共{{filteredEmployees.length}}名员工
+
+
+
+
+
+
+ 员工列表
+
+
+ 添加员工
+
+
+
+
+
+
+ {{errorMessage}}
+ ×
+
+
+ {{successMessage}}
+ ×
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+ 🔎
+
+
+
+
+
+
+
+
+ {{item.name.charAt(0)}}
+
+
+
+ {{item.name}}
+
+ {{item.role === 'ADMIN' ? '👑' : '🚚'}}
+ {{getRoleText(item.role)}}
+
+
+ {{item.phone}}
+ ID: {{item.id}}
+
+
+
+
+
+
+
+
+
+ 👥
+
+ {{searchKeyword ? '没有找到匹配的员工' : '暂无员工数据'}}
+
+ 点击右上角"添加员工"开始管理
+
+
+
+
+
+
+
+
+ 员工姓名
+
+
+
+
+ 手机号码
+
+
+
+
+ 员工角色
+
+
+
+ {{addForm.role === 'ADMIN' ? '管理员' : addForm.role === 'DELIVERY_PERSON' ? '配送员' : '请选择角色'}}
+
+ ▼
+
+
+
+
+
+
+ 提示:添加员工后,用户可以使用该员工的姓名和手机号进行注册
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/employee/employee.wxss b/miniprogram/pages/employee/employee.wxss
new file mode 100644
index 0000000..908a8dc
--- /dev/null
+++ b/miniprogram/pages/employee/employee.wxss
@@ -0,0 +1,485 @@
+/* 员工管理页面样式 */
+.employee-container {
+ width: 100%;
+ height: 100vh;
+ background-color: #f5f5f5;
+ display: flex;
+ flex-direction: column;
+}
+
+/* 顶部导航栏 */
+.top-nav {
+ height: 90rpx;
+ background-color: #1aad19;
+ color: white;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 30rpx;
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.nav-left {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+}
+
+.back-btn {
+ font-size: 40rpx;
+ font-weight: bold;
+ cursor: pointer;
+}
+
+.nav-title {
+ font-size: 36rpx;
+ font-weight: bold;
+}
+
+.nav-right {
+ font-size: 28rpx;
+ opacity: 0.9;
+}
+
+/* 页面标签 */
+.page-tabs {
+ height: 80rpx;
+ background-color: white;
+ display: flex;
+ border-bottom: 1rpx solid #e0e0e0;
+}
+
+.tab-item {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 30rpx;
+ color: #666;
+ border-bottom: 4rpx solid transparent;
+ transition: all 0.3s;
+}
+
+.tab-item.active {
+ color: #1aad19;
+ border-bottom-color: #1aad19;
+ font-weight: bold;
+}
+
+.tab-text {
+ padding: 10rpx 0;
+}
+
+/* 消息提示 */
+.message-container {
+ padding: 20rpx 30rpx;
+}
+
+.error-message, .success-message {
+ padding: 20rpx 30rpx;
+ border-radius: 10rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 28rpx;
+}
+
+.error-message {
+ background-color: #fff2f0;
+ color: #ff4d4f;
+ border: 1rpx solid #ffccc7;
+}
+
+.success-message {
+ background-color: #f6ffed;
+ color: #52c41a;
+ border: 1rpx solid #b7eb8f;
+}
+
+.message-text {
+ flex: 1;
+}
+
+.close-btn {
+ font-size: 36rpx;
+ cursor: pointer;
+ padding-left: 20rpx;
+}
+
+/* 加载状态 */
+.loading-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 100rpx 0;
+}
+
+.loading-spinner {
+ width: 60rpx;
+ height: 60rpx;
+ border: 4rpx solid #f3f3f3;
+ border-top: 4rpx solid #1aad19;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 20rpx;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.loading-text {
+ font-size: 28rpx;
+ color: #666;
+}
+
+/* 列表容器 */
+.list-container {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ padding-bottom: 60rpx; /* 添加底部边距,避免内容被底部遮挡 */
+}
+
+/* 搜索框 */
+.search-container {
+ padding: 30rpx;
+ background: linear-gradient(135deg, #ffffff, #fafafa);
+ border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
+ box-shadow: 0 2rpx 15rpx rgba(0, 0, 0, 0.03);
+}
+
+.search-input-wrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.search-input {
+ flex: 1;
+ height: 80rpx;
+ background: linear-gradient(135deg, #f8f9fa, #f1f3f4);
+ border-radius: 40rpx;
+ padding: 0 80rpx 0 35rpx;
+ font-size: 30rpx;
+ border: 2rpx solid transparent;
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+ transition: all 0.3s ease;
+}
+
+.search-input:focus {
+ border-color: #1aad19;
+ background: linear-gradient(135deg, #ffffff, #f8f9fa);
+ box-shadow: 0 6rpx 25rpx rgba(26, 173, 25, 0.15);
+}
+
+.search-icon {
+ position: absolute;
+ right: 35rpx;
+ font-size: 36rpx;
+ color: #1aad19;
+ filter: drop-shadow(0 2rpx 4rpx rgba(26, 173, 25, 0.3));
+}
+
+/* 员工列表 */
+.employee-list {
+ flex: 1;
+ padding: 30rpx;
+ box-sizing: border-box;
+}
+
+.employee-item {
+ background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 20rpx;
+ padding: 35rpx;
+ margin: 25rpx 0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.08);
+ border: 1rpx solid rgba(255, 255, 255, 0.8);
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.employee-item::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 4rpx;
+ background: linear-gradient(90deg, #1aad19, #52c41a);
+}
+
+.employee-item:active {
+ transform: translateY(2rpx);
+ box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.12);
+}
+
+.employee-info {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+}
+
+.employee-avatar {
+ width: 90rpx;
+ height: 90rpx;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #1aad19, #52c41a);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: 25rpx;
+ box-shadow: 0 4rpx 15rpx rgba(26, 173, 25, 0.3);
+ border: 3rpx solid rgba(255, 255, 255, 0.9);
+}
+
+.avatar-text {
+ color: white;
+ font-size: 36rpx;
+ font-weight: bold;
+ text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
+}
+
+.employee-details {
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+}
+
+.employee-name-row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12rpx;
+ gap: 15rpx;
+}
+
+.employee-name {
+ font-size: 34rpx;
+ font-weight: 700;
+ color: #1a1a1a;
+ letter-spacing: 0.5rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 200rpx;
+}
+
+.employee-role {
+ font-size: 24rpx;
+ padding: 6rpx 16rpx;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, #f0f2f5, #e8ecef);
+ color: #595959;
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+ border: 1rpx solid rgba(0, 0, 0, 0.05);
+}
+
+.role-icon {
+ font-size: 28rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.employee-role.admin-role {
+ background: linear-gradient(135deg, #fff7e6, #ffe7ba);
+ color: #d46b08;
+ border: 1rpx solid rgba(250, 140, 22, 0.2);
+}
+
+.employee-phone {
+ font-size: 28rpx;
+ color: #666;
+ margin-bottom: 8rpx;
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+}
+
+.employee-phone::before {
+ content: '📱';
+ font-size: 24rpx;
+}
+
+.employee-id {
+ font-size: 24rpx;
+ color: #999;
+ display: flex;
+ align-items: center;
+ gap: 8rpx;
+}
+
+.employee-id::before {
+ content: '🆔';
+ font-size: 20rpx;
+}
+
+.employee-actions {
+ margin-left: 25rpx;
+}
+
+.delete-btn {
+ background: linear-gradient(135deg, #ff4d4f, #ff7875);
+ color: white;
+ border: none;
+ border-radius: 25rpx;
+ padding: 12rpx 24rpx;
+ font-size: 26rpx;
+ font-weight: 600;
+ box-shadow: 0 4rpx 12rpx rgba(255, 77, 79, 0.3);
+ transition: all 0.3s ease;
+ border: 1rpx solid rgba(255, 255, 255, 0.2);
+}
+
+.delete-btn:active {
+ background: linear-gradient(135deg, #d9363e, #ff7875);
+ transform: translateY(1rpx);
+ box-shadow: 0 2rpx 8rpx rgba(255, 77, 79, 0.2);
+}
+
+/* 空状态 */
+.empty-state {
+ text-align: center;
+ padding: 120rpx 30rpx;
+ color: #999;
+ background: linear-gradient(135deg, #fafafa, #f5f5f5);
+ border-radius: 25rpx;
+ margin: 30rpx;
+ box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.05);
+ border: 1rpx solid rgba(255, 255, 255, 0.8);
+}
+
+.empty-icon {
+ font-size: 100rpx;
+ display: block;
+ margin-bottom: 25rpx;
+ filter: drop-shadow(0 4rpx 8rpx rgba(0, 0, 0, 0.1));
+}
+
+.empty-text {
+ font-size: 36rpx;
+ display: block;
+ margin-bottom: 15rpx;
+ font-weight: 600;
+ color: #666;
+}
+
+.empty-hint {
+ font-size: 30rpx;
+ opacity: 0.8;
+ line-height: 1.6;
+}
+
+/* 添加容器 */
+.add-container {
+ flex: 1;
+ padding: 30rpx;
+}
+
+.form-container {
+ background-color: white;
+ border-radius: 15rpx;
+ padding: 40rpx;
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.form-item {
+ margin-bottom: 40rpx;
+}
+
+.form-label {
+ display: block;
+ font-size: 30rpx;
+ color: #333;
+ margin-bottom: 15rpx;
+ font-weight: bold;
+}
+
+.form-input {
+ width: 100%;
+ height: 80rpx;
+ border: 1rpx solid #e0e0e0;
+ border-radius: 10rpx;
+ padding: 0 20rpx;
+ font-size: 28rpx;
+ box-sizing: border-box;
+}
+
+.form-input:focus {
+ border-color: #1aad19;
+}
+
+.form-picker {
+ width: 100%;
+ height: 80rpx;
+ border: 1rpx solid #e0e0e0;
+ border-radius: 10rpx;
+ padding: 0 20rpx;
+ font-size: 28rpx;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+}
+
+.picker-display {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.picker-text {
+ color: #333;
+}
+
+.picker-arrow {
+ color: #999;
+ font-size: 24rpx;
+}
+
+.form-hint {
+ background-color: #f6ffed;
+ border: 1rpx solid #b7eb8f;
+ border-radius: 10rpx;
+ padding: 20rpx;
+ margin-bottom: 40rpx;
+}
+
+.hint-text {
+ font-size: 26rpx;
+ color: #52c41a;
+ line-height: 1.5;
+}
+
+.submit-btn {
+ width: 100%;
+ height: 90rpx;
+ background-color: #1aad19;
+ color: white;
+ border: none;
+ border-radius: 45rpx;
+ font-size: 32rpx;
+ font-weight: bold;
+}
+
+.submit-btn:active {
+ background-color: #179b16;
+}
+
+.submit-btn:disabled {
+ background-color: #ccc;
+ color: #999;
+}
\ No newline at end of file
diff --git a/miniprogram/pages/index/index.less b/miniprogram/pages/index/index.less
index 9ce5da3..a74d451 100644
--- a/miniprogram/pages/index/index.less
+++ b/miniprogram/pages/index/index.less
@@ -149,25 +149,68 @@ page {
/* 控制按钮样式统一 */
.control-btn.signin-btn,
-.control-btn.register-btn {
- width: 100rpx;
- height: 100rpx;
+.control-btn.register-btn,
+.control-btn.sign-out-btn,
+.control-btn.auth-btn,
+.control-btn.location-btn,
+.control-btn.reset-btn,
+.control-btn.staff-management-btn {
+ width: 120rpx;
+ height: 120rpx;
border-radius: 50%;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
- box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
+ box-shadow: 0 6rpx 30rpx rgba(0, 0, 0, 0.2);
font-size: 24rpx;
padding: 0;
margin: 0;
+ border: none;
+ color: #333333;
+ text-align: center;
+ line-height: 1;
+ background: rgba(255, 255, 255, 0.9);
+ border: 1rpx solid rgba(0, 0, 0, 0.1);
+}
+
+/* 按钮图标样式 */
+.btn-icon {
+ font-size: 40rpx;
+ margin-bottom: 8rpx;
+ display: block;
+ line-height: 1;
+}
+
+/* 按钮文字样式 */
+.btn-text {
+ font-size: 22rpx;
+ font-weight: 500;
+ display: block;
+ line-height: 1.2;
}
.control-btn.signin-btn:active,
-.control-btn.register-btn:active {
+.control-btn.register-btn:active,
+.control-btn.sign-out-btn:active {
transform: scale(0.95);
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}
+/* 所有按钮点击状态 */
+.control-btn.signin-btn:active,
+.control-btn.register-btn:active,
+.control-btn.auth-btn:active,
+.control-btn.sign-out-btn:active,
+.control-btn.location-btn:active,
+.control-btn.reset-btn:active,
+.control-btn.staff-management-btn:active {
+ background: rgba(240, 240, 240, 0.9);
+ transform: scale(0.95);
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+ border: 1rpx solid rgba(0, 0, 0, 0.2);
+}
+
.btn-signin {
background-color: #1aad19;
color: white;
diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts
index 1079ce3..42c3103 100644
--- a/miniprogram/pages/index/index.ts
+++ b/miniprogram/pages/index/index.ts
@@ -1,61 +1,64 @@
// index.ts
// 引入服务和工具函数
-import { UserInfo, Marker } from '../../types';
import { showToast } from '../../utils/helpers';
import userService from '../../services/userService';
import locationTrackingService from '../../services/locationTrackingService';
-// 引入模块
+// 引入主页面模块
import { MainPageModule } from './modules/mainPageModule';
-// 主页面组件
-Component({
+// 主页面组件接口定义
+interface IndexPageComponent {
data: {
- // 地图中心点坐标
- longitude: 102.833722,
- latitude: 24.880095,
- scale: 13, // 地图缩放级别
- markers: [] as Marker[], // 地图标记点数组
- userInfo: null as UserInfo | null, // 用户信息
- // 用户认证状态(分离外部登录状态和用户二级状态)
- authStatus: {
- hasWxCode: false, // 是否已获取微信code(外部登录状态)
- userStatus: 'unknown' as 'unknown' | 'registered' | 'unregistered' | 'signed_in' | 'signed_out', // 用户二级状态
- },
- showUserPanel: false, // 是否显示用户信息面板
- showOrderPanel: false, // 是否显示订单详情面板
- currentOrder: null as any, // 当前选中的订单
- currentDeliveryPerson: null as any, // 当前选中的货运人员
- currentWarehouse: null as any, // 当前选中的仓库
- currentPanelPosition: { x: 0, y: 0 }, // 当前信息面板位置
- polyline: null as any, // 路线规划结果
- pendingOrders: [] as any[] ,// 待分配订单
- currentRoute: null as any, // 当前路线信息
- showRoute: false, // 是否显示路线
- routeDistance: 0, // 路线距离
- routeDuration: 0, // 路线预计时间
-
- // 底部弹窗相关状态
- showWarehouseModal: false, // 仓库底部弹窗
- showDeliveryPersonModal: false, // 货运人员底部弹窗
- // 底部弹窗状态(bottom或full)
- warehouseModalState: 'bottom',
- deliveryPersonModalState: 'bottom',
- // 操作按钮显示状态(基于用户状态动态计算)
+ mainPageModule: MainPageModule | null;
+ // 核心UI状态
+ showUserPanel: boolean;
+ showOrderPanel: boolean;
+ showDeliveryPersonModal: boolean;
+ showWarehouseModal: boolean;
+ };
+}
- showSignOutButton: false // 是否显示签退按钮
+// 主页面组件
+Component({
+ data: {
+ mainPageModule: null as MainPageModule | null,
+ // 核心UI状态
+ showUserPanel: false,
+ showOrderPanel: false,
+ showDeliveryPersonModal: false,
+ showWarehouseModal: false
},
lifetimes: {
- async attached() {
- // 组件挂载时初始化
- await this.initPage();
+ attached() {
+ console.log('index page attached');
+ this.initPage();
+ },
+
+ detached() {
+ console.log('index page detached');
+ // 清理资源
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.cleanup();
+ }
},
- detached() {
- // 组件卸载时清理
- (this as any).mainPageModule = null;
+ show() {
+ console.log('index page show');
+ // 页面显示时调用主页面模块的onShow方法
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.onShow();
+ }
+ },
+
+ hide() {
+ console.log('index page hide');
+ // 页面隐藏时调用主页面模块的onHide方法
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.onHide();
+ }
}
},
@@ -82,11 +85,14 @@ Component({
const app = getApp();
// 初始化主页面模块
- (this as any).mainPageModule = new MainPageModule(this);
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ this.setData({
+ mainPageModule: new MainPageModule(this)
+ });
// 设置globalData中的loginModule引用,用于废弃方法的重定向
- app.globalData.loginModule = loginModule;
+ if (this.data.mainPageModule) {
+ app.globalData.loginModule = this.data.mainPageModule.getLoginModule();
+ }
// 异步检查登录状态
await this.checkAndUpdateLoginStatus();
@@ -95,9 +101,14 @@ Component({
// 异步检查并更新登录状态
async checkAndUpdateLoginStatus() {
const app = getApp();
- const loginModule = (this as any).mainPageModule.getLoginModule();
try {
+ // 获取登录模块
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+ const loginModule = this.data.mainPageModule.getLoginModule();
// 显示加载状态
wx.showLoading({
title: '检查登录状态...',
@@ -108,21 +119,8 @@ Component({
const isLoggedIn = await this.asyncCheckLoginStatus(loginModule);
if (isLoggedIn) {
- // 已登录状态
- const userStatus = await loginModule.determineUserStatus(app.globalData.userInfo);
- this.setData({
- userInfo: app.globalData.userInfo,
- 'authStatus.hasWxCode': true,
- 'authStatus.userStatus': userStatus,
- // 初始化按钮显示状态
- showSignInButton: loginModule.shouldShowSignInButton(),
- showRegisterButton: loginModule.shouldShowRegisterButton(),
- showAuthButton: false
- });
-
- // === 全局登录流程完成,登录成功 ===
- // 统一在此处执行一次完整的页面刷新
- this.refreshPageAfterLogin();
+ // 已登录状态 - 按钮状态会在refreshPageAfterLogin中统一更新
+ console.log('✅ 登录检查成功,等待统一页面刷新');
} else {
// 未登录状态
this.setData({
@@ -173,6 +171,9 @@ Component({
console.log('✅ 使用本地登录状态,用户状态已更新:', userStatus);
+ // 登录成功后统一刷新页面
+ this.refreshPageAfterLogin();
+
return true;
}
@@ -213,19 +214,24 @@ Component({
// 更新按钮显示状态
updateButtonDisplayStatus() {
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+
+ const loginModule = this.data.mainPageModule.getLoginModule();
const showSignInButton = loginModule.shouldShowSignInButton();
const showRegisterButton = loginModule.shouldShowRegisterButton();
// 签退按钮显示逻辑:已签到用户显示签退按钮
- const showSignOutButton = this.data.authStatus.userStatus === 'signed_in';
+ const showSignOutButton = this.data.authStatus && this.data.authStatus.userStatus === 'signed_in';
console.log('🔄 更新按钮显示状态:');
console.log(' - showSignInButton:', showSignInButton);
console.log(' - showRegisterButton:', showRegisterButton);
console.log(' - showSignOutButton:', showSignOutButton);
- console.log(' - 当前用户状态:', this.data.authStatus.userStatus);
- console.log(' - 当前hasWxCode:', this.data.authStatus.hasWxCode);
+ console.log(' - 当前用户状态:', this.data.authStatus ? this.data.authStatus.userStatus : 'undefined');
+ console.log(' - 当前hasWxCode:', this.data.authStatus ? this.data.authStatus.hasWxCode : 'undefined');
this.setData({
showSignInButton,
@@ -238,7 +244,12 @@ Component({
// 登录成功后统一刷新页面
async refreshPageAfterLogin() {
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+
+ const loginModule = this.data.mainPageModule.getLoginModule();
const app = getApp();
console.log('🔄 === 全局登录流程完成,执行统一页面刷新 ===');
@@ -250,13 +261,14 @@ Component({
this.updateButtonDisplayStatus();
// 3. 初始化主页面模块
- await (this as any).mainPageModule.onLoad();
+ await this.data.mainPageModule.onLoad();
- // 4. 登录成功后主动加载业务数据(仓库、订单等)
+ // 4. 登录成功后只加载公开数据(仓库等),不加载业务数据
+ // 业务数据(员工位置等)只有在用户签到后才应该加载
if (app.globalData.isLoggedIn) {
- console.log('🔍 登录成功,开始加载业务数据...');
- await (this as any).mainPageModule.refreshAllData();
- console.log('✅ 业务数据加载完成');
+ console.log('🔍 登录成功,开始加载公开数据...');
+ await this.data.mainPageModule.loadPublicData();
+ console.log('✅ 公开数据加载完成');
}
console.log('✅ 统一页面刷新完成');
@@ -278,7 +290,12 @@ Component({
// 处理签到 - 已迁移到LoginModule
async handleSignIn() {
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+
+ const loginModule = this.data.mainPageModule.getLoginModule();
const success = await loginModule.handleSignIn();
if (success) {
@@ -286,18 +303,22 @@ Component({
this.updateButtonDisplayStatus();
// 刷新页面数据
- await (this as any).mainPageModule.refreshAllData();
+ await this.data.mainPageModule.refreshAllData();
}
},
// 处理授权登录 - 已迁移到LoginModule
async handleAuthLogin() {
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+
+ const loginModule = this.data.mainPageModule.getLoginModule();
const success = await loginModule.handleAuthLogin();
if (success) {
const app = getApp();
- const loginModule = (this as any).mainPageModule.getLoginModule();
const userStatus = loginModule.determineUserStatus(app.globalData.userInfo);
this.setData({
userInfo: app.globalData.userInfo,
@@ -323,7 +344,11 @@ Component({
});
// 调用loginModule的logout方法
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (!this.data.mainPageModule) {
+ console.error('mainPageModule未初始化');
+ return;
+ }
+ const loginModule = this.data.mainPageModule.getLoginModule();
await loginModule.logout();
// 更新页面状态
@@ -364,10 +389,18 @@ Component({
console.warn('调试信息 - 停止位置追踪失败:', trackingError);
}
- // 清除登录信息,防止自动重新登录
- console.log('调试信息 - 开始清除登录信息');
- const app = getApp();
- app.doGlobalLogout();
+ // 停止位置模块的实时跟踪
+ try {
+ if (this.data.mainPageModule) {
+ const locationModule = this.data.mainPageModule.getLocationModule();
+ if (locationModule) {
+ await locationModule.stopRealTimeTracking();
+ console.log('调试信息 - 位置模块实时跟踪已停止');
+ }
+ }
+ } catch (trackingError) {
+ console.warn('调试信息 - 停止位置模块实时跟踪失败:', trackingError);
+ }
// 保存签退状态到本地存储,防止自动重新登录
console.log('调试信息 - 保存签退状态到本地存储');
@@ -377,13 +410,25 @@ Component({
const savedStatus = wx.getStorageSync('userStatus');
console.log('调试信息 - 验证保存的签退状态:', savedStatus);
- // 更新用户状态为已签退
+ // 更新用户状态为已签退(不调用doGlobalLogout,保持登录状态)
this.setData({
'authStatus.userStatus': 'signed_out',
showSignOutButton: false,
showSignInButton: true
});
+ // 清除所有员工图标
+ if (this.data.mainPageModule) {
+ const employeeModule = this.data.mainPageModule.getEmployeeModule();
+ if (employeeModule) {
+ // 清除员工标记点
+ const { markers } = this.data;
+ const filteredMarkers = markers.filter((marker: any) => marker.type !== 'employee');
+ this.setData({ markers: filteredMarkers });
+ console.log('调试信息 - 已清除所有员工图标');
+ }
+ }
+
wx.hideLoading();
showToast('签退成功');
console.log('调试信息 - 用户签退完成');
@@ -401,9 +446,25 @@ Component({
});
},
+ // 跳转到管理员页面
+ goToAdminPage() {
+ wx.navigateTo({
+ url: '/pages/admin/admin'
+ });
+ },
+
+ // 跳转到员工管理页面
+ goToEmployeeManagement() {
+ wx.navigateTo({
+ url: '/pages/employee/employee'
+ });
+ },
+
// 阻止事件冒泡
stopPropagation(e: any) {
- e.stopPropagation();
+ if (e && typeof e.stopPropagation === 'function') {
+ e.stopPropagation();
+ }
},
@@ -422,8 +483,8 @@ Component({
// 用户登出
userLogout() {
- if ((this as any).mainPageModule) {
- const loginModule = (this as any).mainPageModule.getLoginModule();
+ if (this.data.mainPageModule) {
+ const loginModule = this.data.mainPageModule.getLoginModule();
loginModule.logout();
// 更新页面状态
@@ -449,91 +510,91 @@ Component({
// 隐藏所有面板
hideAllPanels() {
- if ((this as any).mainPageModule) {
- (this as any).mainPageModule.hideAllPanels();
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.hideAllPanels();
}
},
// 重置标记点状态
resetMarkers() {
- if ((this as any).mainPageModule) {
- (this as any).mainPageModule.resetMarkers();
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.resetMarkers();
}
},
// 地图点击事件
onMapTap(e: any) {
- if ((this as any).mainPageModule) {
- (this as any).mainPageModule.onMapTap(e);
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.onMapTap(e);
}
},
// 标记点点击事件
onMarkerTap(e: any) {
- if ((this as any).mainPageModule) {
- (this as any).mainPageModule.onMarkerTap(e);
+ if (this.data.mainPageModule) {
+ this.data.mainPageModule.onMarkerTap(e);
}
},
// 分配订单
async assignOrder(orderId: number, deliveryPersonId: number) {
- if ((this as any).mainPageModule) {
- const orderModule = (this as any).mainPageModule.getOrderModule();
+ if (this.data.mainPageModule) {
+ const orderModule = this.data.mainPageModule.getOrderModule();
await orderModule.assignOrder(orderId, deliveryPersonId);
}
},
// 更新订单状态
async updateOrderStatus(orderId: number, status: 'pending' | 'assigned' | 'in_transit' | 'delivered') {
- if ((this as any).mainPageModule) {
- const orderModule = (this as any).mainPageModule.getOrderModule();
+ if (this.data.mainPageModule) {
+ const orderModule = this.data.mainPageModule.getOrderModule();
await orderModule.updateOrderStatus(orderId, status);
}
},
// 展开仓库面板
expandWarehousePanel() {
- if ((this as any).mainPageModule) {
- const warehouseModule = (this as any).mainPageModule.getWarehouseModule();
+ if (this.data.mainPageModule) {
+ const warehouseModule = this.data.mainPageModule.getWarehouseModule();
warehouseModule.expandWarehousePanel();
}
},
// 收起仓库面板
collapseWarehousePanel() {
- if ((this as any).mainPageModule) {
- const warehouseModule = (this as any).mainPageModule.getWarehouseModule();
+ if (this.data.mainPageModule) {
+ const warehouseModule = this.data.mainPageModule.getWarehouseModule();
warehouseModule.collapseWarehousePanel();
}
},
- // 展开货运人员面板
+ // 展开员工面板
expandDeliveryPersonPanel() {
- if ((this as any).mainPageModule) {
- const deliveryPersonModule = (this as any).mainPageModule.getDeliveryPersonModule();
- deliveryPersonModule.expandDeliveryPersonPanel();
+ if (this.data.mainPageModule) {
+ const employeeModule = this.data.mainPageModule.getEmployeeModule();
+ employeeModule.expandDeliveryPersonPanel();
}
},
- // 收起货运人员面板
+ // 收起员工面板
collapseDeliveryPersonPanel() {
- if ((this as any).mainPageModule) {
- const deliveryPersonModule = (this as any).mainPageModule.getDeliveryPersonModule();
- deliveryPersonModule.collapseDeliveryPersonPanel();
+ if (this.data.mainPageModule) {
+ const employeeModule = this.data.mainPageModule.getEmployeeModule();
+ employeeModule.collapseDeliveryPersonPanel();
}
},
// 刷新所有数据
async refreshAllData() {
- if ((this as any).mainPageModule) {
- await (this as any).mainPageModule.refreshAllData();
+ if (this.data.mainPageModule) {
+ await this.data.mainPageModule.refreshAllData();
}
},
// 开始定位(处理地图控制按钮点击)
async startLocation() {
- if ((this as any).mainPageModule) {
- const mapModule = (this as any).mainPageModule.getMapModule();
+ if (this.data.mainPageModule) {
+ const mapModule = this.data.mainPageModule.getMapModule();
await mapModule.startLocation();
}
},
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 044e8c1..2e112da 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -26,7 +26,8 @@
type="primary"
size="mini"
>
- 签到
+ ✅
+ 签到
@@ -37,7 +38,8 @@
type="default"
size="mini"
>
- 注册
+ 📝
+ 注册
@@ -48,7 +50,8 @@
type="warn"
size="mini"
>
- 登录
+ 🔑
+ 登录
@@ -59,14 +62,16 @@
type="warn"
size="mini"
>
- 签退
+ 🚪
+ 签退
-
+ 📍
+ 定位
@@ -74,11 +79,17 @@
-
- 管理
+ 👤
+ 我的
+
+
+
+
+
+ 👥
+ 员工
@@ -92,7 +103,7 @@
@@ -103,6 +114,7 @@
ID: {{userInfo.id || '未获取'}}
电话:{{userInfo.phone || '未设置'}}
角色:{{userInfo.role === 'ADMIN' ? '管理员' : '货运员'}}
+
diff --git a/miniprogram/pages/index/index.wxss b/miniprogram/pages/index/index.wxss
index f0d9841..5c77205 100644
--- a/miniprogram/pages/index/index.wxss
+++ b/miniprogram/pages/index/index.wxss
@@ -88,6 +88,17 @@
gap: 20rpx;
}
+/* 右侧控制按钮容器 */
+.right-controls-container {
+ position: absolute;
+ top: 20rpx;
+ right: 30rpx;
+ z-index: 10;
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
.control-btn {
width: 80rpx;
height: 80rpx;
@@ -1407,4 +1418,26 @@
100% {
opacity: 0.7;
}
+}
+
+/* 员工管理按钮样式 */
+.staff-management-btn {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50%;
+ background-color: white;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.staff-management-btn:active {
+ background-color: #f5f5f5;
+ transform: scale(0.98);
+}
+
+.staff-management-btn image {
+ width: 40rpx;
+ height: 40rpx;
}
\ No newline at end of file
diff --git a/miniprogram/pages/index/modules/dataModule.ts b/miniprogram/pages/index/modules/dataModule.ts
index efe862f..13222b3 100644
--- a/miniprogram/pages/index/modules/dataModule.ts
+++ b/miniprogram/pages/index/modules/dataModule.ts
@@ -14,11 +14,32 @@ export class DataModule {
/**
* 初始化页面数据
*/
- public initializeData(): void {
+ public async initializeData(): Promise {
+ // 检查是否已静默登录,如果是则尝试获取真实位置
+ let initialLongitude = 102.833722;
+ let initialLatitude = 24.880095;
+
+ const app = getApp();
+ if (app.globalData.isLoggedIn) {
+ try {
+ // 导入地图服务
+ const mapService = require('../../../services/mapService').default;
+ const location = await mapService.getCurrentLocation();
+
+ if (location && !isNaN(location.latitude) && !isNaN(location.longitude)) {
+ initialLongitude = location.longitude;
+ initialLatitude = location.latitude;
+ console.log('[DATA MODULE] 静默登录后使用真实位置:', location);
+ }
+ } catch (error) {
+ console.warn('[DATA MODULE] 获取真实位置失败,使用默认位置:', error);
+ }
+ }
+
this.pageContext.setData({
// 地图相关数据
- longitude: 102.833722,
- latitude: 24.880095,
+ longitude: initialLongitude,
+ latitude: initialLatitude,
scale: 13,
markers: [] as Marker[],
polyline: null,
@@ -148,7 +169,7 @@ export class DataModule {
*/
public updateMarkers(markers: Marker[]): void {
// 验证每个标记点的坐标
- const validatedMarkers = markers.map((marker, index) => {
+ const validatedMarkers = markers.map((marker) => {
// 检查经纬度是否为有效数字
if (isNaN(marker.longitude) || isNaN(marker.latitude)) {
// 为无效坐标设置默认值
@@ -283,4 +304,26 @@ export class DataModule {
public resetAllData(): void {
this.initializeData();
}
+
+ /**
+ * 获取位置模块(用于其他模块访问位置模块)
+ */
+ public getLocationModule(): any {
+ // 通过页面上下文获取位置模块
+ if (this.pageContext.data.mainPageModule) {
+ return this.pageContext.data.mainPageModule.getLocationModule();
+ }
+ return null;
+ }
+
+ /**
+ * 获取地图模块(用于其他模块访问地图模块)
+ */
+ public getMapModule(): any {
+ // 通过页面上下文获取地图模块
+ if (this.pageContext.data.mainPageModule) {
+ return this.pageContext.data.mainPageModule.getMapModule();
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/miniprogram/pages/index/modules/deliveryPersonModule.ts b/miniprogram/pages/index/modules/deliveryPersonModule.ts
index c4180b9..0a03e54 100644
--- a/miniprogram/pages/index/modules/deliveryPersonModule.ts
+++ b/miniprogram/pages/index/modules/deliveryPersonModule.ts
@@ -1,10 +1,12 @@
-// 货运人员模块 - 处理货运人员管理、位置跟踪、交互
-import deliveryPersonService from '../../../services/deliveryPersonService';
+// 员工模块 - 处理所有员工(管理员和货运人员)管理、位置跟踪、交互
+import employeeService from '../../../services/employeeService';
+import { Role } from '../../../utils/roleUtils';
+// getApp是微信小程序全局函数,无需导入
import { showToast } from '../../../utils/helpers';
import { DataModule } from './dataModule';
-export class DeliveryPersonModule {
+export class EmployeeModule {
private pageContext: any;
private dataModule: DataModule;
@@ -14,210 +16,44 @@ export class DeliveryPersonModule {
}
/**
- * 加载货运人员数据
+ * 加载所有员工数据(包括管理员和货运人员)
*/
- async loadDeliveryPersons(): Promise {
+ async loadAllEmployees(): Promise {
try {
- const deliveryPersons = await deliveryPersonService.getDeliveryPersons();
+ // 获取所有员工数据
+ const allEmployees = await employeeService.getEmployees();
- // 更新地图标记点
- this.updateDeliveryPersonMarkers(deliveryPersons);
-
- console.log('货运人员数据加载完成,数量:', deliveryPersons.length);
+ console.log('所有员工数据加载完成,数量:', allEmployees.length);
} catch (error) {
- console.error('加载货运人员数据失败:', error);
- showToast('加载货运人员数据失败');
- }
- }
-
- /**
- * 更新货运人员位置
- */
- async updateDeliveryPersonLocation(personId: number, location: { longitude: number, latitude: number }): Promise {
- try {
- await deliveryPersonService.updateDeliveryPersonLocation(personId, location);
-
- console.log(`货运人员 ${personId} 位置更新:`, location);
-
- // 重新加载货运人员数据
- await this.loadDeliveryPersons();
-
- } catch (error) {
- console.error('更新货运人员位置失败:', error);
- showToast('更新位置失败');
- }
- }
-
-
-
- /**
- * 开始实时位置跟踪
- */
- async startRealTimeTracking(): Promise {
- try {
- // 获取当前用户信息
- const userInfo = this.dataModule.getData().userInfo;
- if (!userInfo || !userInfo.id) {
- throw new Error('用户信息获取失败');
- }
-
- // 使用新的WebSocket接口订阅位置更新
- await deliveryPersonService.subscribeToRealTimeLocations(userInfo.id);
-
- // 设置位置更新回调
- deliveryPersonService.subscribeToRealTimeLocations(this.handleRealTimeLocationUpdate.bind(this));
-
- showToast('开始实时跟踪');
- console.log('开始实时跟踪货运人员位置');
- } catch (error) {
- console.error('开始实时跟踪失败:', error);
- showToast('开始实时跟踪失败');
+ console.error('加载员工数据失败:', error);
+ showToast('加载员工数据失败');
}
}
- handleRealTimeLocationUpdate(location: any): void {
- console.log('收到实时位置更新:', location);
- // 这里可以添加更新地图标记点的逻辑
- // 根据位置更新信息更新对应的货运人员标记点
- if (location && location.deliveryPersonId) {
- this.updateSingleDeliveryPersonMarker(location);
- }
- }
-
/**
- * 更新单个货运人员标记点
+ * 获取员工角色对应的图标
*/
- private updateSingleDeliveryPersonMarker(location: any): void {
- const { markers } = this.pageContext.data;
+ private getEmployeeIcon(role: string): string {
+ console.log(`获取员工图标,角色: ${role}`);
- // 查找并更新对应的货运人员标记点
- const updatedMarkers = markers.map((marker: any) => {
- if (marker.type === 'delivery_person' && marker.data && marker.data.id === location.deliveryPersonId) {
- // 更新标记点位置
- return {
- ...marker,
- longitude: location.longitude,
- latitude: location.latitude,
- data: {
- ...marker.data,
- currentLocation: {
- longitude: location.longitude,
- latitude: location.latitude
- }
- }
- };
- }
- return marker;
- });
-
- // 更新数据模块中的标记点
- this.dataModule.updateMarkers(updatedMarkers);
- }
-
- /**
- * 停止实时跟踪货运人员位置
- */
- async stopRealTimeTracking(): Promise {
- try {
- // 获取当前用户信息
- const userInfo = this.dataModule.getData().userInfo;
- if (!userInfo || !userInfo.id) {
- throw new Error('用户信息获取失败');
- }
-
- // 使用新的WebSocket接口取消订阅
- await deliveryPersonService.unsubscribeFromRealTimeLocations();
-
- showToast('已停止实时跟踪');
- console.log('停止实时跟踪货运人员位置');
- } catch (error) {
- console.error('停止实时跟踪失败:', error);
- showToast('停止实时跟踪失败');
- }
- }
-
- /**
- * 更新货运人员标记点
- */
- private updateDeliveryPersonMarkers(deliveryPersons: any[]): void {
- console.log(`[DELIVERY PERSON MODULE] 开始更新货运人员标记点,货运人员总数: ${deliveryPersons.length}`);
-
- const { markers } = this.pageContext.data;
-
- // 移除现有的货运人员标记点
- const filteredMarkers = markers.filter((marker: any) => marker.type !== 'delivery_person');
- console.log(`[DELIVERY PERSON MODULE] 移除现有货运人员标记点后,剩余标记点数量: ${filteredMarkers.length}`);
-
- // 添加新的货运人员标记点
- const deliveryPersonMarkers = deliveryPersons.map((person, index) => {
- // 验证货运人员坐标是否有效
- // 注意:坐标信息嵌套在currentLocation对象中
- let validLongitude = person.currentLocation?.longitude;
- let validLatitude = person.currentLocation?.latitude;
-
- if (isNaN(validLongitude) || isNaN(validLatitude)) {
- console.error(`[DELIVERY PERSON MODULE] 货运人员${index} (ID: ${person.id}) 坐标无效: (${validLongitude}, ${validLatitude}),使用默认坐标`);
- validLongitude = 102.833722; // 默认经度
- validLatitude = 24.880095; // 默认纬度
- } else {
- console.log(`[DELIVERY PERSON MODULE] 货运人员${index} (ID: ${person.id}) 坐标有效: (${validLongitude}, ${validLatitude})`);
- }
-
- const iconPath = this.getDeliveryPersonIcon(person.status);
-
- return {
- id: 2000 + index, // 货运人员标记点ID从2000开始
- longitude: validLongitude,
- latitude: validLatitude,
- iconPath: iconPath,
- width: 26,
- height: 26,
- zIndex: 20,
- type: 'delivery_person',
- data: person
- };
- });
-
- console.log(`[DELIVERY PERSON MODULE] 生成新货运人员标记点数量: ${deliveryPersonMarkers.length}`);
-
- // 更新数据模块中的标记点
- const allMarkers = [...filteredMarkers, ...deliveryPersonMarkers];
- console.log(`[DELIVERY PERSON MODULE] 准备更新所有标记点,总数: ${allMarkers.length}`);
- this.dataModule.updateMarkers(allMarkers);
- }
-
- /**
- * 获取货运人员状态对应的图标
- */
- private getDeliveryPersonIcon(status: string): string {
- console.log(`获取货运人员图标,状态: ${status}`);
-
- // 根据报错信息,直接使用已知存在的备用图片路径
- // 实际项目中,应确保相关图片资源正确放置在指定路径
- const fallbackIconPath = '/images/trucks.png';
-
- // 根据不同状态使用不同的图片路径
+ // 根据角色使用不同的图标
let iconPath = '';
- switch (status) {
- case 'idle':
- // 为避免图片加载失败,暂时使用备用图片
- iconPath = fallbackIconPath;
- console.log('使用备用图标(idle状态)');
+ switch (role) {
+ case Role.ADMIN:
+ // 管理员使用皇冠图标
+ iconPath = '/images/crown.png';
+ console.log('使用管理员图标(👑)');
break;
- case 'busy':
- // 为避免图片加载失败,暂时使用备用图片
- iconPath = fallbackIconPath;
- console.log('使用备用图标(busy状态)');
- break;
- case 'offline':
- // 为避免图片加载失败,暂时使用备用图片
- iconPath = fallbackIconPath;
- console.log('使用备用图标(offline状态)');
+ case Role.DELIVERY_PERSON:
+ // 货运人员使用货车图标
+ iconPath = '/images/truck.png';
+ console.log('使用货运人员图标(🚚)');
break;
default:
- iconPath = fallbackIconPath;
- console.log('使用备用图标(默认状态)');
+ // 默认使用货车图标
+ iconPath = '/images/truck.png';
+ console.log('使用默认图标(🚚)');
break;
}
diff --git a/miniprogram/pages/index/modules/locationModule.ts b/miniprogram/pages/index/modules/locationModule.ts
new file mode 100644
index 0000000..6f072b8
--- /dev/null
+++ b/miniprogram/pages/index/modules/locationModule.ts
@@ -0,0 +1,278 @@
+// 位置模块 - 处理位置追踪、位置更新和地图标记点更新
+import { DataModule } from './dataModule';
+import { showToast } from '../../../utils/helpers';
+import locationTrackingService from '../../../services/locationTrackingService';
+import { OnlineUserInfo } from '../../../services/locationTrackingService';
+
+// 位置模块接口定义
+export interface LocationModule {
+ initialize(): Promise;
+ cleanup(): void;
+ startRealTimeTracking(): Promise;
+ stopRealTimeTracking(): Promise;
+ updateEmployeeLocation(employeeId: number, location: { longitude: number, latitude: number }): Promise;
+}
+
+export class LocationModule {
+ private pageContext: any;
+ private dataModule: DataModule;
+ private isTracking: boolean;
+
+ constructor(pageContext: any, dataModule: DataModule) {
+ this.pageContext = pageContext;
+ this.dataModule = dataModule;
+ this.isTracking = false;
+
+ // 绑定回调方法
+ this.handleLocationUpdates = this.handleLocationUpdates.bind(this);
+ }
+
+ /**
+ * 初始化位置模块
+ */
+ async initialize(): Promise {
+ console.log('位置模块初始化');
+
+ // 订阅位置更新
+ this.subscribeToLocationUpdates();
+
+ console.log('位置模块初始化完成');
+ }
+
+ /**
+ * 清理位置模块
+ */
+ cleanup(): void {
+ console.log('清理位置模块');
+
+ // 取消位置更新订阅
+ this.unsubscribeFromLocationUpdates();
+
+ // 停止实时跟踪
+ if (this.isTracking) {
+ this.stopRealTimeTracking().catch(error => {
+ console.error('停止实时跟踪失败:', error);
+ });
+ }
+ }
+
+ /**
+ * 开始实时跟踪位置
+ */
+ async startRealTimeTracking(): Promise {
+ try {
+ // 获取当前用户信息
+ const userInfo = this.dataModule.getData().userInfo;
+ if (!userInfo || !userInfo.id) {
+ throw new Error('用户信息获取失败');
+ }
+
+ // 启动位置跟踪订阅
+ await locationTrackingService.startTrackingAfterSignIn();
+
+ this.isTracking = true;
+ showToast('已开始实时跟踪');
+ console.log('开始实时跟踪位置');
+ } catch (error) {
+ console.error('开始实时跟踪失败:', error);
+ showToast('开始实时跟踪失败');
+ throw error;
+ }
+ }
+
+ /**
+ * 停止实时跟踪位置
+ */
+ async stopRealTimeTracking(): Promise {
+ try {
+ // 获取当前用户信息
+ const userInfo = this.dataModule.getData().userInfo;
+ if (!userInfo || !userInfo.id) {
+ throw new Error('用户信息获取失败');
+ }
+
+ // 停止位置跟踪
+ await locationTrackingService.stopTracking();
+
+ this.isTracking = false;
+ showToast('已停止实时跟踪');
+ console.log('停止实时跟踪位置');
+ } catch (error) {
+ console.error('停止实时跟踪失败:', error);
+ showToast('停止实时跟踪失败');
+ throw error;
+ }
+ }
+
+ /**
+ * 更新员工位置
+ */
+ async updateEmployeeLocation(employeeId: number, location: { longitude: number, latitude: number }): Promise {
+ try {
+ console.log(`员工 ${employeeId} 位置更新:`, location);
+
+ // 通过locationTrackingService更新位置
+ await locationTrackingService.updateUserLocation(location);
+
+ console.log(`员工 ${employeeId} 位置更新完成`);
+ } catch (error) {
+ console.error('更新员工位置失败:', error);
+ showToast('更新位置失败');
+ throw error;
+ }
+ }
+
+ /**
+ * 订阅位置更新
+ */
+ private subscribeToLocationUpdates(): void {
+ console.log('订阅位置更新');
+ locationTrackingService.subscribeToLocationUpdates(this.handleLocationUpdates);
+ }
+
+ /**
+ * 取消订阅位置更新
+ */
+ private unsubscribeFromLocationUpdates(): void {
+ console.log('取消订阅位置更新');
+ locationTrackingService.unsubscribeFromLocationUpdates(this.handleLocationUpdates);
+ }
+
+ /**
+ * 处理位置更新回调
+ */
+ private handleLocationUpdates(locationData: any): void {
+ console.log('收到位置更新回调:', locationData);
+
+ if (locationData.type === 'onlineUserList' && locationData.users) {
+ // 处理在线用户列表更新
+ console.log('处理在线用户列表,用户数量:', locationData.users.length);
+ this.updateEmployeeMarkers(locationData.users);
+ } else if (Array.isArray(locationData)) {
+ // 处理位置更新数组(旧格式)
+ console.log('处理位置更新数组,用户数量:', locationData.length);
+ this.updateEmployeeMarkers(locationData);
+ } else if (locationData.userId && locationData.latitude && locationData.longitude) {
+ // 处理单个用户位置更新
+ console.log('处理单个用户位置更新:', locationData.userId);
+ this.updateSingleEmployeeMarker(locationData);
+ } else {
+ console.warn('未知的位置数据格式:', locationData);
+ }
+ }
+
+ /**
+ * 更新员工标记点
+ */
+ private updateEmployeeMarkers(onlineUsers: any[]): void {
+ console.log('开始更新员工标记点,在线用户数量:', onlineUsers.length);
+
+ // 获取地图模块来更新标记点
+ const mapModule = this.dataModule.getMapModule();
+ if (!mapModule) {
+ console.error('地图模块未找到,无法更新员工标记点');
+ return;
+ }
+
+ // 为每个在线用户创建标记点
+ const employeeMarkers = onlineUsers.map(user => {
+ // 获取用户角色信息
+ const userRole = user.role || this.getUserRole(user.userId);
+
+ // 解析位置信息(支持多种格式)
+ const longitude = user.longitude || (user.lastLocation && user.lastLocation.longitude) || 0;
+ const latitude = user.latitude || (user.lastLocation && user.lastLocation.latitude) || 0;
+ const lastUpdateTime = user.lastUpdateTime || Date.now();
+
+ const employeeMarker = {
+ id: 10000 + user.userId, // 避免ID冲突
+ type: 'employee',
+ title: user.userName || `员工${user.userId}`,
+ longitude: longitude,
+ latitude: latitude,
+ iconPath: this.getEmployeeIcon(user.userId, userRole),
+ width: 32,
+ height: 32,
+ zIndex: 30,
+ data: {
+ userId: user.userId,
+ role: userRole,
+ lastUpdateTime: lastUpdateTime
+ }
+ };
+
+ return employeeMarker;
+ });
+
+ // 调用地图模块更新员工标记点
+ mapModule.updateEmployeeMarkers(employeeMarkers);
+
+ console.log(`成功更新了 ${employeeMarkers.length} 个员工标记点`);
+ }
+
+ /**
+ * 更新单个员工标记点
+ */
+ private updateSingleEmployeeMarker(locationUpdate: any): void {
+ console.log('更新单个员工标记点:', locationUpdate.userId);
+
+ // 获取地图模块
+ const mapModule = this.dataModule.getMapModule();
+ if (!mapModule) {
+ console.error('地图模块未找到,无法更新员工标记点');
+ return;
+ }
+
+ // 获取用户角色信息
+ const userRole = this.getUserRole(locationUpdate.userId);
+
+ // 创建单个员工标记点
+ const employeeMarker = {
+ id: 10000 + locationUpdate.userId,
+ type: 'employee',
+ title: `员工${locationUpdate.userId}`,
+ longitude: locationUpdate.longitude,
+ latitude: locationUpdate.latitude,
+ iconPath: this.getEmployeeIcon(locationUpdate.userId, userRole),
+ width: 32,
+ height: 32,
+ zIndex: 30,
+ data: {
+ userId: locationUpdate.userId,
+ role: userRole,
+ lastUpdateTime: locationUpdate.timestamp || Date.now()
+ }
+ };
+
+ // 调用地图模块更新单个标记点
+ mapModule.updateSingleEmployeeMarker(employeeMarker);
+
+ console.log('单个员工标记点更新完成');
+ }
+
+ /**
+ * 获取用户角色
+ */
+ private getUserRole(userId: number): string {
+ // 从数据模块获取用户信息
+ const userInfo = this.dataModule.getData().userInfo;
+ if (userInfo && userInfo.id === userId) {
+ return userInfo.role || 'employee';
+ }
+
+ // 如果是其他用户,默认为货运人员
+ return 'employee';
+ }
+
+ /**
+ * 获取员工图标
+ */
+ private getEmployeeIcon(userId: number, userRole: string): string {
+ // 根据用户角色返回不同的图标
+ if (userRole === 'ADMIN') {
+ return '/images/crown.png'; // 管理员图标
+ } else {
+ return '/images/trucks.png'; // 货运人员图标
+ }
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/pages/index/modules/loginModule.ts b/miniprogram/pages/index/modules/loginModule.ts
index 9090709..b69fce0 100644
--- a/miniprogram/pages/index/modules/loginModule.ts
+++ b/miniprogram/pages/index/modules/loginModule.ts
@@ -234,9 +234,17 @@ export class LoginModule {
// 启动位置追踪服务
try {
+ // 先启动位置追踪服务
await locationTrackingService.startTrackingAfterSignIn();
console.log('位置追踪服务已启动');
+ // 然后调用位置模块的实时跟踪功能
+ const locationModule = this.dataModule.getLocationModule();
+ if (locationModule) {
+ await locationModule.startRealTimeTracking();
+ console.log('位置模块实时跟踪已启动');
+ }
+
// 订阅位置更新回调,采用统一方式更新所有用户位置
locationTrackingService.subscribeToLocationUpdates((onlineUsers) => {
console.log('🚚 位置更新回调 - 在线用户列表已更新,用户数量:', onlineUsers.length);
@@ -433,10 +441,8 @@ export class LoginModule {
// 根据服务器返回的状态映射到前端状态
switch (response.status) {
case 'signed_in':
- case 'online':
return 'signed_in';
case 'signed_out':
- case 'offline':
return 'signed_out';
case 'registered':
return 'registered';
diff --git a/miniprogram/pages/index/modules/mainPageModule.ts b/miniprogram/pages/index/modules/mainPageModule.ts
index 95a5440..26b65ca 100644
--- a/miniprogram/pages/index/modules/mainPageModule.ts
+++ b/miniprogram/pages/index/modules/mainPageModule.ts
@@ -3,17 +3,32 @@ import { LoginModule } from './loginModule';
import { MapModule } from './mapModule';
import { OrderModule } from './orderModule';
import { WarehouseModule } from './warehouseModule';
-import { DeliveryPersonModule } from './deliveryPersonModule';
+import { EmployeeModule } from './deliveryPersonModule';
+import { LocationModule } from './locationModule';
import { DataModule } from './dataModule';
import { showToast } from '../../../utils/helpers';
+// 主页面模块接口定义
+export interface MainPageModule {
+ getLoginModule(): LoginModule;
+ onLoad(): Promise;
+ onShow(): void;
+ onHide(): void;
+ onLogout(): void;
+ hideAllPanels(): void;
+ resetMarkers(): void;
+ cleanup(): void;
+ refreshAllData(): Promise;
+}
+
export class MainPageModule {
private pageContext: any;
private loginModule: LoginModule;
private mapModule: MapModule;
private orderModule: OrderModule;
private warehouseModule: WarehouseModule;
- private deliveryPersonModule: DeliveryPersonModule;
+ private employeeModule: EmployeeModule;
+ private locationModule: LocationModule;
private dataModule: DataModule;
constructor(pageContext: any) {
@@ -25,7 +40,8 @@ export class MainPageModule {
this.mapModule = new MapModule(pageContext, this.dataModule);
this.orderModule = new OrderModule(pageContext, this.dataModule);
this.warehouseModule = new WarehouseModule(pageContext, this.dataModule);
- this.deliveryPersonModule = new DeliveryPersonModule(pageContext, this.dataModule);
+ this.employeeModule = new EmployeeModule(pageContext, this.dataModule);
+ this.locationModule = new LocationModule(pageContext, this.dataModule);
}
/**
@@ -38,6 +54,9 @@ export class MainPageModule {
// 初始化应用
await this.initApp();
+ // 初始化位置模块
+ await this.locationModule.initialize();
+
// 加载地图数据
await this.loadAllData();
@@ -58,12 +77,22 @@ export class MainPageModule {
this.refreshDataIfNeeded();
}
+ /**
+ * 页面隐藏
+ */
+ onHide(): void {
+ console.log('主页面隐藏');
+
+ // 清理位置模块
+ this.locationModule.cleanup();
+ }
+
/**
* 初始化应用
*/
private async initApp(): Promise {
// 初始化数据
- this.dataModule.initializeData();
+ await this.dataModule.initializeData();
console.log('应用初始化');
}
@@ -119,7 +148,7 @@ export class MainPageModule {
// 并行加载各种业务数据(需要登录)
await Promise.all([
this.orderModule.loadPendingOrders(),
- this.deliveryPersonModule.loadDeliveryPersons()
+ this.employeeModule.loadAllEmployees()
]);
console.log('所有业务数据加载完成');
@@ -160,7 +189,7 @@ export class MainPageModule {
this.dataModule.updatePendingOrders([]);
const filteredMarkers = this.pageContext.data.markers.filter((marker: any) =>
- marker.type !== 'warehouse' && marker.type !== 'delivery_person'
+ marker.type !== 'warehouse' && marker.type !== 'employee'
);
this.dataModule.updateMarkers(filteredMarkers);
}
@@ -230,7 +259,7 @@ export class MainPageModule {
this.warehouseModule.onWarehouseMarkerClick(marker.data, e);
break;
case 'delivery_person':
- this.deliveryPersonModule.onDeliveryPersonMarkerClick(marker.data, e);
+ this.employeeModule.onDeliveryPersonMarkerClick(marker.data, e);
break;
default:
this.mapModule.onMarkerTap(e);
@@ -267,10 +296,17 @@ export class MainPageModule {
}
/**
- * 获取货运人员模块
+ * 获取员工模块
*/
- getDeliveryPersonModule(): DeliveryPersonModule {
- return this.deliveryPersonModule;
+ getEmployeeModule(): EmployeeModule {
+ return this.employeeModule;
+ }
+
+ /**
+ * 获取位置模块
+ */
+ getLocationModule(): LocationModule {
+ return this.locationModule;
}
/**
diff --git a/miniprogram/pages/index/modules/mapModule.ts b/miniprogram/pages/index/modules/mapModule.ts
index 8fd8f67..492e868 100644
--- a/miniprogram/pages/index/modules/mapModule.ts
+++ b/miniprogram/pages/index/modules/mapModule.ts
@@ -83,11 +83,16 @@ export class MapModule {
return;
}
- // 更新数据模块中的位置信息和缩放级别
+ // 强制重置地图视角到用户位置
+ // 1. 更新用户位置
this.dataModule.updateUserLocation(location.latitude, location.longitude);
- this.dataModule.setMapScale(15); // 定位成功后放大到更详细的级别
+ // 2. 重置地图缩放级别到15(详细级别)
+ this.dataModule.setMapScale(15);
+ // 3. 添加地图动画效果,让视角平滑回到用户位置
+ this.animateMapToUserLocation(location.latitude, location.longitude);
- console.log('[MAP MODULE] 定位成功,坐标已更新:', location);
+ console.log('[MAP MODULE] 定位成功,坐标已更新,视角已重置:', location);
+ showToast('已定位到您的位置');
} catch (error) {
console.error('[MAP MODULE] 定位失败:', error);
this.setDefaultLocation();
@@ -105,9 +110,44 @@ export class MapModule {
// 更新数据模块中的位置信息和缩放级别
this.dataModule.updateUserLocation(defaultLatitude, defaultLongitude);
this.dataModule.setMapScale(13);
+ // 动画效果回到默认位置
+ this.animateMapToUserLocation(defaultLatitude, defaultLongitude);
console.log('[MAP MODULE] 默认位置设置完成');
}
+
+ /**
+ * 动画效果将地图视角平滑移动到用户位置
+ */
+ private animateMapToUserLocation(latitude: number, longitude: number): void {
+ // 使用微信小程序的MapContext.moveToLocation方法实现地图移动
+ const mapContext = wx.createMapContext('myMap', this.pageContext);
+
+ // 先设置地图中心点
+ this.pageContext.setData({
+ latitude: latitude,
+ longitude: longitude
+ });
+
+ // 使用moveToLocation方法平滑移动地图视角
+ mapContext.moveToLocation({
+ latitude: latitude,
+ longitude: longitude,
+ success: () => {
+ console.log('[MAP MODULE] 地图视角移动成功,位置:', { latitude, longitude });
+ },
+ fail: (err: any) => {
+ console.error('[MAP MODULE] 地图视角移动失败:', err);
+ // 如果moveToLocation失败,直接设置中心点
+ this.pageContext.setData({
+ latitude: latitude,
+ longitude: longitude
+ });
+ }
+ });
+
+ console.log('[MAP MODULE] 地图视角动画已启动,移动到位置:', { latitude, longitude });
+ }
/**
* 加载地图数据(仓库和货运人员)
@@ -126,39 +166,30 @@ export class MapModule {
/**
* 生成初始标记点
*/
- private generateInitialMarkers(): Marker[] {
- // 获取当前坐标
- const longitude = this.pageContext.data.longitude;
- const latitude = this.pageContext.data.latitude;
+ public generateInitialMarkers(): Marker[] {
+ console.log('生成初始标记点');
- // 检查坐标是否为有效数字
- let validLongitude = longitude;
- let validLatitude = latitude;
+ const markers: Marker[] = [];
- if (isNaN(longitude) || isNaN(latitude)) {
- console.warn(`无效的坐标值: longitude=${longitude}, latitude=${latitude}, 使用默认坐标`);
- validLongitude = 102.833722; // 默认经度
- validLatitude = 24.880095; // 默认纬度
-
- // 更新页面数据中的坐标
- this.dataModule.updateUserLocation(validLatitude, validLongitude);
- }
-
-
-
- // 生成初始标记点
- return [
- {
- id: -1, // 用户位置标记
+ // 获取用户位置
+ const userLocation = this.pageContext.data.userLocation;
+ if (userLocation && userLocation.longitude && userLocation.latitude) {
+ // 添加用户位置标记点
+ markers.push({
+ id: -1,
title: '用户位置',
- longitude: validLongitude,
- latitude: validLatitude,
+ longitude: userLocation.longitude,
+ latitude: userLocation.latitude,
iconPath: '/images/trucks.png',
width: 40,
height: 40,
zIndex: 99
- }
- ];
+ });
+
+ console.log('已添加用户位置标记点');
+ }
+
+ return markers;
}
/**
@@ -191,6 +222,57 @@ export class MapModule {
console.log('用户头像已更新');
}
+ /**
+ * 更新员工标记点
+ */
+ updateEmployeeMarkers(employeeMarkers: Marker[]): void {
+ console.log('开始更新员工标记点,数量:', employeeMarkers.length);
+
+ const { markers } = this.pageContext.data;
+
+ // 过滤掉现有的员工标记点
+ const filteredMarkers = markers.filter((marker: any) =>
+ marker.type !== 'employee'
+ );
+
+ // 合并标记点
+ const updatedMarkers = [...filteredMarkers, ...employeeMarkers];
+
+ // 更新数据模块中的标记点
+ this.dataModule.updateMarkers(updatedMarkers);
+
+ console.log('员工标记点更新完成,总标记点数量:', updatedMarkers.length);
+ }
+
+ /**
+ * 更新单个员工标记点
+ */
+ updateSingleEmployeeMarker(employeeMarker: Marker): void {
+ console.log('更新单个员工标记点:', employeeMarker.id);
+
+ const { markers } = this.pageContext.data;
+
+ // 查找现有的员工标记点
+ const markerIndex = markers.findIndex((marker: any) =>
+ marker.type === 'employee' && marker.id === employeeMarker.id
+ );
+
+ let updatedMarkers;
+ if (markerIndex !== -1) {
+ // 更新现有的标记点
+ updatedMarkers = [...markers];
+ updatedMarkers[markerIndex] = employeeMarker;
+ } else {
+ // 添加新的标记点
+ updatedMarkers = [...markers, employeeMarker];
+ }
+
+ // 更新数据模块中的标记点
+ this.dataModule.updateMarkers(updatedMarkers);
+
+ console.log('单个员工标记点更新完成');
+ }
+
/**
* 处理地图点击事件
*/
diff --git a/miniprogram/services/apiService.ts b/miniprogram/services/apiService.ts
index 5f210f4..ce8e59c 100644
--- a/miniprogram/services/apiService.ts
+++ b/miniprogram/services/apiService.ts
@@ -16,7 +16,7 @@ console.log(`当前API地址: ${API_BASE_URL} (${IS_LOCAL_DEV ? '本地开发环
* 是否启用模拟模式
* 该配置控制是否使用模拟数据进行开发测试
*/
-export const isMockMode = false;
+
/**
* API服务类
@@ -28,6 +28,11 @@ class ApiService {
*/
private locationUpdateCallbacks: Set<(location: any) => void> | null;
+ /**
+ * 在线用户列表回调函数集合
+ */
+ private onlineUserListCallbacks: Set<(userList: any) => void> | null;
+
/**
* 位置更新WebSocket连接
*/
@@ -38,6 +43,7 @@ class ApiService {
*/
constructor() {
this.locationUpdateCallbacks = null;
+ this.onlineUserListCallbacks = null;
this.locationWebSocket = null;
}
@@ -194,6 +200,20 @@ async ServerLogin(code: string) {
return await this.request('/user/logout', { method: 'POST' });
}
+ /**
+ * 签退接口
+ * @param userId 用户ID
+ * @returns 签退结果
+ */
+ async userSignOut(userId: number): Promise<{ success: boolean; message?: string }> {
+ console.log('API userSignOut调用,参数userId:', userId);
+
+ return await this.request('/user/signout', {
+ method: 'POST',
+ data: { userId },
+ });
+ }
+
/**
* 获取用户签到状态
* @returns 用户状态信息
@@ -311,6 +331,52 @@ async ServerLogin(code: string) {
});
}
+ // ====== 员工管理接口 ======
+
+ /**
+ * 获取所有员工列表
+ * @returns 员工信息数组
+ */
+ async getEmployees(): Promise {
+ return await this.request('/employees');
+ }
+
+ /**
+ * 添加新员工
+ * @param employeeInfo 员工信息
+ * @returns 添加结果
+ */
+ async addEmployee(employeeInfo: { name: string; phone: string; role: string }): Promise {
+ return await this.request('/employees', {
+ method: 'POST',
+ data: employeeInfo,
+ });
+ }
+
+ /**
+ * 删除员工
+ * @param employeeId 员工ID
+ * @returns 删除结果
+ */
+ async deleteEmployee(employeeId: number): Promise<{ success: boolean; message?: string }> {
+ return await this.request<{ success: boolean; message?: string }>(`/employees/${employeeId}`, {
+ method: 'DELETE',
+ });
+ }
+
+ /**
+ * 更新员工信息
+ * @param employeeId 员工ID
+ * @param employeeInfo 员工信息
+ * @returns 更新结果
+ */
+ async updateEmployee(employeeId: number, employeeInfo: { name?: string; phone?: string; role?: string }): Promise {
+ return await this.request(`/employees/${employeeId}`, {
+ method: 'PUT',
+ data: employeeInfo,
+ });
+ }
+
// ==================== 仓库相关接口 ====================
/**
@@ -464,15 +530,6 @@ async ServerLogin(code: string) {
subscriptionId: string;
expiresAt: number;
}> {
- if (isMockMode) {
- // 模拟数据 - 返回模拟的订阅信息
- console.log('[MOCK] 订阅实时位置更新,货运人员ID列表:', deliveryPersonIds);
- return {
- subscriptionId: `mock-subscription-${Date.now()}`,
- expiresAt: Date.now() + 3600000 // 1小时后过期
- };
- }
-
// 真实环境中调用API
try {
return await this.request<{
@@ -493,12 +550,6 @@ async ServerLogin(code: string) {
* @param subscriptionId 订阅ID
*/
async unsubscribeFromRealTimeLocations(subscriptionId: string): Promise {
- if (isMockMode) {
- // 模拟数据 - 记录取消订阅操作
- console.log('[MOCK] 取消订阅实时位置更新,订阅ID:', subscriptionId);
- return;
- }
-
// 真实环境中调用API
try {
await this.request('/locations/unsubscribe', {
@@ -529,12 +580,31 @@ async ServerLogin(code: string) {
this.locationUpdateCallbacks.delete(callback);
// 如果没有回调了,可以关闭WebSocket连接
if (this.locationUpdateCallbacks.size === 0 && this.locationWebSocket) {
- this.locationWebSocket.close();
- this.locationWebSocket = null;
+ this.closeLocationWebSocket();
}
}
};
}
+
+ /**
+ * 注册在线用户列表回调函数
+ * @param callback 在线用户列表回调函数
+ * @returns 取消订阅函数
+ */
+ onOnlineUserList(callback: (userList: any) => void): () => void {
+ // 添加回调到集合
+ if (!this.onlineUserListCallbacks) {
+ this.onlineUserListCallbacks = new Set();
+ }
+ this.onlineUserListCallbacks.add(callback);
+
+ // 返回取消订阅函数
+ return () => {
+ if (this.onlineUserListCallbacks) {
+ this.onlineUserListCallbacks.delete(callback);
+ }
+ };
+ }
// ===== 私有属性和方法 =====
@@ -578,9 +648,21 @@ async ServerLogin(code: string) {
});
// WebSocket连接关闭事件
- wx.onSocketClose(() => {
- console.log('WebSocket连接已关闭');
+ wx.onSocketClose((res) => {
+ console.log('WebSocket连接已关闭', res);
this.locationWebSocket = null;
+
+ // 如果是非主动关闭,尝试重连
+ if (res.code !== 1000) { // 1000表示正常关闭
+ console.log('WebSocket连接异常关闭,将在3秒后尝试重连');
+ setTimeout(() => {
+ if (!this.locationWebSocket) {
+ this.initLocationWebSocket().catch(err => {
+ console.error('WebSocket重连失败:', err);
+ });
+ }
+ }, 3000);
+ }
});
// WebSocket错误事件
@@ -597,14 +679,28 @@ async ServerLogin(code: string) {
}
}
+ /**
+ * 检查WebSocket连接是否已建立
+ */
+ public isWebSocketConnected(): boolean {
+ return this.locationWebSocket !== null && this.locationWebSocket !== undefined;
+ }
+
/**
* 关闭位置更新WebSocket连接
*/
public async closeLocationWebSocket(): Promise {
if (this.locationWebSocket && typeof wx !== 'undefined' && typeof wx.closeSocket === 'function') {
- wx.closeSocket();
- this.locationWebSocket = null;
- console.log('WebSocket连接已关闭');
+ try {
+ // 先检查WebSocket状态,避免在连接过程中关闭
+ wx.closeSocket();
+ this.locationWebSocket = null;
+ console.log('WebSocket连接已关闭');
+ } catch (error) {
+ console.warn('关闭WebSocket连接时出现异常:', error);
+ // 即使关闭失败,也重置连接状态
+ this.locationWebSocket = null;
+ }
}
}
@@ -621,13 +717,53 @@ async ServerLogin(code: string) {
switch (message.type) {
case 'updateLocation':
// 处理位置更新消息
- this.onLocationUpdate(message);
+ this.triggerLocationUpdateCallbacks(message);
+ break;
+ case 'onlineUserList':
+ // 处理在线用户列表消息
+ this.triggerOnlineUserListCallbacks(message);
+ break;
+ case 'subscribed':
+ // 处理订阅成功响应
+ console.log('WebSocket订阅成功:', message);
break;
default:
console.warn('收到未知类型的WebSocket消息:', message);
break;
}
}
+
+ /**
+ * 触发位置更新回调
+ * @param locationUpdate 位置更新信息
+ */
+ private triggerLocationUpdateCallbacks(locationUpdate: any): void {
+ if (this.locationUpdateCallbacks) {
+ this.locationUpdateCallbacks.forEach(callback => {
+ try {
+ callback(locationUpdate);
+ } catch (error) {
+ console.error('位置更新回调执行失败:', error);
+ }
+ });
+ }
+ }
+
+ /**
+ * 触发在线用户列表回调
+ * @param userList 在线用户列表信息
+ */
+ private triggerOnlineUserListCallbacks(userList: any): void {
+ if (this.onlineUserListCallbacks) {
+ this.onlineUserListCallbacks.forEach(callback => {
+ try {
+ callback(userList);
+ } catch (error) {
+ console.error('在线用户列表回调执行失败:', error);
+ }
+ });
+ }
+ }
/**
* 发送WebSocket消息
@@ -652,62 +788,35 @@ async ServerLogin(code: string) {
}
/**
- * 订阅位置更新
- * @param deliveryPersonId 配送员ID
+ * 订阅位置更新(服务器自动处理,无需发送消息)
+ * @param userId 用户ID
*/
- async subscribeToLocationUpdates(deliveryPersonId: number): Promise {
- if (isMockMode) {
- console.log('[MOCK] 订阅位置更新');
- return;
- }
-
+ async subscribeToLocationUpdates(): Promise {
// 确保WebSocket连接已建立
if (!this.locationWebSocket) {
this.initLocationWebSocket();
}
- // 发送订阅消息
- const subscribeMessage = {
- type: 'subscribe',
- deliveryPersonId: deliveryPersonId,
- timestamp: Date.now()
- };
-
- this.sendWebSocketMessage(subscribeMessage);
+ // 服务器会自动处理订阅逻辑,无需发送订阅消息
+ console.log('位置订阅已初始化,服务器将自动处理订阅逻辑');
}
/**
- * 取消订阅位置更新
- * @param deliveryPersonId 配送员ID
+ * 取消订阅位置更新(服务器自动处理,无需发送消息)
+ * @param userId 用户ID
*/
- async unsubscribeFromLocationUpdates(deliveryPersonId: number): Promise {
- if (isMockMode) {
- console.log('[MOCK] 取消订阅位置更新');
- return;
- }
-
- // 发送取消订阅消息
- const unsubscribeMessage = {
- type: 'unsubscribe',
- deliveryPersonId: deliveryPersonId,
- timestamp: Date.now()
- };
-
- this.sendWebSocketMessage(unsubscribeMessage);
+ async unsubscribeFromLocationUpdates(userId: number): Promise {
+ // 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息
+ console.log('位置取消订阅已初始化,服务器将自动处理取消订阅逻辑');
}
/**
* 发送位置更新
- * @param deliveryPersonId 配送员ID
+ * @param userId 用户ID
* @param latitude 纬度
* @param longitude 经度
*/
- async sendLocationUpdate(deliveryPersonId: number, latitude: number, longitude: number): Promise {
- if (isMockMode) {
- console.log('[MOCK] 发送位置更新');
- return;
- }
-
+ async sendLocationUpdate(userId: number, latitude: number, longitude: number): Promise {
// 确保WebSocket连接已建立
if (!this.locationWebSocket) {
this.initLocationWebSocket();
@@ -716,7 +825,7 @@ async ServerLogin(code: string) {
// 发送位置更新消息
const locationMessage = {
type: 'updateLocation',
- deliveryPersonId: deliveryPersonId,
+ userId: userId,
latitude: latitude,
longitude: longitude,
timestamp: Date.now()
@@ -725,6 +834,8 @@ async ServerLogin(code: string) {
this.sendWebSocketMessage(locationMessage);
}
+
+
// ==================== 订单接口 ====================
/**
diff --git a/miniprogram/services/deliveryPersonService.ts b/miniprogram/services/deliveryPersonService.ts
index 664349a..6fe47e5 100644
--- a/miniprogram/services/deliveryPersonService.ts
+++ b/miniprogram/services/deliveryPersonService.ts
@@ -1,66 +1,18 @@
// 货运人员服务 - 处理货运人员相关的数据操作
-import { DeliveryPerson, LocationData } from '../types';
+import { DeliveryPerson } from '../types';
import apiService from './apiService';
-import { isMockMode } from './apiService';
/**
* 货运人员服务类
- * 提供货运人员信息管理、位置更新、状态管理等功能
+ * 提供货运人员信息管理、状态管理等功能
*/
class DeliveryPersonService {
- // 模拟货运人员数据
- private mockDeliveryPersons: DeliveryPerson[];
/**
* 构造函数
*/
constructor() {
- this.mockDeliveryPersons = [
- {
- id: 101,
- name: '张师傅',
- phone: '13812345678',
- status: 'idle',
- currentLocation: {
- longitude: 102.714585,
- latitude: 25.046321
- },
- currentOrders: []
- },
- {
- id: 102,
- name: '李师傅',
- phone: '13912345678',
- status: 'busy',
- currentLocation: {
- longitude: 102.690181,
- latitude: 24.994788
- },
- currentOrders: [
- {
- id: 1002,
- startPoint: {
- id: 2,
- name: '瓦尔塔蓄电池盘龙区分店',
- longitude: 102.728421,
- latitude: 25.042498
- },
- endPoint: {
- name: '云南中致远汽车销售有限公司',
- longitude: 102.796212,
- latitude: 24.936947
- },
- status: 'assigned',
- goodsType: '瓦尔塔EFB蓄电池',
- goodsWeight: 120,
- createTime: Date.now() - 7200000
- }
- ]
- }
- ];
- this.locationUpdateCallbacks = null;
- this.locationUpdateTimer = null;
- this.unsubscribeFunction = null;
+ // 不再使用模拟数据
}
/**
@@ -68,11 +20,6 @@ class DeliveryPersonService {
* @returns 货运人员列表
*/
async getDeliveryPersons(): Promise {
- if (isMockMode) {
- // 模拟数据 - 返回所有货运人员
- console.log('[MOCK] 获取所有货运人员');
- return [...this.mockDeliveryPersons];
- }
return apiService.getDeliveryPersons();
}
@@ -82,12 +29,6 @@ class DeliveryPersonService {
* @returns 货运人员信息或null
*/
async getDeliveryPersonById(id: number): Promise {
- if (isMockMode) {
- // 模拟数据 - 根据ID查找货运人员
- console.log('[MOCK] 根据ID获取货运人员,ID:', id);
- const person = this.mockDeliveryPersons.find(p => p.id === id);
- return person || null;
- }
try {
const result = await apiService.getDeliveryPersonById(id);
return result;
@@ -97,70 +38,11 @@ class DeliveryPersonService {
}
}
- /**
- * 更新货运人员位置
- * @param id 货运人员ID
- * @param location 位置信息
- */
- async updateDeliveryPersonLocation(
- id: number,
- location: { longitude: number; latitude: number }
- ): Promise {
- if (isMockMode) {
- // 模拟数据 - 更新货运人员位置
- console.log('[MOCK] 更新货运人员位置,ID:', id, '位置:', location);
- const person = this.mockDeliveryPersons.find(p => p.id === id);
- if (person) {
- person.currentLocation = location;
- }
- return;
- }
- return apiService.updateDeliveryPersonLocation(id, location);
- }
-
- /**
- * 批量更新货运人员位置
- * @param locations 位置信息数组
- */
- async batchUpdateDeliveryPersonLocations(locations: Array<{
- deliveryPersonId: number;
- longitude: number;
- latitude: number;
- timestamp: number;
- }>): Promise {
- if (isMockMode) {
- // 模拟数据 - 批量更新货运人员位置
- console.log('[MOCK] 批量更新货运人员位置');
- locations.forEach(item => {
- const person = this.mockDeliveryPersons.find(p => p.id === item.deliveryPersonId);
- if (person) {
- person.currentLocation = { longitude: item.longitude, latitude: item.latitude };
- }
- });
- return;
- }
- // 修复:转换参数格式以匹配apiService的要求
- const formattedLocations = locations.map(loc => ({
- userId: loc.deliveryPersonId, // 将deliveryPersonId转换为userId
- longitude: loc.longitude,
- latitude: loc.latitude,
- timestamp: loc.timestamp
- }));
- return apiService.batchUpdateDeliveryPersonLocations(formattedLocations);
- }
-
-
-
/**
* 获取空闲的货运人员
* @returns 空闲货运人员列表
*/
async getIdleDeliveryPersons(): Promise {
- if (isMockMode) {
- // 模拟数据 - 返回空闲的货运人员
- console.log('[MOCK] 获取空闲的货运人员');
- return this.mockDeliveryPersons.filter(p => p.status === 'idle');
- }
return apiService.getIdleDeliveryPersons();
}
@@ -169,230 +51,9 @@ class DeliveryPersonService {
* @returns 忙碌货运人员列表
*/
async getBusyDeliveryPersons(): Promise {
- if (isMockMode) {
- // 模拟数据 - 返回忙碌的货运人员
- console.log('[MOCK] 获取忙碌的货运人员');
- return this.mockDeliveryPersons.filter(p => p.status === 'busy');
- }
return apiService.getBusyDeliveryPersons();
}
- /**
- * 获取位置历史记录
- * @param deliveryPersonId 货运人员ID
- * @param limit 记录数量
- * @returns 位置历史记录
- */
- async getLocationHistory(deliveryPersonId: number, limit: number = 50): Promise> {
- if (isMockMode) {
- // 模拟数据 - 返回位置历史记录
- console.log('[MOCK] 获取货运人员位置历史记录,ID:', deliveryPersonId);
- const mockHistory = [];
- const person = this.mockDeliveryPersons.find(p => p.id === deliveryPersonId);
-
- if (person) {
- // 生成一些模拟的位置历史记录
- for (let i = 0; i < limit; i++) {
- // 在当前位置附近随机生成一些位置点
- const baseLongitude = person.currentLocation.longitude;
- const baseLatitude = person.currentLocation.latitude;
-
- mockHistory.push({
- timestamp: Date.now() - i * 60000, // 每分钟一个点
- longitude: baseLongitude + (Math.random() - 0.5) * 0.01,
- latitude: baseLatitude + (Math.random() - 0.5) * 0.01,
- speed: Math.random() * 60, // 0-60 km/h
- accuracy: 5 + Math.random() * 15 // 5-20 meters
- });
- }
- }
-
- return mockHistory;
- }
- return apiService.getLocationHistory(deliveryPersonId, limit);
- }
-
- /**
- * 订阅实时位置更新(兼容mapService参数)
- * @param callback 位置更新回调函数
- */
- async subscribeToRealTimeLocations(callback: (location: LocationData) => void): Promise {
- if (isMockMode) {
- // 模拟实时位置更新
- console.log('[MOCK] 订阅实时位置更新');
-
- // 保存回调引用,以便在取消订阅时使用
- if (!this.locationUpdateCallbacks) {
- this.locationUpdateCallbacks = new Set();
- }
- this.locationUpdateCallbacks.add(callback);
-
- // 如果是首次订阅,启动模拟更新
- if (!this.locationUpdateTimer) {
- this.startMockLocationUpdates();
- }
-
- return;
- }
-
- // 真实环境中实现WebSocket连接
- try {
- // 获取当前用户信息
- const userInfo = wx.getStorageSync('userInfo');
- if (!userInfo || !userInfo.id) {
- throw new Error('用户未登录,无法订阅位置更新');
- }
-
- // 初始化WebSocket连接
- await apiService.initLocationWebSocket();
-
- // 订阅位置更新
- await apiService.subscribeToLocationUpdates(userInfo.id);
-
- // 设置位置更新回调
- this.unsubscribeFunction = apiService.onLocationUpdate((location) => {
- callback(location as LocationData);
- });
-
- console.log('WebSocket位置订阅成功');
- } catch (error) {
- console.error('订阅实时位置更新失败:', error);
- throw error;
- }
- }
-
- /**
- * 取消订阅实时位置更新(兼容无参数调用)
- */
- async unsubscribeFromRealTimeLocations(): Promise {
- if (isMockMode) {
- // 模拟取消订阅
- console.log('[MOCK] 取消订阅实时位置更新');
-
- // 清除所有回调
- if (this.locationUpdateCallbacks) {
- this.locationUpdateCallbacks.clear();
- }
-
- // 停止模拟更新定时器
- if (this.locationUpdateTimer) {
- clearInterval(this.locationUpdateTimer);
- this.locationUpdateTimer = null;
- }
-
- return;
- }
-
- // 真实环境中关闭WebSocket连接
- try {
- // 获取当前用户信息
- const userInfo = wx.getStorageSync('userInfo');
- if (userInfo && userInfo.id) {
- // 取消订阅
- await apiService.unsubscribeFromLocationUpdates(userInfo.id);
- }
-
- // 调用取消订阅函数
- if (this.unsubscribeFunction) {
- this.unsubscribeFunction();
- this.unsubscribeFunction = null;
- }
-
- // 关闭WebSocket连接
- apiService.closeLocationWebSocket();
-
- console.log('WebSocket位置订阅已取消');
- } catch (error) {
- console.error('取消订阅实时位置更新失败:', error);
- throw error;
- }
- }
-
- /**
- * 发送位置更新到服务器
- * @param deliveryPersonId 配送员ID
- * @param latitude 纬度
- * @param longitude 经度
- */
- async sendLocationUpdate(deliveryPersonId: number, latitude: number, longitude: number): Promise {
- if (isMockMode) {
- console.log('[MOCK] 发送位置更新:', { deliveryPersonId, latitude, longitude });
- return;
- }
-
- try {
- await apiService.sendLocationUpdate(deliveryPersonId, latitude, longitude);
- console.log('位置更新发送成功');
- } catch (error) {
- console.error('发送位置更新失败:', error);
- throw error;
- }
- }
-
- // ===== 私有辅助方法 =====
-
- /**
- * 存储位置更新回调函数的集合
- */
- private locationUpdateCallbacks: Set<(location: LocationData) => void> | null;
-
- /**
- * 模拟位置更新的定时器
- */
- private locationUpdateTimer: number | null;
-
-
-
- /**
- * 取消订阅函数引用
- */
- private unsubscribeFunction: (() => void) | null;
-
- /**
- * 启动模拟位置更新
- */
- private startMockLocationUpdates(): void {
- // 每5秒发送一次模拟位置更新
- this.locationUpdateTimer = setInterval(() => {
- if (this.locationUpdateCallbacks && this.locationUpdateCallbacks.size > 0) {
- // 为每个货运人员生成模拟位置更新
- this.mockDeliveryPersons.forEach(person => {
- // 在当前位置附近随机生成新位置
- const baseLongitude = person.currentLocation.longitude;
- const baseLatitude = person.currentLocation.latitude;
-
- const newLocation: LocationData = {
- userId: person.id,
- longitude: baseLongitude + (Math.random() - 0.5) * 0.001, // 小范围随机移动
- latitude: baseLatitude + (Math.random() - 0.5) * 0.001, // 小范围随机移动
- timestamp: Date.now()
- };
-
- // 更新模拟数据中的位置
- person.currentLocation = {
- longitude: newLocation.longitude,
- latitude: newLocation.latitude
- };
-
- // 通知所有回调函数
- this.locationUpdateCallbacks!.forEach(callback => {
- try {
- callback(newLocation);
- } catch (error) {
- console.error('位置更新回调执行失败:', error);
- }
- });
- });
- }
- }, 5000); // 5秒更新一次
- }
-
/**
* 获取货运人员当前订单
* @param deliveryPersonId 货运人员ID
@@ -416,12 +77,6 @@ class DeliveryPersonService {
goodsWeight: number;
createTime: number;
}>> {
- if (isMockMode) {
- // 模拟数据 - 返回货运人员当前订单
- console.log('[MOCK] 获取货运人员当前订单,ID:', deliveryPersonId);
- const person = this.mockDeliveryPersons.find(p => p.id === deliveryPersonId);
- return person ? person.currentOrders : [];
- }
return apiService.getDeliveryPersonOrders(deliveryPersonId);
}
}
diff --git a/miniprogram/services/employeeService.ts b/miniprogram/services/employeeService.ts
new file mode 100644
index 0000000..4e7ed55
--- /dev/null
+++ b/miniprogram/services/employeeService.ts
@@ -0,0 +1,118 @@
+// 员工管理服务 - 处理员工相关的数据操作
+import { EmployeeInfo } from '../types';
+
+import apiService from './apiService';
+
+/**
+ * 员工管理服务类
+ * 提供员工信息的增删改查功能
+ */
+class EmployeeService {
+
+ /**
+ * 构造函数
+ */
+ constructor() {
+ // 不再使用模拟数据
+ }
+
+ /**
+ * 获取所有员工列表
+ * @returns 员工信息数组
+ */
+ async getEmployees(): Promise {
+ try {
+ return await apiService.getEmployees();
+ } catch (error) {
+ console.error('获取员工列表失败:', error);
+ // API调用失败时返回空数组
+ return [];
+ }
+ }
+
+ /**
+ * 添加新员工
+ * @param employeeInfo 员工信息
+ * @returns 添加的员工信息
+ */
+ async addEmployee(employeeInfo: { name: string; phone: string; role: string }): Promise {
+ try {
+ return await apiService.addEmployee(employeeInfo);
+ } catch (error) {
+ console.error('添加员工失败:', error);
+ throw new Error('添加员工失败,请稍后重试');
+ }
+ }
+
+ /**
+ * 删除员工
+ * @param employeeId 员工ID
+ * @returns 删除结果
+ */
+ async deleteEmployee(employeeId: number): Promise<{ success: boolean; message?: string }> {
+ try {
+ return await apiService.deleteEmployee(employeeId);
+ } catch (error) {
+ console.error('删除员工失败:', error);
+ return {
+ success: false,
+ message: '删除员工失败,请稍后重试'
+ };
+ }
+ }
+
+ /**
+ * 更新员工信息
+ * @param employeeId 员工ID
+ * @param employeeInfo 员工信息
+ * @returns 更新后的员工信息
+ */
+ async updateEmployee(employeeId: number, employeeInfo: { name?: string; phone?: string; role?: string }): Promise {
+ try {
+ return await apiService.updateEmployee(employeeId, employeeInfo);
+ } catch (error) {
+ console.error('更新员工信息失败:', error);
+ throw new Error('更新员工信息失败,请稍后重试');
+ }
+ }
+
+ /**
+ * 根据手机号查找员工
+ * @param phone 手机号
+ * @returns 员工信息或null
+ */
+ async findEmployeeByPhone(phone: string): Promise {
+ const employees = await this.getEmployees();
+ return employees.find(emp => emp.phone === phone) || null;
+ }
+
+ /**
+ * 验证员工信息(用于注册时检查)
+ * @param name 姓名
+ * @param phone 手机号
+ * @returns 验证结果
+ */
+ async validateEmployee(name: string, phone: string): Promise<{ success: boolean; message?: string; employee?: EmployeeInfo }> {
+ const employees = await this.getEmployees();
+ const employee = employees.find(emp => emp.name === name && emp.phone === phone);
+
+ if (employee) {
+ return {
+ success: true,
+ message: '员工信息验证成功',
+ employee
+ };
+ } else {
+ return {
+ success: false,
+ message: '员工信息不存在,请联系管理员添加'
+ };
+ }
+ }
+}
+
+/**
+ * 员工管理服务单例实例
+ * 导出供应用程序全局使用
+ */
+export default new EmployeeService();
\ No newline at end of file
diff --git a/miniprogram/services/locationTrackingService.ts b/miniprogram/services/locationTrackingService.ts
index fa04268..2fad5e6 100644
--- a/miniprogram/services/locationTrackingService.ts
+++ b/miniprogram/services/locationTrackingService.ts
@@ -1,8 +1,8 @@
// 位置追踪服务 - 处理用户签到后的位置追踪和状态管理
import { UserInfo, LocationData } from '../types';
import apiService from './apiService';
-import { isMockMode } from './apiService';
import userService from './userService';
+import mapService from './mapService';
/**
* 在线用户信息接口
@@ -11,6 +11,7 @@ export interface OnlineUserInfo {
userId: number;
name: string;
avatarUrl: string;
+ role: string; // 用户角色:'admin' | 'delivery_person'
lastLocation: LocationData;
lastUpdateTime: number;
status: 'online' | 'offline' | 'timeout';
@@ -73,17 +74,50 @@ class LocationTrackingService {
// 将当前用户添加到在线列表
await this.addUserToOnlineList(userInfo);
+ // 签到消息已通过REST API处理,服务器会自动处理订阅逻辑
+
+ // 设置实时位置订阅(初始化WebSocket连接)
+ await this.setupRealTimeSubscription();
+
+ // WebSocket连接成功后立即发送第一次位置信息
+ try {
+ const location = await this.getCurrentLocation();
+ await this.updateUserLocation(location);
+ console.log('WebSocket连接成功后立即发送第一次位置信息');
+ } catch (error) {
+ console.error('立即发送位置信息失败:', error);
+ }
+
// 启动位置更新定时器(每30秒更新一次)
this.startLocationUpdateTimer();
// 启动状态检查定时器(每60秒检查一次)
this.startStatusCheckTimer();
- // 订阅其他用户的位置更新
- await this.subscribeToOtherUsersLocations();
-
console.log('位置追踪服务已启动');
}
+
+ /**
+ * 初始化位置追踪服务
+ */
+ public async initialize(): Promise {
+ try {
+ // 注册位置更新回调
+ apiService.onLocationUpdate((locationUpdate) => {
+ this.handleLocationUpdate(locationUpdate);
+ });
+
+ // 注册在线用户列表回调
+ apiService.onOnlineUserList((userList) => {
+ this.handleOnlineUserList(userList);
+ });
+
+ console.log('[LocationTrackingService] 位置追踪服务初始化完成');
+ } catch (error) {
+ console.error('[LocationTrackingService] 初始化失败:', error);
+ throw error;
+ }
+ }
/**
* 用户签退后停止位置追踪
@@ -96,14 +130,16 @@ class LocationTrackingService {
this.isSignedIn = false;
+ // 签退消息已通过REST API处理,服务器会自动处理取消订阅逻辑
+
// 从在线列表中移除用户
this.removeUserFromOnlineList(userInfo.id);
// 停止定时器
this.stopTimers();
- // 取消订阅
- await this.unsubscribeFromLocations();
+ // 关闭实时位置订阅(服务器会自动处理取消订阅)
+ this.teardownRealTimeSubscription();
console.log('位置追踪服务已停止');
}
@@ -131,18 +167,12 @@ class LocationTrackingService {
timestamp: Date.now()
};
- if (isMockMode) {
- // 模拟模式:更新本地在线用户列表
- console.log('[位置追踪服务] 模拟模式:更新本地位置');
- this.updateLocalUserLocation(userInfo.id, locationData);
- } else {
- // 真实模式:调用API更新位置
- console.log('[位置追踪服务] 真实模式:调用API更新位置');
- await apiService.updateDeliveryPersonLocation(userInfo.id, location);
-
- // 同时更新本地缓存
- this.updateLocalUserLocation(userInfo.id, locationData);
- }
+ // 真实模式:通过WebSocket发送位置更新
+ console.log('[位置追踪服务] 真实模式:通过WebSocket发送位置更新');
+ await apiService.sendLocationUpdate(userInfo.id, location.latitude, location.longitude);
+
+ // 同时更新本地缓存
+ this.updateLocalUserLocation(userInfo.id, locationData);
// 通知所有回调
this.notifyLocationUpdateCallbacks();
@@ -206,6 +236,7 @@ class LocationTrackingService {
userId: userInfo.id,
name: userInfo.name || '未知用户',
avatarUrl: '/images/user-avatar.png',
+ role: userInfo.role || 'delivery_person', // 用户角色
lastLocation: currentLocation,
lastUpdateTime: Date.now(),
status: 'online'
@@ -213,7 +244,7 @@ class LocationTrackingService {
this.onlineUsers.set(userInfo.id, onlineUser);
-
+
console.log(`用户 ${userInfo.id} 已添加到在线列表`);
}
@@ -305,101 +336,49 @@ class LocationTrackingService {
throw new Error('用户未登录');
}
- // 这里应该调用地图服务获取当前位置
- // 暂时返回一个默认位置
- return {
- userId: userInfo.id,
- longitude: 102.833722,
- latitude: 24.880095,
- timestamp: Date.now()
- };
- }
-
- /**
- * 订阅其他用户的位置更新
- */
- private async subscribeToOtherUsersLocations(): Promise {
- if (isMockMode) {
- // 模拟模式:定时生成其他用户的位置更新
- this.startMockOtherUsersUpdates();
- } else {
- // 真实模式:通过WebSocket订阅
- await this.setupRealTimeSubscription();
+ try {
+ // 使用mapService获取真实位置,保持与项目中其他地方的一致性
+ const location = await mapService.getLocation();
+
+ console.log('[位置追踪服务] 获取真实位置成功:', location);
+
+ return {
+ userId: userInfo.id,
+ longitude: location.longitude,
+ latitude: location.latitude,
+ timestamp: Date.now()
+ };
+ } catch (error) {
+ console.error('[位置追踪服务] 获取真实位置失败,使用默认位置:', error);
+
+ // 定位失败时返回默认位置作为降级方案
+ return {
+ userId: userInfo.id,
+ longitude: 102.833722,
+ latitude: 24.880095,
+ timestamp: Date.now()
+ };
}
}
+
+
/**
* 取消订阅位置更新
*/
private async unsubscribeFromLocations(): Promise {
- if (!isMockMode) {
- // 真实模式:取消WebSocket订阅
- await this.teardownRealTimeSubscription();
- }
+ // 真实模式:取消WebSocket订阅
+ await this.teardownRealTimeSubscription();
}
- /**
- * 启动模拟的其他用户位置更新
- */
- private startMockOtherUsersUpdates(): void {
- // 模拟其他用户的位置更新(每45秒一次)
- setInterval(() => {
- if (this.isSignedIn) {
- this.generateMockOtherUsersLocations();
- this.notifyLocationUpdateCallbacks();
- }
- }, 45000);
- }
+
- /**
- * 生成模拟的其他用户位置
- */
- private generateMockOtherUsersLocations(): void {
- const currentUserInfo = userService.getGlobalUserInfo();
- if (!currentUserInfo) return;
-
- // 模拟2个其他在线用户
- const mockUserIds = [101, 102]; // 模拟用户ID
-
- mockUserIds.forEach(userId => {
- if (userId !== currentUserInfo.id) {
- const existingUser = this.onlineUsers.get(userId);
-
- if (existingUser) {
- // 更新现有用户位置
- existingUser.lastLocation.longitude += (Math.random() - 0.5) * 0.001;
- existingUser.lastLocation.latitude += (Math.random() - 0.5) * 0.001;
- existingUser.lastUpdateTime = Date.now();
- existingUser.status = 'online';
-
- this.onlineUsers.set(userId, existingUser);
- } else {
- // 添加新用户
- const newUser: OnlineUserInfo = {
- userId,
- name: `用户${userId}`,
- avatarUrl: '/images/user-avatar.png',
- lastLocation: {
- userId,
- longitude: 102.833722 + (Math.random() - 0.5) * 0.01,
- latitude: 24.880095 + (Math.random() - 0.5) * 0.01,
- timestamp: Date.now()
- },
- lastUpdateTime: Date.now(),
- status: 'online'
- };
-
- this.onlineUsers.set(userId, newUser);
- this.notifyUserStatusChange(userId, 'online');
- }
- }
- });
- }
+
/**
* 设置实时位置订阅
*/
- private setupRealTimeSubscription(): void {
+ private async setupRealTimeSubscription(): Promise {
// 获取用户信息
const userInfo = userService.getGlobalUserInfo();
if (!userInfo || !userInfo.id) {
@@ -410,25 +389,31 @@ class LocationTrackingService {
// 缓存用户信息
this.userInfo = userInfo;
- if (isMockMode) {
- console.log('[MOCK] 设置实时位置订阅');
- this.startMockLocationGeneration();
- return;
- }
-
- // 真实环境:初始化WebSocket连接并订阅位置更新
+ // 真实环境:初始化WebSocket连接(服务器会自动处理订阅逻辑)
try {
- // 使用新的API接口
- apiService.initLocationWebSocket();
+ // 初始化WebSocket连接(如果尚未连接)
+ await apiService.initLocationWebSocket();
- // 订阅当前用户的位置更新
- apiService.subscribeToLocationUpdates(this.userInfo.id)
- .then(() => {
- console.log('成功订阅位置更新');
- })
- .catch((error) => {
- console.error('订阅位置更新失败:', error);
- });
+ // 等待WebSocket连接建立(最多等待5秒)
+ await new Promise((resolve, reject) => {
+ let attempts = 0;
+ const maxAttempts = 50; // 5秒超时
+
+ const checkConnection = () => {
+ if (apiService.isWebSocketConnected()) {
+ resolve();
+ } else if (attempts < maxAttempts) {
+ attempts++;
+ setTimeout(checkConnection, 100);
+ } else {
+ reject(new Error('WebSocket连接建立超时'));
+ }
+ };
+ checkConnection();
+ });
+
+ // 服务器会自动处理订阅逻辑,无需发送订阅消息
+ console.log('WebSocket连接已建立,服务器将自动处理位置订阅逻辑');
// 设置位置更新回调
this.unsubscribeCallback = apiService.onLocationUpdate((locationUpdate) => {
@@ -444,24 +429,10 @@ class LocationTrackingService {
* 关闭实时位置订阅
*/
private teardownRealTimeSubscription(): void {
- if (isMockMode) {
- console.log('[MOCK] 关闭实时位置订阅');
- this.stopMockLocationGeneration();
- return;
- }
-
- // 真实环境:取消订阅并关闭WebSocket连接
+ // 真实环境:关闭WebSocket连接(服务器会自动处理取消订阅逻辑)
try {
- if (this.userInfo) {
- // 使用新的API接口取消订阅
- apiService.unsubscribeFromLocationUpdates(this.userInfo.id)
- .then(() => {
- console.log('成功取消订阅位置更新');
- })
- .catch((error) => {
- console.error('取消订阅位置更新失败:', error);
- });
- }
+ // 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息
+ console.log('关闭WebSocket连接,服务器将自动处理取消订阅逻辑');
// 移除位置更新回调
if (this.unsubscribeCallback) {
@@ -532,28 +503,88 @@ class LocationTrackingService {
});
}
- /**
- * 启动模拟位置生成
- */
- private startMockLocationGeneration(): void {
- console.log('[MOCK] 启动模拟位置生成');
- // 这里可以添加模拟位置生成的逻辑
- }
- /**
- * 停止模拟位置生成
- */
- private stopMockLocationGeneration(): void {
- console.log('[MOCK] 停止模拟位置生成');
- // 这里可以添加停止模拟位置生成的逻辑
- }
/**
* 处理位置更新
*/
private handleLocationUpdate(locationUpdate: any): void {
console.log('收到位置更新:', locationUpdate);
- // 这里可以添加处理位置更新的逻辑
+
+ // 验证位置更新消息格式
+ if (!locationUpdate || typeof locationUpdate !== 'object') {
+ console.warn('无效的位置更新消息格式');
+ return;
+ }
+
+ // 提取关键信息
+ const { deliveryPersonId, latitude, longitude, timestamp } = locationUpdate;
+
+ if (!deliveryPersonId || latitude === undefined || longitude === undefined) {
+ console.warn('位置更新消息缺少必要字段:', locationUpdate);
+ return;
+ }
+
+ console.log(`处理员工 ${deliveryPersonId} 的位置更新: (${longitude}, ${latitude})`);
+
+ // 更新本地缓存中的员工位置
+ this.updateLocalUserLocation(deliveryPersonId, {
+ userId: deliveryPersonId,
+ longitude: longitude,
+ latitude: latitude,
+ timestamp: timestamp || Date.now()
+ });
+
+ // 通知所有位置更新回调(UI层会监听这些回调来更新地图标记点)
+ this.notifyLocationUpdateCallbacks();
+
+ console.log(`员工 ${deliveryPersonId} 位置更新处理完成`);
+ }
+
+ /**
+ * 处理在线用户列表消息
+ * @param userList 在线用户列表消息
+ */
+ private handleOnlineUserList(userList: any): void {
+ try {
+ console.log('[LocationTrackingService] 收到在线用户列表:', userList);
+
+ if (!userList || !userList.users) {
+ console.warn('[LocationTrackingService] 无效的在线用户列表消息');
+ return;
+ }
+
+ // 清空当前在线用户列表
+ this.onlineUsers.clear();
+
+ // 更新在线用户列表
+ userList.users.forEach((userInfo: any) => {
+ if (userInfo && userInfo.userId) {
+ this.onlineUsers.set(userInfo.userId, {
+ userId: userInfo.userId,
+ name: userInfo.userName || '未知用户',
+ avatarUrl: '/images/user-avatar.png',
+ role: userInfo.role || 'delivery_person', // 用户角色,默认为配送员
+ lastLocation: {
+ userId: userInfo.userId,
+ longitude: userInfo.longitude || 0,
+ latitude: userInfo.latitude || 0,
+ timestamp: userInfo.lastUpdateTime || Date.now()
+ },
+ lastUpdateTime: userInfo.lastUpdateTime || Date.now(),
+ status: 'online'
+ });
+ }
+ });
+
+ console.log('[LocationTrackingService] 在线用户列表已更新,当前在线用户数:', this.onlineUsers.size);
+
+ // 通知位置模块更新地图标记点
+ this.notifyLocationUpdateCallbacks();
+
+ } catch (error) {
+ console.error('[LocationTrackingService] 处理在线用户列表失败:', error);
+ }
}
}
diff --git a/miniprogram/services/mapService.ts b/miniprogram/services/mapService.ts
index fb7d2be..0e346a8 100644
--- a/miniprogram/services/mapService.ts
+++ b/miniprogram/services/mapService.ts
@@ -1,6 +1,5 @@
// 地图服务 - 处理地图相关的功能,包括定位、路线规划、地理编码等
-import apiService from './apiService';
-import { isMockMode } from './apiService';
+
import { AMapRegeoResponse, RoutePlanResult, SearchResult, LocationData } from '../types';
/**
@@ -20,14 +19,7 @@ class MapService {
// 高德地图实例
private amapInstance: any;
- // 定时器引用,用于模拟实时位置更新
- private locationUpdateTimer: number | null;
-
- // 位置更新回调函数
- private locationUpdateCallback: ((location: LocationData) => void) | null;
-
- // 取消订阅函数
- private unsubscribeFunction: (() => void) | null;
+
/**
* 构造函数,初始化地图实例
@@ -40,9 +32,6 @@ class MapService {
latitude: 24.880095
};
this.amapInstance = new this.amapFile.AMapWX({ key: this.MAP_KEY });
- this.locationUpdateTimer = null;
- this.locationUpdateCallback = null;
- this.unsubscribeFunction = null;
}
/**
@@ -50,15 +39,6 @@ class MapService {
* @returns 用户位置坐标
*/
async getLocation(): Promise<{ longitude: number; latitude: number }> {
- if (isMockMode) {
- // 模拟位置 - 昆明市中心
- console.log('[MOCK] 获取用户位置信息');
- return {
- longitude: 102.7123,
- latitude: 25.0409
- };
- }
-
return new Promise((resolve, reject) => {
console.log('开始调用高德地图SDK获取位置...');
@@ -149,18 +129,6 @@ class MapService {
speed?: number;
altitude?: number;
}> {
- if (isMockMode) {
- // 模拟位置 - 昆明市中心
- console.log('[MOCK] 获取当前位置信息(兼容格式)');
- return {
- latitude: 25.0409,
- longitude: 102.7123,
- accuracy: 50,
- speed: 0,
- altitude: 1891
- };
- }
-
const location = await this.getLocation();
return {
latitude: location.latitude,
@@ -178,23 +146,6 @@ class MapService {
* @returns 逆地理编码结果
*/
async getLocationInfo(longitude: number, latitude: number): Promise {
- if (isMockMode) {
- // 模拟逆地理编码结果 - 完全符合AMapRegeoResponse接口
- console.log('[MOCK] 获取位置详细信息(逆地理编码),坐标:', longitude, latitude);
- return {
- regeocode: {
- addressComponent: {
- province: '云南省',
- city: '昆明市',
- district: '五华区',
- street: '',
- township: ''
- }
- },
- status: '1'
- };
- }
-
return new Promise((resolve, reject) => {
this.amapInstance.getRegeo({
location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`,
@@ -225,32 +176,6 @@ class MapService {
* @returns 搜索结果列表
*/
async searchPoi(keyword: string, city: string): Promise {
- if (isMockMode) {
- // 模拟搜索结果
- console.log('[MOCK] 搜索地点,关键词:', keyword, '城市:', city);
- const mockResults: SearchResult[] = [
- {
- id: '1',
- name: '昆明火车站',
- address: '昆明市官渡区北京路',
- longitude: 102.7222,
- latitude: 25.0157,
- phone: ''
- },
- {
- id: '2',
- name: '昆明长水国际机场',
- address: '昆明市官渡区长水村',
- longitude: 102.9292,
- latitude: 25.1012,
- phone: ''
- }
- ];
- return mockResults.filter(item =>
- item.name.includes(keyword) || item.address.includes(keyword)
- );
- }
-
return new Promise((resolve, reject) => {
this.amapInstance.getPoiAround({
querykeywords: keyword,
@@ -289,22 +214,6 @@ class MapService {
* @returns 路线规划结果
*/
async getDrivingRoute(origin: string, destination: string): Promise {
- if (isMockMode) {
- // 模拟路线规划结果
- console.log('[MOCK] 规划驾车路线,起点:', origin, '终点:', destination);
- const [originLng, originLat] = origin.split(',').map(Number);
- const [destLng, destLat] = destination.split(',').map(Number);
-
- const distance = this.calculateDistance(originLat, originLng, destLat, destLng);
- const duration = Math.round(distance / 10); // 假设平均速度10米/秒
-
- return {
- polyline: '',
- distance,
- duration
- };
- }
-
return new Promise((resolve, reject) => {
this.amapInstance.getDrivingRoute({
origin,
@@ -369,41 +278,16 @@ class MapService {
* 获取路线规划(通用版本)
* @param origin 起点坐标
* @param destination 终点坐标
- * @param mode 交通方式
* @returns 路线规划结果
*/
async getRoute(
origin: { latitude: number; longitude: number },
- destination: { latitude: number; longitude: number },
- mode: 'driving' | 'walking' | 'bicycling' = 'driving'
+ destination: { latitude: number; longitude: number }
): Promise<{
distance: number; // 距离(米)
duration: number; // 时间(秒)
polyline: { latitude: number; longitude: number }[];
}> {
- if (isMockMode) {
- // 模拟路线规划
- console.log('[MOCK] 获取路线规划,起点:', origin, '终点:', destination, '模式:', mode);
- const distance = this.calculateDistance(
- origin.latitude, origin.longitude,
- destination.latitude, destination.longitude
- );
-
- const duration = Math.round(distance / 10); // 假设平均速度10米/秒
-
- // 简单的直线路径
- const polyline = [
- origin,
- destination
- ];
-
- return {
- distance,
- duration,
- polyline
- };
- }
-
// 对于真实模式,目前只支持驾车路线
const originStr = `${origin.longitude},${origin.latitude}`;
const destStr = `${destination.longitude},${destination.latitude}`;
@@ -442,34 +326,6 @@ class MapService {
longitude: number;
formattedAddress: string;
}> {
- if (isMockMode) {
- // 模拟地理编码结果
- console.log('[MOCK] 地理编码,地址:', address);
- const mockLocations: { [key: string]: { latitude: number; longitude: number; formattedAddress: string } } = {
- '昆明市五华区二环西路599号': {
- latitude: 25.055281,
- longitude: 102.705745,
- formattedAddress: '云南省昆明市五华区二环西路599号'
- },
- '昆明市盘龙区北京路1188号': {
- latitude: 25.042498,
- longitude: 102.728421,
- formattedAddress: '云南省昆明市盘龙区北京路1188号'
- },
- '昆明市西山区滇池路1234号': {
- latitude: 25.028234,
- longitude: 102.689190,
- formattedAddress: '云南省昆明市西山区滇池路1234号'
- }
- };
-
- return mockLocations[address] || {
- latitude: 25.0409,
- longitude: 102.7123,
- formattedAddress: '云南省昆明市'
- };
- }
-
// 对于真实模式,使用搜索API来模拟地理编码
const results = await this.searchPoi(address, '昆明');
if (results.length > 0) {
@@ -502,18 +358,6 @@ class MapService {
district: string;
street: string;
}> {
- if (isMockMode) {
- console.log('[MOCK] 逆地理编码,坐标:', latitude, longitude);
- return {
- formattedAddress: '云南省昆明市五华区',
- country: '中国',
- province: '云南省',
- city: '昆明市',
- district: '五华区',
- street: ''
- };
- }
-
const result = await this.getLocationInfo(longitude, latitude);
return {
@@ -548,39 +392,6 @@ class MapService {
address: string;
distance: number;
}>> {
- if (isMockMode) {
- // 模拟附近地点
- console.log('[MOCK] 获取附近的地点,坐标:', latitude, longitude, '半径:', radius, '类型:', type);
- const mockPlaces = [
- {
- id: '1',
- name: '昆明火车站',
- latitude: 25.0157,
- longitude: 102.7222,
- address: '昆明市官渡区北京路',
- distance: this.calculateDistance(latitude, longitude, 25.0157, 102.7222)
- },
- {
- id: '2',
- name: '昆明长水国际机场',
- latitude: 25.1012,
- longitude: 102.9292,
- address: '昆明市官渡区长水村',
- distance: this.calculateDistance(latitude, longitude, 25.1012, 102.9292)
- },
- {
- id: '3',
- name: '翠湖公园',
- latitude: 25.0486,
- longitude: 102.7042,
- address: '昆明市五华区翠湖公园',
- distance: this.calculateDistance(latitude, longitude, 25.0486, 102.7042)
- }
- ];
-
- return mockPlaces.filter(place => place.distance <= radius);
- }
-
// 对于真实模式,使用搜索API
const keyword = type ? type : '';
const results = await this.searchPoi(keyword, '昆明');
@@ -595,164 +406,13 @@ class MapService {
})).filter(place => place.distance <= radius);
}
- /**
- * 获取位置历史记录
- * @param userId 用户ID
- * @param limit 记录数量限制
- * @returns 位置历史记录列表
- */
- async getLocationHistory(userId: number, limit: number = 50): Promise> {
- if (isMockMode) {
- // 模拟位置历史记录
- console.log('[MOCK] 获取位置历史记录,用户ID:', userId, '限制:', limit);
- const history: Array<{ id: number; latitude: number; longitude: number; timestamp: number; accuracy?: number; speed?: number; altitude?: number }> = [];
-
- const baseLat = 25.0409;
- const baseLng = 102.7123;
-
- for (let i = 0; i < limit; i++) {
- history.push({
- id: i + 1,
- latitude: baseLat + (Math.random() - 0.5) * 0.01,
- longitude: baseLng + (Math.random() - 0.5) * 0.01,
- timestamp: Date.now() - i * 3600000, // 每小时一个记录
- accuracy: 20 + Math.random() * 30,
- speed: Math.random() * 10,
- altitude: 1891 + (Math.random() - 0.5) * 20
- });
- }
-
- return history.sort((a, b) => b.timestamp - a.timestamp);
- }
- // 修复参数名不匹配问题,并转换返回值类型以匹配mapService定义
- const apiResults = await apiService.getLocationHistory(userId, limit);
- return apiResults.map((result, index) => ({
- ...result,
- id: index + 1, // 添加id字段
- altitude: 1891 + (Math.random() - 0.5) * 20 // 添加默认海拔高度
- })).sort((a, b) => b.timestamp - a.timestamp);
- }
+
- /**
- * 批量更新货运人员位置
- * @param locations 位置信息数组
- */
- async batchUpdateDeliveryPersonLocations(
- locations: Array<{ userId: number; latitude: number; longitude: number; timestamp: number }>
- ): Promise {
- if (isMockMode) {
- console.log('[MOCK] 批量更新货运人员位置:', locations);
- return;
- }
- return apiService.batchUpdateDeliveryPersonLocations(locations);
- }
+
- /**
- * 订阅实时位置更新
- * @param callback 位置更新回调函数
- */
- async subscribeToRealTimeLocations(callback: (location: LocationData) => void): Promise {
- if (isMockMode) {
- // 模拟模式下,定期生成随机位置更新
- console.log('[MOCK] 订阅实时位置更新');
-
- // 保存回调引用
- this.locationUpdateCallback = callback;
-
- // 启动模拟更新定时器
- if (!this.locationUpdateTimer) {
- this.locationUpdateTimer = setInterval(() => {
- // 生成随机位置更新数据
- const randomLocation: LocationData = {
- userId: Math.floor(Math.random() * 100) + 100, // 模拟货运人员ID
- longitude: 102.714585 + (Math.random() - 0.5) * 0.05, // 昆明附近随机经度
- latitude: 25.046321 + (Math.random() - 0.5) * 0.05, // 昆明附近随机纬度
- timestamp: Date.now()
- };
-
- // 调用回调函数
- if (this.locationUpdateCallback) {
- this.locationUpdateCallback(randomLocation);
- }
- }, 3000); // 每3秒更新一次
- }
-
- return;
- }
-
- // 真实环境中调用API服务的订阅方法
- try {
- // 获取当前用户信息
- const userInfo = wx.getStorageSync('userInfo');
- if (!userInfo || !userInfo.id) {
- throw new Error('用户未登录,无法订阅位置更新');
- }
-
- // 初始化WebSocket连接
- await apiService.initLocationWebSocket();
-
- // 订阅位置更新
- await apiService.subscribeToLocationUpdates(userInfo.id);
-
- // 使用新的onLocationUpdate方法来注册回调
- this.unsubscribeFunction = apiService.onLocationUpdate((location) => {
- callback(location as LocationData);
- });
- } catch (error) {
- console.error('订阅实时位置更新失败:', error);
- throw error;
- }
- }
+
- /**
- * 取消订阅实时位置更新
- */
- async unsubscribeFromRealTimeLocations(): Promise {
- if (isMockMode) {
- // 模拟模式下,清除定时器
- console.log('[MOCK] 取消订阅实时位置更新');
-
- if (this.locationUpdateTimer) {
- clearInterval(this.locationUpdateTimer);
- this.locationUpdateTimer = null;
- }
-
- // 清除回调引用
- this.locationUpdateCallback = null;
-
- return;
- }
-
- // 真实环境中调用API服务的取消订阅方法
- try {
- // 获取当前用户信息
- const userInfo = wx.getStorageSync('userInfo');
- if (userInfo && userInfo.id) {
- // 取消订阅
- await apiService.unsubscribeFromLocationUpdates(userInfo.id);
- }
-
- // 调用取消订阅函数
- if (this.unsubscribeFunction) {
- this.unsubscribeFunction();
- this.unsubscribeFunction = null;
- }
-
- // 关闭WebSocket连接
- apiService.closeLocationWebSocket();
- } catch (error) {
- console.error('取消订阅实时位置更新失败:', error);
- throw error;
- }
- }
+
/**
* 格式化路线距离
diff --git a/miniprogram/services/orderService.ts b/miniprogram/services/orderService.ts
index 2f683b6..7ba97de 100644
--- a/miniprogram/services/orderService.ts
+++ b/miniprogram/services/orderService.ts
@@ -1,80 +1,19 @@
import { Order } from '../types';
import apiService from './apiService';
-import { isMockMode } from './apiService';
class OrderService {
- // 模拟订单数据
- private mockOrders: Order[];
/**
- * 构造函数,初始化模拟订单数据
+ * 构造函数
*/
constructor() {
- this.mockOrders = [
- {
- id: 1001,
- startPoint: {
- id: 1,
- name: '瓦尔塔蓄电池昆明总店',
- longitude: 102.705745,
- latitude: 25.055281
- },
- endPoint: {
- name: '昆明德众汽车销售服务有限公司',
- longitude: 102.714686,
- latitude: 25.047134
- },
- status: 'pending',
- goodsType: '瓦尔塔AGM蓄电池',
- goodsWeight: 50,
- createTime: Date.now() - 3600000 // 1小时前创建
- },
- {
- id: 1002,
- startPoint: {
- id: 2,
- name: '瓦尔塔蓄电池盘龙区分店',
- longitude: 102.728421,
- latitude: 25.042498
- },
- endPoint: {
- name: '云南中致远汽车销售有限公司',
- longitude: 102.796212,
- latitude: 24.936947
- },
- status: 'pending',
- goodsType: '瓦尔塔EFB蓄电池',
- goodsWeight: 120,
- createTime: Date.now() - 7200000 // 2小时前创建
- },
- {
- id: 1003,
- startPoint: {
- id: 3,
- name: '瓦尔塔蓄电池西山区分店',
- longitude: 102.689190,
- latitude: 25.028234
- },
- endPoint: {
- name: '昆明宝远汽车销售服务有限公司',
- longitude: 102.756212,
- latitude: 24.986947
- },
- status: 'pending',
- goodsType: '瓦尔塔普通铅酸蓄电池',
- goodsWeight: 80,
- createTime: Date.now() - 10800000 // 3小时前创建
- }
- ];
+ // 不再使用模拟数据
}
/**
* 获取所有待处理订单
*/
async getPendingOrders(): Promise {
- if (isMockMode) {
- return [...this.mockOrders];
- }
return apiService.getPendingOrders();
}
@@ -82,10 +21,6 @@ class OrderService {
* 根据ID获取订单
*/
async getOrderById(id: number): Promise {
- if (isMockMode) {
- const order = this.mockOrders.find(o => o.id === id);
- return order || null;
- }
return apiService.getOrderById(id);
}
@@ -96,20 +31,6 @@ class OrderService {
* @returns 指派结果,包含success状态和可选消息
*/
async assignOrder(orderId: number, deliveryPersonId: number): Promise<{ success: boolean; message?: string }> {
- if (isMockMode) {
- // 模拟数据 - 查找订单并更新状态
- const order = this.mockOrders.find(o => o.id === orderId);
- if (order) {
- order.status = 'assigned';
- order.deliveryPersonId = deliveryPersonId;
- console.log(`[MOCK] 订单 ${orderId} 已指派给货运人员 ${deliveryPersonId}`);
- return { success: true, message: '指派成功' };
- } else {
- console.warn(`[MOCK] 订单 ${orderId} 不存在`);
- return { success: false, message: '订单不存在' };
- }
- }
-
// 真实环境中调用API
try {
return await apiService.assignOrder(orderId, deliveryPersonId);
@@ -123,15 +44,6 @@ class OrderService {
* 更新订单状态
*/
async updateOrderStatus(orderId: number, status: Order['status']): Promise<{ success: boolean; message?: string }> {
- if (isMockMode) {
- const order = this.mockOrders.find(o => o.id === orderId);
- if (order) {
- order.status = status;
- console.log(`[MOCK] 订单 ${orderId} 状态更新为 ${status}`);
- return { success: true, message: '状态更新成功' };
- }
- return { success: false, message: '订单不存在' };
- }
return apiService.updateOrderStatus(orderId, status).then(result => ({
success: result.success,
message: result.message || '状态更新成功'
@@ -142,15 +54,6 @@ class OrderService {
* 创建新订单
*/
async createOrder(orderData: Omit): Promise {
- if (isMockMode) {
- const newOrder: Order = {
- ...orderData,
- id: Math.max(...this.mockOrders.map(o => o.id), 1000) + 1,
- createTime: Date.now()
- };
- this.mockOrders.push(newOrder);
- return newOrder;
- }
return apiService.createOrder(orderData);
}
@@ -158,10 +61,6 @@ class OrderService {
* 获取货运人员的订单列表
*/
async getDeliveryPersonOrders(deliveryPersonId: number): Promise {
- if (isMockMode) {
- // 模拟模式下返回部分订单
- return this.mockOrders.slice(0, 2);
- }
return apiService.getDeliveryPersonOrders(deliveryPersonId);
}
@@ -169,17 +68,14 @@ class OrderService {
* 删除订单
* @param orderId 订单ID
*/
- async deleteOrder(orderId: number): Promise {
- if (isMockMode) {
- // 在模拟数据中删除订单
- const index = this.mockOrders.findIndex(o => o.id === orderId);
- if (index !== -1) {
- this.mockOrders.splice(index, 1);
- console.log(`订单 ${orderId} 已删除`);
- }
- return;
+ async deleteOrder(orderId: number): Promise<{ success: boolean; message?: string }> {
+ try {
+ const result = await apiService.deleteOrder(orderId);
+ return { success: true, message: '删除成功' };
+ } catch (error) {
+ console.error('删除订单失败:', error);
+ return { success: false, message: error instanceof Error ? error.message : '删除失败' };
}
- return apiService.deleteOrder(orderId);
}
}
diff --git a/miniprogram/services/userService.ts b/miniprogram/services/userService.ts
index 75281ee..b2ae98c 100644
--- a/miniprogram/services/userService.ts
+++ b/miniprogram/services/userService.ts
@@ -1,32 +1,21 @@
// 用户服务文件
-// 用户服务 - 处理用户相关的数据操作
+// 用户服务 - 处理用户认证、会话管理、权限验证等
import { UserInfo, EmployeeInfo } from '../types';
+import { Role } from '../utils/roleUtils';
import apiService from './apiService';
-import { isMockMode } from './apiService';
/**
* 用户服务类
* 提供用户认证、信息管理、权限验证等功能
*/
class UserService {
- // 模拟用户数据
- private mockUsers: UserInfo[];
/**
- * 构造函数,初始化模拟用户数据
+ * 构造函数
*/
constructor() {
- this.mockUsers = [
- {
- id: 1,
- role: 'ADMIN'
- },
- {
- id: 2,
- role: 'DELIVERY_PERSON'
- }
- ];
+ // 不再使用模拟数据
}
/**
@@ -34,14 +23,6 @@ class UserService {
* @returns 用户信息
*/
async getUserInfo(): Promise {
- if (isMockMode) {
- // 模拟数据 - 返回管理员信息
- console.log('[MOCK] 获取用户信息');
- return {
- id: 1,
- role: 'ADMIN'
- };
- }
return apiService.getUserInfo();
}
@@ -49,11 +30,6 @@ class UserService {
* 用户退出登录
*/
async logout(): Promise {
- if (isMockMode) {
- // 模拟登出
- console.log('[MOCK] 用户登出');
- return;
- }
return apiService.logout();
}
@@ -80,7 +56,7 @@ class UserService {
* @returns 是否为管理员
*/
isAdmin(): boolean {
- return this.getUserRole() === 'ADMIN';
+ return this.getUserRole() === Role.ADMIN;
}
/**
@@ -88,7 +64,7 @@ class UserService {
* @returns 是否为货运人员
*/
isDeliveryPerson(): boolean {
- return this.getUserRole() === 'DELIVERY_PERSON';
+ return this.getUserRole() === Role.DELIVERY_PERSON;
}
/**
@@ -194,25 +170,6 @@ class UserService {
*/
async ServerLogin(code: string): Promise<{ success: boolean; openid?: string; token?: string; userInfo?: UserInfo }> {
try {
- if (isMockMode) {
- // 模拟登录成功
- console.log('[MOCK] 微信小程序登录');
-
- // 使用模拟用户数据
- const userInfo = this.mockUsers[0];
- const openid = 'mock-openid-' + Math.random().toString(36).substring(2);
- const token = 'mock-token-' + Date.now();
- const session_key = 'mock-session-key-' + Math.random().toString(36).substring(2);
-
- // 保存到本地存储
- wx.setStorageSync('userInfo', userInfo);
- wx.setStorageSync('token', token);
- wx.setStorageSync('openid', openid);
- wx.setStorageSync('session_key', session_key);
-
- return { success: true, openid, token, userInfo };
- }
-
// 真实API模式
//TODO: 登录成功的基础数据:服务器下发的公共游客可看的数据
const result = await apiService.ServerLogin(code);
@@ -268,14 +225,6 @@ class UserService {
* @returns 更新后的用户信息
*/
async updateUserInfo(userInfo: Partial): Promise {
- if (isMockMode) {
- // 模拟更新成功
- console.log('[MOCK] 更新用户信息');
- return {
- id: 1,
- role: userInfo.role || 'GUEST'
- };
- }
return apiService.updateUserInfo(userInfo);
}
@@ -284,14 +233,6 @@ class UserService {
* @returns 用户状态信息
*/
async getUserStatus(): Promise<{ status: 'signed_in' | 'signed_out' | 'registered' | 'unregistered'; lastSignInTime?: string; lastSignOutTime?: string }> {
- if (isMockMode) {
- // 模拟数据 - 默认返回已注册状态
- console.log('[MOCK] 获取用户状态');
- return {
- status: 'registered'
- };
- }
-
try {
// 调用服务器接口获取用户状态
const response = await apiService.getUserStatus();
@@ -328,24 +269,26 @@ class UserService {
throw new Error('用户认证信息缺失,请重新登录');
}
- if (isMockMode) {
- // 模拟签到成功,返回员工信息
- console.log('[MOCK] 用户签到成功');
- const employeeInfo: EmployeeInfo = {
- id: userInfo.id,
- name: '测试用户',
- phone: '13800138000',
- role: userInfo.role || 'DELIVERY_PERSON'
- };
-
- return {
- success: true,
- employeeInfo: employeeInfo,
- message: '签到成功'
- };
+ return apiService.userSignIn(userInfo.id);
+ }
+
+ /**
+ * 用户签退
+ * @returns 签退结果
+ */
+ async signOut(): Promise<{ success: boolean; message?: string }> {
+ const userInfo = this.getGlobalUserInfo();
+ if (!userInfo || !userInfo.id) {
+ throw new Error('用户未登录,无法签退');
+ }
+
+ // 检查是否有有效的token(防止后端空指针异常)
+ const app = getApp();
+ if (!app.globalData.token) {
+ throw new Error('用户认证信息缺失,请重新登录');
}
- return apiService.userSignIn(userInfo.id);
+ return apiService.userSignOut(userInfo.id);
}
/**
@@ -354,29 +297,6 @@ class UserService {
* @returns 注册结果和员工信息
*/
async register(registerInfo: { name: string; phone: string }): Promise<{ success: boolean; employeeInfo: EmployeeInfo; message?: string }> {
- if (isMockMode) {
- // 模拟注册成功
- console.log('[MOCK] 用户注册成功');
- // 确保所有必需字段都有值
- const employeeInfo: EmployeeInfo = {
- id: Date.now(),
- name: registerInfo.name,
- phone: registerInfo.phone,
- role: 'DELIVERY_PERSON'
- };
- this.mockUsers.push({
- id: employeeInfo.id,
- role: employeeInfo.role || 'DELIVERY_PERSON',
- name: employeeInfo.name,
- phone: employeeInfo.phone
- });
- return {
- success: true,
- employeeInfo: employeeInfo,
- message: '注册成功'
- };
- }
-
return apiService.userRegister(registerInfo);
}
@@ -385,20 +305,6 @@ class UserService {
* @returns 权限列表
*/
async getUserPermissions(): Promise {
- if (isMockMode) {
- // 模拟权限列表
- console.log('[MOCK] 获取用户权限列表');
- return [
- 'order:view',
- 'order:edit',
- 'delivery:view',
- 'delivery:edit',
- 'warehouse:view',
- 'warehouse:edit',
- 'user:view',
- 'user:edit'
- ];
- }
return apiService.getUserPermissions();
}
@@ -407,11 +313,6 @@ class UserService {
* @returns 是否在线
*/
async checkUserOnline(): Promise {
- if (isMockMode) {
- // 模拟用户在线
- console.log('[MOCK] 检查用户在线状态');
- return true;
- }
return apiService.checkUserOnline();
}
@@ -425,25 +326,6 @@ class UserService {
expiresAt: number;
permissions: string[];
}> {
- if (isMockMode) {
- // 模拟会话信息
- console.log('[MOCK] 获取用户会话信息');
- return {
- userId: 1,
- sessionId: 'mock-session-' + Date.now(),
- expiresAt: Date.now() + 86400000, // 24小时后过期
- permissions: [
- 'order:view',
- 'order:edit',
- 'delivery:view',
- 'delivery:edit',
- 'warehouse:view',
- 'warehouse:edit',
- 'user:view',
- 'user:edit'
- ]
- };
- }
return apiService.getSessionInfo();
}
@@ -453,14 +335,6 @@ class UserService {
* @returns 新令牌及过期时间
*/
async refreshToken(oldToken: string): Promise<{ token: string; expiresAt: number }> {
- if (isMockMode) {
- // 模拟令牌刷新
- console.log('[MOCK] 刷新用户令牌');
- return {
- token: 'mock-refreshed-token-' + Date.now(),
- expiresAt: Date.now() + 86400000 // 24小时后过期
- };
- }
return apiService.refreshToken(oldToken);
}
@@ -470,11 +344,6 @@ class UserService {
* @returns 是否拥有该权限
*/
async verifyPermission(permission: string): Promise {
- if (isMockMode) {
- // 模拟权限验证(管理员拥有所有权限)
- console.log('[MOCK] 验证用户权限:', permission);
- return true;
- }
return apiService.verifyPermission(permission);
}
}
diff --git a/miniprogram/services/warehouseService.ts b/miniprogram/services/warehouseService.ts
index 518f5c0..1336f02 100644
--- a/miniprogram/services/warehouseService.ts
+++ b/miniprogram/services/warehouseService.ts
@@ -1,156 +1,19 @@
import { WarehouseInfo } from '../types';
import apiService from './apiService';
-import { isMockMode } from './apiService';
/**
* 仓库服务类
* 封装了所有仓库相关的操作
*/
class WarehouseService {
- // 模拟仓库数据
- private warehouses: WarehouseInfo[];
-
/**
- * 构造函数,初始化模拟仓库数据
+ * 构造函数
*/
constructor() {
- this.warehouses = [
- {
- id: 1,
- name: '瓦尔塔蓄电池昆明总店',
- address: '昆明市五华区二环西路599号',
- contact: '刘经理',
- phone: '0871-65123456',
- description: '瓦尔塔蓄电池云南总代理,提供全系列蓄电池产品',
- status: 'open',
- capacity: 2000, // 仓库容量(吨)
- longitude: 102.705745,
- latitude: 25.055281
- },
- {
- id: 2,
- name: '瓦尔塔蓄电池盘龙区分店',
- address: '昆明市盘龙区北京路1188号',
- contact: '张经理',
- phone: '0871-65678901',
- description: '专业销售瓦尔塔汽车蓄电池,提供安装和售后服务',
- status: 'open',
- capacity: 800, // 仓库容量(吨)
- longitude: 102.728421,
- latitude: 25.042498
- },
- {
- id: 3,
- name: '瓦尔塔蓄电池西山区分店',
- address: '昆明市西山区滇池路1234号',
- contact: '李经理',
- phone: '0871-65234567',
- description: '瓦尔塔蓄电池授权经销商,专业汽车电池解决方案',
- status: 'open',
- capacity: 600, // 仓库容量(吨)
- longitude: 102.689190,
- latitude: 25.028234
- },
- {
- id: 4,
- name: '瓦尔塔蓄电池官渡区分店',
- address: '昆明市官渡区春城路456号',
- contact: '王经理',
- phone: '0871-65345678',
- description: '瓦尔塔蓄电池专卖店,各类车型电池齐全',
- status: 'open',
- capacity: 700, // 仓库容量(吨)
- longitude: 102.725745,
- latitude: 25.025281
- },
- {
- id: 5,
- name: '瓦尔塔蓄电池呈贡区分店',
- address: '昆明市呈贡区春融街789号',
- contact: '赵经理',
- phone: '0871-65456789',
- description: '瓦尔塔蓄电池呈贡区授权经销商,提供专业安装服务',
- status: 'open',
- capacity: 500, // 仓库容量(吨)
- longitude: 102.788421,
- latitude: 24.902498
- },
- {
- id: 6,
- name: '瓦尔塔蓄电池五华区服务中心',
- address: '昆明市五华区一二一大街200号',
- contact: '陈经理',
- phone: '0871-65567890',
- description: '瓦尔塔蓄电池专业售后服务中心,提供电池检测和更换',
- status: 'open',
- capacity: 400, // 仓库容量(吨)
- longitude: 102.722745,
- latitude: 25.040281
- },
- {
- id: 7,
- name: '瓦尔塔蓄电池安宁市分店',
- address: '昆明市安宁市金方路300号',
- contact: '杨经理',
- phone: '0871-65678902',
- description: '瓦尔塔蓄电池安宁市授权经销商,各类车型电池销售',
- status: 'open',
- capacity: 300, // 仓库容量(吨)
- longitude: 102.458421,
- latitude: 24.902498
- },
- {
- id: 8,
- name: '瓦尔塔蓄电池晋宁区分店',
- address: '昆明市晋宁区昆阳大街120号',
- contact: '黄经理',
- phone: '0871-65789012',
- description: '瓦尔塔蓄电池晋宁区销售点,提供汽车和电动车电池',
- status: 'open',
- capacity: 250, // 仓库容量(吨)
- longitude: 102.519190,
- latitude: 24.688234
- },
- {
- id: 9,
- name: '瓦尔塔蓄电池宜良县分店',
- address: '昆明市宜良县匡远镇汇东路88号',
- contact: '吴经理',
- phone: '0871-65890123',
- description: '瓦尔塔蓄电池宜良县授权经销商,专业汽车电池服务',
- status: 'open',
- capacity: 200, // 仓库容量(吨)
- longitude: 103.115745,
- latitude: 24.905281
- },
- {
- id: 10,
- name: '瓦尔塔蓄电池嵩明县分店',
- address: '昆明市嵩明县嵩阳镇玉明路66号',
- contact: '郑经理',
- phone: '0871-65901234',
- description: '瓦尔塔蓄电池嵩明县销售中心,各类蓄电池产品齐全',
- status: 'open',
- capacity: 180, // 仓库容量(吨)
- longitude: 103.048421,
- latitude: 25.362498
- }
- ];
+ // 真实API模式下不需要模拟数据
}
- /**
- * 获取所有仓库信息(同步版本)
- */
- private getAllWarehousesSync(): WarehouseInfo[] {
- return [...this.warehouses];
- }
- /**
- * 根据ID获取仓库信息(同步版本)
- */
- private getWarehouseByIdSync(id: number): WarehouseInfo | undefined {
- return this.warehouses.find(warehouse => warehouse.id === id);
- }
/**
* 获取仓库状态的中文描述
@@ -168,9 +31,6 @@ class WarehouseService {
* 获取所有仓库信息
*/
async getWarehouses(): Promise {
- if (isMockMode) {
- return this.getAllWarehousesSync();
- }
return apiService.getWarehouses();
}
@@ -178,9 +38,6 @@ class WarehouseService {
* 根据ID获取仓库信息
*/
async getWarehouseById(id: number): Promise {
- if (isMockMode) {
- return this.getWarehouseByIdSync(id) || null;
- }
return apiService.getWarehouseById(id);
}
diff --git a/miniprogram/types/index.ts b/miniprogram/types/index.ts
index c012c22..ba469a0 100644
--- a/miniprogram/types/index.ts
+++ b/miniprogram/types/index.ts
@@ -26,10 +26,13 @@ export interface SearchResult {
phone: string;
}
+// 导入角色枚举
+import { Role } from '../utils/roleUtils';
+
// 用户信息接口
export interface UserInfo {
id: number; // 用户ID
- role: 'ADMIN' | 'DELIVERY_PERSON' | 'GUEST'; // 用户角色
+ role: Role; // 用户角色
token?: string; // 认证token
openid?: string; // 微信openid
session_key?: string; // 微信会话密钥
@@ -42,7 +45,7 @@ export interface EmployeeInfo {
id: number; // 员工ID
name: string; // 员工姓名
phone: string; // 员工电话
- role?: 'ADMIN' | 'DELIVERY_PERSON' | 'GUEST'; // 员工角色
+ role?: Role; // 员工角色
}
// 地图标记点接口
@@ -143,4 +146,55 @@ export interface AMapRegeoResponse {
};
};
status: string;
+}
+
+// 页面组件接口定义
+export interface IndexPageComponent {
+ data: {
+ longitude: number;
+ latitude: number;
+ scale: number;
+ markers: Marker[];
+ userInfo: UserInfo | null;
+ authStatus: {
+ hasWxCode: boolean;
+ userStatus: 'unknown' | 'registered' | 'unregistered' | 'signed_in' | 'signed_out';
+ };
+ showUserPanel: boolean;
+ showOrderPanel: boolean;
+ currentOrder: any;
+ currentDeliveryPerson: any;
+ currentWarehouse: any;
+ currentPanelPosition: { x: number; y: number };
+ polyline: any;
+ pendingOrders: any[];
+ currentRoute: any;
+ showRoute: boolean;
+ routeDistance: number;
+ routeDuration: number;
+ showWarehouseModal: boolean;
+ showDeliveryPersonModal: boolean;
+ warehouseModalState: 'bottom' | 'full';
+ deliveryPersonModalState: 'bottom' | 'full';
+ showSignOutButton: boolean;
+ showSignInButton: boolean;
+ showRegisterButton: boolean;
+ showAuthButton: boolean;
+ };
+ setData(data: Partial): void;
+ mainPageModule?: any;
+}
+
+
+
+// 登录模块接口定义
+export interface LoginModule {
+ processUserInfo(userInfo: UserInfo): Promise;
+ updatePageAfterLogin(userInfo: UserInfo): void;
+ checkLoginStatus(): boolean;
+ logout(): Promise;
+ showManualLoginModal(): Promise;
+ handleLoginFailure(resolve: (value: boolean) => void): Promise;
+ showCloseAppOption(resolve: (value: boolean) => void): void;
+ handleSignIn(): Promise;
}
\ No newline at end of file
diff --git a/miniprogram/utils/helpers.js b/miniprogram/utils/helpers.js
deleted file mode 100644
index 7e9611c..0000000
--- a/miniprogram/utils/helpers.js
+++ /dev/null
@@ -1,166 +0,0 @@
-"use strict";
-// 工具函数文件
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.formatCoordinate = formatCoordinate;
-exports.formatSingleCoordinate = formatSingleCoordinate;
-exports.calculateDistance = calculateDistance;
-exports.formatDistance = formatDistance;
-exports.showToast = showToast;
-exports.showLoading = showLoading;
-exports.hideLoading = hideLoading;
-
-exports.cacheUserAvatar = cacheUserAvatar;
-exports.showConfirmDialog = showConfirmDialog;
-/**
- * 格式化坐标信息
- * @param longitude 经度
- * @param latitude 纬度
- * @param decimalPlaces 保留小数位数,默认6位
- * @returns 格式化后的坐标字符串
- */
-function formatCoordinate(longitude, latitude, decimalPlaces = 6) {
- const formattedLongitude = longitude.toFixed(decimalPlaces);
- const formattedLatitude = latitude.toFixed(decimalPlaces);
- return `经度 ${formattedLongitude}, 纬度 ${formattedLatitude}`;
-}
-/**
- * 格式化单个坐标值
- * @param coordinate 坐标值
- * @param decimalPlaces 保留小数位数,默认6位
- * @returns 格式化后的坐标字符串
- */
-function formatSingleCoordinate(coordinate, decimalPlaces = 6) {
- return coordinate.toFixed(decimalPlaces);
-}
-/**
- * 计算两点之间的距离(简单的平面距离,非球面距离)
- * @param lng1 第一个点的经度
- * @param lat1 第一个点的纬度
- * @param lng2 第二个点的经度
- * @param lat2 第二个点的纬度
- * @returns 两点之间的距离(单位:米)
- */
-function calculateDistance(lng1, lat1, lng2, lat2) {
- // 地球半径(单位:米)
- const EARTH_RADIUS = 6378137;
- // 将角度转换为弧度
- const radLat1 = (lat1 * Math.PI) / 180.0;
- const radLat2 = (lat2 * Math.PI) / 180.0;
- const a = radLat1 - radLat2;
- const b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0;
- // 应用haversine公式计算球面距离
- let s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
- Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
- s = s * EARTH_RADIUS;
- s = Math.round(s * 10000) / 10000; // 保留4位小数
- return s;
-}
-/**
- * 格式化距离(米转换为千米)
- * @param distance 距离(单位:米)
- * @returns 格式化后的距离字符串
- */
-function formatDistance(distance) {
- if (distance < 1000) {
- return `${Math.round(distance)} 米`;
- }
- else {
- return `${(distance / 1000).toFixed(1)} 千米`;
- }
-}
-/**
- * 显示消息提示
- * @param title 提示信息
- * @param icon 图标类型,默认'none'
- * @param duration 显示时长,默认2000毫秒
- */
-function showToast(title, icon = 'none', duration = 2000) {
- wx.showToast({
- title,
- icon,
- duration
- });
-}
-/**
- * 显示加载提示
- * @param title 提示信息,默认'加载中...'
- */
-function showLoading(title = '加载中...') {
- wx.showLoading({
- title
- });
-}
-/**
- * 隐藏加载提示
- */
-function hideLoading() {
- wx.hideLoading();
-}
-
-/**
- * 缓存用户头像图片
- * @param avatarUrl 头像URL地址
- * @returns Promise 返回缓存后的头像路径或原始URL
- */
-async function cacheUserAvatar(avatarUrl) {
- if (!avatarUrl)
- return '';
- try {
- // 检查是否已缓存
- const cachedPath = wx.getStorageSync(`avatar_${avatarUrl}`);
- if (cachedPath) {
- console.log('使用缓存的头像:', cachedPath);
- return cachedPath;
- }
- // 下载头像
- const downloadRes = await new Promise((resolve) => {
- wx.downloadFile({
- url: avatarUrl,
- success: (res) => resolve(res),
- fail: (err) => {
- console.error('下载头像失败:', err);
- resolve({ statusCode: 0 }); // 返回失败状态
- }
- });
- });
- if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
- // 缓存头像路径
- wx.setStorageSync(`avatar_${avatarUrl}`, downloadRes.tempFilePath);
- console.log('头像下载并缓存成功:', downloadRes.tempFilePath);
- return downloadRes.tempFilePath;
- }
- else {
- console.warn('头像下载失败,使用原始URL:', avatarUrl);
- return avatarUrl;
- }
- }
- catch (error) {
- console.error('缓存头像过程中发生错误:', error);
- return avatarUrl;
- }
-}
-/**
- * 显示确认对话框
- * @param title 标题
- * @param content 内容
- * @returns Promise 用户点击确定返回true,点击取消返回false
- */
-function showConfirmDialog(title, content) {
- return new Promise((resolve) => {
- wx.showModal({
- title,
- content,
- success: (res) => {
- if (res.confirm) {
- resolve(true);
- }
- else if (res.cancel) {
- resolve(false);
- }
- },
- fail: () => {
- resolve(false);
- }
- });
- });
-}
diff --git a/miniprogram/utils/roleUtils.ts b/miniprogram/utils/roleUtils.ts
new file mode 100644
index 0000000..604839d
--- /dev/null
+++ b/miniprogram/utils/roleUtils.ts
@@ -0,0 +1,85 @@
+/**
+ * 角色工具类 - 处理角色枚举的转换和验证
+ */
+
+/**
+ * 角色枚举定义
+ * 服务器可能返回数字角色值,前端使用字符串枚举
+ */
+export enum Role {
+ DELIVERY_PERSON = 'DELIVERY_PERSON', // 配送员
+ ADMIN = 'ADMIN' // 管理员
+}
+
+/**
+ * 角色值映射
+ * 服务器返回的数字角色值到前端字符串枚举的映射
+ */
+export const RoleMapping = {
+ 0: Role.DELIVERY_PERSON, // 0对应配送员
+ 1: Role.ADMIN // 1对应管理员
+} as const;
+
+/**
+ * 将服务器返回的角色值转换为前端枚举
+ * @param roleValue 服务器返回的角色值(可能是数字或字符串)
+ * @returns 标准化的角色枚举值
+ */
+export function normalizeRole(roleValue: string | number): Role {
+ if (typeof roleValue === 'number') {
+ // 如果是数字,使用映射表转换
+ return RoleMapping[roleValue as keyof typeof RoleMapping] || Role.DELIVERY_PERSON;
+ }
+
+ // 如果是字符串,直接转换为大写进行比较
+ const normalizedRole = roleValue.toUpperCase();
+
+ // 检查是否是有效的角色值
+ if (normalizedRole === Role.ADMIN) {
+ return Role.ADMIN;
+ }
+
+ // 默认返回配送员
+ return Role.DELIVERY_PERSON;
+}
+
+/**
+ * 获取角色显示文本
+ * @param role 角色枚举值
+ * @returns 对应的中文显示文本
+ */
+export function getRoleText(role: Role): string {
+ switch (role) {
+ case Role.ADMIN:
+ return '管理员';
+ case Role.DELIVERY_PERSON:
+ return '配送员';
+ default:
+ return '未知角色';
+ }
+}
+
+/**
+ * 验证角色值是否有效
+ * @param roleValue 角色值
+ * @returns 是否有效
+ */
+export function isValidRole(roleValue: string | number): boolean {
+ try {
+ const normalized = normalizeRole(roleValue);
+ return normalized === Role.ADMIN || normalized === Role.DELIVERY_PERSON;
+ } catch {
+ return false;
+ }
+}
+
+/**
+ * 获取角色选项列表(用于下拉选择器)
+ * @returns 角色选项数组
+ */
+export function getRoleOptions(): Array<{ value: Role; label: string }> {
+ return [
+ { value: Role.DELIVERY_PERSON, label: '配送员' },
+ { value: Role.ADMIN, label: '管理员' }
+ ];
+}
\ No newline at end of file
diff --git a/miniprogram/utils/util.js b/miniprogram/utils/util.js
deleted file mode 100644
index 131ded6..0000000
--- a/miniprogram/utils/util.js
+++ /dev/null
@@ -1,19 +0,0 @@
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.formatTime = void 0;
-const formatTime = (date) => {
- const year = date.getFullYear();
- const month = date.getMonth() + 1;
- const day = date.getDate();
- const hour = date.getHours();
- const minute = date.getMinutes();
- const second = date.getSeconds();
- return ([year, month, day].map(formatNumber).join('/') +
- ' ' +
- [hour, minute, second].map(formatNumber).join(':'));
-};
-exports.formatTime = formatTime;
-const formatNumber = (n) => {
- const s = n.toString();
- return s[1] ? s : '0' + s;
-};
diff --git a/tsconfig.json b/tsconfig.json
index ade784e..1056576 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -19,7 +19,8 @@
"lib": ["ES2020"],
"typeRoots": [
"./typings"
- ]
+ ],
+ "outDir": "./dist"
},
"include": [
"./**/*.ts"