diff --git a/README.md b/README.md index 9d8b16f..cac16a7 100644 --- a/README.md +++ b/README.md @@ -85,15 +85,23 @@ - `loadEmployees()`: 加载员工列表,支持搜索过滤 - `submitAddForm()`: 提交添加员工表单,包含表单验证 - `deleteEmployee()`: 删除员工,包含确认对话框 +- `editEmployee()`: 编辑员工信息,加载员工数据到编辑表单 +- `submitEditForm()`: 提交编辑员工表单,包含表单验证 +- `cancelEdit()`: 取消编辑操作,重置编辑表单 - `validateForm()`: 验证员工信息(姓名、手机号、角色) - `getFilteredEmployees()`: 根据关键词过滤员工列表 **功能特性**: -- **双标签页设计**: 员工列表页和添加员工页 +- **三标签页设计**: 员工列表页、添加员工页和编辑员工页 - **实时搜索**: 支持按姓名、手机号、角色搜索 - **表单验证**: 手机号格式验证、必填项检查 - **权限控制**: 过滤当前登录用户,防止误删 - **角色管理**: 支持管理员和配送员角色分配 +- **编辑功能**: 支持员工信息的完整编辑流程,包含数据加载、表单验证和正确的更新API调用 + +**API集成**: +- 使用 `updateEmployee` 方法调用后端 PUT `/employees/{id}` 接口更新员工信息 +- 编辑操作通过正确的API调用确保数据一致性 #### 订单管理 (order-management) **核心方法**: @@ -115,15 +123,21 @@ - `loadWarehouses()`: 加载仓库列表 - `submitAddForm()`: 提交添加仓库表单 - `deleteWarehouse()`: 删除仓库,包含确认对话框 +- `editWarehouse()`: 编辑仓库信息,加载仓库数据到编辑表单 +- `submitEditForm()`: 提交编辑仓库表单,包含表单验证和正确的API调用 +- `cancelEdit()`: 取消编辑操作,重置编辑表单 - `filterWarehouses()`: 按名称、地址、联系人搜索仓库 - `getWarehouseInventoryStatus()`: 获取库存状态评估 **功能特性**: +- **三标签页设计**: 仓库列表页、添加仓库页和编辑仓库页 - **库存监控**: 实时显示库存状态(充足/正常/偏低/不足) - **位置管理**: 支持仓库经纬度坐标管理 - **容量管理**: 设置仓库最大容量限制 - **状态管理**: 支持营业中/已关闭/维护中状态 - **联系人管理**: 仓库联系人和电话信息 +- **编辑功能**: 支持仓库信息的完整编辑流程,包含数据加载、表单验证和正确的更新API调用 +- **API集成**: 使用正确的`updateWarehouse`方法调用后端PUT接口更新仓库信息 #### 员工界面 (employee-dashboard) **功能菜单**: @@ -266,7 +280,18 @@ 2. **获取仓库详情** - `GET /warehouses/{id}` - 响应: `WarehouseInfo` -3. **获取仓库订单** - `GET /warehouses/{id}/orders` +3. **创建仓库** - `POST /warehouses` + - 请求: `Omit` + - 响应: `{ success: boolean; message?: string; data?: WarehouseInfo }` + +4. **更新仓库信息** - `PUT /warehouses/{id}` + - 请求: `{ name?: string; address?: string; contact?: string; phone?: string; description?: string; capacity?: number; longitude?: number; latitude?: number; status?: 'open' | 'closed' | 'maintenance' }` + - 响应: `{ success: boolean; message?: string; data?: WarehouseInfo }` + +5. **删除仓库** - `DELETE /warehouses/{id}` + - 响应: `{ success: boolean; message?: string }` + +6. **获取仓库订单** - `GET /warehouses/{id}/orders` - 响应: `Order[]` ### 货运人员管理模块 diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 0000000..d1fb76f --- /dev/null +++ b/dist/app.js @@ -0,0 +1,172 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// app.ts +const userService_1 = __importDefault(require("./services/userService")); +const locationTrackingService_1 = __importDefault(require("./services/locationTrackingService")); +const helpers_1 = require("./utils/helpers"); +// 定义应用类 +class MyApp { + constructor() { + this.globalData = { + userInfo: null, + isLoggedIn: false, + openid: undefined, + session_key: undefined, + token: undefined, + }; + // 绑定方法,确保this指向正确 + this.onLaunch = this.onLaunch.bind(this); + this.doGlobalLogin = this.doGlobalLogin.bind(this); + this.doGlobalLogout = this.doGlobalLogout.bind(this); + } + // 应用初始化 + async onLaunch() { + console.log("=== 应用启动 ==="); + console.log("调试信息 - 开始执行onLaunch"); + try { + // 初始化位置追踪服务 + console.log("调试信息 - 初始化位置追踪服务"); + await locationTrackingService_1.default.initialize(); + console.log("调试信息 - 位置追踪服务初始化完成"); + // 初始化日志存储 + const logs = wx.getStorageSync("logs") || []; + logs.unshift(Date.now()); + wx.setStorageSync("logs", logs); + console.log("初始化日志存储完成"); + // 检查用户是否已签退(避免自动重新登录) + const userStatus = wx.getStorageSync("userStatus"); + console.log("调试信息 - 检查本地存储的userStatus:", userStatus); + if (userStatus === 'signed_out') { + console.log("调试信息 - 检测到用户已签退,跳过自动登录"); + this.globalData.isLoggedIn = false; + // 直接跳转到首页,不进行登录 + console.log("调试信息 - 直接跳转到首页,不进行登录"); + wx.switchTab({ + url: "/pages/index/index", + }); + return; + } + else { + console.log("调试信息 - 用户状态不是signed_out,继续登录流程"); + } + // 检查本地存储的登录信息 + const token = wx.getStorageSync("token"); + const userInfo = wx.getStorageSync("userInfo"); + const openid = wx.getStorageSync("openid"); + const session_key = wx.getStorageSync("session_key"); + console.log("调试信息 - 本地存储检查结果:"); + console.log(" - token:", token ? "存在" : "不存在"); + console.log(" - openid:", openid ? "存在" : "不存在"); + console.log(" - userInfo:", userInfo ? "存在" : "不存在"); + if (token && openid) { + console.log("调试信息 - 检测到本地登录信息,恢复登录状态"); + this.globalData.isLoggedIn = true; + this.globalData.userInfo = userInfo; + this.globalData.openid = openid; + this.globalData.session_key = session_key; + this.globalData.token = token; + } + else { + console.log("调试信息 - 本地登录信息不完整,需要重新登录"); + } + // 静默登录,无论是否已有登录信息都重新验证 + console.log("调试信息 - 开始静默登录流程"); + const loginSuc = await this.doGlobalLogin(); + // 静默登录后跳转到首页 + if (loginSuc) { + console.log("调试信息 - 登录成功,跳转到首页"); + wx.switchTab({ + url: "/pages/index/index", + }); + } + else { + console.log("调试信息 - 登录失败"); + //TODO:退出小程序或者是重试逻辑 + (0, helpers_1.showToast)("登录失败,请重试"); + } + } + catch (error) { + console.error("应用启动失败:", error); + this.globalData.isLoggedIn = false; + (0, helpers_1.showToast)("应用启动失败,请重试"); + } + } + // 全局静默登录方法 - 仅保留核心登录逻辑 + async doGlobalLogin() { + console.log("=== 开始全局登录流程 ==="); + try { + // 注意:不再清除签退状态,让onLaunch中的签退状态检查逻辑正常工作 + // 只有在登录成功后才清除签退状态 + // 调用userService进行登录,分离请求逻辑 + const loginResult = await userService_1.default.wxLogin(); + if (loginResult.success) { + // 登录成功,更新全局状态 + this.globalData.isLoggedIn = true; + this.globalData.userInfo = loginResult.userInfo || null; + this.globalData.openid = loginResult.openid || undefined; + this.globalData.session_key = loginResult.session_key || undefined; + this.globalData.token = loginResult.token || undefined; + // 登录成功后清除签退状态 + wx.removeStorageSync("userStatus"); + console.log("=== 全局登录流程完成,登录成功 ==="); + return true; + } + else { + console.error("=== 全局登录流程失败 ==="); + this.globalData.isLoggedIn = false; + // 如果返回了openid,保存openid + if (loginResult.openid) { + this.globalData.openid = loginResult.openid; + } + (0, helpers_1.showToast)("登录失败,请重试"); + return false; + } + } + catch (error) { + console.error("=== 全局登录流程异常 ===", error); + this.globalData.isLoggedIn = false; + // 清空存储的登录信息 + wx.removeStorageSync("userInfo"); + wx.removeStorageSync("token"); + wx.removeStorageSync("openid"); + wx.removeStorageSync("session_key"); + wx.removeStorageSync("userStatus"); + (0, helpers_1.showToast)("登录失败,请重试"); + return false; + } + } + // 全局退出登录方法 + doGlobalLogout() { + this.globalData.isLoggedIn = false; + this.globalData.userInfo = null; + this.globalData.openid = undefined; + this.globalData.session_key = undefined; + this.globalData.token = undefined; + // 清空存储的登录信息,但保留签退状态 + wx.removeStorageSync("userInfo"); + wx.removeStorageSync("token"); + wx.removeStorageSync("openid"); + wx.removeStorageSync("session_key"); + // 注意:不删除userStatus,以便签退状态能够保持 + console.log("用户已退出登录"); + (0, helpers_1.showToast)("已退出登录"); + // 退出登录后跳转到首页 + wx.switchTab({ + url: "/pages/index/index", + }); + } +} +// 创建应用实例并注册 +const app = new MyApp(); +// 注册小程序 +App({ + globalData: app.globalData, + onLaunch: app.onLaunch, + doGlobalLogin: app.doGlobalLogin, + doGlobalLogout: app.doGlobalLogout, +}); +// 导出应用实例,以便在其他地方使用 +exports.default = app; diff --git a/dist/components/navigation-bar/navigation-bar.js b/dist/components/navigation-bar/navigation-bar.js new file mode 100644 index 0000000..cbdd39c --- /dev/null +++ b/dist/components/navigation-bar/navigation-bar.js @@ -0,0 +1,105 @@ +"use strict"; +Component({ + options: { + multipleSlots: true // 在组件定义时的选项中启用多slot支持 + }, + /** + * 组件的属性列表 + */ + properties: { + extClass: { + type: String, + value: '' + }, + title: { + type: String, + value: '' + }, + background: { + type: String, + value: '' + }, + color: { + type: String, + value: '' + }, + back: { + type: Boolean, + value: true + }, + loading: { + type: Boolean, + value: false + }, + homeButton: { + type: Boolean, + value: false, + }, + animated: { + // 显示隐藏的时候opacity动画效果 + type: Boolean, + value: true + }, + show: { + // 显示隐藏导航,隐藏的时候navigation-bar的高度占位还在 + type: Boolean, + value: true, + observer: '_showChange' + }, + // back为true的时候,返回的页面深度 + delta: { + type: Number, + value: 1 + }, + }, + /** + * 组件的初始数据 + */ + data: { + displayStyle: '' + }, + lifetimes: { + attached() { + const rect = wx.getMenuButtonBoundingClientRect(); + wx.getSystemInfo({ + success: (res) => { + const isAndroid = res.platform === 'android'; + const isDevtools = res.platform === 'devtools'; + this.setData({ + ios: !isAndroid, + innerPaddingRight: `padding-right: ${res.windowWidth - rect.left}px`, + leftWidth: `width: ${res.windowWidth - rect.left}px`, + safeAreaTop: isDevtools || isAndroid ? `height: calc(var(--height) + ${res.safeArea.top}px); padding-top: ${res.safeArea.top}px` : `` + }); + } + }); + }, + }, + /** + * 组件的方法列表 + */ + methods: { + _showChange(show) { + const animated = this.data.animated; + let displayStyle = ''; + if (animated) { + displayStyle = `opacity: ${show ? '1' : '0'};transition:opacity 0.5s;`; + } + else { + displayStyle = `display: ${show ? '' : 'none'}`; + } + this.setData({ + displayStyle + }); + }, + back() { + const data = this.data; + if (data.delta) { + wx.navigateBack({ + delta: data.delta + }); + } + this.triggerEvent('back', { delta: data.delta }, {}); + } + }, +}); diff --git a/dist/pages/admin/admin.js b/dist/pages/admin/admin.js new file mode 100644 index 0000000..9250fa2 --- /dev/null +++ b/dist/pages/admin/admin.js @@ -0,0 +1,521 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const mapService_1 = __importDefault(require("../../services/mapService")); +const warehouseService_1 = __importDefault(require("../../services/warehouseService")); +const orderService_1 = __importDefault(require("../../services/orderService")); +const deliveryPersonService_1 = __importDefault(require("../../services/deliveryPersonService")); +Page({ + data: { + userInfo: null, + mapCenter: mapService_1.default.getKunmingCenter(), + markers: [], + polyline: {}, + // 订单相关 + orders: [], + filteredOrders: [], + activeTab: 'all', + showAssignModal: false, + selectedOrderId: 0, + showAddModal: false, + // 新增订单表单数据 + warehouseIndex: 0, + warehouses: [], + endName: '', + goodsType: '', + goodsWeight: 0, + // 员工相关 + deliveryPersons: [], + availableStaff: [], + // 底部页签 + currentTab: 'main' + }, + onLoad() { + // 模拟管理员登录状态 + this.setData({ + userInfo: { + id: 1, + name: '管理员', + role: 'ADMIN', + phone: '13800138000' + } + }); + // 初始化数据 + this.initData(); + }, + onShow() { + // 页面显示时刷新数据 + this.refreshData(); + }, + // 初始化数据 + initData() { + // 获取仓库数据 + warehouseService_1.default.getWarehouses().then(warehouses => { + this.setData({ + warehouses + }); + }).catch(error => { + console.error('获取仓库数据失败:', error); + }); + // 刷新数据 + this.refreshData(); + }, + // 刷新数据 + refreshData() { + // 获取订单数据 - 使用正确的getPendingOrders方法 + orderService_1.default.getPendingOrders().then(orders => { + // 获取员工数据 - 使用正确的getDeliveryPersons方法 + deliveryPersonService_1.default.getDeliveryPersons().then(deliveryPersons => { + // 更新地图标记点 + const markers = this.generateMarkers(orders, deliveryPersons); + this.setData({ + orders, + deliveryPersons, + markers + }); + // 过滤订单 + this.filterOrders(); + }).catch(error => { + console.error('获取货运人员数据失败:', error); + }); + }).catch(error => { + console.error('获取订单数据失败:', error); + }); + }, + // 生成地图标记点 + generateMarkers(orders, deliveryPersons) { + const markers = []; + // 添加订单起点标记(仓库) + const warehouseMarkers = new Map(); + orders.forEach(order => { + if (!warehouseMarkers.has(order.startPoint.id)) { + warehouseMarkers.set(order.startPoint.id, { + id: order.startPoint.id, + longitude: order.startPoint.longitude, + latitude: order.startPoint.latitude, + title: order.startPoint.name, + iconPath: '/images/warehouse.png', + width: 32, + height: 32, + zIndex: 10, + callout: { + content: order.startPoint.name, + color: '#333', + fontSize: 24, + borderRadius: 10, + bgColor: '#fff', + padding: 10, + display: 'BYCLICK' + } + }); + } + }); + // 添加订单终点标记 + orders.forEach((order) => { + markers.push({ + id: 10000 + order.id, // 避免ID冲突 + longitude: order.endPoint.longitude, + latitude: order.endPoint.latitude, + title: order.endPoint.name, + iconPath: this.getOrderStatusIcon(order.status), + width: 32, + height: 32, + zIndex: 20, + callout: { + content: `${order.endPoint.name}\n${order.goodsType} ${order.goodsWeight}kg`, + color: '#333', + fontSize: 24, + borderRadius: 10, + bgColor: '#fff', + padding: 10, + display: 'BYCLICK' + } + }); + }); + // 添加员工位置标记 + deliveryPersons.forEach(person => { + // 使用默认坐标,实际位置由locationTrackingService统一管理 + markers.push({ + id: 20000 + person.id, // 避免ID冲突 + longitude: 102.833722, // 默认经度 + latitude: 24.880095, // 默认纬度 + title: person.name, + iconPath: this.getStaffStatusIcon(person.status), + width: 32, + height: 32, + zIndex: 30, + callout: { + content: `${person.name}\n订单数: ${person.currentOrders.length}\n状态: ${this.getStatusText(person.status)}`, + color: '#333', + fontSize: 24, + borderRadius: 10, + bgColor: '#fff', + padding: 10, + display: 'BYCLICK' + } + }); + }); + // 添加仓库标记 + markers.push(...Array.from(warehouseMarkers.values())); + return markers; + }, + // 获取订单状态对应的图标 + getOrderStatusIcon(status) { + switch (status) { + case 'pending': + return '/images/order-pending.png'; + case 'assigned': + return '/images/order-assigned.png'; + case 'in_transit': + return '/images/order-transit.png'; + case 'delivered': + return '/images/order-delivered.png'; + default: + return '/images/order-pending.png'; + } + }, + // 获取员工状态对应的图标 + getStaffStatusIcon(staff) { + // 根据员工角色返回不同的图标 + const userRole = staff?.role || 'employee'; + return userRole === 'ADMIN' ? '/images/crown.png' : '/images/truck.png'; + }, + // 获取状态文本 + getStatusText(status) { + const statusMap = { + 'pending': '未分配', + 'assigned': '已分配', + 'in_transit': '配送中', + 'delivered': '已完成', + 'idle': '空闲', + 'busy': '忙碌', + 'offline': '离线' + }; + return statusMap[status] || status; + }, + // 过滤订单 + filterOrders() { + const { orders, activeTab } = this.data; + let filteredOrders = orders; + if (activeTab !== 'all') { + filteredOrders = orders.filter(order => order.status === activeTab); + } + this.setData({ + filteredOrders + }); + }, + // 切换订单标签 + switchTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + activeTab: tab + }); + this.filterOrders(); + }, + // 处理订单点击 + handleOrderTap(e) { + const orderId = e.currentTarget.dataset.id; + const order = this.data.orders.find(o => o.id === orderId); + if (!order) + return; + if (order.status === 'pending') { + // 未分配的订单,显示分配弹窗 + const availableStaff = this.data.deliveryPersons.filter(person => person.status === 'idle'); + this.setData({ + showAssignModal: true, + selectedOrderId: orderId, + availableStaff + }); + } + else if (order.status === 'assigned' || order.status === 'in_transit') { + // 已分配的订单,跳转到地图并聚焦到配送员 + const assignedPerson = this.data.deliveryPersons.find(person => person.currentOrders.some(o => o.id === orderId)); + if (assignedPerson) { + this.setData({ + mapCenter: { longitude: 102.833722, latitude: 24.880095 } // 默认坐标 + }); + // 绘制从起点到终点的路线 + this.drawRoute(order.startPoint.longitude, order.startPoint.latitude, order.endPoint.longitude, order.endPoint.latitude); + } + } + }, + // 处理员工点击 + handleStaffTap(e) { + const staffId = e.currentTarget.dataset.id; + const staff = this.data.deliveryPersons.find(p => p.id === staffId); + if (staff) { + // 跳转到地图并聚焦到员工位置 + this.setData({ + mapCenter: staff.currentLocation + }); + } + }, + // 绘制路线 + drawRoute(startLng, startLat, endLng, endLat) { + const origin = `${startLng},${startLat}`; + const destination = `${endLng},${endLat}`; + mapService_1.default.getDrivingRoute(origin, destination).then(result => { + if (result.polyline) { + // 解码polyline + const points = this.decodePolyline(result.polyline); + if (points.length > 0) { + this.setData({ + polyline: { + points, + color: '#0091ff', + width: 8, + dottedLine: false + } + }); + } + } + }).catch(err => { + console.error('绘制路线失败:', err); + }); + }, + // 解码polyline + decodePolyline(polyline) { + const points = []; + let index = 0, len = polyline.length; + let lat = 0, lng = 0; + while (index < len) { + let b, shift = 0, result = 0; + do { + b = polyline.charCodeAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + const dlat = ((result & 1) !== 0 ? ~(result >> 1) : (result >> 1)); + lat += dlat; + shift = 0; + result = 0; + do { + b = polyline.charCodeAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + const dlng = ((result & 1) !== 0 ? ~(result >> 1) : (result >> 1)); + lng += dlng; + points.push({ + longitude: lng / 100000, + latitude: lat / 100000 + }); + } + return points; + }, + // 显示分配弹窗 + showAssignModal() { + this.setData({ + showAssignModal: true + }); + }, + // 隐藏分配弹窗 + hideAssignModal() { + this.setData({ + showAssignModal: false + }); + }, + // 分配订单 + assignOrder(e) { + const staffId = e.currentTarget.dataset.id; + const { selectedOrderId } = this.data; + if (!selectedOrderId || !staffId) { + wx.showToast({ + title: '请选择订单和货运人员', + icon: 'none' + }); + return; + } + // 修复:使用正确的orderService.assignOrder方法 + orderService_1.default.assignOrder(selectedOrderId, staffId).then(result => { + if (result.success) { + wx.showToast({ + title: '指派成功', + icon: 'success' + }); + // 刷新数据 + this.refreshData(); + } + else { + wx.showToast({ + title: result.message || '指派失败', + icon: 'none' + }); + } + }).catch(error => { + console.error('指派订单失败:', error); + wx.showToast({ + title: '指派失败,请重试', + icon: 'none' + }); + }); + this.hideAssignModal(); + }, + // 显示新增订单弹窗 + showAddOrderModal() { + this.setData({ + showAddModal: true, + endName: '', + goodsType: '', + goodsWeight: 0, + warehouseIndex: 0 + }); + }, + // 隐藏新增订单弹窗 + hideAddModal() { + this.setData({ + showAddModal: false + }); + }, + // 仓库选择变化 + onWarehouseChange(e) { + this.setData({ + warehouseIndex: e.detail.value + }); + }, + // 终点名称输入 + onEndNameInput(e) { + this.setData({ + endName: e.detail.value + }); + }, + // 货物类型输入 + onGoodsTypeInput(e) { + this.setData({ + goodsType: e.detail.value + }); + }, + // 货物重量输入 + onGoodsWeightInput(e) { + this.setData({ + goodsWeight: parseFloat(e.detail.value) || 0 + }); + }, + // 创建订单 + createOrder() { + const { warehouses, warehouseIndex, endName, goodsType, goodsWeight } = this.data; + // 表单验证 + if (!warehouses[warehouseIndex]) { + wx.showToast({ + title: '请选择仓库', + icon: 'none' + }); + return; + } + if (!endName) { + wx.showToast({ + title: '请输入终点名称', + icon: 'none' + }); + return; + } + if (!goodsType) { + wx.showToast({ + title: '请输入货物类型', + icon: 'none' + }); + return; + } + if (goodsWeight <= 0) { + wx.showToast({ + title: '请输入有效的货物重量', + icon: 'none' + }); + return; + } + // 获取仓库信息 + const warehouse = warehouses[warehouseIndex]; + // 模拟终点坐标(实际应用中应该通过地图选择或搜索获取) + // 从已有仓库中获取一个坐标作为参考点 + const referenceWarehouse = this.data.warehouses[0]; + const endPoint = { + name: endName, + longitude: referenceWarehouse.address ? 102.715 + (Math.random() - 0.5) * 0.1 : 102.715, + latitude: referenceWarehouse.address ? 25.045 + (Math.random() - 0.5) * 0.1 : 25.045 + }; + // 创建订单数据 + const newOrder = { + startPoint: { + id: warehouse.id, + name: warehouse.name, + longitude: 102.715, // 临时使用固定坐标 + latitude: 25.045 // 临时使用固定坐标 + }, + endPoint, + status: 'pending', + goodsType, + goodsWeight + }; + // 调用服务创建订单 - 使用正确的orderService.createOrder方法 + orderService_1.default.createOrder(newOrder).then(() => { + wx.showToast({ + title: '创建成功', + icon: 'success' + }); + // 刷新数据 + this.refreshData(); + }).catch(error => { + console.error('创建订单失败:', error); + wx.showToast({ + title: '创建失败', + icon: 'error' + }); + }); + this.hideAddModal(); + }, + // 底部页签切换 + switchMainTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + currentTab: tab + }); + }, + // 跳转到员工管理页面 + goToEmployeeManagement() { + wx.navigateTo({ + url: '/pages/employee/employee' + }); + }, + // 地图点击事件 + onMapTap() { + // 清除路线 + this.setData({ + polyline: {} + }); + }, + // 格式化时间 + formatTime(time) { + const date = new Date(time); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + if (diff < 60000) { + return '刚刚'; + } + else if (diff < 3600000) { + return `${Math.floor(diff / 60000)}分钟前`; + } + else if (diff < 86400000) { + return `${Math.floor(diff / 3600000)}小时前`; + } + else { + const month = date.getMonth() + 1; + const day = date.getDate(); + const hour = date.getHours(); + const minute = date.getMinutes(); + return `${month}月${day}日 ${hour}:${minute < 10 ? '0' + minute : minute}`; + } + }, + // 返回上一页 + goBack() { + // 尝试使用navigateBack,如果失败则使用redirectTo返回首页 + wx.navigateBack({ + fail: () => { + wx.redirectTo({ + url: '/pages/index/index' + }); + } + }); + } +}); diff --git a/dist/pages/apply/apply.js b/dist/pages/apply/apply.js new file mode 100644 index 0000000..34e7632 --- /dev/null +++ b/dist/pages/apply/apply.js @@ -0,0 +1,161 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// pages/apply/apply.ts +const userService_1 = __importDefault(require("../../services/userService")); +Page({ + /** + * 页面的初始数据 + */ + data: { + applyForm: { + name: '', + phone: '' + } + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad() { + console.log('申请页面加载'); + }, + /** + * 表单输入处理 + */ + onApplyFormInput(e) { + const { field } = e.currentTarget.dataset; + const { value } = e.detail; + this.setData({ + [`applyForm.${field}`]: value + }); + }, + /** + * 取消按钮点击 + */ + onCancel() { + // 返回上一页 + wx.navigateBack(); + }, + /** + * 提交申请 + */ + async onSubmit() { + const { name, phone } = this.data.applyForm; + // 表单验证 + if (!this.validateForm()) { + return; + } + try { + wx.showLoading({ + title: '提交中...', + mask: true + }); + // 调用API提交申请 + const result = await this.submitApplication({ name, phone }); + if (result.success) { + wx.showToast({ + title: '绑定成功', + icon: 'success', + duration: 2000 + }); + // 更新全局用户信息 + const app = getApp(); + if (app.globalData.userInfo) { + app.globalData.userInfo = { + ...app.globalData.userInfo, + name: result.employeeInfo.name, + phone: result.employeeInfo.phone, + role: result.employeeInfo.role + }; + console.log('全局用户信息已更新:', app.globalData.userInfo); + } + // 延迟返回并刷新首页 + setTimeout(async () => { + // 获取当前页面栈 + const pages = getCurrentPages(); + if (pages.length >= 2) { + // 获取首页实例并调用刷新方法 + const indexPage = pages[pages.length - 2]; + if (indexPage && indexPage.refreshPageAfterLogin) { + // 重新获取用户状态,确保绑定成功后状态正确更新 + if (indexPage.data.mainPageModule) { + const loginModule = indexPage.data.mainPageModule.getLoginModule(); + const app = getApp(); + const userStatus = await loginModule.determineUserStatus(app.globalData.userInfo); + // 更新页面状态 + indexPage.setData({ + 'authStatus.userStatus': userStatus + }); + console.log('✅ 绑定成功后重新获取用户状态:', userStatus); + } + indexPage.refreshPageAfterLogin(); + } + } + wx.navigateBack(); + }, 1500); + } + else { + wx.showToast({ + title: result.message || '提交失败', + icon: 'none', + duration: 2000 + }); + } + } + catch (error) { + console.error('提交申请失败:', error); + wx.showToast({ + title: '网络错误,请重试', + icon: 'none', + duration: 2000 + }); + } + finally { + wx.hideLoading(); + } + }, + /** + * 表单验证 + */ + validateForm() { + const { name, phone } = this.data.applyForm; + if (!name || !phone) { + wx.showToast({ + title: '请填写完整信息', + icon: 'none', + duration: 2000 + }); + return false; + } + return true; + }, + /** + * 提交申请到服务器 + */ + async submitApplication(data) { + try { + // 先执行微信登录获取token + const loginResult = await userService_1.default.wxLogin(); + if (!loginResult.success) { + throw new Error('微信登录失败,请重试'); + } + // 调用实际的绑定接口 + return await userService_1.default.register({ + name: data.name, + phone: data.phone + }); + } + catch (error) { + console.error('绑定流程失败:', error); + throw error; + } + }, + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + console.log('申请页面卸载'); + } +}); diff --git a/dist/pages/employee/employee.js b/dist/pages/employee/employee.js new file mode 100644 index 0000000..8772a4a --- /dev/null +++ b/dist/pages/employee/employee.js @@ -0,0 +1,299 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const employeeService_1 = __importDefault(require("../../services/employeeService")); +const roleUtils_1 = require("../../utils/roleUtils"); +Page({ + data: { + // 员工列表 + employees: [], + filteredEmployees: [], + // 页面状态 + currentTab: 'list', // list: 列表页, add: 添加页 + loading: false, + // 添加员工表单数据 + addForm: { + name: '', + phone: '', + role: roleUtils_1.Role.DELIVERY_PERSON + }, + // 错误信息 + errorMessage: '', + successMessage: '', + // 搜索关键词 + searchKeyword: '', + // 角色选项 + roleOptions: (0, roleUtils_1.getRoleOptions)() + }, + onLoad() { + // 页面加载时获取员工列表 + this.loadEmployees(); + }, + onShow() { + // 页面显示时刷新数据 + this.loadEmployees(); + }, + /** + * 加载员工列表 + */ + async loadEmployees() { + this.setData({ + loading: true, + errorMessage: '', + successMessage: '' + }); + try { + const employees = await employeeService_1.default.getEmployees(); + // 获取过滤后的员工列表 + const filteredEmployees = this.getFilteredEmployees(employees); + this.setData({ + employees, + filteredEmployees, + loading: false + }); + } + catch (error) { + console.error('加载员工列表失败:', error); + this.setData({ + loading: false, + errorMessage: '加载员工列表失败,请稍后重试' + }); + } + }, + /** + * 切换页面标签 + */ + switchTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + currentTab: tab, + errorMessage: '', + successMessage: '' + }); + if (tab === 'list') { + this.loadEmployees(); + } + }, + /** + * 处理添加员工表单输入 + */ + onFormInput(e) { + const { field } = e.currentTarget.dataset; + const value = e.detail.value; + this.setData({ + [`addForm.${field}`]: value, + errorMessage: '', + successMessage: '' + }); + }, + /** + * 处理角色选择 + */ + onRoleChange(e) { + const index = e.detail.value; + const selectedRole = this.data.roleOptions[index].value; + this.setData({ + 'addForm.role': selectedRole + }); + }, + /** + * 验证表单数据 + */ + validateForm() { + 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 = /^\d+$/; + 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_1.default.addEmployee(this.data.addForm); + this.setData({ + loading: false, + successMessage: '员工添加成功', + addForm: { + name: '', + phone: '', + role: roleUtils_1.Role.DELIVERY_PERSON + } + }); + // 添加成功后自动切换到列表页 + setTimeout(() => { + this.setData({ + currentTab: 'list' + }); + this.loadEmployees(); + }, 1500); + } + catch (error) { + console.error('添加员工失败:', error); + this.setData({ + loading: false, + errorMessage: error.message || '添加员工失败,请稍后重试' + }); + } + }, + /** + * 删除员工 + */ + async deleteEmployee(e) { + 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_1.default.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) { + 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) { + 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) { + const roleMap = { + 'DELIVERY_PERSON': '配送员', + 'ADMIN': '管理员' + }; + return roleMap[role] || role; + }, + /** + * 清空消息 + */ + clearMessages() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + }, + /** + * 返回上一页 + */ + goBack() { + wx.navigateBack(); + } +}); diff --git a/dist/pages/index/index.js b/dist/pages/index/index.js new file mode 100644 index 0000000..e0724d0 --- /dev/null +++ b/dist/pages/index/index.js @@ -0,0 +1,472 @@ +"use strict"; +// index.ts +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// 引入服务和工具函数 +const userService_1 = __importDefault(require("../../services/userService")); +// 引入主页面模块 +const mainPageModule_1 = require("./modules/mainPageModule"); +// 主页面组件 +Component({ + data: { + longitude: 116.397428, + latitude: 39.90923, + scale: 16, + markers: [], + userInfo: null, + authStatus: { + hasWxCode: false, + userStatus: 'unknown' + }, + showUserPanel: false, + showOrderPanel: false, + currentOrder: null, + currentDeliveryPerson: null, + currentWarehouse: null, + currentPanelPosition: { x: 0, y: 0 }, + polyline: null, + pendingOrders: [], + currentRoute: null, + showRoute: false, + routeDistance: 0, + routeDuration: 0, + showWarehouseModal: false, + showDeliveryPersonModal: false, + warehouseModalState: 'bottom', + deliveryPersonModalState: 'bottom', + showSignOutButton: false, + showSignInButton: false, + showRegisterButton: false, + showAuthButton: true, + // mainPageModule: null as MainPageModule | null + }, + lifetimes: { + attached() { + console.log('index page attached'); + this.initPage(); + }, + detached() { + console.log('index page detached'); + // 清理资源 + if (this.data.mainPageModule) { + this.data.mainPageModule.cleanup(); + } + }, + // 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(); + // } + // } + }, + methods: { + // 处理货运人员底部弹窗关闭 + onDeliveryPersonModalClose() { + this.setData({ + showDeliveryPersonModal: false + }); + }, + // 处理仓库底部弹窗关闭 + onWarehouseModalClose() { + this.setData({ + showWarehouseModal: false + }); + }, + // 初始化页面 + async initPage() { + // 初始化主页面模块 + this.setData({ + mainPageModule: new mainPageModule_1.MainPageModule(this) + }); + // 异步检查登录状态 + await this.checkAndUpdateLoginStatus(); + }, + // 异步检查并更新登录状态 + async checkAndUpdateLoginStatus() { + try { + // 获取登录模块 + if (!this.data.mainPageModule) { + console.error('mainPageModule未初始化'); + return; + } + const loginModule = this.data.mainPageModule.getLoginModule(); + // 显示加载状态 + wx.showLoading({ + title: '检查登录状态...', + mask: true + }); + // 异步检查登录状态 + const isLoggedIn = await this.asyncCheckLoginStatus(loginModule); + if (isLoggedIn) { + // 已登录状态 - 按钮状态会在refreshPageAfterLogin中统一更新 + console.log('✅ 登录检查成功,等待统一页面刷新'); + } + else { + // 未登录状态 + this.setData({ + userInfo: null, + 'authStatus.hasWxCode': false, + 'authStatus.userStatus': 'unknown', + // 初始化按钮显示状态 + showSignInButton: false, + showRegisterButton: false, + showAuthButton: true + }); + } + } + catch (error) { + console.error('检查登录状态失败:', error); + // 出错时设置为未登录状态 + this.setData({ + userInfo: null, + 'authStatus.hasWxCode': false, + 'authStatus.userStatus': 'unknown', + showSignInButton: false, + showRegisterButton: false, + showAuthButton: true + }); + console.log('❌ 登录检查失败,重置为未登录状态'); + console.log(' - authStatus.hasWxCode 设置为 false'); + console.log(' - showRegisterButton 设置为 false'); + } + finally { + wx.hideLoading(); + } + }, + // 异步检查登录状态 + async asyncCheckLoginStatus(loginModule) { + getApp(); + const app = getApp(); + // 首先检查本地是否有登录状态 + if (app.globalData.isLoggedIn && app.globalData.userInfo) { + // 本地已有登录状态,此时可以安全获取用户状态(因为有token) + const userStatus = await loginModule.determineUserStatus(app.globalData.userInfo); + // 更新页面状态 + this.setData({ + userInfo: app.globalData.userInfo, + 'authStatus.hasWxCode': true, + 'authStatus.userStatus': userStatus + }); + console.log('✅ 使用本地登录状态,用户状态已更新:', userStatus); + // 登录成功后统一刷新页面 + this.refreshPageAfterLogin(); + return true; + } + // 如果本地没有登录状态,尝试执行静默登录 + try { + const loginResult = await userService_1.default.wxLogin(); + if (loginResult.success && loginResult.userInfo) { + const app = getApp(); + // 登录成功,更新全局用户信息 + app.globalData.userInfo = loginResult.userInfo; + app.globalData.isLoggedIn = true; + // 登录成功后,此时已经有token,可以安全获取用户状态 + const userStatus = await loginModule.determineUserStatus(loginResult.userInfo); + // 更新页面状态 + this.setData({ + userInfo: loginResult.userInfo, + 'authStatus.hasWxCode': true, + 'authStatus.userStatus': userStatus + }); + console.log('✅ 静默登录成功,用户状态已更新:', userStatus); + // === 全局登录流程完成,登录成功 === + // 统一在此处执行一次完整的页面刷新 + this.refreshPageAfterLogin(); + return true; + } + } + catch (error) { + console.warn('静默登录失败:', error); + } + return false; + }, + // 更新按钮显示状态 + updateButtonDisplayStatus() { + 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 && 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 ? this.data.authStatus.userStatus : 'undefined'); + console.log(' - 当前hasWxCode:', this.data.authStatus ? this.data.authStatus.hasWxCode : 'undefined'); + this.setData({ + showSignInButton, + showRegisterButton, + showSignOutButton + }); + console.log('✅ 按钮状态已更新到页面'); + }, + // 登录成功后统一刷新页面 + async refreshPageAfterLogin() { + if (!this.data.mainPageModule) { + console.error('mainPageModule未初始化'); + return; + } + const loginModule = this.data.mainPageModule.getLoginModule(); + getApp(); + console.log('🔄 === 全局登录流程完成,执行统一页面刷新 ==='); + // 1. 更新页面登录状态 + const app = getApp(); + loginModule.updatePageAfterLogin(app.globalData.userInfo); + // 2. 更新按钮显示状态 + this.updateButtonDisplayStatus(); + // 3. 初始化主页面模块 + await this.data.mainPageModule.onLoad(); + // 4. 登录成功后只加载公开数据(仓库等),不加载业务数据 + // 业务数据(员工位置等)只有在用户签到后才应该加载 + if (app.globalData.isLoggedIn && this.data.authStatus.userStatus === 'signed_in') { + console.log('🔍 登录成功,开始加载公开数据...'); + await this.data.mainPageModule.loadPublicData(); + console.log('✅ 公开数据加载完成'); + } + console.log('✅ 统一页面刷新完成'); + }, + // 处理仓库底部弹窗状态变化 + onWarehouseModalStateChange(e) { + const state = e.detail.state; + this.setData({ warehouseModalState: state }); + }, + // 处理货运人员底部弹窗状态变化 + onDeliveryPersonModalStateChange(e) { + const state = e.detail.state; + this.setData({ deliveryPersonModalState: state }); + }, + // 处理签到 - 已迁移到LoginModule + async handleSignIn() { + if (!this.data.mainPageModule) { + console.error('mainPageModule未初始化'); + return; + } + const loginModule = this.data.mainPageModule.getLoginModule(); + const success = await loginModule.handleSignIn(); + if (success) { + // 更新按钮显示状态 + this.updateButtonDisplayStatus(); + // 刷新页面数据 + await this.data.mainPageModule.refreshAllData(); + } + }, + // 处理授权登录 - 已迁移到LoginModule + async handleAuthLogin() { + 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 userStatus = loginModule.determineUserStatus(app.globalData.userInfo); + this.setData({ + userInfo: app.globalData.userInfo, + 'authStatus.hasWxCode': true, + 'authStatus.userStatus': userStatus + }); + console.log('✅ 手动登录成功,authStatus.hasWxCode 设置为 true'); + console.log('✅ 用户状态已更新:', userStatus); + // === 全局登录流程完成,登录成功 === + // 统一在此处执行一次完整的页面刷新 + this.refreshPageAfterLogin(); + } + }, + // 处理签退 - 已迁移到LoginModule + async handleSignOut() { + if (!this.data.mainPageModule) { + console.error('mainPageModule未初始化'); + return; + } + const loginModule = this.data.mainPageModule.getLoginModule(); + const success = await loginModule.handleSignOut(); + if (success) { + // 更新按钮显示状态 + this.updateButtonDisplayStatus(); + // 刷新页面数据 + await this.data.mainPageModule.refreshAllData(); + } + }, + // 处理绑定按钮点击 + handleRegister() { + console.log('用户点击绑定按钮'); + // 跳转到绑定页面 + wx.navigateTo({ + url: '/pages/apply/apply' + }); + }, + // 显示员工账号绑定表单 - 跳转到独立页面 + showApplyForm() { + wx.navigateTo({ + url: '/pages/apply/apply' + }); + }, + // 跳转到管理员页面 + goToAdminPage() { + wx.navigateTo({ + url: '/pages/admin/admin' + }); + }, + // 跳转到员工管理页面 + goToEmployeeManagement() { + wx.navigateTo({ + url: '/pages/employee/employee' + }); + }, + // 阻止事件冒泡 + stopPropagation(e) { + if (e && typeof e.stopPropagation === 'function') { + e.stopPropagation(); + } + }, + // 获取性别文本表示 + getGenderText(gender) { + if (gender === 1) + return '男'; + if (gender === 2) + return '女'; + return '未知'; + }, + // 格式化坐标对 + formatCoordinatePair(longitude, latitude) { + return `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`; + }, + // 用户登出 + userLogout() { + if (this.data.mainPageModule) { + const loginModule = this.data.mainPageModule.getLoginModule(); + loginModule.logout(); + // 更新页面状态 + this.setData({ + isLoggedIn: false, + userInfo: null + }); + // 更新按钮显示状态 + this.updateButtonDisplayStatus(); + } + }, + // 显示用户面板 + showUserPanel() { + this.setData({ showUserPanel: true }); + }, + // 跳转到管理界面(基于用户角色) + goToManagementPage() { + if (this.data.userInfo && this.data.userInfo.role) { + if (this.data.userInfo.role === 'ADMIN') { + // 管理员跳转到管理员界面 - 使用navigateTo保持当前页面状态 + wx.navigateTo({ + url: '/pages/staff/admin-dashboard' + }); + } + else { + // 员工跳转到员工界面 - 使用navigateTo保持当前页面状态 + wx.navigateTo({ + url: '/pages/staff/employee-dashboard' + }); + } + } + else { + // 未登录用户显示用户面板 + this.setData({ showUserPanel: true }); + } + }, + // 隐藏用户面板 + hideUserPanel() { + this.setData({ showUserPanel: false }); + }, + // 隐藏所有面板 + hideAllPanels() { + if (this.data.mainPageModule) { + this.data.mainPageModule.hideAllPanels(); + } + }, + // 重置标记点状态 + resetMarkers() { + if (this.data.mainPageModule) { + this.data.mainPageModule.resetMarkers(); + } + }, + // 地图点击事件 + onMapTap(e) { + if (this.data.mainPageModule) { + this.data.mainPageModule.onMapTap(e); + } + }, + // 标记点点击事件 + onMarkerTap(e) { + if (this.data.mainPageModule) { + this.data.mainPageModule.onMarkerTap(e); + } + }, + // 分配订单 + async assignOrder(orderId, deliveryPersonId) { + if (this.data.mainPageModule) { + const orderModule = this.data.mainPageModule.getOrderModule(); + await orderModule.assignOrder(orderId, deliveryPersonId); + } + }, + // 更新订单状态 + async updateOrderStatus(orderId, status) { + if (this.data.mainPageModule) { + const orderModule = this.data.mainPageModule.getOrderModule(); + await orderModule.updateOrderStatus(orderId, status); + } + }, + // 展开仓库面板 + expandWarehousePanel() { + if (this.data.mainPageModule) { + const warehouseModule = this.data.mainPageModule.getWarehouseModule(); + warehouseModule.expandWarehousePanel(); + } + }, + // 收起仓库面板 + collapseWarehousePanel() { + if (this.data.mainPageModule) { + const warehouseModule = this.data.mainPageModule.getWarehouseModule(); + warehouseModule.collapseWarehousePanel(); + } + }, + // 展开员工面板 + expandDeliveryPersonPanel() { + if (this.data.mainPageModule) { + const employeeModule = this.data.mainPageModule.getEmployeeModule(); + employeeModule.expandDeliveryPersonPanel(); + } + }, + // 收起员工面板 + collapseDeliveryPersonPanel() { + if (this.data.mainPageModule) { + const employeeModule = this.data.mainPageModule.getEmployeeModule(); + employeeModule.collapseDeliveryPersonPanel(); + } + }, + // 刷新所有数据 + async refreshAllData() { + if (this.data.mainPageModule) { + await this.data.mainPageModule.refreshAllData(); + } + }, + // 开始定位(处理地图控制按钮点击) + async startLocation() { + if (this.data.mainPageModule) { + const mapModule = this.data.mainPageModule.getMapModule(); + await mapModule.startLocation(); + } + }, + } +}); diff --git a/dist/pages/index/modules/adminModule.js b/dist/pages/index/modules/adminModule.js new file mode 100644 index 0000000..abe8764 --- /dev/null +++ b/dist/pages/index/modules/adminModule.js @@ -0,0 +1,59 @@ +"use strict"; +// 管理员模块 - 专门处理管理员相关功能 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AdminModule = void 0; +class AdminModule { + constructor(_pageContext, dataModule) { + this.dataModule = dataModule; + } + /** + * 处理管理员标记点点击 + */ + onAdminMarkerClick(admin, position) { + console.log('管理员被点击:', admin); + // 显示管理员详情面板 + this.showAdminPanel(admin, position); + } + /** + * 显示管理员详情面板 + */ + showAdminPanel(admin, _position) { + console.log('显示管理员详情面板:', admin); + // 设置当前管理员 + this.dataModule.setCurrentDeliveryPerson(admin); + // 显示面板 + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 隐藏管理员详情面板 + */ + hideAdminPanel() { + this.dataModule.toggleDeliveryPersonModal(false); + this.dataModule.setCurrentDeliveryPerson(null); + } + /** + * 展开管理员详情面板 + */ + expandAdminPanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'full'); + } + /** + * 收起管理员详情面板 + */ + collapseAdminPanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 获取管理员信息摘要 + */ + getAdminSummary(admin) { + return `${admin.name || '管理员'} - ${admin.role || 'ADMIN'}`; + } + /** + * 清理资源 + */ + cleanup() { + console.log('清理管理员模块资源'); + } +} +exports.AdminModule = AdminModule; diff --git a/dist/pages/index/modules/dataModule.js b/dist/pages/index/modules/dataModule.js new file mode 100644 index 0000000..70e8c79 --- /dev/null +++ b/dist/pages/index/modules/dataModule.js @@ -0,0 +1,254 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DataModule = void 0; +/** + * 页面数据管理模块 + * 负责管理index页面的所有数据状态 + */ +class DataModule { + constructor(pageContext) { + this.pageContext = pageContext; + } + /** + * 初始化页面数据 + */ + async initializeData() { + // 检查是否已静默登录,如果是则尝试获取真实位置 + 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: initialLongitude, + latitude: initialLatitude, + scale: 13, + markers: [], + polyline: null, + showRoute: false, + routeDistance: 0, + routeDuration: 0, + currentRoute: null, + // 用户相关数据 + userInfo: null, + isLoggedIn: false, + showUserPanel: false, + // 订单相关数据 + pendingOrders: [], + showOrderPanel: false, + currentOrder: null, + // 货运人员相关数据 + showDeliveryPersonModal: false, + deliveryPersonModalState: 'bottom', + currentDeliveryPerson: null, + // 仓库相关数据 + showWarehouseModal: false, + warehouseModalState: 'bottom', + currentWarehouse: null, + // 面板位置数据 + currentPanelPosition: { x: 0, y: 0 }, + // 对话框相关数据 + showNickNameDialog: false, + tempNickName: '' + }); + } + /** + * 更新用户信息 + */ + updateUserInfo(userInfo) { + this.pageContext.setData({ + userInfo, + isLoggedIn: !!userInfo + }); + // 更新按钮显示状态 + if (this.pageContext.updateButtonDisplayStatus) { + this.pageContext.updateButtonDisplayStatus(); + } + } + /** + * 设置登录状态 + */ + setLoginStatus(isLoggedIn) { + this.pageContext.setData({ + isLoggedIn + }); + } + /** + * 设置地图缩放级别 + */ + setMapScale(scale) { + this.pageContext.setData({ + scale + }); + } + /** + * 更新用户位置信息 + */ + updateUserLocation(latitude, longitude) { + this.pageContext.setData({ + latitude, + longitude + }); + } + /** + * 更新地图标记点 + */ + updateMarkers(markers) { + // 验证每个标记点的坐标 + const validatedMarkers = markers.map((marker) => { + // 检查经纬度是否为有效数字 + if (isNaN(marker.longitude) || isNaN(marker.latitude)) { + // 为无效坐标设置默认值 + return { + ...marker, + longitude: 102.833722, // 默认经度 + latitude: 24.880095 // 默认纬度 + }; + } + else { + return marker; + } + }); + // 执行更新 + this.pageContext.setData({ markers: validatedMarkers }); + } + /** + * 更新待分配订单列表 + */ + updatePendingOrders(orders) { + this.pageContext.setData({ pendingOrders: orders }); + } + /** + * 设置当前选中订单 + */ + setCurrentOrder(order) { + this.pageContext.setData({ currentOrder: order }); + } + /** + * 设置当前选中货运人员 + */ + setCurrentDeliveryPerson(person) { + this.pageContext.setData({ currentDeliveryPerson: person }); + } + /** + * 设置当前选中仓库 + */ + setCurrentWarehouse(warehouse) { + this.pageContext.setData({ currentWarehouse: warehouse }); + } + /** + * 设置面板位置 + */ + setPanelPosition(position) { + this.pageContext.setData({ currentPanelPosition: position }); + } + /** + * 显示/隐藏用户面板 + */ + toggleUserPanel(show) { + this.pageContext.setData({ showUserPanel: show }); + } + /** + * 显示/隐藏订单面板 + */ + toggleOrderPanel(show) { + this.pageContext.setData({ showOrderPanel: show }); + } + /** + * 显示/隐藏仓库弹窗 + */ + toggleWarehouseModal(show, state = 'bottom') { + this.pageContext.setData({ + showWarehouseModal: show, + warehouseModalState: state + }); + } + /** + * 显示/隐藏货运人员弹窗 + */ + toggleDeliveryPersonModal(show, state = 'bottom') { + this.pageContext.setData({ + showDeliveryPersonModal: show, + deliveryPersonModalState: state + }); + } + /** + * 显示/隐藏昵称修改对话框 + */ + toggleNickNameDialog(show, tempNickName = '') { + this.pageContext.setData({ + showNickNameDialog: show, + tempNickName + }); + } + /** + * 更新路线信息 + */ + updateRouteInfo(polyline, distance, duration) { + this.pageContext.setData({ + polyline, + routeDistance: distance, + routeDuration: duration, + showRoute: true + }); + } + /** + * 清除路线信息 + */ + clearRouteInfo() { + this.pageContext.setData({ + polyline: null, + showRoute: false, + routeDistance: 0, + routeDuration: 0, + currentRoute: null + }); + } + /** + * 获取当前数据状态 + */ + getData() { + return this.pageContext.data; + } + /** + * 重置所有数据到初始状态 + */ + resetAllData() { + this.initializeData(); + } + /** + * 获取位置模块(用于其他模块访问位置模块) + */ + getLocationModule() { + // 通过页面上下文获取位置模块 + if (this.pageContext.data.mainPageModule) { + return this.pageContext.data.mainPageModule.getLocationModule(); + } + return null; + } + /** + * 获取地图模块(用于其他模块访问地图模块) + */ + getMapModule() { + // 通过页面上下文获取地图模块 + if (this.pageContext.data.mainPageModule) { + return this.pageContext.data.mainPageModule.getMapModule(); + } + return null; + } +} +exports.DataModule = DataModule; diff --git a/dist/pages/index/modules/deliveryPersonModule.js b/dist/pages/index/modules/deliveryPersonModule.js new file mode 100644 index 0000000..11c659b --- /dev/null +++ b/dist/pages/index/modules/deliveryPersonModule.js @@ -0,0 +1,108 @@ +"use strict"; +// 货运人员模块 - 专门处理货运人员相关功能 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DeliveryPersonModule = void 0; +class DeliveryPersonModule { + constructor(_pageContext, dataModule) { + this.dataModule = dataModule; + } + /** + * 处理货运人员标记点点击 + */ + onDeliveryPersonMarkerClick(deliveryPerson, position) { + console.log('货运人员被点击:', deliveryPerson); + // 显示货运人员详情面板(包含订单列表) + this.showDeliveryPersonPanel(deliveryPerson, position); + } + /** + * 显示货运人员详情面板 + */ + showDeliveryPersonPanel(deliveryPerson, _position) { + console.log('显示货运人员详情面板:', deliveryPerson); + // 设置当前货运人员 + this.dataModule.setCurrentDeliveryPerson(deliveryPerson); + // 显示面板 + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 隐藏货运人员详情面板 + */ + hideDeliveryPersonPanel() { + this.dataModule.toggleDeliveryPersonModal(false); + this.dataModule.setCurrentDeliveryPerson(null); + } + /** + * 展开货运人员详情面板 + */ + expandDeliveryPersonPanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'full'); + } + /** + * 收起货运人员详情面板 + */ + collapseDeliveryPersonPanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 获取货运人员信息摘要 + */ + getDeliveryPersonSummary(deliveryPerson) { + return `${deliveryPerson.name || '货运人员'} - ${deliveryPerson.role || 'DRIVER'}`; + } + /** + * 获取货运人员状态文本 + */ + getDeliveryPersonStatusText(status) { + const statusMap = { + 'idle': '空闲', + 'busy': '忙碌', + 'offline': '离线' + }; + return statusMap[status] || status; + } + /** + * 获取货运人员状态颜色 + */ + getDeliveryPersonStatusColor(status) { + switch (status) { + case 'idle': + return '#52c41a'; // 绿色 + case 'busy': + return '#faad14'; // 橙色 + case 'offline': + return '#d9d9d9'; // 灰色 + default: + return '#d9d9d9'; // 灰色 + } + } + /** + * 获取货运人员当前订单信息 + */ + getCurrentOrderInfo(person) { + if (!person.currentOrder) { + return '暂无订单'; + } + const order = person.currentOrder; + return `订单 #${order.id} - ${this.getOrderStatusText(order.status)}`; + } + /** + * 获取订单状态文本 + */ + getOrderStatusText(status) { + const statusMap = { + 'pending': '未分配', + 'assigned': '已分配', + 'in_transit': '配送中', + 'delivered': '已完成', + 'cancelled': '已取消' + }; + return statusMap[status] || status; + } + /** + * 清理资源 + */ + cleanup() { + console.log('清理货运人员模块资源'); + } +} +exports.DeliveryPersonModule = DeliveryPersonModule; diff --git a/dist/pages/index/modules/employeeModule.js b/dist/pages/index/modules/employeeModule.js new file mode 100644 index 0000000..480a131 --- /dev/null +++ b/dist/pages/index/modules/employeeModule.js @@ -0,0 +1,81 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EmployeeModule = void 0; +const employeeService_1 = __importDefault(require("../../../services/employeeService")); +class EmployeeModule { + constructor(_pageContext, dataModule) { + this.dataModule = dataModule; + } + /** + * 处理员工标记点点击 - 通用处理逻辑 + */ + onEmployeeMarkerClick(employee, _position) { + console.log('员工被点击:', employee); + // 设置当前员工 + this.dataModule.setCurrentDeliveryPerson(employee); + // 显示面板 + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 隐藏员工详情面板 + */ + hideEmployeePanel() { + this.dataModule.toggleDeliveryPersonModal(false); + this.dataModule.setCurrentDeliveryPerson(null); + } + /** + * 展开员工详情面板 + */ + expandEmployeePanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'full'); + } + /** + * 收起员工详情面板 + */ + collapseEmployeePanel() { + this.dataModule.toggleDeliveryPersonModal(true, 'bottom'); + } + /** + * 获取员工信息摘要 + */ + getEmployeeSummary(employee) { + return `${employee.name || '员工'} - ${employee.role || '未知角色'}`; + } + /** + * 获取员工状态文本 + */ + getEmployeeStatusText(status) { + const statusMap = { + 'idle': '空闲', + 'busy': '忙碌', + 'offline': '离线' + }; + return statusMap[status] || status; + } + /** + * 加载所有员工数据 + */ + async loadAllEmployees() { + try { + console.log('开始加载所有员工数据'); + const employees = await employeeService_1.default.getEmployees(); + console.log('员工数据加载完成:', employees); + // 这里可以添加员工数据处理逻辑,比如更新到dataModule + // this.dataModule.updateEmployees(employees); + } + catch (error) { + console.error('加载员工数据失败:', error); + throw error; // 重新抛出错误,让调用方处理 + } + } + /** + * 清理资源 + */ + cleanup() { + console.log('清理员工模块资源'); + } +} +exports.EmployeeModule = EmployeeModule; diff --git a/dist/pages/index/modules/locationModule.js b/dist/pages/index/modules/locationModule.js new file mode 100644 index 0000000..bbf06a4 --- /dev/null +++ b/dist/pages/index/modules/locationModule.js @@ -0,0 +1,265 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LocationModule = void 0; +// 位置模块 - 处理位置追踪、位置更新和地图标记点更新 +const helpers_1 = require("../../../utils/helpers"); +const locationTrackingService_1 = __importDefault(require("../../../services/locationTrackingService")); +class LocationModule { + constructor(_pageContext, dataModule) { + this.dataModule = dataModule; + this.isTracking = false; + } + /** + * 初始化位置模块 + */ + async initialize() { + console.log('位置模块初始化'); + // 注册位置更新回调,当位置追踪服务有更新时自动刷新地图标记 + this.setupLocationUpdateCallback(); + console.log('✅ 位置模块初始化完成'); + } + /** + * 设置位置更新回调 + */ + setupLocationUpdateCallback() { + console.log('📝 [LocationModule] 设置位置更新回调'); + // 注册位置更新回调 + locationTrackingService_1.default.subscribeToLocationUpdates(async (onlineUsers) => { + console.log('🔔 [LocationModule] 收到位置更新通知'); + console.log('👥 [LocationModule] 在线用户数量:', onlineUsers.length); + // 调用updateEmployeeMarkers方法更新地图标记 + try { + await this.updateEmployeeMarkers(onlineUsers); + } + catch (error) { + console.error('更新员工标记点失败:', error); + } + }); + console.log('✅ [LocationModule] 位置更新回调设置完成'); + } + /** + * 清理位置模块 + */ + cleanup() { + console.log('清理位置模块'); + // 停止实时跟踪 + if (this.isTracking) { + this.stopRealTimeTracking().catch(error => { + console.error('停止实时跟踪失败:', error); + }); + } + console.log('✅ 位置模块清理完成'); + } + /** + * 开始实时跟踪位置 + */ + async startRealTimeTracking() { + try { + // 获取当前用户信息 + const userInfo = this.dataModule.getData().userInfo; + if (!userInfo || !userInfo.id) { + throw new Error('用户信息获取失败'); + } + // 启动位置跟踪订阅(WebSocket连接) + await locationTrackingService_1.default.startTracking(); + this.isTracking = true; + (0, helpers_1.showToast)('已开始实时跟踪'); + console.log('开始实时跟踪位置'); + } + catch (error) { + console.error('开始实时跟踪失败:', error); + (0, helpers_1.showToast)('开始实时跟踪失败'); + throw error; + } + } + /** + * 停止实时跟踪位置 + */ + async stopRealTimeTracking() { + try { + // 获取当前用户信息 + const userInfo = this.dataModule.getData().userInfo; + if (!userInfo || !userInfo.id) { + throw new Error('用户信息获取失败'); + } + // 停止位置跟踪 + await locationTrackingService_1.default.stopTracking(); + this.isTracking = false; + (0, helpers_1.showToast)('已停止实时跟踪'); + console.log('停止实时跟踪位置'); + } + catch (error) { + console.error('停止实时跟踪失败:', error); + (0, helpers_1.showToast)('停止实时跟踪失败'); + throw error; + } + } + /** + * 更新员工标记点 + */ + async updateEmployeeMarkers(onlineUsers) { + console.log('📍 [LocationModule] 开始更新员工标记点'); + console.log('👥 [LocationModule] 传入用户数量:', onlineUsers.length); + // 获取地图模块来更新标记点 + const mapModule = this.dataModule.getMapModule(); + if (!mapModule) { + console.error('地图模块未找到,无法更新员工标记点'); + return; + } + // 获取当前地图上的所有标记点 + const { markers } = this.dataModule.getData(); + const currentEmployeeMarkers = markers.filter((marker) => marker.type === 'employee'); + console.log('🗺️ [LocationModule] 当前地图上员工标记点数量:', currentEmployeeMarkers.length); + // 为每个在线用户创建标记点 + const newEmployeeMarkers = await Promise.all(onlineUsers.map(async (user, index) => { + console.log(`🔍 [LocationModule] 处理第 ${index + 1} 个用户:`, user.userId || '未知ID'); + // 获取用户角色信息 + 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(); + console.log(`📡 [LocationModule] 用户 ${index + 1} 位置: ${latitude}, ${longitude}`); + console.log(`👤 [LocationModule] 用户 ${index + 1} 信息: ${user.userName || '未知用户'} (${userRole})`); + // 获取员工图标 + const iconPath = await this.getEmployeeIcon(user.userId, userRole); + const employeeMarker = { + id: 10000 + user.userId, // 避免ID冲突 + type: 'employee', + title: user.userName || `员工${user.userId}`, + longitude: longitude, + latitude: latitude, + iconPath: iconPath, + width: 32, + height: 32, + zIndex: 30, + data: { + userId: user.userId, + role: userRole, + lastUpdateTime: lastUpdateTime + } + }; + return employeeMarker; + })); + console.log('📍 [LocationModule] 新生成标记点数量:', newEmployeeMarkers.length); + // 对比新旧标记点,精确更新 + this.updateMarkersPrecisely(mapModule, currentEmployeeMarkers, newEmployeeMarkers); + console.log('✅ [LocationModule] 员工标记点更新完成'); + } + /** + * 精确更新标记点 - 通过对比新旧标记点来添加、更新和删除 + */ + updateMarkersPrecisely(mapModule, currentMarkers, newMarkers) { + console.log('🎯 [LocationModule] 开始精确更新标记点'); + // 创建用户ID到标记点的映射 + const currentMarkerMap = new Map(); + currentMarkers.forEach(marker => { + if (marker.data && marker.data.userId) { + currentMarkerMap.set(marker.data.userId, marker); + } + }); + const newMarkerMap = new Map(); + newMarkers.forEach(marker => { + if (marker.data && marker.data.userId) { + newMarkerMap.set(marker.data.userId, marker); + } + }); + console.log('📊 [LocationModule] 当前标记点用户ID:', Array.from(currentMarkerMap.keys())); + console.log('📊 [LocationModule] 新标记点用户ID:', Array.from(newMarkerMap.keys())); + // 找出需要删除的标记点(在当前但不在新的) + const markersToRemove = currentMarkers.filter(marker => { + if (marker.data && marker.data.userId) { + return !newMarkerMap.has(marker.data.userId); + } + return false; + }); + // 找出需要添加的标记点(在新的但不在当前的) + const markersToAdd = newMarkers.filter(marker => { + if (marker.data && marker.data.userId) { + return !currentMarkerMap.has(marker.data.userId); + } + return false; + }); + // 找出需要更新的标记点(位置或信息有变化) + const markersToUpdate = newMarkers.filter(marker => { + if (marker.data && marker.data.userId) { + const currentMarker = currentMarkerMap.get(marker.data.userId); + if (currentMarker) { + // 检查位置是否变化 + return currentMarker.longitude !== marker.longitude || + currentMarker.latitude !== marker.latitude || + currentMarker.title !== marker.title || + currentMarker.iconPath !== marker.iconPath; + } + } + return false; + }); + console.log(`🗑️ [LocationModule] 需要删除的标记点数量: ${markersToRemove.length}`); + console.log(`➕ [LocationModule] 需要添加的标记点数量: ${markersToAdd.length}`); + console.log(`🔄 [LocationModule] 需要更新的标记点数量: ${markersToUpdate.length}`); + // 执行删除操作 + markersToRemove.forEach(marker => { + console.log(`🗑️ [LocationModule] 删除标记点: 用户 ${marker.data.userId}`); + mapModule.removeEmployeeMarker(marker.id); + }); + // 执行添加操作 + markersToAdd.forEach(marker => { + console.log(`➕ [LocationModule] 添加标记点: 用户 ${marker.data.userId}`); + mapModule.addEmployeeMarker(marker); + }); + // 执行更新操作 + markersToUpdate.forEach(marker => { + console.log(`🔄 [LocationModule] 更新标记点: 用户 ${marker.data.userId}`); + mapModule.updateSingleEmployeeMarker(marker); + }); + console.log('🎯 [LocationModule] 精确更新标记点完成'); + } + /** + * 获取用户角色 + */ + getUserRole(userId) { + // 从数据模块获取用户信息 + const userInfo = this.dataModule.getData().userInfo; + if (userInfo && userInfo.id === userId) { + return userInfo.role || 'employee'; + } + // 如果是其他用户,默认为货运人员 + return 'employee'; + } + /** + * 获取员工图标 + */ + async getEmployeeIcon(userId, userRole) { + // 首先尝试获取用户头像URL + const userInfo = this.dataModule.getData().userInfo; + // 如果是当前用户,使用当前用户的头像 + if (userInfo && userInfo.id === userId && userInfo.avatarUrl) { + return userInfo.avatarUrl; + } + // 对于其他用户,尝试从员工服务获取头像URL + try { + const employeeService = require('../../services/employeeService').default; + const avatarUrl = await employeeService.getAvatarUrl(userId); + if (avatarUrl) { + return avatarUrl; + } + } + catch (error) { + console.warn(`获取用户 ${userId} 的头像失败:`, error); + } + // 如果获取头像失败,使用默认图标 + if (userRole === 'ADMIN') { + return '/images/crown.png'; // 管理员图标 + } + else if (userRole === 'DRIVER') { + return '/images/truck.png'; // 司机图标 + } + else { + return '/images/truck.png'; // 普通员工也使用货运图标 + } + } +} +exports.LocationModule = LocationModule; diff --git a/dist/pages/index/modules/loginModule.js b/dist/pages/index/modules/loginModule.js new file mode 100644 index 0000000..4c2707e --- /dev/null +++ b/dist/pages/index/modules/loginModule.js @@ -0,0 +1,482 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LoginModule = void 0; +// 登录模块 - 处理用户登录、授权、用户信息管理 +const helpers_1 = require("../../../utils/helpers"); +const userService_1 = __importDefault(require("../../../services/userService")); +const locationTrackingService_1 = __importDefault(require("../../../services/locationTrackingService")); +class LoginModule { + constructor(pageContext, dataModule) { + this.pageContext = pageContext; + this.dataModule = dataModule; + } + /** + * 处理用户信息 + */ + async processUserInfo(userInfo) { + // 直接返回用户信息,不进行额外处理 + return userInfo; + } + /** + * 登录成功后更新页面状态 + */ + updatePageAfterLogin(userInfo) { + this.dataModule.updateUserInfo(userInfo); + } + /** + * 检查登录状态 + */ + checkLoginStatus() { + const globalUserInfo = userService_1.default.getGlobalUserInfo(); + if (globalUserInfo) { + this.dataModule.updateUserInfo(globalUserInfo); + return true; + } + return false; + } + /** + * 用户退出登录 + */ + async logout() { + try { + // 停止位置追踪服务 + try { + await locationTrackingService_1.default.stopTracking(); + console.log('位置追踪服务已停止'); + } + catch (trackingError) { + console.warn('停止位置追踪失败,但不影响退出登录:', trackingError); + } + // 注意:这里不调用userService.logout(),因为服务器端的logout接口会删除token + // 而用户只是签退不接单,不是完全退出应用,需要保持token有效 + console.log('用户已退出登录(本地签退,保持token有效)'); + (0, helpers_1.showToast)('已退出登录'); + } + catch (error) { + console.error('退出登录失败:', error); + } + } + /** + * 显示手动登录模态框:如果静默登录失败后,让用户尝试手动登录 + */ + async showManualLoginModal() { + console.log('显示手动登录模态框'); + return new Promise((resolve) => { + wx.showModal({ + title: '手动登录', + content: '静默登录失败,请手动登录以使用完整功能', + confirmText: '手动登录', + cancelText: '暂不', + success: async (res) => { + if (res.confirm) { + // 用户点击确定,执行手动登录 + console.log('用户选择手动登录'); + const success = await this.performLogin(); + if (!success) { + // 登录失败,再次提供选项 + this.handleLoginFailure(resolve); + } + else { + resolve(success); + } + } + else { + // 用户取消登录,显示关闭小程序选项 + console.log('用户选择暂不登录'); + this.showCloseAppOption(resolve); + } + } + }); + }); + } + /** + * 执行登录流程 - 调用userService的登录方法 + * 静默登录失败后,只专注于登录本身,不涉及绑定、签到等复杂逻辑 + */ + async performLogin() { + try { + // 执行微信登录 + const result = await userService_1.default.wxLogin(); + if (result.success && result.userInfo) { + // 登录成功,更新页面状态 + this.updatePageAfterLogin(result.userInfo); + console.log('手动登录成功'); + return true; + } + console.log('手动登录失败'); + return false; + } + catch (error) { + console.error('执行登录流程失败:', error); + return false; + } + } + /** + * 处理登录失败的情况 + */ + async handleLoginFailure(resolve) { + console.log('登录失败,显示重试选项'); + wx.showModal({ + title: '登录失败', + content: '登录遇到问题,是否重试?', + confirmText: '重新登录', + cancelText: '取消并关闭', + success: async (res) => { + if (res.confirm) { + console.log('用户选择重新登录'); + const success = await this.performLogin(); + if (!success) { + // 再次登录失败,递归调用以避免嵌套过深 + this.handleLoginFailure(resolve); + } + else { + resolve(success); + } + } + else { + console.log('用户选择取消并关闭小程序'); + wx.showToast({ + title: '即将退出小程序', + icon: 'none', + duration: 1500, + complete: () => { + setTimeout(() => { + // 使用类型断言解决类型问题 + wx.exitMiniProgram(); + }, 1500); + } + }); + resolve(false); + } + } + }); + } + /** + * 显示关闭小程序选项 + */ + showCloseAppOption(resolve) { + console.log('显示关闭小程序选项'); + wx.showModal({ + title: '确认退出', + content: '不登录将无法使用完整功能,是否退出小程序?', + confirmText: '退出小程序', + cancelText: '留在页面', + success: (res) => { + if (res.confirm) { + console.log('用户确认退出小程序'); + // 使用类型断言解决类型问题 + wx.exitMiniProgram(); + resolve(false); + } + else { + console.log('用户选择留在页面'); + resolve(false); + } + } + }); + } + /** + * 处理签退流程 + */ + async handleSignOut() { + try { + // 显示加载中提示 + wx.showLoading({ + title: '签退中...', + }); + // 调用签退接口 + const signOutResult = await userService_1.default.signOut(); + wx.hideLoading(); + if (signOutResult.success) { + console.log('签退成功:', signOutResult); + wx.showToast({ + title: '签退成功', + icon: 'success', + duration: 2000 + }); + // 停止位置追踪服务 + try { + await locationTrackingService_1.default.stopTracking(); + console.log('位置追踪服务已停止'); + } + catch (trackingError) { + console.warn('停止位置追踪失败,但不影响签退:', trackingError); + } + // 设置用户状态为已签退 + if (this.pageContext && this.pageContext.setData) { + this.pageContext.setData({ + 'authStatus.userStatus': 'signed_out' + }); + // 更新按钮显示状态 + if (this.pageContext.updateButtonDisplayStatus) { + this.pageContext.updateButtonDisplayStatus(); + } + } + return true; + } + else { + console.warn('签退失败:', signOutResult.message); + wx.showToast({ + title: signOutResult.message || '签退失败', + icon: 'none', + duration: 2000 + }); + return false; + } + } + catch (error) { + console.error('签退过程中发生错误:', error); + wx.hideLoading(); + wx.showToast({ + title: '签退失败,请重试', + icon: 'none', + duration: 2000 + }); + return false; + } + } + /** + * 处理签到流程 + */ + async handleSignIn() { + try { + // 显示加载中提示 + wx.showLoading({ + title: '签到中...', + }); + // 先获取地图位置和当前时间 + let initialLocation; + try { + const location = await locationTrackingService_1.default.getCurrentLocation(); + // 使用秒级时间戳(除以1000取整) + initialLocation = { + latitude: location.latitude, + longitude: location.longitude, + timestamp: Math.floor(Date.now() / 1000) + }; + console.log('获取到签到位置数据:', initialLocation); + } + catch (error) { + console.error('获取位置失败:', error); + throw new Error('获取位置失败,请检查位置权限设置'); + } + // 调用实际的签到接口,传递位置数据 + const signInResult = await userService_1.default.signIn(initialLocation); + wx.hideLoading(); + if (signInResult.success) { + console.log('签到成功:', signInResult); + wx.showToast({ + title: '签到成功', + icon: 'success', + duration: 2000 + }); + // 更新用户信息 + if (signInResult.employeeInfo) { + // 将员工信息合并到用户信息中 + const app = getApp(); + if (app.globalData.userInfo) { + app.globalData.userInfo = { + ...app.globalData.userInfo, + name: signInResult.employeeInfo.name, + phone: signInResult.employeeInfo.phone + }; + this.updatePageAfterLogin(app.globalData.userInfo); + } + } + // 设置用户状态为已签到 + if (this.pageContext && this.pageContext.setData) { + this.pageContext.setData({ + 'authStatus.userStatus': 'signed_in' + }); + // 更新按钮显示状态 + if (this.pageContext.updateButtonDisplayStatus) { + this.pageContext.updateButtonDisplayStatus(); + } + } + // 启动位置追踪服务(WebSocket连接) + try { + // 启动位置追踪服务(建立WebSocket连接) + await locationTrackingService_1.default.startTracking(); + console.log('位置追踪服务已启动'); + } + catch (trackingError) { + console.warn('启动位置追踪失败,但不影响签到:', trackingError); + } + // 加载业务数据(所有登录用户) + try { + console.log('用户签到成功,开始加载业务数据'); + // 获取主页面模块并加载业务数据 + const mainPageModule = this.pageContext.data.mainPageModule; + if (mainPageModule && mainPageModule.loadBusinessData) { + await mainPageModule.loadBusinessData(); + console.log('业务数据加载完成'); + } + } + catch (businessError) { + console.warn('加载业务数据失败,但不影响签到:', businessError); + } + // 如果是管理员用户,加载员工数据 + if (signInResult.employeeInfo && signInResult.employeeInfo.role === 'ADMIN') { + try { + console.log('管理员用户签到成功,开始加载员工数据'); + // 获取主页面模块并加载员工数据 + const mainPageModule = this.pageContext.data.mainPageModule; + if (mainPageModule && mainPageModule.loadEmployeeData) { + await mainPageModule.loadEmployeeData(); + console.log('员工数据加载完成'); + } + } + catch (employeeError) { + console.warn('加载员工数据失败,但不影响签到:', employeeError); + } + } + return true; + } + else { + console.warn('签到失败:', signInResult.message); + wx.showToast({ + title: signInResult.message || '签到失败', + icon: 'none', + duration: 2000 + }); + return false; + } + } + catch (error) { + console.error('签到过程中发生错误:', error); + wx.hideLoading(); + wx.showToast({ + title: '签到失败,请重试', + icon: 'none', + duration: 2000 + }); + return false; + } + } + /** + * 处理授权登录流程 + */ + async handleAuthLogin() { + try { + const success = await this.showManualLoginModal(); + if (success) { + console.log('手动登录成功'); + return true; + } + else { + console.log('用户取消手动登录'); + return false; + } + } + catch (error) { + console.error('手动登录过程中发生错误:', error); + return false; + } + } + /** + * 判断用户是否为游客(已登录但信息不完整) + */ + isTourist() { + const app = getApp(); + // 游客定义:已登录但没有完善基本信息(姓名和电话)的用户 + if (app.globalData.isLoggedIn && app.globalData.userInfo) { + const userInfo = app.globalData.userInfo; + return !userInfo.name || !userInfo.phone; + } + return false; + } + /** + * 计算是否显示签到按钮(基于用户状态和角色) + */ + shouldShowSignInButton() { + // 从页面上下文中获取数据 + const pageData = this.dataModule.getData(); + const authStatus = pageData.authStatus || {}; + const userInfo = pageData.userInfo; + // 显示条件:已获取微信code、用户状态不是已签到、且用户不是游客(已绑定用户) + const result = (authStatus.hasWxCode && + (authStatus.userStatus === 'registered' || authStatus.userStatus === 'signed_out') && + userInfo !== null && + userInfo.role !== 'GUEST'); + return result; + } + /** + * 计算是否显示注册按钮(基于用户状态和角色) + */ + shouldShowRegisterButton() { + // 从页面上下文中获取数据 + const pageData = this.dataModule.getData(); + const authStatus = pageData.authStatus || {}; + const userInfo = pageData.userInfo; + // 显示条件:已获取微信code、用户状态为未绑定、且用户是游客 + const result = (authStatus.hasWxCode && + authStatus.userStatus === 'unregistered' && + userInfo !== null && + userInfo.role === 'GUEST'); + // 调试信息已删除 + return result; + } + /** + * 根据用户信息确定用户状态(从服务器获取真实状态) + */ + async determineUserStatus(userInfo) { + if (!userInfo) + return 'signed_out'; + try { + // 从服务器获取用户真实状态 + const serverStatus = await this.getUserStatusFromServer(); + if (serverStatus) { + console.log('从服务器获取的用户状态:', serverStatus); + return serverStatus; + } + // 如果服务器获取失败,使用本地逻辑作为降级方案 + console.warn('服务器状态获取失败,使用本地逻辑判断'); + const isRegistered = userInfo.name && userInfo.phone; + return isRegistered ? 'registered' : 'unregistered'; + } + catch (error) { + console.error('获取用户状态失败:', error); + // 网络错误时使用本地逻辑 + const isRegistered = userInfo.name && userInfo.phone; + return isRegistered ? 'registered' : 'unregistered'; + } + } + /** + * 从服务器获取用户签到状态 + */ + async getUserStatusFromServer() { + try { + // 调用服务器接口获取用户状态 + const response = await userService_1.default.getUserStatus(); + // 如果返回null,表示服务器接口不存在 + if (response === null) { + console.log('服务器状态接口不存在,跳过服务器状态获取'); + return null; + } + if (response && response.status) { + // 根据服务器返回的状态映射到前端状态 + switch (response.status) { + case 'signed_in': + return 'signed_in'; + case 'signed_out': + return 'signed_out'; + case 'registered': + return 'registered'; + case 'unregistered': + return 'unregistered'; + default: + return null; + } + } + return null; + } + catch (error) { + console.error('获取服务器状态失败:', error); + return null; + } + } +} +exports.LoginModule = LoginModule; diff --git a/dist/pages/index/modules/mainPageModule.js b/dist/pages/index/modules/mainPageModule.js new file mode 100644 index 0000000..0ceb630 --- /dev/null +++ b/dist/pages/index/modules/mainPageModule.js @@ -0,0 +1,330 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MainPageModule = void 0; +// 主页面模块 - 协调各个子模块,处理页面生命周期和事件分发 +const loginModule_1 = require("./loginModule"); +const mapModule_1 = require("./mapModule"); +const orderModule_1 = require("./orderModule"); +const warehouseModule_1 = require("./warehouseModule"); +const employeeModule_1 = require("./employeeModule"); +const adminModule_1 = require("./adminModule"); +const deliveryPersonModule_1 = require("./deliveryPersonModule"); +const locationModule_1 = require("./locationModule"); +const dataModule_1 = require("./dataModule"); +const helpers_1 = require("../../../utils/helpers"); +class MainPageModule { + constructor(pageContext) { + this.pageContext = pageContext; + // 初始化各个子模块 + this.dataModule = new dataModule_1.DataModule(pageContext); + this.loginModule = new loginModule_1.LoginModule(pageContext, this.dataModule); + this.mapModule = new mapModule_1.MapModule(pageContext, this.dataModule); + this.orderModule = new orderModule_1.OrderModule(pageContext, this.dataModule); + this.warehouseModule = new warehouseModule_1.WarehouseModule(pageContext, this.dataModule); + this.employeeModule = new employeeModule_1.EmployeeModule(this.pageContext, this.dataModule); + this.adminModule = new adminModule_1.AdminModule(this.pageContext, this.dataModule); + this.deliveryPersonModule = new deliveryPersonModule_1.DeliveryPersonModule(this.pageContext, this.dataModule); + this.locationModule = new locationModule_1.LocationModule(pageContext, this.dataModule); + } + /** + * 页面初始化 + */ + async onLoad() { + console.log('主页面模块初始化'); + try { + // 初始化应用 + await this.initApp(); + // 初始化位置模块 + await this.locationModule.initialize(); + // 加载地图数据 + await this.loadAllData(); + console.log('主页面初始化完成'); + } + catch (error) { + console.error('页面初始化失败:', error); + (0, helpers_1.showToast)('页面初始化失败'); + } + } + /** + * 页面显示 + */ + onShow() { + console.log('主页面显示'); + // 检查是否需要刷新数据 + this.refreshDataIfNeeded(); + } + /** + * 页面隐藏 + */ + onHide() { + console.log('主页面隐藏'); + // 清理位置模块 + this.locationModule.cleanup(); + } + /** + * 初始化应用 + */ + async initApp() { + // 初始化数据 + await this.dataModule.initializeData(); + console.log('应用初始化'); + } + /** + * 加载所有数据 + */ + async loadAllData() { + try { + // 初始化地图 + await this.mapModule.initMap(); + // 加载公开数据(不需要登录) + await this.loadPublicData(); + // 业务数据(订单和员工数据)在用户签到后单独加载 + console.log('公开数据加载完成,业务数据将在用户签到后加载'); + } + catch (error) { + console.error('加载数据失败:', error); + (0, helpers_1.showToast)('数据加载失败'); + } + } + /** + * 加载公开数据(不需要登录) + */ + async loadPublicData() { + console.log('加载公开数据'); + try { + // 加载仓库数据(公开数据,不需要登录) + await this.warehouseModule.loadWarehouses(); + console.log('公开数据加载完成'); + } + catch (error) { + console.error('加载公开数据失败:', error); + // 公开数据加载失败不影响主要功能,只记录日志 + } + } + /** + * 加载业务数据(仅在用户签到后调用) + */ + async loadBusinessData() { + console.log('加载业务数据'); + try { + // 加载待处理订单(所有登录用户都可以看到) + await this.orderModule.loadPendingOrders(); + console.log('业务数据加载完成'); + } + catch (error) { + console.error('加载业务数据失败:', error); + throw error; // 重新抛出错误,让调用方处理 + } + } + /** + * 加载员工数据(仅在管理员签到后调用) + */ + async loadEmployeeData() { + console.log('加载员工数据'); + try { + // 获取用户信息 + const app = getApp(); + const userInfo = app.globalData.userInfo; + // 只有管理员才加载员工列表 + if (userInfo && userInfo.role === 'ADMIN') { + console.log('管理员用户,加载员工列表'); + await this.employeeModule.loadAllEmployees(); + console.log('员工数据加载完成'); + } + else { + console.log('非管理员用户,跳过员工列表加载'); + } + } + catch (error) { + console.error('加载员工数据失败:', error); + throw error; // 重新抛出错误,让调用方处理 + } + } + /** + * 检查是否需要刷新数据 + */ + refreshDataIfNeeded() { + // 这里可以添加数据刷新逻辑 + // 例如:检查上次刷新时间,如果超过一定时间则重新加载数据 + console.log('检查数据刷新'); + } + /** + * 处理登出 + */ + onLogout() { + console.log('用户登出'); + // 清除业务数据 + this.clearBusinessData(); + // 重置用户标记点图标 + this.resetUserMarker(); + (0, helpers_1.showToast)('已登出'); + } + /** + * 清除业务数据 + */ + clearBusinessData() { + this.dataModule.updatePendingOrders([]); + const filteredMarkers = this.pageContext.data.markers.filter((marker) => marker.type !== 'warehouse' && marker.type !== 'employee'); + this.dataModule.updateMarkers(filteredMarkers); + } + /** + * 重置用户标记点 + */ + resetUserMarker() { + const { markers, userInfo } = this.pageContext.data; + const userRole = userInfo?.role || 'employee'; + const iconPath = userRole === 'ADMIN' ? '/images/crown.png' : '/images/truck.png'; + const updatedMarkers = markers.map((marker) => { + if (marker.id === -1) { + return { + ...marker, + iconPath: iconPath + }; + } + return marker; + }); + this.dataModule.updateMarkers(updatedMarkers); + } + /** + * 隐藏所有面板 + */ + hideAllPanels() { + this.dataModule.toggleUserPanel(false); + this.dataModule.toggleOrderPanel(false); + this.dataModule.toggleWarehouseModal(false); + this.dataModule.toggleDeliveryPersonModal(false); + } + /** + * 重置标记点状态 + */ + resetMarkers() { + // 这里可以添加标记点状态重置逻辑 + console.log('重置标记点状态'); + } + /** + * 分发地图点击事件 + */ + onMapTap(e) { + this.mapModule.onMapTap(e); + } + /** + * 分发标记点点击事件 + */ + onMarkerTap(e) { + const markerId = e.markerId; + const { markers } = this.pageContext.data; + // 找到被点击的标记点 + const marker = markers.find((m) => m.id === markerId); + if (!marker) { + console.warn('未找到标记点:', markerId); + return; + } + // 根据标记点类型分发到不同的模块 + switch (marker.type) { + case 'warehouse': + this.warehouseModule.onWarehouseMarkerClick(marker.data, e); + break; + case 'employee': + // 员工标记点点击 - 根据角色分发到不同模块 + if (marker.data.role === 'ADMIN') { + // 管理员 - 由adminModule处理 + this.adminModule.onAdminMarkerClick(marker.data, e); + } + else { + // 货运人员 - 由deliveryPersonModule处理(包含订单列表) + this.deliveryPersonModule.onDeliveryPersonMarkerClick(marker.data, e); + } + break; + default: + this.mapModule.onMarkerTap(e); + break; + } + } + /** + * 获取登录模块 + */ + getLoginModule() { + return this.loginModule; + } + /** + * 获取地图模块 + */ + getMapModule() { + return this.mapModule; + } + /** + * 获取订单模块 + */ + getOrderModule() { + return this.orderModule; + } + /** + * 获取仓库模块 + */ + getWarehouseModule() { + return this.warehouseModule; + } + /** + * 获取员工模块 + */ + getEmployeeModule() { + return this.employeeModule; + } + /** + * 获取管理员模块 + */ + getAdminModule() { + return this.adminModule; + } + /** + * 获取位置模块 + */ + getLocationModule() { + return this.locationModule; + } + /** + * 刷新所有数据 + */ + async refreshAllData() { + console.log('刷新所有数据'); + try { + // 刷新公开数据(不需要登录) + await this.loadPublicData(); + // 检查是否已登录,只有已登录用户才能刷新业务数据 + const app = getApp(); + if (app.globalData.isLoggedIn) { + await this.loadBusinessData(); + (0, helpers_1.showToast)('数据刷新成功'); + } + else { + console.log('用户未登录,无法刷新业务数据'); + (0, helpers_1.showToast)('公开数据已刷新,请登录以刷新业务数据'); + } + } + catch (error) { + console.error('刷新数据失败:', error); + (0, helpers_1.showToast)('刷新数据失败'); + } + } + /** + * 清理资源 + */ + cleanup() { + console.log('清理主页面模块资源'); + // 清理位置模块 + this.locationModule.cleanup(); + // 清理员工和管理员模块 + this.employeeModule.cleanup(); + this.adminModule.cleanup(); + // 这里可以添加其他需要清理的资源 + console.log('✅ 主页面模块清理完成'); + } + /** + * 处理错误 + */ + handleError(error, context) { + console.error(`在 ${context} 中发生错误:`, error); + (0, helpers_1.showToast)(`操作失败: ${context}`); + } +} +exports.MainPageModule = MainPageModule; diff --git a/dist/pages/index/modules/mapModule.js b/dist/pages/index/modules/mapModule.js new file mode 100644 index 0000000..1b3d040 --- /dev/null +++ b/dist/pages/index/modules/mapModule.js @@ -0,0 +1,292 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MapModule = void 0; +// 地图模块 - 处理地图显示、定位、标记点管理 +const mapService_1 = __importDefault(require("../../../services/mapService")); +const userService_1 = __importDefault(require("../../../services/userService")); +const helpers_1 = require("../../../utils/helpers"); +class MapModule { + constructor(pageContext, dataModule) { + this.pageContext = pageContext; + this.dataModule = dataModule; + } + /** + * 初始化地图数据 + */ + async initMap() { + try { + // 检查并请求位置权限 + await this.checkAndRequestLocationPermission(); + // 加载地图数据 + this.loadMapData(); + } + catch (error) { + console.error('初始化地图失败:', error); + (0, helpers_1.showToast)('地图初始化失败,请重试'); + } + } + /** + * 检查并请求位置权限 + */ + async checkAndRequestLocationPermission() { + try { + // 检查位置权限 + const hasPermission = await userService_1.default.checkLocationPermission(); + if (hasPermission) { + // 已授权,开始定位 + await this.startLocation(); + } + else { + // 未授权,请求权限 + const permissionGranted = await userService_1.default.requestLocationPermission(); + if (permissionGranted) { + await this.startLocation(); + } + else { + // 授权失败,使用默认位置 + this.setDefaultLocation(); + (0, helpers_1.showToast)('已使用默认位置'); + } + } + } + catch (error) { + console.error('检查位置权限失败:', error); + this.setDefaultLocation(); + } + } + /** + * 开始定位 + */ + async startLocation() { + try { + console.log('[MAP MODULE] 开始获取用户位置...'); + const location = await mapService_1.default.getCurrentLocation(); + console.log('[MAP MODULE] 获取位置成功,原始坐标:', location); + console.log('[MAP MODULE] 详细位置信息:'); + console.log(`- 经度: ${location.longitude}`); + console.log(`- 纬度: ${location.latitude}`); + console.log(`- 精度: ${location.accuracy}`); + console.log(`- 速度: ${location.speed}`); + // 验证获取的坐标是否有效 + if (isNaN(location.latitude) || isNaN(location.longitude)) { + console.error('[MAP MODULE] 获取的位置坐标无效:', location); + this.setDefaultLocation(); + (0, helpers_1.showToast)('获取的位置无效,已使用默认位置'); + return; + } + // 强制重置地图视角到用户位置 + // 1. 更新用户位置 + this.dataModule.updateUserLocation(location.latitude, location.longitude); + // 2. 重置地图缩放级别到15(详细级别) + this.dataModule.setMapScale(15); + // 3. 添加地图动画效果,让视角平滑回到用户位置 + this.animateMapToUserLocation(location.latitude, location.longitude); + console.log('[MAP MODULE] 定位成功,坐标已更新,视角已重置:', location); + (0, helpers_1.showToast)('已定位到您的位置'); + } + catch (error) { + console.error('[MAP MODULE] 定位失败:', error); + this.setDefaultLocation(); + (0, helpers_1.showToast)('定位失败,已使用默认位置'); + } + } + /** + * 设置默认位置 + */ + setDefaultLocation() { + const defaultLatitude = 24.880095; + const defaultLongitude = 102.833722; + // 更新数据模块中的位置信息和缩放级别 + this.dataModule.updateUserLocation(defaultLatitude, defaultLongitude); + this.dataModule.setMapScale(13); + // 动画效果回到默认位置 + this.animateMapToUserLocation(defaultLatitude, defaultLongitude); + console.log('[MAP MODULE] 默认位置设置完成'); + } + /** + * 动画效果将地图视角平滑移动到用户位置 + */ + animateMapToUserLocation(latitude, longitude) { + // 使用微信小程序的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) => { + console.error('[MAP MODULE] 地图视角移动失败:', err); + // 如果moveToLocation失败,直接设置中心点 + this.pageContext.setData({ + latitude: latitude, + longitude: longitude + }); + } + }); + console.log('[MAP MODULE] 地图视角动画已启动,移动到位置:', { latitude, longitude }); + } + /** + * 加载地图数据(仓库和货运人员) + */ + loadMapData() { + // 这里会调用具体的服务来获取数据 + // 目前使用模拟数据 + const markers = this.generateInitialMarkers(); + // 更新数据模块中的标记点 + this.dataModule.updateMarkers(markers); + console.log('地图数据加载完成'); + } + /** + * 生成初始标记点 + */ + generateInitialMarkers() { + console.log('生成初始标记点'); + const markers = []; + // 地图自带用户位置显示功能,无需手动添加用户位置标记点 + return markers; + } + /** + * 更新用户标记图标为用户头像 + * 注意:由于地图自带用户位置显示功能,此方法已不再需要 + */ + updateUserMarkerIcon() { + console.log('地图自带用户位置显示功能,无需更新用户标记图标'); + } + /** + * 更新员工标记点 + */ + updateEmployeeMarkers(employeeMarkers) { + console.log('开始更新员工标记点,数量:', employeeMarkers.length); + const { markers } = this.pageContext.data; + // 过滤掉现有的员工标记点 + const filteredMarkers = markers.filter((marker) => marker.type !== 'employee'); + // 合并标记点 + const updatedMarkers = [...filteredMarkers, ...employeeMarkers]; + // 更新数据模块中的标记点 + this.dataModule.updateMarkers(updatedMarkers); + console.log('员工标记点更新完成,总标记点数量:', updatedMarkers.length); + } + /** + * 清除所有员工标记点 + */ + clearEmployeeMarkers() { + console.log('开始清除所有员工标记点'); + const { markers } = this.pageContext.data; + // 过滤掉员工标记点,只保留其他类型的标记点 + const filteredMarkers = markers.filter((marker) => marker.type !== 'employee'); + // 更新数据模块中的标记点 + this.dataModule.updateMarkers(filteredMarkers); + console.log('员工标记点清除完成,剩余标记点数量:', filteredMarkers.length); + } + /** + * 删除单个员工标记点 + */ + removeEmployeeMarker(markerId) { + console.log('删除单个员工标记点:', markerId); + const { markers } = this.pageContext.data; + // 只删除员工类型的标记点,避免误删其他类型标记点 + const filteredMarkers = markers.filter((marker) => !(marker.id === markerId && marker.type === 'employee')); + // 检查是否真的删除了标记点 + const originalCount = markers.length; + const newCount = filteredMarkers.length; + if (originalCount === newCount) { + console.warn('未找到员工标记点,可能标记点不存在或不是员工类型:', markerId); + } + else { + console.log('成功删除员工标记点:', markerId); + } + // 更新数据模块中的标记点 + this.dataModule.updateMarkers(filteredMarkers); + console.log('单个员工标记点删除完成,剩余标记点数量:', filteredMarkers.length); + } + /** + * 添加单个员工标记点 + */ + addEmployeeMarker(employeeMarker) { + console.log('添加单个员工标记点:', employeeMarker.id); + // 确保标记点类型为员工 + const markerWithType = { + ...employeeMarker, + type: 'employee' + }; + const { markers } = this.pageContext.data; + // 检查是否已存在相同ID的标记点 + const existingIndex = markers.findIndex((marker) => marker.id === markerWithType.id && marker.type === 'employee'); + let updatedMarkers; + if (existingIndex !== -1) { + // 如果已存在相同ID的员工标记点,则更新 + updatedMarkers = [...markers]; + updatedMarkers[existingIndex] = markerWithType; + console.log('员工标记点已存在,执行更新操作'); + } + else { + // 如果不存在,则添加新的员工标记点 + updatedMarkers = [...markers, markerWithType]; + console.log('员工标记点不存在,执行添加操作'); + } + // 更新数据模块中的标记点 + this.dataModule.updateMarkers(updatedMarkers); + console.log('单个员工标记点添加完成,总标记点数量:', updatedMarkers.length); + } + /** + * 更新单个员工标记点 + */ + updateSingleEmployeeMarker(employeeMarker) { + console.log('更新单个员工标记点:', employeeMarker.id); + const { markers } = this.pageContext.data; + // 查找现有的员工标记点 + const markerIndex = markers.findIndex((marker) => 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('单个员工标记点更新完成'); + } + /** + * 处理地图点击事件 + */ + onMapTap(e) { + console.log('地图被点击:', e); + // 可以在这里添加地图点击的处理逻辑 + } + /** + * 处理标记点点击事件 + */ + onMarkerTap(e) { + const markerId = e.markerId; + console.log('标记点被点击:', markerId); + // 根据标记点ID处理不同的点击逻辑 + this.handleMarkerClick(markerId); + } + /** + * 处理标记点点击 + */ + handleMarkerClick(markerId) { + // 这里可以根据标记点ID进行不同的处理 + // 例如:显示订单详情、货运人员信息等 + if (markerId === -1) { + // 用户位置标记 + this.pageContext.showUserPanel(); + } + // 可以添加其他标记点的处理逻辑 + } +} +exports.MapModule = MapModule; diff --git a/dist/pages/index/modules/orderModule.js b/dist/pages/index/modules/orderModule.js new file mode 100644 index 0000000..9f5c3ef --- /dev/null +++ b/dist/pages/index/modules/orderModule.js @@ -0,0 +1,234 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OrderModule = void 0; +const orderService_1 = __importDefault(require("../../../services/orderService")); +const helpers_1 = require("../../../utils/helpers"); +class OrderModule { + constructor(pageContext, dataModule) { + this.pageContext = pageContext; + this.dataModule = dataModule; + } + /** + * 加载待处理订单 + */ + async loadPendingOrders() { + try { + this.pageContext.setData({ + loadingOrders: true + }); + const orders = await orderService_1.default.getPendingOrders(); + this.dataModule.updatePendingOrders(orders); + // 更新地图标记点 + // this.updateOrderMarkers(); + } + catch (error) { + console.error('加载订单失败:', error); + (0, helpers_1.showToast)('加载订单失败'); + } + finally { + this.pageContext.setData({ + loadingOrders: false + }); + } + } + /** + * 显示订单详情面板 + */ + showOrderPanel(order, position) { + // 关闭其他面板 + this.pageContext.hideAllPanels(); + // 更新数据模块中的当前订单和面板状态 + this.dataModule.setCurrentOrder(order); + this.dataModule.setPanelPosition(position); + this.dataModule.toggleOrderPanel(true); + // 规划订单路线 + this.planOrderRoute(order); + } + /** + * 隐藏订单详情面板 + */ + hideOrderPanel() { + // 更新数据模块中的面板状态 + this.dataModule.toggleOrderPanel(false); + this.dataModule.setCurrentOrder(null); + // 同时取消路线显示 + this.cancelRoute(); + // 重置标记点状态 + this.pageContext.resetMarkers(); + } + /** + * 规划订单路线 + */ + async planOrderRoute(order) { + try { + const origin = `${order.startPoint.longitude},${order.startPoint.latitude}`; + const destination = `${order.endPoint.longitude},${order.endPoint.latitude}`; + console.log(`开始规划路线: 起点=${origin}, 终点=${destination}`); + // 这里应该调用地图服务的路线规划功能 + // 目前先模拟一个简单的路线规划结果 + const routeResult = { + distance: 1500, // 1.5公里 + duration: 300, // 5分钟 + polyline: null // 路线数据 + }; + console.log('路线规划结果:', routeResult); + // 显示距离和时长信息 + if (routeResult.distance > 0 || routeResult.duration > 0) { + const distanceStr = this.formatDistance(routeResult.distance); + const durationStr = this.formatDuration(routeResult.duration); + (0, helpers_1.showToast)(`距离: ${distanceStr}, 预计时间: ${durationStr}`); + } + // 更新数据模块中的路线信息 + this.dataModule.updateRouteInfo(routeResult.polyline, routeResult.distance, routeResult.duration); + } + catch (error) { + console.error('路线规划失败:', error); + (0, helpers_1.showToast)('路线规划失败'); + } + } + /** + * 取消路线显示 + */ + cancelRoute() { + // 清除数据模块中的路线信息 + this.dataModule.clearRouteInfo(); + } + /** + * 分配订单给货运人员 + */ + async assignOrder(orderId, deliveryPersonId) { + try { + const result = await orderService_1.default.assignOrder(orderId, deliveryPersonId); + if (result.success) { + (0, helpers_1.showToast)('订单分配成功'); + // 刷新订单列表 + await this.loadPendingOrders(); + // 刷新货运人员状态 + // TODO this.dataModule.notifyDeliveryPersonsUpdated(); + } + else { + (0, helpers_1.showToast)(result.message || '订单分配失败'); + } + } + catch (error) { + console.error('分配订单失败:', error); + (0, helpers_1.showToast)('订单分配失败'); + } + } + /** + * 更新订单状态 + */ + async updateOrderStatus(orderId, status) { + try { + const result = await orderService_1.default.updateOrderStatus(orderId, status); + if (result.success) { + (0, helpers_1.showToast)('订单状态已更新'); + // 刷新订单列表 + await this.loadPendingOrders(); + // 刷新货运人员状态 + // this.dataModule.notifyDeliveryPersonsUpdated(); + } + else { + (0, helpers_1.showToast)(result.message || '订单状态更新失败'); + } + } + catch (error) { + console.error('更新订单状态失败:', error); + (0, helpers_1.showToast)('订单状态更新失败'); + } + } + /** + * 删除订单 + */ + async deleteOrder(orderId) { + try { + // 确认删除操作 + const confirmResult = await new Promise((resolve) => { + wx.showModal({ + title: '确认删除', + content: '确定要删除这个订单吗?此操作不可恢复。', + success: (res) => { + resolve(res.confirm); + } + }); + }); + if (!confirmResult) { + return; // 用户取消删除 + } + await orderService_1.default.deleteOrder(orderId); + (0, helpers_1.showToast)('订单已删除'); + // 刷新订单列表 + await this.loadPendingOrders(); + // 刷新货运人员状态 + // this.dataModule.notifyDeliveryPersonsUpdated(); + // 如果当前正在查看被删除的订单,关闭订单面板 + //TODO + // const currentOrder = this.dataModule.getCurrentOrder(); + // if (currentOrder && currentOrder.id === orderId) { + // this.hideOrderPanel(); + // } + } + catch (error) { + console.error('删除订单失败:', error); + (0, helpers_1.showToast)('删除订单失败'); + } + } + /** + * 格式化距离 + */ + formatDistance(distance) { + if (distance < 1000) { + return `${distance}米`; + } + else { + return `${(distance / 1000).toFixed(1)}公里`; + } + } + /** + * 格式化时长 + */ + formatDuration(duration) { + const minutes = Math.floor(duration / 60); + const seconds = duration % 60; + if (minutes > 0) { + return `${minutes}分${seconds}秒`; + } + else { + return `${seconds}秒`; + } + } + /** + * 获取订单状态文本 + */ + getOrderStatusText(status) { + const statusMap = { + 'pending': '未分配', + 'assigned': '已分配', + 'in_transit': '配送中', + 'delivered': '已完成', + 'cancelled': '已取消' + }; + return statusMap[status] || status; + } + /** + * 获取订单状态对应的图标 + */ + getOrderStatusIcon(status) { + switch (status) { + case 'pending': + return '/images/order-pending.png'; + case 'assigned': + return '/images/order-assigned.png'; + case 'in_transit': + return '/images/order-transit.png'; + case 'delivered': + return '/images/order-delivered.png'; + default: + return '/images/order-pending.png'; + } + } +} +exports.OrderModule = OrderModule; diff --git a/dist/pages/index/modules/warehouseModule.js b/dist/pages/index/modules/warehouseModule.js new file mode 100644 index 0000000..687cff4 --- /dev/null +++ b/dist/pages/index/modules/warehouseModule.js @@ -0,0 +1,213 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WarehouseModule = void 0; +// 仓库模块 - 处理仓库管理、显示、交互 +const warehouseService_1 = __importDefault(require("../../../services/warehouseService")); +const helpers_1 = require("../../../utils/helpers"); +class WarehouseModule { + constructor(pageContext, dataModule) { + this.lastLoadTime = 0; + this.isLoading = false; + this.loadPromise = null; + this.pageContext = pageContext; + this.dataModule = dataModule; + } + /** + * 加载仓库数据(带缓存和防抖机制) + */ + async loadWarehouses(forceRefresh = false) { + // 防抖机制:如果正在加载,返回同一个Promise + if (this.isLoading && this.loadPromise && !forceRefresh) { + return this.loadPromise; + } + // 缓存机制:5分钟内不重复加载相同数据 + const now = Date.now(); + const cacheTime = 5 * 60 * 1000; // 5分钟缓存 + if (!forceRefresh && now - this.lastLoadTime < cacheTime) { + console.log('使用缓存的仓库数据'); + return; + } + this.isLoading = true; + this.loadPromise = (async () => { + try { + const warehouses = await warehouseService_1.default.getWarehouses(); + // 更新地图标记点 + this.updateWarehouseMarkers(warehouses); + this.lastLoadTime = Date.now(); + console.log('仓库数据加载完成,数量:', warehouses.length); + } + catch (error) { + console.error('加载仓库数据失败:', error); + (0, helpers_1.showToast)('加载仓库数据失败'); + } + finally { + this.isLoading = false; + this.loadPromise = null; + } + })(); + return this.loadPromise; + } + /** + * 更新仓库标记点 + */ + updateWarehouseMarkers(warehouses) { + const { markers } = this.pageContext.data; + // 移除现有的仓库标记点 + const filteredMarkers = markers.filter((marker) => marker.type !== 'warehouse'); + // 添加新的仓库标记点 + const warehouseMarkers = warehouses.map((warehouse, index) => { + // 验证仓库坐标是否有效 + let validLongitude = warehouse.longitude; + let validLatitude = warehouse.latitude; + if (isNaN(validLongitude) || isNaN(validLatitude)) { + validLongitude = 102.833722; // 默认经度 + validLatitude = 24.880095; // 默认纬度 + } + return { + id: 1000 + index, // 仓库标记点ID从1000开始 + longitude: validLongitude, + latitude: validLatitude, + iconPath: '/images/warehouse.png', + width: 24, + height: 24, + zIndex: 10, + type: 'warehouse', + data: warehouse + }; + }); + // 更新数据模块中的标记点 + const allMarkers = [...filteredMarkers, ...warehouseMarkers]; + this.dataModule.updateMarkers(allMarkers); + } + /** + * 显示仓库详情面板 + */ + showWarehousePanel(warehouse, position) { + // 关闭其他面板 + this.pageContext.hideAllPanels(); + // 更新数据模块中的当前仓库和面板状态 + this.dataModule.setCurrentWarehouse(warehouse); + this.dataModule.setPanelPosition(position); + this.dataModule.toggleWarehouseModal(true, 'bottom'); + } + /** + * 隐藏仓库详情面板 + */ + hideWarehousePanel() { + // 更新数据模块中的面板状态 + this.dataModule.toggleWarehouseModal(false); + this.dataModule.setCurrentWarehouse(null); + } + /** + * 展开仓库详情面板 + */ + expandWarehousePanel() { + this.dataModule.toggleWarehouseModal(true, 'full'); + } + /** + * 收起仓库详情面板 + */ + collapseWarehousePanel() { + this.dataModule.toggleWarehouseModal(true, 'bottom'); + } + /** + * 处理仓库标记点点击 + */ + onWarehouseMarkerClick(warehouse, position) { + console.log('仓库被点击:', warehouse); + // 显示仓库详情面板 + this.showWarehousePanel(warehouse, position); + } + /** + * 获取仓库库存状态 + */ + getWarehouseInventoryStatus(warehouse) { + const capacity = warehouse.capacity || 100; + const currentStock = warehouse.currentStock || 0; + const percentage = (currentStock / capacity) * 100; + if (percentage >= 80) { + return 'high'; + } + else if (percentage >= 50) { + return 'medium'; + } + else { + return 'low'; + } + } + /** + * 获取仓库库存状态文本 + */ + getWarehouseInventoryStatusText(warehouse) { + const status = this.getWarehouseInventoryStatus(warehouse); + switch (status) { + case 'high': + return '库存充足'; + case 'medium': + return '库存适中'; + case 'low': + return '库存不足'; + default: + return '未知状态'; + } + } + /** + * 获取仓库库存状态颜色 + */ + getWarehouseInventoryStatusColor(warehouse) { + const status = this.getWarehouseInventoryStatus(warehouse); + switch (status) { + case 'high': + return '#52c41a'; // 绿色 + case 'medium': + return '#faad14'; // 橙色 + case 'low': + return '#f5222d'; // 红色 + default: + return '#d9d9d9'; // 灰色 + } + } + /** + * 创建新仓库 + */ + async createWarehouse(warehouseData) { + try { + await warehouseService_1.default.createWarehouse(warehouseData); + (0, helpers_1.showToast)('仓库创建成功'); + console.log('新仓库创建成功:', warehouseData); + // 不需要重新加载仓库数据,因为仓库数据变化不频繁 + // 用户可以通过手动刷新来获取最新数据 + } + catch (error) { + console.error('创建仓库失败:', error); + (0, helpers_1.showToast)('创建仓库失败'); + } + } + /** + * 删除仓库 + */ + async deleteWarehouse(warehouseId) { + try { + await warehouseService_1.default.deleteWarehouse(warehouseId); + (0, helpers_1.showToast)('仓库删除成功'); + console.log(`仓库 ${warehouseId} 已删除`); + // 不需要重新加载仓库数据,因为仓库数据变化不频繁 + // 用户可以通过手动刷新来获取最新数据 + } + catch (error) { + console.error('删除仓库失败:', error); + (0, helpers_1.showToast)('删除仓库失败'); + } + } + /** + * 强制刷新仓库数据(忽略缓存) + */ + async forceRefreshWarehouses() { + console.log('强制刷新仓库数据'); + return this.loadWarehouses(true); + } +} +exports.WarehouseModule = WarehouseModule; diff --git a/dist/pages/staff/admin-dashboard.js b/dist/pages/staff/admin-dashboard.js new file mode 100644 index 0000000..88e664a --- /dev/null +++ b/dist/pages/staff/admin-dashboard.js @@ -0,0 +1,300 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const roleUtils_1 = require("../../utils/roleUtils"); +const userService_1 = __importDefault(require("../../services/userService")); +const statisticsService_1 = __importDefault(require("../../services/statisticsService")); +Page({ + data: { + userInfo: null, + // Grid布局的功能菜单 + menuItems: [ + { + id: 'employee', + name: '员工管理', + icon: '👥', + color: '#3498db', + description: '管理员工信息' + }, + { + id: 'order', + name: '订单管理', + icon: '📦', + color: '#e74c3c', + description: '处理订单分配' + }, + { + id: 'warehouse', + name: '仓库管理', + icon: '🏭', + color: '#f39c12', + description: '管理库存信息' + }, + { + id: 'statistics', + name: '数据统计', + icon: '📊', + color: '#9b59b6', + description: '查看业务数据' + }, + { + id: 'settings', + name: '系统设置', + icon: '⚙️', + color: '#34495e', + description: '系统配置管理' + }, + { + id: 'map', + name: '地图监控', + icon: '🗺️', + color: '#27ae60', + description: '实时位置监控' + } + ], + // 统计数据 + stats: { + employeeCount: 0, + orderCount: 0, + warehouseCount: 0 + }, + // 当前时间 + currentTime: '' + }, + onLoad() { + this.getUserInfo(); + this.loadStatistics(); + this.updateCurrentTime(); + // 每秒更新一次时间 + setInterval(() => { + this.updateCurrentTime(); + }, 1000); + }, + onShow() { + this.loadStatistics(); + this.updateCurrentTime(); + }, + /** + * 获取用户信息 + */ + async getUserInfo() { + try { + const app = getApp(); + // 如果全局数据中没有用户信息,尝试从用户服务获取 + if (!app.globalData.userInfo) { + const userInfo = await userService_1.default.getUserInfo(); + if (userInfo) { + app.globalData.userInfo = userInfo; + } + } + if (app.globalData.userInfo) { + const userInfo = app.globalData.userInfo; + this.setData({ userInfo }); + // 验证是否为管理员 + if (userInfo.role !== roleUtils_1.Role.ADMIN) { + wx.showToast({ + title: '无权限访问', + icon: 'none' + }); + wx.navigateBack(); + } + } + else { + // 如果仍然没有用户信息,显示默认信息 + this.setData({ + userInfo: { + id: 0, + role: roleUtils_1.Role.ADMIN, + name: '管理员', + avatarUrl: undefined + } + }); + } + } + catch (error) { + console.error('获取用户信息失败:', error); + // 出错时显示默认信息 + this.setData({ + userInfo: { + id: 0, + role: roleUtils_1.Role.ADMIN, + name: '管理员', + avatarUrl: undefined + } + }); + } + }, + /** + * 加载统计数据 + */ + async loadStatistics() { + try { + wx.showLoading({ + title: '加载数据中...' + }); + // 使用统计服务获取真实数据 + const stats = await statisticsService_1.default.getSystemStats(); + this.setData({ + stats: { + employeeCount: stats.employeeCount, + orderCount: stats.orderCount, + warehouseCount: stats.warehouseCount + } + }); + wx.hideLoading(); + } + catch (error) { + console.error('加载统计数据失败:', error); + wx.hideLoading(); + // 出错时使用默认值 + this.setData({ + stats: { + employeeCount: 0, + orderCount: 0, + warehouseCount: 0 + } + }); + wx.showToast({ + title: '数据加载失败', + icon: 'none' + }); + } + }, + /** + * 菜单项点击事件 + */ + onMenuItemTap(e) { + const itemId = e.currentTarget.dataset.id; + switch (itemId) { + case 'employee': + wx.navigateTo({ + url: '/pages/staff/employee-management' + }); + break; + case 'order': + wx.navigateTo({ + url: '/pages/staff/order-management' + }); + break; + case 'warehouse': + wx.navigateTo({ + url: '/pages/staff/warehouse-management' + }); + break; + case 'statistics': + wx.navigateTo({ + url: '/pages/staff/statistics' + }); + break; + case 'settings': + wx.navigateTo({ + url: '/pages/staff/settings' + }); + break; + case 'map': + wx.navigateTo({ + url: '/pages/map/map' + }); + break; + } + }, + /** + * 返回首页 + */ + goBack() { + wx.navigateBack(); + }, + /** + * 刷新数据 + */ + onRefresh() { + this.loadStatistics(); + this.updateCurrentTime(); + wx.showToast({ + title: '数据已刷新', + icon: 'success' + }); + }, + /** + * 更新当前时间 + */ + updateCurrentTime() { + const now = new Date(); + const timeString = now.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }); + this.setData({ + currentTime: timeString + }); + }, + /** + * 解绑微信处理 + */ + async onUnbindWechat() { + try { + // 确认对话框 + wx.showModal({ + title: '确认解绑', + content: '解绑微信后,您将需要重新登录并绑定账号。确定要继续吗?', + confirmText: '确定解绑', + confirmColor: '#e74c3c', + cancelText: '取消', + success: async (res) => { + if (res.confirm) { + // 显示加载中 + wx.showLoading({ + title: '解绑中...', + mask: true + }); + try { + // 调用解绑服务 + const result = await userService_1.default.unbindWechat(); + wx.hideLoading(); + if (result.success) { + wx.showToast({ + title: '解绑成功', + icon: 'success', + duration: 2000 + }); + // 延迟跳转到登录页面 + setTimeout(() => { + wx.reLaunch({ + url: '/pages/login/login' + }); + }, 2000); + } + else { + wx.showToast({ + title: result.message || '解绑失败', + icon: 'none' + }); + } + } + catch (error) { + wx.hideLoading(); + wx.showToast({ + title: '解绑失败,请重试', + icon: 'none' + }); + console.error('解绑微信错误:', error); + } + } + } + }); + } + catch (error) { + console.error('解绑微信处理错误:', error); + wx.showToast({ + title: '操作失败,请重试', + icon: 'none' + }); + } + } +}); diff --git a/dist/pages/staff/employee-dashboard.js b/dist/pages/staff/employee-dashboard.js new file mode 100644 index 0000000..9381fb1 --- /dev/null +++ b/dist/pages/staff/employee-dashboard.js @@ -0,0 +1,217 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const userService_1 = __importDefault(require("../../services/userService")); +Page({ + data: { + userInfo: null, + // Grid布局的功能菜单 + menuItems: [ + { + id: 'orders', + name: '我的订单', + icon: '📦', + color: '#3498db', + description: '查看分配订单' + }, + { + id: 'location', + name: '位置上报', + icon: '📍', + color: '#e74c3c', + description: '上报当前位置' + }, + { + id: 'schedule', + name: '工作安排', + icon: '📅', + color: '#f39c12', + description: '查看工作计划' + }, + { + id: 'profile', + name: '个人信息', + icon: '👤', + color: '#9b59b6', + description: '个人资料管理' + }, + { + id: 'messages', + name: '消息通知', + icon: '📨', + color: '#34495e', + description: '查看系统消息' + }, + { + id: 'help', + name: '帮助中心', + icon: '❓', + color: '#27ae60', + description: '使用帮助文档' + } + ], + // 工作统计 + workStats: { + todayOrders: 0, + completedOrders: 0, + pendingOrders: 0 + } + }, + onLoad() { + this.getUserInfo(); + this.loadWorkStats(); + }, + onShow() { + this.loadWorkStats(); + }, + /** + * 获取用户信息 + */ + getUserInfo() { + const app = getApp(); + if (app.globalData.userInfo) { + const userInfo = app.globalData.userInfo; + this.setData({ userInfo }); + } + }, + /** + * 加载工作统计数据 + */ + loadWorkStats() { + // 模拟加载工作统计数据 + this.setData({ + workStats: { + todayOrders: 8, + completedOrders: 5, + pendingOrders: 3 + } + }); + }, + /** + * 菜单项点击事件 + */ + onMenuItemTap(e) { + const itemId = e.currentTarget.dataset.id; + switch (itemId) { + case 'orders': + wx.navigateTo({ + url: '/pages/staff/my-orders' + }); + break; + case 'location': + wx.navigateTo({ + url: '/pages/staff/location-report' + }); + break; + case 'schedule': + wx.navigateTo({ + url: '/pages/staff/work-schedule' + }); + break; + case 'profile': + wx.navigateTo({ + url: '/pages/staff/profile' + }); + break; + case 'messages': + wx.navigateTo({ + url: '/pages/staff/messages' + }); + break; + case 'help': + wx.navigateTo({ + url: '/pages/staff/help' + }); + break; + } + }, + /** + * 返回首页 + */ + goBack() { + wx.navigateBack(); + }, + /** + * 刷新数据 + */ + onRefresh() { + this.loadWorkStats(); + wx.showToast({ + title: '数据已刷新', + icon: 'success' + }); + }, + /** + * 紧急联系 + */ + onEmergencyContact() { + wx.makePhoneCall({ + phoneNumber: '400-123-4567' + }); + }, + /** + * 解绑微信处理 + */ + async onUnbindWechat() { + try { + // 确认对话框 + wx.showModal({ + title: '确认解绑', + content: '解绑微信后,您将需要重新登录并绑定账号。确定要继续吗?', + confirmText: '确定解绑', + confirmColor: '#e74c3c', + cancelText: '取消', + success: async (res) => { + if (res.confirm) { + // 显示加载中 + wx.showLoading({ + title: '解绑中...', + mask: true + }); + try { + // 调用解绑服务 + const result = await userService_1.default.unbindWechat(); + wx.hideLoading(); + if (result.success) { + wx.showToast({ + title: '解绑成功', + icon: 'success', + duration: 2000 + }); + // 延迟跳转到登录页面 + setTimeout(() => { + wx.reLaunch({ + url: '/pages/login/login' + }); + }, 2000); + } + else { + wx.showToast({ + title: result.message || '解绑失败', + icon: 'none' + }); + } + } + catch (error) { + wx.hideLoading(); + wx.showToast({ + title: '解绑失败,请重试', + icon: 'none' + }); + console.error('解绑微信错误:', error); + } + } + } + }); + } + catch (error) { + console.error('解绑微信处理错误:', error); + wx.showToast({ + title: '操作失败,请重试', + icon: 'none' + }); + } + } +}); diff --git a/dist/pages/staff/employee-management.js b/dist/pages/staff/employee-management.js new file mode 100644 index 0000000..3311886 --- /dev/null +++ b/dist/pages/staff/employee-management.js @@ -0,0 +1,535 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const employeeService_1 = __importDefault(require("../../services/employeeService")); +const roleUtils_1 = require("../../utils/roleUtils"); +Page({ + data: { + // 员工列表 + employees: [], + filteredEmployees: [], + // 页面状态 + currentTab: 'list', // list: 列表页, add: 添加页, edit: 编辑页 + loading: false, + // 添加员工表单数据 + addForm: { + name: '', + phone: '', + role: roleUtils_1.Role.DELIVERY_PERSON + }, + // 编辑员工表单数据 + editForm: { + id: 0, + name: '', + phone: '', + role: roleUtils_1.Role.DELIVERY_PERSON, + avatarUrl: '' + }, + // 错误信息 + errorMessage: '', + successMessage: '', + // 搜索关键词 + searchKeyword: '', + // 角色选项 + roleOptions: (0, roleUtils_1.getRoleOptions)(), + // 用户信息 + userInfo: null, + // 临时头像路径(用于头像上传) + tempAvatarPath: '' + }, + onLoad() { + this.getUserInfo(); + this.loadEmployees(); + }, + onShow() { + // 只在页面显示时检查是否需要刷新数据,避免频繁请求 + if (this.data.employees.length === 0) { + this.loadEmployees(); + } + }, + /** + * 获取用户信息 + */ + getUserInfo() { + const app = getApp(); + if (app.globalData.userInfo) { + const userInfo = app.globalData.userInfo; + this.setData({ userInfo }); + // 验证是否为管理员 + if (userInfo.role !== 'ADMIN') { + wx.showToast({ + title: '无权限访问', + icon: 'none' + }); + wx.navigateBack(); + } + } + }, + /** + * 加载员工列表 + */ + async loadEmployees() { + this.setData({ + loading: true, + errorMessage: '', + successMessage: '' + }); + try { + const employees = await employeeService_1.default.getEmployees(); + // 为每个员工获取头像URL + const employeesWithAvatars = await Promise.all(employees.map(async (employee) => { + try { + const avatarUrl = await employeeService_1.default.getAvatarUrl(employee.id); + return { + ...employee, + avatarUrl: avatarUrl + }; + } + catch (error) { + console.error(`获取员工 ${employee.id} 头像失败:`, error); + return { + ...employee, + avatarUrl: '' + }; + } + })); + // 获取过滤后的员工列表 + const filteredEmployees = this.getFilteredEmployees(employeesWithAvatars); + this.setData({ + employees: employeesWithAvatars, + filteredEmployees, + loading: false + }); + } + catch (error) { + console.error('加载员工列表失败:', error); + this.setData({ + loading: false, + errorMessage: '加载员工列表失败,请稍后重试' + }); + } + }, + /** + * 切换页面标签 + */ + switchTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + currentTab: tab, + errorMessage: '', + successMessage: '' + }); + // 只在切换到列表页且没有数据时才加载,避免频繁请求 + if (tab === 'list' && this.data.employees.length === 0) { + this.loadEmployees(); + } + }, + /** + * 处理添加员工表单输入 + */ + onFormInput(e) { + const { field } = e.currentTarget.dataset; + const value = e.detail.value; + this.setData({ + [`addForm.${field}`]: value, + errorMessage: '', + successMessage: '' + }); + }, + /** + * 处理角色选择 + */ + onRoleChange(e) { + const index = e.detail.value; + const selectedRole = this.data.roleOptions[index].value; + this.setData({ + 'addForm.role': selectedRole + }); + }, + /** + * 验证表单数据 + */ + validateForm() { + 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 = /^\d+$/; + 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_1.default.addEmployee(this.data.addForm); + this.setData({ + loading: false, + successMessage: '员工添加成功', + addForm: { + name: '', + phone: '', + role: roleUtils_1.Role.DELIVERY_PERSON + } + }); + // 添加成功后自动切换到列表页 + setTimeout(() => { + this.setData({ + currentTab: 'list' + }); + this.loadEmployees(); + }, 1500); + } + catch (error) { + console.error('添加员工失败:', error); + this.setData({ + loading: false, + errorMessage: error.message || '添加员工失败,请稍后重试' + }); + } + }, + /** + * 编辑员工 + */ + editEmployee(e) { + const employeeId = e.currentTarget.dataset.id; + const employee = this.data.employees.find(emp => emp.id === employeeId); + if (employee) { + this.setData({ + editForm: { + id: employee.id || 0, + name: employee.name || '', + phone: employee.phone || '', + role: employee.role || roleUtils_1.Role.DELIVERY_PERSON, + avatarUrl: employee.avatarUrl || '' + }, + currentTab: 'edit' + }); + } + }, + /** + * 提交编辑员工表单 + */ + async submitEditForm() { + if (!this.validateEditForm()) { + return; + } + this.setData({ + loading: true, + errorMessage: '', + successMessage: '' + }); + try { + // 调用更新员工的API方法 + await employeeService_1.default.updateEmployee(this.data.editForm.id, { + name: this.data.editForm.name, + phone: this.data.editForm.phone, + role: this.data.editForm.role + }); + // 如果有新头像,上传头像 + if (this.data.tempAvatarPath) { + try { + const result = await employeeService_1.default.uploadAvatar(this.data.editForm.id, this.data.tempAvatarPath); + if (result.success && result.avatarUrl) { + // 更新头像URL + this.setData({ + 'editForm.avatarUrl': result.avatarUrl + }); + } + } + catch (avatarError) { + console.error('上传头像失败:', avatarError); + wx.showToast({ + title: '头像上传失败', + icon: 'error' + }); + } + } + this.setData({ + loading: false, + successMessage: '员工更新成功', + currentTab: 'list', + tempAvatarPath: '' + }); + // 重新加载员工列表 + this.loadEmployees(); + } + catch (error) { + console.error('更新员工失败:', error); + this.setData({ + loading: false, + errorMessage: '更新员工失败,请重试' + }); + } + }, + /** + * 选择头像 + */ + chooseAvatar() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album'], + maxDuration: 30, + camera: 'back', + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFilePath = res.tempFiles[0].tempFilePath; + this.setData({ + 'editForm.avatarUrl': tempFilePath, + tempAvatarPath: tempFilePath + }); + } + }, + fail: (err) => { + console.error('选择头像失败:', err); + wx.showToast({ + title: '选择头像失败', + icon: 'error' + }); + } + }); + }, + /** + * 拍照 + */ + takePhoto() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['camera'], + maxDuration: 30, + camera: 'back', + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFilePath = res.tempFiles[0].tempFilePath; + this.setData({ + 'editForm.avatarUrl': tempFilePath, + tempAvatarPath: tempFilePath + }); + } + }, + fail: (err) => { + console.error('拍照失败:', err); + wx.showToast({ + title: '拍照失败', + icon: 'error' + }); + } + }); + }, + /** + * 验证编辑表单 + */ + validateEditForm() { + const { name, phone, role } = this.data.editForm; + if (!name.trim()) { + this.setData({ + errorMessage: '请输入员工姓名' + }); + return false; + } + if (!phone.trim()) { + this.setData({ + errorMessage: '请输入员工工号' + }); + return false; + } + // 简单的工号格式验证 + const phoneRegex = /^\d+$/; + if (!phoneRegex.test(phone)) { + this.setData({ + errorMessage: '工号只能包含数字' + }); + return false; + } + if (!role) { + this.setData({ + errorMessage: '请选择员工角色' + }); + return false; + } + return true; + }, + /** + * 取消编辑 + */ + cancelEdit() { + this.setData({ + currentTab: 'list', + errorMessage: '', + successMessage: '' + }); + }, + /** + * 编辑表单输入处理 + */ + onEditFormInput(e) { + const { field } = e.currentTarget.dataset; + const value = e.detail.value; + this.setData({ + [`editForm.${field}`]: value, + errorMessage: '', + successMessage: '' + }); + }, + /** + * 处理编辑角色选择 + */ + onEditRoleChange(e) { + const index = e.detail.value; + const selectedRole = this.data.roleOptions[index].value; + this.setData({ + 'editForm.role': selectedRole + }); + }, + /** + * 删除员工 + */ + async deleteEmployee(e) { + 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_1.default.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) { + 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) { + const { searchKeyword } = this.data; + // 如果employees为空,返回空数组 + if (!employees || !Array.isArray(employees)) { + return []; + } + // 显示所有员工,包括当前用户 + let filteredEmployees = employees; + // 更严格的搜索关键词检查 + 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) { + const roleMap = { + 'ADMIN': '管理员', + 'DELIVERY_PERSON': '配送员', + 'EMPLOYEE': '员工' + }; + return roleMap[role] || role; + }, + /** + * 清空消息 + */ + clearMessages() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + }, + /** + * 返回上一页 + */ + goBack() { + wx.navigateBack(); + } +}); diff --git a/dist/pages/staff/order-management.js b/dist/pages/staff/order-management.js new file mode 100644 index 0000000..f654230 --- /dev/null +++ b/dist/pages/staff/order-management.js @@ -0,0 +1,385 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const orderService_1 = __importDefault(require("../../services/orderService")); +const deliveryPersonService_1 = __importDefault(require("../../services/deliveryPersonService")); +const warehouseService_1 = __importDefault(require("../../services/warehouseService")); +Page({ + data: { + userInfo: null, + // 订单相关数据 + orders: [], + filteredOrders: [], + activeTab: 'all', + // 员工相关数据 + deliveryPersons: [], + availableStaff: [], + // 仓库数据 + warehouses: [], + // 弹窗状态 + showAssignModal: false, + showAddModal: false, + selectedOrderId: 0, + // 新增订单表单数据 + warehouseIndex: 0, + endName: '', + goodsType: '', + goodsWeight: 0, + // 页面状态 + loading: false, + errorMessage: '', + successMessage: '' + }, + onLoad() { + // 获取用户信息 + this.getUserInfo(); + // 初始化数据 + this.initData(); + }, + onShow() { + // 页面显示时刷新数据 + this.refreshData(); + }, + onPullDownRefresh() { + // 下拉刷新 + this.refreshData().then(() => { + wx.stopPullDownRefresh(); + }); + }, + // 获取用户信息 + getUserInfo() { + try { + const userInfo = wx.getStorageSync('userInfo'); + if (userInfo) { + this.setData({ + userInfo + }); + } + } + catch (error) { + console.error('获取用户信息失败:', error); + } + }, + // 初始化数据 + initData() { + // 获取仓库数据 + warehouseService_1.default.getWarehouses().then(warehouses => { + this.setData({ + warehouses + }); + }).catch(error => { + console.error('获取仓库数据失败:', error); + this.showError('获取仓库数据失败'); + }); + }, + // 刷新数据 + async refreshData() { + this.setData({ + loading: true, + errorMessage: '', + successMessage: '' + }); + try { + // 获取订单数据 + const orders = await orderService_1.default.getPendingOrders(); + // 获取员工数据 + const deliveryPersons = await deliveryPersonService_1.default.getDeliveryPersons(); + this.setData({ + orders, + deliveryPersons, + loading: false + }); + // 过滤订单 + this.filterOrders(); + } + catch (error) { + console.error('刷新数据失败:', error); + this.setData({ + loading: false + }); + this.showError('刷新数据失败,请重试'); + } + }, + // 过滤订单 + filterOrders() { + const { orders, activeTab } = this.data; + let filteredOrders = orders; + if (activeTab !== 'all') { + filteredOrders = orders.filter(order => order.status === activeTab); + } + this.setData({ + filteredOrders + }); + }, + // 切换订单标签 + switchTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + activeTab: tab + }); + this.filterOrders(); + }, + // 处理订单点击 + handleOrderTap(e) { + const orderId = e.currentTarget.dataset.id; + const order = this.data.orders.find(o => o.id === orderId); + if (!order) + return; + if (order.status === 'pending') { + // 未分配的订单,显示分配弹窗 + const availableStaff = this.data.deliveryPersons.filter(person => person.status === 'idle'); + this.setData({ + showAssignModal: true, + selectedOrderId: orderId, + availableStaff + }); + } + else { + // 显示订单详情 + this.showOrderDetail(order); + } + }, + // 显示订单详情 + showOrderDetail(order) { + wx.showModal({ + title: `订单 #${order.id}`, + content: `起点: ${order.startPoint.name}\n终点: ${order.endPoint.name}\n货物: ${order.goodsType} ${order.goodsWeight}kg\n状态: ${this.getStatusText(order.status)}`, + showCancel: false + }); + }, + // 显示新增订单弹窗 + showAddOrderModal() { + this.setData({ + showAddModal: true, + endName: '', + goodsType: '', + goodsWeight: 0, + warehouseIndex: 0 + }); + }, + // 隐藏新增订单弹窗 + hideAddModal() { + this.setData({ + showAddModal: false + }); + }, + // 隐藏分配弹窗 + hideAssignModal() { + this.setData({ + showAssignModal: false + }); + }, + // 仓库选择变化 + onWarehouseChange(e) { + this.setData({ + warehouseIndex: e.detail.value + }); + }, + // 终点名称输入 + onEndNameInput(e) { + this.setData({ + endName: e.detail.value + }); + }, + // 货物类型输入 + onGoodsTypeInput(e) { + this.setData({ + goodsType: e.detail.value + }); + }, + // 货物重量输入 + onGoodsWeightInput(e) { + this.setData({ + goodsWeight: parseFloat(e.detail.value) || 0 + }); + }, + // 创建订单 + createOrder() { + const { warehouses, warehouseIndex, endName, goodsType, goodsWeight } = this.data; + // 表单验证 + if (!warehouses[warehouseIndex]) { + this.showError('请选择仓库'); + return; + } + if (!endName) { + this.showError('请输入终点名称'); + return; + } + if (!goodsType) { + this.showError('请输入货物类型'); + return; + } + if (goodsWeight <= 0) { + this.showError('请输入有效的货物重量'); + return; + } + // 获取仓库信息 + const warehouse = warehouses[warehouseIndex]; + // 创建订单数据 + const newOrder = { + startPoint: { + id: warehouse.id, + name: warehouse.name, + longitude: warehouse.longitude || 102.715, + latitude: warehouse.latitude || 25.045 + }, + endPoint: { + name: endName, + longitude: 102.715 + (Math.random() - 0.5) * 0.1, + latitude: 25.045 + (Math.random() - 0.5) * 0.1 + }, + status: 'pending', + goodsType, + goodsWeight + }; + // 调用服务创建订单 + orderService_1.default.createOrder(newOrder).then(() => { + this.showSuccess('订单创建成功'); + this.refreshData(); + }).catch(error => { + console.error('创建订单失败:', error); + this.showError('创建订单失败'); + }); + this.hideAddModal(); + }, + // 分配订单 + assignOrder(e) { + const staffId = e.currentTarget.dataset.id; + const { selectedOrderId } = this.data; + if (!selectedOrderId || !staffId) { + this.showError('请选择订单和货运人员'); + return; + } + // 分配订单 + orderService_1.default.assignOrder(selectedOrderId, staffId).then(result => { + if (result.success) { + this.showSuccess('订单指派成功'); + this.refreshData(); + } + else { + this.showError(result.message || '指派失败'); + } + }).catch(error => { + console.error('指派订单失败:', error); + this.showError('指派失败,请重试'); + }); + this.hideAssignModal(); + }, + // 删除订单 + deleteOrder(e) { + const orderId = e.currentTarget.dataset.id; + wx.showModal({ + title: '确认删除', + content: '确定要删除这个订单吗?', + success: (res) => { + if (res.confirm) { + // 调用删除订单接口 + // orderService.deleteOrder(orderId).then(() => { + // this.showSuccess('订单删除成功'); + // this.refreshData(); + // }).catch(error => { + // console.error('删除订单失败:', error); + // this.showError('删除订单失败'); + // }); + // 暂时使用模拟删除 + const orders = this.data.orders.filter(order => order.id !== orderId); + this.setData({ + orders + }); + this.filterOrders(); + this.showSuccess('订单删除成功'); + } + } + }); + }, + // 更新订单状态 + updateOrderStatus(e) { + const orderId = e.currentTarget.dataset.id; + const status = e.currentTarget.dataset.status; + // 调用更新订单状态接口 + orderService_1.default.updateOrderStatus(orderId, status).then(result => { + if (result.success) { + this.showSuccess('订单状态更新成功'); + this.refreshData(); + } + else { + this.showError(result.message || '更新失败'); + } + }).catch(error => { + console.error('更新订单状态失败:', error); + this.showError('更新失败,请重试'); + }); + }, + // 获取状态文本 + getStatusText(status) { + const statusMap = { + 'pending': '未分配', + 'assigned': '已分配', + 'in_transit': '配送中', + 'delivered': '已完成', + 'cancelled': '已取消' + }; + return statusMap[status] || status; + }, + // 格式化时间 + formatTime(time) { + const date = new Date(time); + const now = new Date(); + const diff = now.getTime() - date.getTime(); + if (diff < 60000) { + return '刚刚'; + } + else if (diff < 3600000) { + return `${Math.floor(diff / 60000)}分钟前`; + } + else if (diff < 86400000) { + return `${Math.floor(diff / 3600000)}小时前`; + } + else { + const month = date.getMonth() + 1; + const day = date.getDate(); + const hour = date.getHours(); + const minute = date.getMinutes(); + return `${month}月${day}日 ${hour}:${minute < 10 ? '0' + minute : minute}`; + } + }, + // 显示错误消息 + showError(message) { + this.setData({ + errorMessage: message, + successMessage: '' + }); + // 3秒后自动清除 + setTimeout(() => { + this.setData({ + errorMessage: '' + }); + }, 3000); + }, + // 显示成功消息 + showSuccess(message) { + this.setData({ + successMessage: message, + errorMessage: '' + }); + // 3秒后自动清除 + setTimeout(() => { + this.setData({ + successMessage: '' + }); + }, 3000); + }, + // 清除消息 + clearMessage() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + }, + // 返回上一页 + goBack() { + wx.navigateBack(); + } +}); diff --git a/dist/pages/staff/profile.js b/dist/pages/staff/profile.js new file mode 100644 index 0000000..81a1099 --- /dev/null +++ b/dist/pages/staff/profile.js @@ -0,0 +1,202 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const userService_1 = __importDefault(require("../../services/userService")); +const statisticsService_1 = __importDefault(require("../../services/statisticsService")); +Page({ + data: { + userInfo: null, + // 个人统计数据 + personalStats: { + totalOrders: 0, // 总订单数 + completedOrders: 0, // 已完成订单数 + pendingOrders: 0, // 待处理订单数 + deliveryRate: 0, // 配送成功率 + averageTime: 0 // 平均配送时间 + }, + // 系统统计数据 + systemStats: { + employeeCount: 0, // 员工总数 + orderCount: 0, // 订单总数 + warehouseCount: 0 // 仓库总数 + }, + loading: true + }, + onLoad() { + this.loadUserInfo(); + this.loadStatistics(); + }, + onShow() { + // 页面显示时重新加载数据 + this.loadUserInfo(); + this.loadStatistics(); + }, + /** + * 加载用户信息 + */ + async loadUserInfo() { + try { + const app = getApp(); + if (app.globalData.userInfo) { + this.setData({ + userInfo: app.globalData.userInfo + }); + } + else { + // 如果没有全局用户信息,尝试从API获取 + const userInfo = await userService_1.default.getUserInfo(); + this.setData({ userInfo }); + } + } + catch (error) { + console.error('加载用户信息失败:', error); + wx.showToast({ + title: '加载用户信息失败', + icon: 'none' + }); + } + }, + /** + * 加载统计数据 + */ + async loadStatistics() { + this.setData({ loading: true }); + try { + wx.showLoading({ + title: '加载统计数据...' + }); + // 强制刷新统计数据,不使用缓存 + const systemStats = await statisticsService_1.default.getSystemStats(true); + // 获取个人统计数据(这里需要根据用户ID获取个人统计数据) + const personalStats = await this.getPersonalStats(); + this.setData({ + systemStats: { + employeeCount: systemStats.employeeCount, + orderCount: systemStats.orderCount, + warehouseCount: systemStats.warehouseCount + }, + personalStats, + loading: false + }); + wx.hideLoading(); + } + catch (error) { + console.error('加载统计数据失败:', error); + this.setData({ loading: false }); + wx.hideLoading(); + wx.showToast({ + title: '加载统计数据失败', + icon: 'none' + }); + } + }, + /** + * 获取个人统计数据 + */ + async getPersonalStats() { + try { + // 这里应该调用个人统计API + // 暂时使用模拟数据 + return { + totalOrders: 156, + completedOrders: 142, + pendingOrders: 14, + deliveryRate: 91.0, + averageTime: 45 + }; + } + catch (error) { + console.error('获取个人统计数据失败:', error); + // 返回默认值 + return { + totalOrders: 0, + completedOrders: 0, + pendingOrders: 0, + deliveryRate: 0, + averageTime: 0 + }; + } + }, + /** + * 刷新数据 + */ + onRefresh() { + this.loadStatistics(); + }, + /** + * 返回上一页 + */ + goBack() { + wx.navigateBack(); + }, + /** + * 编辑个人信息 + */ + onEditProfile() { + wx.showToast({ + title: '编辑功能开发中', + icon: 'none' + }); + }, + /** + * 查看历史订单 + */ + onViewHistory() { + wx.navigateTo({ + url: '/pages/staff/order-management' + }); + }, + /** + * 联系客服 + */ + onContactSupport() { + wx.makePhoneCall({ + phoneNumber: '400-123-4567' + }); + }, + /** + * 退出登录 + */ + onLogout() { + wx.showModal({ + title: '确认退出', + content: '确定要退出登录吗?', + success: (res) => { + if (res.confirm) { + this.performLogout(); + } + } + }); + }, + /** + * 执行退出登录操作 + */ + async performLogout() { + try { + wx.showLoading({ + title: '退出中...' + }); + // 调用用户服务的退出登录方法 + const userService = require('../../services/userService').default; + await userService.logout(); + // 清除全局用户信息 + const app = getApp(); + app.globalData.userInfo = null; + wx.hideLoading(); + // 跳转到首页 + wx.reLaunch({ + url: '/pages/index/index' + }); + } + catch (error) { + console.error('退出登录失败:', error); + wx.hideLoading(); + wx.showToast({ + title: '退出登录失败', + icon: 'none' + }); + } + } +}); diff --git a/dist/pages/staff/staff.js b/dist/pages/staff/staff.js new file mode 100644 index 0000000..0725aff --- /dev/null +++ b/dist/pages/staff/staff.js @@ -0,0 +1,73 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +Page({ + data: { + userInfo: null, + isAdmin: false, + loading: true + }, + onLoad() { + // 获取用户信息 + this.getUserInfo(); + }, + onShow() { + // 页面显示时重新获取用户信息 + this.getUserInfo(); + }, + /** + * 获取用户信息并判断角色 + */ + getUserInfo() { + this.setData({ loading: true }); + // 从全局数据获取用户信息 + const app = getApp(); + if (app.globalData.userInfo) { + const userInfo = app.globalData.userInfo; + const isAdmin = userInfo.role === 'ADMIN'; + this.setData({ + userInfo, + isAdmin, + loading: false + }); + // 根据角色自动跳转到对应界面 + this.redirectToRolePage(); + } + else { + // 如果没有用户信息,跳转到登录页面 + wx.redirectTo({ + url: '/pages/index/index' + }); + } + }, + /** + * 根据用户角色跳转到对应页面 + */ + redirectToRolePage() { + const { isAdmin } = this.data; + if (isAdmin) { + // 管理员跳转到管理员界面 + wx.redirectTo({ + url: '/pages/staff/admin-dashboard' + }); + } + else { + // 员工跳转到员工界面 + wx.redirectTo({ + url: '/pages/staff/employee-dashboard' + }); + } + }, + /** + * 手动选择界面(用于测试或特殊情况) + */ + goToAdminPage() { + wx.redirectTo({ + url: '/pages/staff/admin-dashboard' + }); + }, + goToEmployeePage() { + wx.redirectTo({ + url: '/pages/staff/employee-dashboard' + }); + } +}); diff --git a/dist/pages/staff/warehouse-management.js b/dist/pages/staff/warehouse-management.js new file mode 100644 index 0000000..822303a --- /dev/null +++ b/dist/pages/staff/warehouse-management.js @@ -0,0 +1,480 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// 仓库管理页面 - 处理仓库的增删改查和库存管理 +const helpers_1 = require("../../utils/helpers"); +const warehouseService_1 = __importDefault(require("../../services/warehouseService")); +Page({ + data: { + // 仓库列表 + warehouses: [], + filteredWarehouses: [], + // 页面状态 + currentTab: 'list', // list: 列表页, add: 添加页, edit: 编辑页 + loading: false, + // 添加仓库表单数据 + addForm: { + name: '', + address: '', + contact: '', + phone: '', + description: '', + capacity: 500, + longitude: 102.833722, + latitude: 24.880095 + }, + // 编辑仓库表单数据 + editForm: { + id: 0, + name: '', + address: '', + contact: '', + phone: '', + description: '', + capacity: 500, + longitude: 102.833722, + latitude: 24.880095, + status: 'open' + }, + // 错误信息 + errorMessage: '', + successMessage: '', + // 搜索关键词 + searchKeyword: '', + // 用户信息 + userInfo: null + }, + onLoad() { + this.loadWarehouses(); + }, + onShow() { + // 只在页面显示时检查是否需要刷新数据,避免频繁请求 + if (this.data.warehouses.length === 0) { + this.loadWarehouses(); + } + }, + // 加载仓库列表 + async loadWarehouses() { + this.setData({ + loading: true, + errorMessage: '' + }); + try { + const warehouses = await warehouseService_1.default.getWarehouses(); + this.setData({ + warehouses, + filteredWarehouses: warehouses, + loading: false + }); + } + catch (error) { + console.error('加载仓库列表失败:', error); + this.setData({ + loading: false, + errorMessage: '加载仓库列表失败,请重试' + }); + } + }, + // 切换标签页 + switchTab(e) { + const tab = e.currentTarget.dataset.tab; + this.setData({ + currentTab: tab + }); + }, + // 搜索仓库 + onSearchInput(e) { + const keyword = e.detail.value.trim(); + this.setData({ + searchKeyword: keyword + }); + this.filterWarehouses(keyword); + }, + // 过滤仓库列表 + filterWarehouses(keyword) { + const { warehouses } = this.data; + if (!keyword) { + this.setData({ + filteredWarehouses: warehouses + }); + return; + } + const filtered = warehouses.filter(warehouse => warehouse.name.includes(keyword) || + warehouse.address.includes(keyword) || + warehouse.contact?.includes(keyword) || + warehouse.phone?.includes(keyword)); + this.setData({ + filteredWarehouses: filtered + }); + }, + // 表单输入处理 + onNameInput(e) { + this.setData({ + 'addForm.name': e.detail.value + }); + }, + onAddressInput(e) { + this.setData({ + 'addForm.address': e.detail.value + }); + }, + onContactInput(e) { + this.setData({ + 'addForm.contact': e.detail.value + }); + }, + onPhoneInput(e) { + this.setData({ + 'addForm.phone': e.detail.value + }); + }, + onDescriptionInput(e) { + this.setData({ + 'addForm.description': e.detail.value + }); + }, + onCapacityInput(e) { + this.setData({ + 'addForm.capacity': parseInt(e.detail.value) || 500 + }); + }, + // 经度输入处理 + onLongitudeInput(e) { + const value = parseFloat(e.detail.value) || 0; + this.setData({ + 'addForm.longitude': value + }); + }, + // 纬度输入处理 + onLatitudeInput(e) { + const value = parseFloat(e.detail.value) || 0; + this.setData({ + 'addForm.latitude': value + }); + }, + // 地图选点功能 + pickLocationFromMap() { + console.log('开始地图选点...'); + wx.chooseLocation({ + success: (res) => { + console.log('地图选点成功:', res); + if (res.longitude && res.latitude) { + console.log('更新表单数据 - 经度:', res.longitude, '纬度:', res.latitude); + this.setData({ + 'addForm.longitude': res.longitude, + 'addForm.latitude': res.latitude + }); + console.log('表单数据更新后:', this.data.addForm); + wx.showToast({ + title: '位置已选择', + icon: 'success' + }); + } + }, + fail: (error) => { + console.error('地图选点失败:', error); + wx.showToast({ + title: '选点失败,请重试', + icon: 'none' + }); + } + }); + }, + // 验证表单 + validateForm() { + const { name, address, contact, phone } = this.data.addForm; + if (!name.trim()) { + this.setData({ + errorMessage: '请输入仓库名称' + }); + return false; + } + if (!address.trim()) { + this.setData({ + errorMessage: '请输入仓库地址' + }); + return false; + } + if (!contact?.trim()) { + this.setData({ + errorMessage: '请输入联系人' + }); + return false; + } + if (!phone?.trim()) { + this.setData({ + errorMessage: '请输入联系电话' + }); + return false; + } + return true; + }, + // 提交添加仓库表单 + async submitAddForm() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + if (!this.validateForm()) { + return; + } + this.setData({ + loading: true + }); + try { + await warehouseService_1.default.createWarehouse(this.data.addForm); + this.setData({ + loading: false, + successMessage: '仓库添加成功', + currentTab: 'list' + }); + // 重置表单 + this.resetForm(); + // 重新加载仓库列表 + this.loadWarehouses(); + (0, helpers_1.showToast)('仓库添加成功'); + } + catch (error) { + console.error('添加仓库失败:', error); + this.setData({ + loading: false, + errorMessage: '添加仓库失败,请重试' + }); + } + }, + // 重置表单 + resetForm() { + this.setData({ + addForm: { + name: '', + address: '', + contact: '', + phone: '', + description: '', + capacity: 500, + longitude: 102.833722, + latitude: 24.880095 + } + }); + }, + // 取消编辑 + cancelEdit() { + this.setData({ + currentTab: 'list', + errorMessage: '', + successMessage: '' + }); + }, + // 编辑表单输入处理 + onEditNameInput(e) { + this.setData({ + 'editForm.name': e.detail.value + }); + }, + onEditAddressInput(e) { + this.setData({ + 'editForm.address': e.detail.value + }); + }, + onEditContactInput(e) { + this.setData({ + 'editForm.contact': e.detail.value + }); + }, + onEditPhoneInput(e) { + this.setData({ + 'editForm.phone': e.detail.value + }); + }, + onEditDescriptionInput(e) { + this.setData({ + 'editForm.description': e.detail.value + }); + }, + onEditCapacityInput(e) { + this.setData({ + 'editForm.capacity': parseInt(e.detail.value) || 500 + }); + }, + onEditLongitudeInput(e) { + this.setData({ + 'editForm.longitude': parseFloat(e.detail.value) || 102.833722 + }); + }, + onEditLatitudeInput(e) { + this.setData({ + 'editForm.latitude': parseFloat(e.detail.value) || 24.880095 + }); + }, + onEditStatusChange(e) { + this.setData({ + 'editForm.status': e.detail.value + }); + }, + // 编辑仓库 + editWarehouse(e) { + const warehouseId = e.currentTarget.dataset.id; + const warehouse = this.data.warehouses.find(w => w.id === warehouseId); + if (warehouse) { + this.setData({ + editForm: { + id: warehouse.id || 0, + name: warehouse.name || '', + address: warehouse.address || '', + contact: warehouse.contact || '', + phone: warehouse.phone || '', + description: warehouse.description || '', + capacity: warehouse.capacity || 500, + longitude: warehouse.longitude || 102.833722, + latitude: warehouse.latitude || 24.880095, + status: warehouse.status || 'open' + }, + currentTab: 'edit' + }); + } + }, + // 提交编辑仓库表单 + async submitEditForm() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + if (!this.validateEditForm()) { + return; + } + this.setData({ + loading: true + }); + try { + // 使用updateWarehouse更新仓库信息 + await warehouseService_1.default.updateWarehouse(this.data.editForm.id, this.data.editForm); + this.setData({ + loading: false, + successMessage: '仓库更新成功', + currentTab: 'list' + }); + // 重新加载仓库列表 + this.loadWarehouses(); + (0, helpers_1.showToast)('仓库更新成功'); + } + catch (error) { + console.error('更新仓库失败:', error); + this.setData({ + loading: false, + errorMessage: '更新仓库失败,请重试' + }); + } + }, + // 验证编辑表单 + validateEditForm() { + const { name, address, contact, phone } = this.data.editForm; + if (!name.trim()) { + this.setData({ + errorMessage: '请输入仓库名称' + }); + return false; + } + if (!address.trim()) { + this.setData({ + errorMessage: '请输入仓库地址' + }); + return false; + } + if (!contact?.trim()) { + this.setData({ + errorMessage: '请输入联系人' + }); + return false; + } + if (!phone?.trim()) { + this.setData({ + errorMessage: '请输入联系电话' + }); + return false; + } + return true; + }, + // 删除仓库 + async deleteWarehouse(e) { + const warehouseId = e.currentTarget.dataset.id; + const warehouseName = e.currentTarget.dataset.name; + wx.showModal({ + title: '确认删除', + content: `确定要删除仓库"${warehouseName}"吗?此操作不可恢复。`, + confirmText: '删除', + confirmColor: '#ff4757', + cancelText: '取消', + success: async (res) => { + if (res.confirm) { + try { + await warehouseService_1.default.deleteWarehouse(warehouseId); + (0, helpers_1.showToast)('仓库删除成功'); + // 重新加载仓库列表 + this.loadWarehouses(); + } + catch (error) { + console.error('删除仓库失败:', error); + (0, helpers_1.showToast)('删除仓库失败'); + } + } + } + }); + }, + // 获取仓库状态文本 + getWarehouseStatusText(status) { + const statusMap = { + 'open': '营业中', + 'closed': '已关闭', + 'maintenance': '维护中' + }; + return statusMap[status || 'open'] || '未知状态'; + }, + // 获取仓库库存状态 + getWarehouseInventoryStatus(currentStock, capacity) { + const percentage = (currentStock / capacity) * 100; + if (percentage >= 90) + return '库存充足'; + if (percentage >= 60) + return '库存正常'; + if (percentage >= 30) + return '库存偏低'; + return '库存不足'; + }, + // 获取库存状态对应的颜色 + getInventoryStatusColor(currentStock, capacity) { + const percentage = (currentStock / capacity) * 100; + if (percentage >= 90) + return '#52c41a'; // 绿色 + if (percentage >= 60) + return '#1890ff'; // 蓝色 + if (percentage >= 30) + return '#faad14'; // 橙色 + return '#f5222d'; // 红色 + }, + // 下拉刷新 + onPullDownRefresh() { + this.loadWarehouses().finally(() => { + wx.stopPullDownRefresh(); + }); + }, + // 清除错误信息 + clearError() { + this.setData({ + errorMessage: '' + }); + }, + // 清除成功信息 + clearSuccess() { + this.setData({ + successMessage: '' + }); + }, + /** + * 返回上一页 + */ + goBack() { + wx.navigateBack(); + } +}); diff --git a/dist/services/apiService.js b/dist/services/apiService.js new file mode 100644 index 0000000..736a1d4 --- /dev/null +++ b/dist/services/apiService.js @@ -0,0 +1,883 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const roleUtils_1 = require("../utils/roleUtils"); +const locationTrackingService_1 = __importDefault(require("./locationTrackingService")); +/** + * API服务基类 + * 提供与后端API交互的核心功能 + */ +// API基础URL配置 +const IS_LOCAL_DEV = true; // true: 本地开发环境, false: 生产环境 +const API_BASE_URL = IS_LOCAL_DEV ? 'http://localhost:8080' : 'https://www.doubleyin.cn'; +console.log(`当前API地址: ${API_BASE_URL} (${IS_LOCAL_DEV ? '本地开发环境' : '生产环境'})`); +/** + * 是否启用模拟模式 + * 该配置控制是否使用模拟数据进行开发测试 + */ +/** + * API服务类 + * 封装了所有与后端API交互的方法,并按照功能模块进行组织 + */ +class ApiService { + /** + * 构造函数 + */ + constructor() { + this.locationWebSocket = null; + } + /** + * 基础请求方法 + * @param endpoint API端点路径 + * @param options 请求配置选项 + * @returns Promise 返回泛型类型的响应数据 + */ + async request(endpoint, options = {}) { + const requestUrl = `${API_BASE_URL}${endpoint}`; + // 获取全局token + const app = getApp(); + const token = app.globalData.token; + // 构建请求头,自动添加Authorization头(如果存在token) + const headers = { + 'Content-Type': 'application/json', + ...options.headers, + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + const requestOptions = { + method: options.method || 'GET', + data: options.data, + header: headers, + }; + // 打印请求信息 + console.log('\n========== API Request =========='); + console.log(`Method: ${requestOptions.method}`); + console.log(`URL: ${requestUrl}`); + console.log(`Headers:`, requestOptions.header); + console.log(`Data:`, requestOptions.data); + console.log('================================'); + return new Promise((resolve, reject) => { + wx.request({ + url: requestUrl, + method: requestOptions.method, + data: requestOptions.data, + header: requestOptions.header, + success: (res) => { + // 打印响应信息 + console.log('\n========== API Response =========='); + console.log(`URL: ${requestUrl}`); + console.log(`Status: ${res.statusCode}`); + console.log(`Response Data:`, res.data); + console.log('=================================='); + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(res.data); + } + else { + // 优先使用服务器返回的具体错误信息,如果没有则使用默认错误信息 + const errorMessage = res.data || res.errMsg || '请求失败'; + console.error(`API Error: HTTP ${res.statusCode}: ${errorMessage}`); + reject(new Error(`HTTP ${res.statusCode}: ${errorMessage}`)); + } + }, + fail: (err) => { + console.error('\n========== API Request Failed =========='); + console.error(`URL: ${requestUrl}`); + console.error(`Error: ${err.errMsg}`); + console.error('======================================='); + reject(new Error(`Request failed: ${err.errMsg}`)); + } + }); + }); + } + // ==================== 用户模块 ==================== + // ====== 认证相关 ====== + /** + * 微信登录接口 + * @param code 微信登录授权码 + * @returns 用户信息及认证令牌 + */ + async ServerLogin(code) { + console.log('API wxLogin调用,参数code:', code); + return await this.request('/user/wxlogin', { + method: 'POST', + data: { code: code }, + }); + } + /** + * 签到接口 + * @param userId 用户ID + * @param initialLocation 初始位置数据(必须) + * @returns 签到结果和员工信息 + */ + async userSignIn(userId, initialLocation) { + console.log('API userSignIn调用,参数userId:', userId, 'initialLocation:', initialLocation); + // 构建请求数据,必须包含位置数据 + const requestData = { + latitude: initialLocation.latitude, + longitude: initialLocation.longitude, + timestamp: initialLocation.timestamp + }; + // 服务器现在返回统一的DTO格式,直接使用 + const response = await this.request('/user/signin', { + method: 'POST', + data: requestData, + }); + // 服务器返回格式:{ success: boolean, userInfo: UserInfoResponse } + return { + success: response.success, + employeeInfo: { + id: response.userInfo.id, + name: response.userInfo.name, + phone: response.userInfo.phone, + role: response.userInfo.role || roleUtils_1.Role.DELIVERY_PERSON + }, + message: response.message + }; + } + /** + * 绑定接口 + * @param userInfo 绑定用户信息 + * @returns 绑定结果和员工信息 + */ + async userRegister(userInfo) { + console.log('API userRegister调用,参数userInfo:', userInfo); + // 服务器返回直接的用户信息格式:{id, name, phone, role, openid} + const response = await this.request('/user/register', { + method: 'POST', + data: userInfo, + }); + // 检查响应格式,兼容不同的返回格式 + if (response.id) { + // 直接返回用户信息格式 + return { + success: true, + employeeInfo: { + id: response.id, + name: response.name || userInfo.name, + phone: response.phone || userInfo.phone, + role: response.role || roleUtils_1.Role.DELIVERY_PERSON + }, + message: '绑定成功' + }; + } + else if (response.userInfo && response.userInfo.id) { + // 包装格式:{success, userInfo, message} + return { + success: response.success !== false, + employeeInfo: { + id: response.userInfo.id, + name: response.userInfo.name || userInfo.name, + phone: response.userInfo.phone || userInfo.phone, + role: response.userInfo.role || roleUtils_1.Role.DELIVERY_PERSON + }, + message: response.message + }; + } + else { + // 未知格式,返回错误 + console.error('未知的绑定响应格式:', response); + return { + success: false, + employeeInfo: { + id: 0, + name: userInfo.name, + phone: userInfo.phone, + role: roleUtils_1.Role.DELIVERY_PERSON + }, + message: '绑定响应格式错误' + }; + } + } + /** + * 用户登出 + */ + async logout() { + return await this.request('/user/logout', { method: 'POST' }); + } + /** + * 解绑微信接口 + * 清除当前用户的openid绑定,允许重新注册其他账号 + * @returns 解绑结果 + */ + async unbindWechat() { + console.log('API unbindWechat调用'); + const response = await this.request('/user/unbind', { + method: 'POST', + data: {} + }); + return { + success: response.success, + message: response.message + }; + } + /** + * 签退接口 + * @param userId 用户ID + * @returns 签退结果 + */ + async userSignOut(userId) { + console.log('API userSignOut调用,参数userId:', userId); + // 服务器现在返回统一的DTO格式:{ success: boolean, message: string } + const response = await this.request('/user/signout', { + method: 'POST', + data: { userId }, + }); + return { + success: response.success, + message: response.message + }; + } + /** + * 获取用户签到状态 + * @returns 用户状态信息 + */ + async getUserStatus() { + return await this.request('/user/status', { method: 'GET' }); + } + // ====== 信息相关 ====== + /** + * 获取当前用户信息 + * @returns 用户详细信息 + */ + async getUserInfo() { + return await this.request('/user/info'); + } + /** + * 更新用户信息 + * @param userInfo 待更新的用户信息 + * @returns 更新后的用户信息 + */ + async updateUserInfo(userInfo) { + return await this.request('/user/update', { + method: 'PUT', + data: userInfo, + }); + } + /** + * 获取用户权限列表 + * @returns 权限字符串数组 + */ + async getUserPermissions() { + return await this.request('/user/permissions'); + } + /** + * 检查用户是否在线 + * @returns 在线状态 + */ + async checkUserOnline() { + return await this.request('/user/online'); + } + /** + * 获取用户会话信息 + * @returns 会话详细信息 + */ + async getSessionInfo() { + return await this.request('/user/session'); + } + /** + * 刷新用户令牌 + * @param oldToken 旧令牌 + * @returns 新令牌及过期时间 + */ + async refreshToken(oldToken) { + return await this.request('/user/refresh-token', { + method: 'POST', + data: { token: oldToken }, + }); + } + /** + * 验证用户权限 + * @param permission 待验证的权限 + * @returns 是否拥有该权限 + */ + async verifyPermission(permission) { + return await this.request(`/user/verify-permission?permission=${permission}`); + } + // ====== 管理相关 ====== + /** + * 搜索用户 + * @param query 搜索关键词 + * @param page 页码 + * @param pageSize 每页数量 + * @returns 用户列表及分页信息 + */ + async searchUsers(query, page = 1, pageSize = 20) { + return await this.request(`/user/search?query=${query}&page=${page}&pageSize=${pageSize}`); + } + /** + * 批量操作用户状态 + * @param userIds 用户ID列表 + * @param status 目标状态 + */ + async batchUpdateUserStatus(userIds, status) { + return await this.request('/user/batch-update-status', { + method: 'POST', + data: { userIds, status }, + }); + } + // ====== 员工管理接口 ====== + /** + * 获取所有员工列表 + * @returns 员工信息数组 + */ + async getEmployees() { + return await this.request('/employees'); + } + /** + * 添加新员工 + * @param employeeInfo 员工信息 + * @returns 添加结果 + */ + async addEmployee(employeeInfo) { + return await this.request('/employees', { + method: 'POST', + data: employeeInfo, + }); + } + /** + * 删除员工 + * @param employeeId 员工ID + * @returns 删除结果 + */ + async deleteEmployee(employeeId) { + return await this.request(`/employees/${employeeId}`, { + method: 'DELETE', + }); + } + /** + * 更新员工信息 + * @param employeeId 员工ID + * @param employeeInfo 员工信息 + * @returns 更新结果 + */ + async updateEmployee(employeeId, employeeInfo) { + return await this.request(`/employees/${employeeId}`, { + method: 'PUT', + data: employeeInfo, + }); + } + /** + * 上传员工头像 + * @param employeeId 员工ID + * @param filePath 头像文件路径 + * @returns 上传结果 + */ + async uploadEmployeeAvatar(employeeId, filePath) { + return new Promise((resolve, reject) => { + // 使用微信小程序的文件上传API + wx.uploadFile({ + url: `${API_BASE_URL}/employees/${employeeId}/avatar`, + filePath: filePath, + name: 'avatar', + header: { + 'Authorization': `Bearer ${getApp().globalData.token}` + }, + success: (res) => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + const data = JSON.parse(res.data); + resolve({ + success: true, + avatarUrl: data.avatarUrl + }); + } + catch (error) { + resolve({ + success: true, + avatarUrl: `${API_BASE_URL}/avatars/${employeeId}.jpg` + }); + } + } + else { + reject(new Error(`上传失败: HTTP ${res.statusCode}`)); + } + }, + fail: (err) => { + reject(new Error(`上传失败: ${err.errMsg}`)); + } + }); + }); + } + /** + * 获取员工头像 + * @param employeeId 员工ID + * @returns 头像URL + */ + async getEmployeeAvatar(employeeId) { + try { + // 尝试获取头像信息 + const response = await this.request(`/employees/${employeeId}/avatar`); + return response.avatarUrl || `${API_BASE_URL}/avatars/${employeeId}.jpg`; + } + catch (error) { + // 如果获取失败,返回默认头像URL + return `${API_BASE_URL}/avatars/${employeeId}.jpg`; + } + } + // ==================== 仓库相关接口 ==================== + /** + * 获取所有仓库列表 + * @returns 仓库信息数组 + */ + async getWarehouses() { + return await this.request('/warehouses'); + } + /** + * 获取指定仓库详情 + * @param id 仓库ID + * @returns 仓库详细信息 + */ + async getWarehouseById(id) { + return await this.request(`/warehouses/${id}`); + } + /** + * 获取仓库相关订单 + * @param warehouseId 仓库ID + * @returns 订单列表 + */ + async getWarehouseOrders(warehouseId) { + return await this.request(`/warehouses/${warehouseId}/orders`); + } + /** + * 创建仓库 + * @param warehouseData 仓库数据 + * @returns 创建结果 + */ + async createWarehouse(warehouseData) { + return await this.request('/warehouses', { + method: 'POST', + data: warehouseData + }); + } + /** + * 更新仓库信息 + * @param warehouseId 仓库ID + * @param warehouseInfo 仓库信息 + * @returns 更新结果 + */ + async updateWarehouse(warehouseId, warehouseInfo) { + return await this.request(`/warehouses/${warehouseId}`, { + method: 'PUT', + data: warehouseInfo + }); + } + /** + * 删除仓库 + * @param warehouseId 仓库ID + * @returns 删除结果 + */ + async deleteWarehouse(warehouseId) { + return await this.request(`/warehouses/${warehouseId}`, { + method: 'DELETE', + }); + } + // ==================== 货运人员接口 ==================== + /** + * 获取所有货运人员 + * @returns 货运人员列表 + */ + async getDeliveryPersons() { + return await this.request('/delivery-persons'); + } + /** + * 获取所有配送员实时位置 + * @returns 所有配送员实时位置信息 + */ + async getAllDeliveryPersonLocations() { + return await this.request('/location-sync/delivery-persons/locations'); + } + /** + * 获取指定货运人员详情 + * @param id 货运人员ID + * @returns 货运人员详细信息 + */ + async getDeliveryPersonById(id) { + return await this.request(`/delivery-persons/${id}`); + } + /** + * 更新货运人员位置 + * @param id 货运人员ID + * @param location 位置信息 + */ + async updateDeliveryPersonLocation(id, location) { + return await this.request(`/delivery-persons/${id}/location`, { + method: 'PUT', + data: location, + }); + } + // 地图相关接口支持 + /** + * 批量更新货运人员位置(兼容mapService参数) + * @param locations 位置信息数组 + */ + async batchUpdateDeliveryPersonLocations(locations) { + // 转换参数格式以匹配原有API + const formattedLocations = locations.map(loc => ({ + deliveryPersonId: loc.userId, + longitude: loc.longitude, + latitude: loc.latitude, + timestamp: loc.timestamp + })); + await this.request('/delivery-persons/locations/batch', { + method: 'POST', + data: { locations: formattedLocations } + }); + } + /** + * 获取空闲的货运人员 + * @returns 空闲货运人员列表 + */ + async getIdleDeliveryPersons() { + return await this.request('/delivery-persons/idle'); + } + /** + * 获取忙碌的货运人员 + * @returns 忙碌货运人员列表 + */ + async getBusyDeliveryPersons() { + return await this.request('/delivery-persons/busy'); + } + /** + * 获取位置历史记录 + * @param deliveryPersonId 货运人员ID + * @param limit 记录数量 + * @returns 位置历史记录 + */ + async getLocationHistory(deliveryPersonId, limit = 50) { + return await this.request(`/delivery-persons/${deliveryPersonId}/location-history?limit=${limit}`); + } + /** + * 订阅实时位置更新(接收货运人员ID列表) + * @param deliveryPersonIds 货运人员ID列表 + * @returns 订阅信息 + */ + async subscribeToRealTimeLocations(deliveryPersonIds) { + // 真实环境中调用API + try { + return await this.request('/locations/subscribe', { + method: 'POST', + data: { deliveryPersonIds } + }); + } + catch (error) { + console.error('订阅实时位置更新失败:', error); + throw error; + } + } + /** + * 取消订阅实时位置更新 + * @param subscriptionId 订阅ID + */ + async unsubscribeFromRealTimeLocations(subscriptionId) { + // 真实环境中调用API + try { + await this.request('/locations/unsubscribe', { + method: 'POST', + data: { subscriptionId } + }); + } + catch (error) { + console.error('取消订阅实时位置更新失败:', error); + throw error; + } + } + // ===== 私有属性和方法 ===== + /** + * 初始化位置更新WebSocket连接 + */ + async initLocationWebSocket() { + if (this.locationWebSocket) { + console.log('WebSocket连接已存在'); + return; + } + try { + if (typeof wx !== 'undefined' && typeof wx.connectSocket === 'function') { + console.log('初始化位置更新WebSocket连接(小程序环境)'); + // 连接到服务端的WebSocket地址,使用配置的服务器地址 + const serverUrl = API_BASE_URL.replace('http', 'ws'); + const wsUrl = `${serverUrl}/ws/location`; + // 获取当前用户ID(从全局应用数据中获取) + const app = getApp(); + const userInfo = app.globalData.userInfo; + const userId = userInfo?.id; + // 构建带用户ID参数的WebSocket URL + const finalWsUrl = userId ? `${wsUrl}?userId=${userId}` : wsUrl; + console.log(`WebSocket连接URL: ${finalWsUrl}`); + this.locationWebSocket = wx.connectSocket({ + url: finalWsUrl, + header: { + 'content-type': 'application/json' + } + }); + // WebSocket连接打开事件 + wx.onSocketOpen(() => { + console.log('WebSocket连接已打开'); + }); + // WebSocket消息接收事件 + wx.onSocketMessage((res) => { + try { + const message = JSON.parse(typeof res.data === 'string' ? res.data : String(res.data)); + this.handleWebSocketMessage(message); + } + catch (error) { + console.error('解析WebSocket消息失败:', error); + } + }); + // 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错误事件 + wx.onSocketError((error) => { + console.error('WebSocket连接错误:', error); + this.locationWebSocket = null; + }); + } + else { + console.warn('当前环境不支持WebSocket连接'); + } + } + catch (error) { + console.error('初始化位置更新WebSocket连接失败:', error); + this.locationWebSocket = null; + } + } + /** + * 检查WebSocket连接是否已建立 + */ + isWebSocketConnected() { + return this.locationWebSocket !== null && this.locationWebSocket !== undefined; + } + /** + * 关闭位置更新WebSocket连接 + */ + async closeLocationWebSocket() { + if (this.locationWebSocket && typeof wx !== 'undefined' && typeof wx.closeSocket === 'function') { + try { + // 先检查WebSocket状态,避免在连接过程中关闭 + wx.closeSocket(); + this.locationWebSocket = null; + console.log('WebSocket连接已关闭'); + } + catch (error) { + console.warn('关闭WebSocket连接时出现异常:', error); + // 即使关闭失败,也重置连接状态 + this.locationWebSocket = null; + } + } + } + /** + * 处理WebSocket消息 + * @param message 接收到的消息 + */ + handleWebSocketMessage(message) { + if (!message || typeof message.type !== 'string') { + console.warn('收到无效的WebSocket消息:', message); + return; + } + locationTrackingService_1.default.handleWebSocketMessage(message); + } + /** + * 发送WebSocket消息 + * @param message 要发送的消息对象 + */ + sendWebSocketMessage(message) { + if (!this.locationWebSocket) { + console.warn('WebSocket连接未建立,无法发送消息'); + return; + } + try { + const messageStr = JSON.stringify(message); + if (typeof wx !== 'undefined' && typeof wx.sendSocketMessage === 'function') { + wx.sendSocketMessage({ + data: messageStr + }); + } + } + catch (error) { + console.error('发送WebSocket消息失败:', error); + } + } + /** + * 订阅位置更新(服务器自动处理,无需发送消息) + * @param userId 用户ID + */ + async subscribeToLocationUpdates() { + // 确保WebSocket连接已建立 + if (!this.locationWebSocket) { + this.initLocationWebSocket(); + } + // 服务器会自动处理订阅逻辑,无需发送订阅消息 + console.log('位置订阅已初始化,服务器将自动处理订阅逻辑'); + } + /** + * 取消订阅位置更新(服务器自动处理,无需发送消息) + * @param userId 用户ID + */ + async unsubscribeFromLocationUpdates(_userId) { + // 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息 + console.log('位置取消订阅已初始化,服务器将自动处理取消订阅逻辑'); + } + /** + * 发送位置更新 + * @param userId 用户ID + * @param latitude 纬度 + * @param longitude 经度 + */ + async sendLocationUpdate(userId, latitude, longitude) { + // 确保WebSocket连接已建立 + if (!this.locationWebSocket) { + this.initLocationWebSocket(); + } + // 发送位置更新消息 + const locationMessage = { + type: 'updateLocation', + userId: userId, + latitude: latitude, + longitude: longitude, + timestamp: Date.now() + }; + this.sendWebSocketMessage(locationMessage); + } + // ==================== 订单接口 ==================== + /** + * 获取待指派订单 + * @returns 待指派订单列表 + */ + async getPendingOrders() { + return await this.request('/orders/pending'); + } + /** + * 指派订单给货运人员 + * @param orderId 订单ID + * @param deliveryPersonId 货运人员ID + */ + async assignOrder(orderId, deliveryPersonId) { + return await this.request(`/orders/${orderId}/assign`, { + method: 'POST', + data: { deliveryPersonId }, + }); + } + /** + * 获取货运人员当前订单 + * @param deliveryPersonId 货运人员ID + * @returns 订单列表 + */ + async getDeliveryPersonOrders(deliveryPersonId) { + return await this.request(`/delivery-persons/${deliveryPersonId}/orders`); + } + /** + * 根据ID获取订单 + * @param id 订单ID + * @returns 订单详细信息 + */ + async getOrderById(id) { + return await this.request(`/orders/${id}`); + } + /** + * 更新订单状态 + * @param orderId 订单ID + * @param status 目标状态 + */ + async updateOrderStatus(orderId, status) { + return await this.request(`/orders/${orderId}/status`, { + method: 'PUT', + data: { status }, + }); + } + /** + * 创建新订单 + * @param orderData 订单数据 + * @returns 创建的订单信息 + */ + async createOrder(orderData) { + return await this.request('/orders', { + method: 'POST', + data: orderData, + }); + } + /** + * 删除订单 + * @param orderId 订单ID + */ + async deleteOrder(orderId) { + return await this.request(`/orders/${orderId}`, { + method: 'DELETE', + }); + } + // ==================== 统计和日志接口 ==================== + /** + * 获取系统统计信息 + * @returns 系统统计数据 + */ + async getSystemStats() { + return await this.request('/stats/system'); + } + /** + * 获取订单统计信息 + * @param timeRange 时间范围 + * @returns 订单统计数据 + */ + async getOrderStats(timeRange) { + return await this.request(`/stats/orders?timeRange=${timeRange}`); + } + /** + * 获取用户统计信息 + * @returns 用户统计数据 + */ + async getUserStats() { + return await this.request('/user/stats'); + } + /** + * 获取用户活动日志 + * @param userId 用户ID + * @param limit 日志数量 + * @returns 活动日志列表 + */ + async getUserActivityLogs(userId, limit = 20) { + return await this.request(`/user/${userId}/activity-logs?limit=${limit}`); + } + /** + * 上传错误日志 + * @param error 错误信息 + */ + async uploadErrorLog(error) { + await this.request('/logs/error', { + method: 'POST', + data: { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + timestamp: Date.now(), + userAgent: 'miniprogram/' + (typeof wx !== 'undefined' ? wx.getSystemInfoSync().platform : 'unknown') + } + }); + } + // ==================== 系统接口 ==================== + /** + * 健康检查 + * @returns 系统健康状态 + */ + async healthCheck() { + return await this.request('/health'); + } +} +/** + * API服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new ApiService(); diff --git a/dist/services/deliveryPersonService.js b/dist/services/deliveryPersonService.js new file mode 100644 index 0000000..484773d --- /dev/null +++ b/dist/services/deliveryPersonService.js @@ -0,0 +1,88 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const apiService_1 = __importDefault(require("./apiService")); +/** + * 货运人员服务类 + * 提供货运人员信息管理、状态管理等功能 + */ +class DeliveryPersonService { + /** + * 构造函数 + */ + constructor() { + // 不再使用模拟数据 + } + /** + * 获取所有货运人员 + * @returns 货运人员列表 + */ + async getDeliveryPersons() { + return apiService_1.default.getDeliveryPersons(); + } + /** + * 根据ID获取货运人员 + * @param id 货运人员ID + * @returns 货运人员信息或null + */ + async getDeliveryPersonById(id) { + try { + const result = await apiService_1.default.getDeliveryPersonById(id); + return result; + } + catch (error) { + console.error('获取货运人员失败:', error); + return null; + } + } + /** + * 获取空闲的货运人员 + * @returns 空闲货运人员列表 + */ + async getIdleDeliveryPersons() { + return apiService_1.default.getIdleDeliveryPersons(); + } + /** + * 获取忙碌的货运人员 + * @returns 忙碌货运人员列表 + */ + async getBusyDeliveryPersons() { + return apiService_1.default.getBusyDeliveryPersons(); + } + /** + * 获取货运人员当前订单 + * @param deliveryPersonId 货运人员ID + * @returns 订单列表 + */ + async getDeliveryPersonOrders(deliveryPersonId) { + return apiService_1.default.getDeliveryPersonOrders(deliveryPersonId); + } + /** + * 获取货运人员头像URL + * @param deliveryPersonId 货运人员ID + * @returns 头像URL + */ + async getAvatarUrl(deliveryPersonId) { + try { + // 首先尝试获取货运人员详细信息 + const deliveryPerson = await this.getDeliveryPersonById(deliveryPersonId); + if (deliveryPerson && deliveryPerson.avatarUrl) { + return deliveryPerson.avatarUrl; + } + // 如果货运人员信息中没有头像URL,尝试使用员工服务获取 + const employeeService = require('./employeeService').default; + return await employeeService.getAvatarUrl(deliveryPersonId); + } + catch (error) { + console.error('获取货运人员头像失败:', error); + return ''; + } + } +} +/** + * 货运人员服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new DeliveryPersonService(); diff --git a/dist/services/employeeService.js b/dist/services/employeeService.js new file mode 100644 index 0000000..5a45706 --- /dev/null +++ b/dist/services/employeeService.js @@ -0,0 +1,147 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const apiService_1 = __importDefault(require("./apiService")); +/** + * 员工管理服务类 + * 提供员工信息的增删改查功能 + */ +class EmployeeService { + /** + * 构造函数 + */ + constructor() { + // 不再使用模拟数据 + } + /** + * 获取所有员工列表 + * @returns 员工信息数组 + */ + async getEmployees() { + try { + return await apiService_1.default.getEmployees(); + } + catch (error) { + console.error('获取员工列表失败:', error); + // API调用失败时返回空数组 + return []; + } + } + /** + * 添加新员工 + * @param employeeInfo 员工信息 + * @returns 添加的员工信息 + */ + async addEmployee(employeeInfo) { + try { + return await apiService_1.default.addEmployee(employeeInfo); + } + catch (error) { + console.error('添加员工失败:', error); + throw new Error('添加员工失败,请稍后重试'); + } + } + /** + * 删除员工 + * @param employeeId 员工ID + * @returns 删除结果 + */ + async deleteEmployee(employeeId) { + try { + return await apiService_1.default.deleteEmployee(employeeId); + } + catch (error) { + console.error('删除员工失败:', error); + return { + success: false, + message: '删除员工失败,请稍后重试' + }; + } + } + /** + * 更新员工信息 + * @param employeeId 员工ID + * @param employeeInfo 员工信息 + * @returns 更新后的员工信息 + */ + async updateEmployee(employeeId, employeeInfo) { + try { + return await apiService_1.default.updateEmployee(employeeId, employeeInfo); + } + catch (error) { + console.error('更新员工信息失败:', error); + throw new Error('更新员工信息失败,请稍后重试'); + } + } + /** + * 根据工号查找员工 + * @param phone 工号 + * @returns 员工信息或null + */ + async findEmployeeByPhone(phone) { + const employees = await this.getEmployees(); + return employees.find(emp => emp.phone === phone) || null; + } + /** + * 验证员工信息(用于绑定时检查) + * @param name 姓名 + * @param phone 工号 + * @returns 验证结果 + */ + async validateEmployee(name, phone) { + 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: '员工信息不存在,请联系管理员添加' + }; + } + } + /** + * 上传员工头像 + * @param employeeId 员工ID + * @param filePath 头像文件路径 + * @returns 上传结果 + */ + async uploadAvatar(employeeId, filePath) { + try { + return await apiService_1.default.uploadEmployeeAvatar(employeeId, filePath); + } + catch (error) { + console.error('上传头像失败:', error); + return { + success: false, + message: '上传头像失败,请稍后重试' + }; + } + } + /** + * 获取员工头像URL + * @param employeeId 员工ID + * @returns 头像URL + */ + async getAvatarUrl(employeeId) { + try { + return await apiService_1.default.getEmployeeAvatar(employeeId); + } + catch (error) { + console.error('获取头像失败:', error); + return ''; + } + } +} +/** + * 员工管理服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new EmployeeService(); diff --git a/dist/services/locationTrackingService.js b/dist/services/locationTrackingService.js new file mode 100644 index 0000000..460ff1f --- /dev/null +++ b/dist/services/locationTrackingService.js @@ -0,0 +1,482 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const apiService_1 = __importDefault(require("./apiService")); +const userService_1 = __importDefault(require("./userService")); +const mapService_1 = __importDefault(require("./mapService")); +/** + * 位置追踪服务类 + * 提供用户签到后的位置追踪、状态管理和位置分发功能 + */ +class LocationTrackingService { + /** + * 构造函数 + */ + constructor() { + // 取消订阅回调函数 + this.unsubscribeCallback = null; + this.onlineUsers = new Map(); + this.locationUpdateTimer = null; + this.userStatusCallback = null; + this.locationUpdateCallback = null; + this.isSignedIn = false; + this.unsubscribeCallback = null; + } + /** + * 启动位置追踪服务(WebSocket连接) + */ + async startTracking() { + const userInfo = userService_1.default.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + throw new Error('用户未登录,无法启动位置追踪'); + } + this.isSignedIn = true; + // 设置实时位置订阅(初始化WebSocket连接) + await this.setupRealTimeSubscription(); + // 启动位置更新定时器(每30秒向服务器发送位置信息) + this.startLocationUpdateTimer(); + console.log('位置追踪服务已启动'); + } + /** + * 初始化位置追踪服务 + */ + async initialize() { + } + /** + * 停止位置追踪服务(WebSocket断开时调用) + */ + async stopTracking() { + const userInfo = userService_1.default.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + return; + } + this.isSignedIn = false; + // 停止位置更新定时器 + this.stopLocationUpdateTimer(); + // 关闭WebSocket连接 + try { + await this.teardownRealTimeSubscription(); + console.log('调试信息 - WebSocket连接已关闭'); + } + catch (error) { + console.warn('调试信息 - 关闭WebSocket连接失败:', error); + } + // 清空在线用户列表(WebSocket断开时清空所有数据) + this.onlineUsers.clear(); + // 通知所有位置更新回调,清空地图标记点 + this.notifyLocationUpdateCallbacks(); + console.log('位置追踪服务已停止,在线用户列表已清空'); + } + /** + * 向服务器发送位置更新(仅发送,不更新本地缓存) + */ + async updateUserLocation(location) { + const userInfo = userService_1.default.getGlobalUserInfo(); + if (!userInfo || !userInfo.id || !this.isSignedIn) { + console.warn('[位置追踪服务] 用户未登录或未签到,无法更新位置'); + return; + } + console.log('[位置追踪服务] 向服务器发送位置信息:'); + console.log(`- 用户ID: ${userInfo.id}`); + console.log(`- 经度: ${location.longitude}`); + console.log(`- 纬度: ${location.latitude}`); + // 通过WebSocket发送位置更新给服务器 + await apiService_1.default.sendLocationUpdate(userInfo.id, location.latitude, location.longitude); + console.log('[位置追踪服务] 位置信息已发送到服务器'); + } + /** + * 获取所有在线用户信息 + */ + getOnlineUsers() { + return Array.from(this.onlineUsers.values()).filter(user => user.status === 'online'); + } + /** + * 订阅用户状态变化(单一方法赋值) + * @param callback 用户状态变化回调函数 + * @returns 取消订阅函数 + */ + subscribeToUserStatusChanges(callback) { + console.log('📝 [LocationTrackingService] 注册用户状态变化回调函数'); + // 直接赋值,覆盖之前的回调 + this.userStatusCallback = callback; + console.log('✅ [LocationTrackingService] 用户状态变化回调注册完成'); + // 返回取消订阅函数 + return () => { + if (this.userStatusCallback === callback) { + this.userStatusCallback = null; + console.log('✅ [LocationTrackingService] 用户状态变化回调已取消'); + } + }; + } + /** + * 检查用户是否在线 + */ + isUserOnline(userId) { + const userInfo = this.onlineUsers.get(userId); + return userInfo ? userInfo.status === 'online' : false; + } + // ===== 私有方法 ===== + /** + * 启动位置更新定时器 + */ + startLocationUpdateTimer() { + this.stopLocationUpdateTimer(); + this.locationUpdateTimer = setInterval(async () => { + if (this.isSignedIn) { + try { + const location = await this.getCurrentLocation(); + await this.updateUserLocation(location); + } + catch (error) { + console.error('自动位置更新失败:', error); + } + } + }, 30000); // 每30秒更新一次 + } + /** + * 获取当前位置 + */ + async getCurrentLocation() { + const userInfo = userService_1.default.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + throw new Error('用户未登录'); + } + try { + // 使用mapService获取真实位置,保持与项目中其他地方的一致性 + const location = await mapService_1.default.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() + }; + } + } + /** + * 设置实时位置订阅 + */ + async setupRealTimeSubscription() { + // 获取用户信息 + const userInfo = userService_1.default.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + console.warn('用户未登录,无法设置实时位置订阅'); + return; + } + // 缓存用户信息(已移除) + // 真实环境:初始化WebSocket连接(服务器会自动处理订阅逻辑) + try { + // 初始化WebSocket连接(如果尚未连接) + await apiService_1.default.initLocationWebSocket(); + // 等待WebSocket连接建立(最多等待5秒) + await new Promise((resolve, reject) => { + let attempts = 0; + const maxAttempts = 50; // 5秒超时 + const checkConnection = () => { + if (apiService_1.default.isWebSocketConnected()) { + resolve(); + } + else if (attempts < maxAttempts) { + attempts++; + setTimeout(checkConnection, 100); + } + else { + reject(new Error('WebSocket连接建立超时')); + } + }; + checkConnection(); + }); + // 服务器会自动处理订阅逻辑,无需发送订阅消息 + console.log('WebSocket连接已建立,服务器将自动处理位置订阅逻辑'); + } + catch (error) { + console.error('设置实时位置订阅失败:', error); + } + } + /** + * 关闭实时位置订阅 + */ + teardownRealTimeSubscription() { + // 真实环境:关闭WebSocket连接(服务器会自动处理取消订阅逻辑) + try { + // 服务器会自动处理取消订阅逻辑,无需发送取消订阅消息 + console.log('关闭WebSocket连接,服务器将自动处理取消订阅逻辑'); + // 移除位置更新回调 + if (this.unsubscribeCallback) { + this.unsubscribeCallback(); + this.unsubscribeCallback = null; + } + // 关闭WebSocket连接 + apiService_1.default.closeLocationWebSocket(); + } + catch (error) { + console.error('关闭实时位置订阅失败:', error); + } + } + /** + * 停止位置更新定时器 + */ + stopLocationUpdateTimer() { + if (this.locationUpdateTimer) { + clearInterval(this.locationUpdateTimer); + this.locationUpdateTimer = null; + } + } + /** + * 通知位置更新回调 + */ + notifyLocationUpdateCallbacks() { + const onlineUsers = this.getOnlineUsers(); + console.log('🔔 [LocationTrackingService] 开始通知位置更新回调'); + console.log('👥 [LocationTrackingService] 在线用户数量:', onlineUsers.length); + // 触发位置更新回调 + if (this.locationUpdateCallback) { + try { + console.log('🔄 [LocationTrackingService] 执行位置更新回调'); + this.locationUpdateCallback(onlineUsers); + console.log('✅ [LocationTrackingService] 位置更新回调执行成功'); + } + catch (error) { + console.error('❌ [LocationTrackingService] 位置更新回调执行失败:', error); + } + } + else { + console.log('⚠️ [LocationTrackingService] 没有注册位置更新回调'); + } + console.log('🏁 [LocationTrackingService] 位置更新回调通知完成'); + } + /** + * 手动触发位置更新(公共方法,可从外部调用) + * 用于手动刷新地图上的用户标记 + */ + triggerLocationUpdate() { + console.log('🔔 [LocationTrackingService] 手动触发位置更新'); + this.notifyLocationUpdateCallbacks(); + } + /** + * 订阅位置更新(用于locationModule注册回调) + * @param callback 位置更新回调函数 + * @returns 取消订阅函数 + */ + subscribeToLocationUpdates(callback) { + console.log('📝 [LocationTrackingService] 注册位置更新回调函数'); + // 直接赋值,覆盖之前的回调 + this.locationUpdateCallback = callback; + console.log('✅ [LocationTrackingService] 位置更新回调注册完成'); + // 返回取消订阅函数 + return () => { + if (this.locationUpdateCallback === callback) { + this.locationUpdateCallback = null; + console.log('✅ [LocationTrackingService] 位置更新回调已取消'); + } + }; + } + /** + * 通知用户状态变化回调 + */ + notifyUserStatusChange(userId, status) { + console.log(`🔔 [LocationTrackingService] 开始通知用户状态变化回调: 用户 ${userId} 状态变为 ${status}`); + if (this.userStatusCallback) { + try { + console.log('🔄 [LocationTrackingService] 执行用户状态变化回调'); + this.userStatusCallback(userId, status); + console.log('✅ [LocationTrackingService] 用户状态变化回调执行成功'); + } + catch (error) { + console.error('❌ [LocationTrackingService] 用户状态变化回调执行失败:', error); + } + } + else { + console.log('⚠️ [LocationTrackingService] 没有注册用户状态变化回调'); + } + console.log('🏁 [LocationTrackingService] 用户状态变化回调通知完成'); + } + /** + * 处理WebSocket消息 + * @param message 服务器下发的消息 + */ + handleWebSocketMessage(message) { + console.log(`📡 收到WebSocket消息类型: ${message.type}`, message); + switch (message.type) { + case 'onlineUserList': + // 处理在线用户列表消息(服务器发送当前在线用户列表) + console.log('👥 处理在线用户列表,用户数量:', message.users ? message.users.length : 0); + if (message.users && Array.isArray(message.users)) { + this.triggerOnlineUserListCallbacks({ + type: 'onlineUserList', + users: message.users.map((user) => ({ + userId: user.userId, + name: user.name, + role: user.role, + userStatus: user.userStatus, + lastUpdateTime: user.lastUpdateTime, + latitude: user.locationData?.latitude || user.latitude, + longitude: user.locationData?.longitude || user.longitude, + timestamp: user.locationData?.timestamp || user.timestamp + })) + }); + } + else { + console.warn('❌ onlineUserList消息格式错误,users字段不存在或不是数组'); + } + break; + case 'userLocationList': + // 处理用户位置列表消息(服务器每30秒发送所有在线用户的位置列表) + console.log('👥 处理用户位置列表,用户数量:', message.users ? message.users.length : 0); + // 确保用户列表存在且是数组 + if (message.users && Array.isArray(message.users)) { + // 转换用户数据格式,确保与位置追踪服务兼容 + const formattedUsers = message.users.map((user) => { + // 支持多种数据格式:locationData字段或直接字段 + const locationData = user.locationData || user; + // 验证必需字段是否存在 + if (!user.userId && !locationData.userId) { + console.error('❌ 用户数据缺少userId字段:', user); + return null; + } + if (locationData.latitude === undefined || locationData.longitude === undefined) { + console.error('❌ 用户数据缺少位置信息:', user); + return null; + } + // 对于位置更新消息,允许缺少name和role字段,从本地缓存中获取 + const existingUser = this.onlineUsers.get(user.userId || locationData.userId); + const formattedUser = { + userId: user.userId || locationData.userId, + name: user.name || user.userName || (existingUser ? existingUser.name : `用户${user.userId || locationData.userId}`), + role: user.role || (existingUser ? existingUser.role : 'DRIVER'), + userStatus: user.userStatus !== false, // 转换为布尔值 + lastUpdateTime: user.lastUpdateTime || locationData.timestamp || Date.now(), + latitude: locationData.latitude, + longitude: locationData.longitude, + timestamp: locationData.timestamp || user.timestamp || Date.now() + }; + // 验证必需字段 + if (!formattedUser.userId) { + console.error('❌ 用户数据缺少userId字段:', formattedUser); + return null; + } + return formattedUser; + }).filter((user) => user !== null); // 过滤掉无效数据 + console.log('📊 转换后的用户位置数据:', formattedUsers); + this.triggerOnlineUserListCallbacks({ + type: 'userLocationList', + users: formattedUsers + }); + } + else { + console.warn('❌ userLocationList消息格式错误,users字段不存在或不是数组'); + } + break; + default: + console.warn('收到未知类型的WebSocket消息:', message); + break; + } + } + /** + * 触发在线用户列表回调函数 + * @param userList 在线用户列表数据 + */ + triggerOnlineUserListCallbacks(userList) { + console.log('🔔 [apiService] 开始触发在线用户列表回调,消息类型:', userList.type); + console.log('📊 [apiService] 回调数据内容:', JSON.stringify(userList, null, 2)); + try { + console.log('🔄 [apiService] 执行在线用户列表回调函数'); + // 传递正确的数据格式:只传递users数组,而不是整个消息对象 + this.handleOnlineUserList(userList.users); + console.log('✅ [apiService] 在线用户列表回调函数执行成功'); + } + catch (error) { + console.error('❌ [apiService] 在线用户列表回调函数执行失败:', error); + } + console.log('🏁 [apiService] 在线用户列表回调触发完成'); + } + /** + * 处理在线用户列表 + * @param userList 服务器下发的在线用户列表 + */ + handleOnlineUserList(userList) { + try { + // 验证数据格式 + if (!Array.isArray(userList)) { + console.warn('[LocationTrackingService] 无效的在线用户列表数据格式'); + return; + } + console.log(`[LocationTrackingService] 开始处理在线用户列表,用户数量: ${userList.length}`); + // 获取当前本地用户列表的userId集合 + const currentUserIds = new Set(this.onlineUsers.keys()); + // 处理服务器下发的用户列表 + const newUserIds = new Set(); + userList.forEach(user => { + if (user && user.userId && user.latitude !== undefined && user.longitude !== undefined) { + newUserIds.add(user.userId); + // 提取位置数据(支持多种数据格式) + const latitude = user.latitude; + const longitude = user.longitude; + const timestamp = user.timestamp || user.lastUpdateTime || Date.now(); + // 对于位置更新消息,允许缺少name和role字段,从本地缓存中获取 + const existingUser = this.onlineUsers.get(user.userId); + // 更新或添加用户信息 + this.onlineUsers.set(user.userId, { + userId: user.userId, + name: user.name || user.userName || (existingUser ? existingUser.name : `用户${user.userId}`), + avatarUrl: '/images/user-avatar.png', + role: user.role || (existingUser ? existingUser.role : 'DRIVER'), + lastLocation: { + userId: user.userId, + longitude: longitude, + latitude: latitude, + timestamp: timestamp + }, + lastUpdateTime: timestamp, + status: user.userStatus === false ? 'offline' : 'online' + }); + console.log(`[LocationTrackingService] 更新用户 ${user.userId} 位置: (${latitude}, ${longitude})`); + // 如果用户是新上线的,触发用户状态回调 + if (!currentUserIds.has(user.userId)) { + this.notifyUserStatusChange(user.userId, 'online'); + console.log(`[LocationTrackingService] 新用户上线: ${user.userId}`); + } + } + else { + console.warn(`[LocationTrackingService] 跳过无效用户数据:`, user); + } + }); + // 删除本地列表中不在服务器列表中的用户 + let removedCount = 0; + currentUserIds.forEach(userId => { + if (!newUserIds.has(userId)) { + // 删除离线用户 + this.onlineUsers.delete(userId); + removedCount++; + // 触发用户状态回调(离线) + this.notifyUserStatusChange(userId, 'offline'); + console.log(`[LocationTrackingService] 用户离线: ${userId}`); + } + }); + console.log(`[LocationTrackingService] 在线用户列表更新完成: 新增 ${newUserIds.size - (currentUserIds.size - removedCount)} 个用户,删除 ${removedCount} 个用户`); + // 通知位置模块更新地图标记点(无论是否有变化都通知,确保地图标记点同步) + this.notifyLocationUpdateCallbacks(); + console.log('[LocationTrackingService] 已通知位置模块更新地图标记点'); + } + catch (error) { + console.error('[LocationTrackingService] 处理在线用户列表失败:', error); + } + } +} +/** + * 位置追踪服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new LocationTrackingService(); diff --git a/dist/services/mapService.js b/dist/services/mapService.js new file mode 100644 index 0000000..8e51d0a --- /dev/null +++ b/dist/services/mapService.js @@ -0,0 +1,350 @@ +"use strict"; +// 地图服务 - 处理地图相关的功能,包括定位、路线规划、地理编码等 +Object.defineProperty(exports, "__esModule", { value: true }); +/** + * 地图服务类 + * 提供地图相关功能,包括定位、路线规划、地理编码等 + */ +class MapService { + /** + * 构造函数,初始化地图实例 + */ + constructor() { + this.amapFile = require('../libs/amap-wx.js'); + this.MAP_KEY = '1fc5cafd570d9fbfd14c39359d41823d'; + this.KUNMING_CENTER = { + longitude: 102.833722, + latitude: 24.880095 + }; + this.amapInstance = new this.amapFile.AMapWX({ key: this.MAP_KEY }); + } + /** + * 获取用户位置信息 + * @returns 用户位置坐标 + */ + async getLocation() { + return new Promise((resolve, reject) => { + console.log('开始调用高德地图SDK获取位置...'); + // 添加超时机制 + const timeout = setTimeout(() => { + console.error('高德地图SDK调用超时,使用模拟位置'); + resolve({ + longitude: 102.7123, + latitude: 25.0409 + }); + }, 10000); // 10秒超时 + try { + // 高德地图SDK的getWxLocation方法需要两个参数:配置对象和回调函数 + this.amapInstance.getWxLocation({ + type: 'gcj02', // 使用国测局坐标系 + success: (res) => { + clearTimeout(timeout); + console.log('高德地图SDK定位成功:', res); + resolve(res); + }, + fail: (err) => { + clearTimeout(timeout); + console.error('高德地图SDK定位失败:', err); + // 注释:高德SDK失败后,直接使用模拟位置,不调用微信原生API + // 原因:微信小程序可能没有位置权限,避免权限错误 + console.log('高德地图SDK定位失败,使用模拟位置'); + resolve({ + longitude: 102.7123, + latitude: 25.0409 + }); + } + }, (locationString) => { + // 这是高德地图SDK的回调函数,参数是格式化的位置字符串 + console.log('高德地图SDK格式化位置:', locationString); + // 如果格式化位置回调被调用,说明SDK已经获取到位置 + // 尝试从格式化字符串中解析经纬度 + if (locationString) { + const coords = locationString.split(','); + if (coords.length === 2) { + const longitude = parseFloat(coords[0]); + const latitude = parseFloat(coords[1]); + if (!isNaN(longitude) && !isNaN(latitude)) { + clearTimeout(timeout); + console.log('从格式化位置解析成功,使用解析的位置:', { longitude, latitude }); + resolve({ longitude, latitude }); + return; + } + } + } + // 如果无法解析,继续等待success回调 + console.log('格式化位置回调被调用,但无法解析位置,继续等待success回调...'); + }); + } + catch (error) { + clearTimeout(timeout); + console.error('高德地图SDK调用异常:', error); + reject(error); + } + }); + } + /** + * 获取当前位置信息(兼容格式) + * @returns 当前位置的详细信息 + */ + async getCurrentLocation() { + const location = await this.getLocation(); + return { + latitude: location.latitude, + longitude: location.longitude, + accuracy: 50, + speed: 0, + altitude: 1891 + }; + } + /** + * 获取位置详细信息(逆地理编码) + * @param longitude 经度 + * @param latitude 纬度 + * @returns 逆地理编码结果 + */ + async getLocationInfo(longitude, latitude) { + return new Promise((resolve, reject) => { + this.amapInstance.getRegeo({ + location: `${longitude.toFixed(6)},${latitude.toFixed(6)}`, + success: (res) => { + console.log('逆地理编码成功:', res); + resolve(res); + }, + fail: (err) => { + console.log('逆地理编码失败:', err); + reject(err); + } + }); + }); + } + /** + * 获取昆明中心点坐标 + * @returns 昆明中心点坐标 + */ + getKunmingCenter() { + return this.KUNMING_CENTER; + } + /** + * 搜索地点 + * @param keyword 搜索关键词 + * @param city 城市名称 + * @returns 搜索结果列表 + */ + async searchPoi(keyword, city) { + return new Promise((resolve, reject) => { + this.amapInstance.getPoiAround({ + querykeywords: keyword, + city: city, + radius: 50000, // 搜索半径50公里 + offset: 20, // 返回20条结果 + success: (res) => { + console.log('搜索POI成功:', res); + if (res.pois && res.pois.length > 0) { + const results = res.pois.map((poi) => ({ + id: poi.id, + name: poi.name, + address: poi.address, + longitude: parseFloat(poi.location.split(',')[0]), + latitude: parseFloat(poi.location.split(',')[1]), + phone: poi.tel || '' + })); + resolve(results); + } + else { + resolve([]); + } + }, + fail: (err) => { + console.error('搜索POI失败:', err); + reject(err); + } + }); + }); + } + /** + * 规划驾车路线 + * @param origin 起点坐标字符串 (经度,纬度) + * @param destination 终点坐标字符串 (经度,纬度) + * @returns 路线规划结果 + */ + async getDrivingRoute(origin, destination) { + return new Promise((resolve, reject) => { + this.amapInstance.getDrivingRoute({ + origin, + destination, + strategy: '0', // 推荐路线策略 + showtraffic: false, // 不显示交通状况 + success: (res) => { + console.log('高德地图路线规划API调用成功'); + if (res.paths && res.paths.length > 0) { + const path = res.paths[0]; + const result = { + polyline: path.polyline || '', + distance: path.distance || 0, + duration: path.duration || 0 + }; + console.log(`路线规划完成: 距离${result.distance}米, 预计${result.duration}秒`); + resolve(result); + } + else { + resolve({ + polyline: '', + distance: 0, + duration: 0 + }); + } + }, + fail: (err) => { + console.error('高德地图路线规划API调用失败:', err); + reject(err); + } + }); + }); + } + /** + * 计算两点之间的距离(米) + * @param lat1 第一个点的纬度 + * @param lng1 第一个点的经度 + * @param lat2 第二个点的纬度 + * @param lng2 第二个点的经度 + * @returns 两点之间的距离(米) + */ + calculateDistance(lat1, lng1, lat2, lng2) { + const R = 6371000; // 地球半径(米) + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLng = (lng2 - lng1) * Math.PI / 180; + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLng / 2) * Math.sin(dLng / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; + } + /** + * 获取路线规划(通用版本) + * @param origin 起点坐标 + * @param destination 终点坐标 + * @returns 路线规划结果 + */ + async getRoute(origin, destination) { + // 对于真实模式,目前只支持驾车路线 + const originStr = `${origin.longitude},${origin.latitude}`; + const destStr = `${destination.longitude},${destination.latitude}`; + const result = await this.getDrivingRoute(originStr, destStr); + // 转换polyline格式 + const polylinePoints = []; + if (result.polyline) { + const points = result.polyline.split(';'); + for (const point of points) { + const [lng, lat] = point.split(','); + if (lng && lat) { + polylinePoints.push({ + latitude: parseFloat(lat), + longitude: parseFloat(lng) + }); + } + } + } + return { + distance: result.distance, + duration: result.duration, + polyline: polylinePoints + }; + } + /** + * 地理编码 - 地址转坐标 + * @param address 地址字符串 + * @returns 坐标信息 + */ + async geocode(address) { + // 对于真实模式,使用搜索API来模拟地理编码 + const results = await this.searchPoi(address, '昆明'); + if (results.length > 0) { + return { + latitude: results[0].latitude, + longitude: results[0].longitude, + formattedAddress: results[0].address + }; + } + // 如果没有找到结果,返回默认位置 + return { + latitude: 25.0409, + longitude: 102.7123, + formattedAddress: '云南省昆明市' + }; + } + /** + * 逆地理编码 - 坐标转地址 + * @param latitude 纬度 + * @param longitude 经度 + * @returns 地址信息 + */ + async reverseGeocode(latitude, longitude) { + const result = await this.getLocationInfo(longitude, latitude); + return { + // 修复:不再使用不存在的formatted_address字段,而是根据已有的addressComponent字段构建格式化地址 + formattedAddress: `${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.province) || '云南省'}${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.city) || '昆明市'}${(result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.district) || '五华区'}` || '未知地址', + country: '中国', + province: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.province) || '云南省', + city: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.city) || '昆明市', + district: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.district) || '五华区', + street: (result.regeocode && result.regeocode.addressComponent && result.regeocode.addressComponent.township) || '' + }; + } + /** + * 获取附近的地点 + * @param latitude 纬度 + * @param longitude 经度 + * @param radius 搜索半径 + * @param type 地点类型 + * @returns 附近地点列表 + */ + async getNearbyPlaces(latitude, longitude, radius, type) { + // 对于真实模式,使用搜索API + const keyword = type ? type : ''; + const results = await this.searchPoi(keyword, '昆明'); + return results.map(place => ({ + id: place.id, + name: place.name, + latitude: place.latitude, + longitude: place.longitude, + address: place.address, + distance: this.calculateDistance(latitude, longitude, place.latitude, place.longitude) + })).filter(place => place.distance <= radius); + } + /** + * 格式化路线距离 + * @param distance 距离(米) + * @returns 格式化后的距离字符串 + */ + formatRouteDistance(distance) { + if (distance < 1000) { + return `${distance}米`; + } + else { + return `${(distance / 1000).toFixed(1)}公里`; + } + } + /** + * 格式化路线时间 + * @param duration 时间(秒) + * @returns 格式化后的时间字符串 + */ + formatRouteDuration(duration) { + if (duration < 60) { + return `${Math.round(duration)} 秒`; + } + else if (duration < 3600) { + return `${Math.round(duration / 60)} 分钟`; + } + else { + const hours = Math.floor(duration / 3600); + const minutes = Math.round((duration % 3600) / 60); + return `${hours}小时${minutes}分钟`; + } + } +} +/** + * 地图服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new MapService(); diff --git a/dist/services/orderService.js b/dist/services/orderService.js new file mode 100644 index 0000000..de79665 --- /dev/null +++ b/dist/services/orderService.js @@ -0,0 +1,78 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const apiService_1 = __importDefault(require("./apiService")); +class OrderService { + /** + * 构造函数 + */ + constructor() { + // 不再使用模拟数据 + } + /** + * 获取所有待处理订单 + */ + async getPendingOrders() { + return apiService_1.default.getPendingOrders(); + } + /** + * 根据ID获取订单 + */ + async getOrderById(id) { + return apiService_1.default.getOrderById(id); + } + /** + * 指派订单给货运人员 + * @param orderId 订单ID + * @param deliveryPersonId 货运人员ID + * @returns 指派结果,包含success状态和可选消息 + */ + async assignOrder(orderId, deliveryPersonId) { + // 真实环境中调用API + try { + return await apiService_1.default.assignOrder(orderId, deliveryPersonId); + } + catch (error) { + console.error('指派订单失败:', error); + return { success: false, message: error instanceof Error ? error.message : '指派失败' }; + } + } + /** + * 更新订单状态 + */ + async updateOrderStatus(orderId, status) { + return apiService_1.default.updateOrderStatus(orderId, status).then(result => ({ + success: result.success, + message: result.message || '状态更新成功' + })); + } + /** + * 创建新订单 + */ + async createOrder(orderData) { + return apiService_1.default.createOrder(orderData); + } + /** + * 获取货运人员的订单列表 + */ + async getDeliveryPersonOrders(deliveryPersonId) { + return apiService_1.default.getDeliveryPersonOrders(deliveryPersonId); + } + /** + * 删除订单 + * @param orderId 订单ID + */ + async deleteOrder(orderId) { + try { + await apiService_1.default.deleteOrder(orderId); + return { success: true, message: '删除成功' }; + } + catch (error) { + console.error('删除订单失败:', error); + return { success: false, message: error instanceof Error ? error.message : '删除失败' }; + } + } +} +exports.default = new OrderService(); diff --git a/dist/services/statisticsService.js b/dist/services/statisticsService.js new file mode 100644 index 0000000..0f7c715 --- /dev/null +++ b/dist/services/statisticsService.js @@ -0,0 +1,133 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +// 统计服务 - 处理系统统计数据获取和缓存 +const apiService_1 = __importDefault(require("./apiService")); +/** + * 统计服务类 + * 提供系统统计数据的获取和缓存功能 + */ +class StatisticsService { + constructor() { + // 数据缓存 + this.cache = null; + // 缓存超时时间(5分钟) + this.cacheTimeout = 5 * 60 * 1000; + } + /** + * 获取系统统计数据 + * 采用一个API请求获取三个统计数据,后续操作客户端自行统计 + * @param forceRefresh 是否强制刷新,不使用缓存 + */ + async getSystemStats(forceRefresh = false) { + // 检查缓存是否有效,且不强制刷新 + if (!forceRefresh && this.cache && Date.now() - this.cache.timestamp < this.cacheTimeout) { + console.log('使用缓存的统计数据'); + return this.cache; + } + console.log('重新获取统计数据'); + try { + // 使用系统统计API获取三个统计数据 + const systemStats = await this.getSystemStatsFromAPI(); + this.cache = { + ...systemStats, + timestamp: Date.now() + }; + return this.cache; + } + catch (error) { + console.warn('系统统计API不可用,使用默认值:', error); + // 如果API不可用,返回默认值 + this.cache = { + employeeCount: 0, + orderCount: 0, + warehouseCount: 0, + pendingOrders: 0, + activeOrders: 0, + completedOrders: 0, + timestamp: Date.now() + }; + return this.cache; + } + } + /** + * 从API获取系统统计数据 + */ + async getSystemStatsFromAPI() { + try { + // 调用系统统计API + const response = await apiService_1.default.getSystemStats(); + console.log('API返回的统计数据:', response); + // API返回的数据结构是 { data: { ... }, success: true } + // 需要提取data字段中的数据 + const stats = response.data || response; + console.log('提取的统计数据:', stats); + // 转换API响应格式为StatisticsData格式 + // 根据API实际返回的字段进行映射 + const result = { + employeeCount: stats.totalEmployees || stats.totalDeliveryPersons || 0, + orderCount: stats.totalOrders || (stats.pendingOrders || 0) + (stats.activeOrders || 0), + warehouseCount: stats.totalWarehouses || 0, + pendingOrders: stats.pendingOrders || 0, + activeOrders: stats.activeOrders || 0, + completedOrders: 0, // 需要从其他接口获取 + timestamp: Date.now() + }; + console.log('转换后的统计数据:', result); + return result; + } + catch (error) { + console.error('获取系统统计数据失败:', error); + throw error; + } + } + /** + * 手动刷新统计数据(清除缓存) + */ + refreshStats() { + this.cache = null; + console.log('统计数据缓存已清除'); + } + /** + * 获取缓存状态 + */ + getCacheStatus() { + if (!this.cache) { + return { hasCache: false, isExpired: true, age: 0 }; + } + const age = Date.now() - this.cache.timestamp; + const isExpired = age > this.cacheTimeout; + return { hasCache: true, isExpired, age }; + } + /** + * 客户端更新统计数据(直接传值更新) + * @param type 数据类型:'employee' | 'order' | 'warehouse' + * @param count 对应的数量值 + */ + updateStats(type, count) { + if (!this.cache) { + console.warn('没有缓存数据,无法更新统计'); + return; + } + switch (type) { + case 'employee': + this.cache.employeeCount = Math.max(0, count); + break; + case 'order': + this.cache.orderCount = Math.max(0, count); + break; + case 'warehouse': + this.cache.warehouseCount = Math.max(0, count); + break; + } + this.cache.timestamp = Date.now(); + console.log(`统计数据已更新: ${type} = ${count}`); + } +} +/** + * 统计服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new StatisticsService(); diff --git a/dist/services/userService.js b/dist/services/userService.js new file mode 100644 index 0000000..b24dc4e --- /dev/null +++ b/dist/services/userService.js @@ -0,0 +1,357 @@ +"use strict"; +// 用户服务文件 +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const roleUtils_1 = require("../utils/roleUtils"); +const apiService_1 = __importDefault(require("./apiService")); +/** + * 用户服务类 + * 提供用户认证、信息管理、权限验证等功能 + */ +class UserService { + /** + * 构造函数 + */ + constructor() { + // 不再使用模拟数据 + } + /** + * 获取用户信息 + * @returns 用户信息 + */ + async getUserInfo() { + return apiService_1.default.getUserInfo(); + } + /** + * 用户退出登录 + */ + async logout() { + return apiService_1.default.logout(); + } + /** + * 检查用户是否已登录 + * @returns 是否已登录 + */ + isLoggedIn() { + const app = getApp(); + return app.globalData.isLoggedIn && !!app.globalData.userInfo; + } + /** + * 获取用户角色 + * @returns 用户角色或null + */ + getUserRole() { + const app = getApp(); + return app.globalData.userInfo && app.globalData.userInfo.role || null; + } + /** + * 检查用户是否为管理员 + * @returns 是否为管理员 + */ + isAdmin() { + return this.getUserRole() === roleUtils_1.Role.ADMIN; + } + /** + * 检查用户是否为货运人员 + * @returns 是否为货运人员 + */ + isDeliveryPerson() { + return this.getUserRole() === roleUtils_1.Role.DELIVERY_PERSON; + } + /** + * 检查位置权限 + * @returns 是否拥有位置权限 + */ + checkLocationPermission() { + return new Promise((resolve) => { + wx.getSetting({ + success: (res) => { + const hasPermission = res.authSetting && res.authSetting['scope.userLocation']; + resolve(!!hasPermission); + }, + fail: () => { + resolve(false); + } + }); + }); + } + /** + * 请求位置权限 + * @returns 请求是否成功 + */ + requestLocationPermission() { + return new Promise((resolve) => { + wx.authorize({ + scope: 'scope.userLocation', + success: () => { + resolve(true); + }, + fail: () => { + resolve(false); + } + }); + }); + } + /** + * 获取全局用户信息 + * @returns 全局用户信息或null + */ + getGlobalUserInfo() { + const app = getApp(); + return app.globalData.userInfo; + } + /** + * 执行静默登录流程:微信登录->个人服务器登录->进入基础界面->签到/绑定 + * @returns 登录结果 + */ + async wxLogin() { + try { + // 获取微信登录code + console.log('步骤1: 获取微信登录code'); + const code = await this.getWxLoginCode(); + if (!code) { + console.error('步骤1失败: 获取微信登录code失败'); + throw new Error('获取微信登录code失败'); + } + console.log('步骤1成功: 获取到微信登录code'); + // 调用微信登录API + console.log('步骤2: 调用微信登录API'); + const wxLoginResult = await this.ServerLogin(code); + if (wxLoginResult.success) { + console.log('步骤2成功: 微信登录API调用成功'); + // 静默登录模式下,不主动获取用户信息 + let userInfo = wxLoginResult.userInfo; + return { + success: true, + userInfo, + openid: wxLoginResult.openid, + session_key: wx.getStorageSync('session_key'), //TODO:服务器已经下发 + token: wxLoginResult.token + }; + } + else { + console.error('步骤2失败: 微信登录API返回失败'); + return { + success: false, + openid: wxLoginResult.openid + }; + } + } + catch (error) { + console.error('登录流程异常:', error); + return { success: false }; + } + } + /** + * 微信登录成功后调用个人服务器登录消息 + * @param code 微信登录授权码 + * @returns 登录结果 + */ + async ServerLogin(code) { + try { + // 真实API模式 + //TODO: 登录成功的基础数据:服务器下发的公共游客可看的数据 + const result = await apiService_1.default.ServerLogin(code); + // 确保用户信息包含头像URL,如果为空则设置默认头像 + const userInfoWithAvatar = { + ...result.user, + avatarUrl: result.user.avatarUrl || '/images/truck.png' + }; + // 保存到本地存储 + wx.setStorageSync('userInfo', userInfoWithAvatar); + wx.setStorageSync('token', result.token); + wx.setStorageSync('openid', result.openid); + wx.setStorageSync('session_key', result.session_key); + // 同时保存到全局数据,确保后续API调用能正确获取token + const app = getApp(); + app.globalData.token = result.token; + return { + success: true, + openid: result.openid, + token: result.token, + userInfo: userInfoWithAvatar + }; + } + catch (error) { + console.error('微信登录失败:', error); + return { success: false }; + } + } + /** + * 获取微信登录code + */ + async getWxLoginCode() { + return new Promise((resolve) => { + wx.login({ + success: (res) => { + if (res.code) { + console.log('成功获取到登录code'); + resolve(res.code); + } + else { + console.error('获取登录code失败,返回值为空'); + resolve(null); + } + }, + fail: (error) => { + console.error('wx.login调用失败:', error); + resolve(null); + } + }); + }); + } + /** + * 更新用户信息 + * @param userInfo 待更新的用户信息 + * @returns 更新后的用户信息 + */ + async updateUserInfo(userInfo) { + return apiService_1.default.updateUserInfo(userInfo); + } + /** + * 获取用户签到状态 + * @returns 用户状态信息 + */ + async getUserStatus() { + try { + // 调用服务器接口获取用户状态 + const response = await apiService_1.default.getUserStatus(); + return response; + } + catch (error) { + console.error('获取用户状态失败:', error); + // 如果是404错误(接口不存在),返回null让前端使用本地逻辑 + if (error.message && error.message.includes('404')) { + console.log('服务器状态接口不存在,使用本地逻辑'); + return null; // 返回null表示服务器接口不可用 + } + // 其他网络错误时返回默认状态 + return { + status: 'registered' + }; + } + } + /** + * 用户签到 + * @param initialLocation 初始位置数据(必须) + * @returns 签到结果和更新后的用户信息 + */ + async signIn(initialLocation) { + const userInfo = this.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + throw new Error('用户未登录,无法签到'); + } + // 检查是否有有效的token(防止后端空指针异常) + const app = getApp(); + if (!app.globalData.token) { + throw new Error('用户认证信息缺失,请重新登录'); + } + return apiService_1.default.userSignIn(userInfo.id, initialLocation); + } + /** + * 用户签退 + * @returns 签退结果 + */ + async signOut() { + const userInfo = this.getGlobalUserInfo(); + if (!userInfo || !userInfo.id) { + throw new Error('用户未登录,无法签退'); + } + // 检查是否有有效的token(防止后端空指针异常) + const app = getApp(); + if (!app.globalData.token) { + throw new Error('用户认证信息缺失,请重新登录'); + } + return apiService_1.default.userSignOut(userInfo.id); + } + /** + * 用户绑定 + * @param registerInfo 绑定信息 + * @returns 绑定结果和员工信息 + */ + async register(registerInfo) { + return apiService_1.default.userRegister(registerInfo); + } + /** + * 解绑微信 + * 清除当前用户的openid绑定,允许重新注册其他账号 + * @returns 解绑结果 + */ + async unbindWechat() { + try { + console.log('开始解绑微信流程'); + // 调用API解绑 + const result = await apiService_1.default.unbindWechat(); + if (result.success) { + console.log('微信解绑成功'); + // 清除本地存储的登录信息 + wx.removeStorageSync('userInfo'); + wx.removeStorageSync('token'); + wx.removeStorageSync('openid'); + wx.removeStorageSync('session_key'); + // 清除全局数据 + const app = getApp(); + app.globalData.userInfo = null; + app.globalData.token = null; + app.globalData.openid = null; + app.globalData.isLoggedIn = false; + console.log('本地登录信息已清除'); + // 解绑成功后跳转到主界面 + setTimeout(() => { + wx.reLaunch({ + url: '/pages/index/index' + }); + }, 500); + } + return result; + } + catch (error) { + console.error('解绑微信失败:', error); + return { success: false, message: '解绑微信失败,请重试' }; + } + } + /** + * 获取用户权限列表 + * @returns 权限列表 + */ + async getUserPermissions() { + return apiService_1.default.getUserPermissions(); + } + /** + * 检查用户是否在线 + * @returns 是否在线 + */ + async checkUserOnline() { + return apiService_1.default.checkUserOnline(); + } + /** + * 获取用户会话信息 + * @returns 会话信息 + */ + async getSessionInfo() { + return apiService_1.default.getSessionInfo(); + } + /** + * 刷新用户令牌 + * @param oldToken 旧令牌 + * @returns 新令牌及过期时间 + */ + async refreshToken(oldToken) { + return apiService_1.default.refreshToken(oldToken); + } + /** + * 验证用户权限 + * @param permission 待验证的权限 + * @returns 是否拥有该权限 + */ + async verifyPermission(permission) { + return apiService_1.default.verifyPermission(permission); + } +} +/** + * 用户服务单例实例 + * 导出供应用程序全局使用 + */ +exports.default = new UserService(); diff --git a/dist/services/warehouseService.js b/dist/services/warehouseService.js new file mode 100644 index 0000000..f7afffa --- /dev/null +++ b/dist/services/warehouseService.js @@ -0,0 +1,95 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const apiService_1 = __importDefault(require("./apiService")); +/** + * 仓库服务类 + * 封装了所有仓库相关的操作 + */ +class WarehouseService { + /** + * 构造函数 + */ + constructor() { + // 真实API模式下不需要模拟数据 + } + /** + * 获取仓库状态的中文描述 + */ + getWarehouseStatusText(status) { + const statusMap = { + 'open': '营业中', + 'closed': '已关闭', + 'maintenance': '维护中' + }; + return statusMap[status || 'open'] || '未知状态'; + } + /** + * 获取所有仓库信息 + */ + async getWarehouses() { + return apiService_1.default.getWarehouses(); + } + /** + * 根据ID获取仓库信息 + */ + async getWarehouseById(id) { + return apiService_1.default.getWarehouseById(id); + } + /** + * 创建仓库 + */ + async createWarehouse(warehouseData) { + try { + const result = await apiService_1.default.createWarehouse(warehouseData); + console.log('创建仓库结果:', result); + if (result.success && result.data) { + return result.data; + } + else { + throw new Error(result.message || '创建仓库失败'); + } + } + catch (error) { + console.error('创建仓库失败:', error); + throw new Error('创建仓库失败'); + } + } + /** + * 更新仓库信息 + */ + async updateWarehouse(warehouseId, warehouseInfo) { + try { + const result = await apiService_1.default.updateWarehouse(warehouseId, warehouseInfo); + console.log(`更新仓库 ${warehouseId} 结果:`, result); + if (result.success && result.data) { + return result.data; + } + else { + throw new Error(result.message || '更新仓库失败'); + } + } + catch (error) { + console.error('更新仓库失败:', error); + throw new Error('更新仓库失败'); + } + } + /** + * 删除仓库 + */ + async deleteWarehouse(warehouseId) { + try { + const result = await apiService_1.default.deleteWarehouse(warehouseId); + console.log(`删除仓库 ${warehouseId} 结果:`, result); + return result.success; + } + catch (error) { + console.error('删除仓库失败:', error); + throw new Error('删除仓库失败'); + } + } +} +// 导出单例实例 +exports.default = new WarehouseService(); diff --git a/dist/types/index.js b/dist/types/index.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/types/index.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/utils/avatarUtils.js b/dist/utils/avatarUtils.js new file mode 100644 index 0000000..f861fce --- /dev/null +++ b/dist/utils/avatarUtils.js @@ -0,0 +1,326 @@ +"use strict"; +// 头像工具类 - 处理头像相关的功能 +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AvatarUtils = exports.DefaultAvatars = exports.AvatarQuality = exports.AvatarSizes = void 0; +/** + * 头像尺寸配置 + */ +exports.AvatarSizes = { + // 小尺寸 - 用于列表、地图标记点等 + SMALL: 40, + // 中等尺寸 - 用于用户面板、详情页等 + MEDIUM: 80, + // 大尺寸 - 用于个人资料页等 + LARGE: 120, + // 超大尺寸 - 用于编辑页面等 + XLARGE: 160 +}; +/** + * 头像质量配置 + */ +exports.AvatarQuality = { + // 缩略图质量 + THUMBNAIL: 60, + // 标准质量 + STANDARD: 80, + // 高质量 + HIGH: 90 +}; +/** + * 默认头像配置 + */ +exports.DefaultAvatars = { + // 默认用户头像 + USER: '/images/user-avatar.png', + // 管理员头像 + ADMIN: '/images/admin-avatar.png', + // 员工头像 + EMPLOYEE: '/images/employee-avatar.png', + // 货运人员头像 + DELIVERY_PERSON: '/images/delivery-avatar.png' +}; +/** + * 头像工具类 + */ +class AvatarUtils { + /** + * 获取默认头像URL + * @param role 用户角色 + * @returns 默认头像URL + */ + static getDefaultAvatar(role) { + switch (role) { + case 'ADMIN': + return exports.DefaultAvatars.ADMIN; + case 'DELIVERY_PERSON': + return exports.DefaultAvatars.DELIVERY_PERSON; + case 'EMPLOYEE': + return exports.DefaultAvatars.EMPLOYEE; + default: + return exports.DefaultAvatars.USER; + } + } + /** + * 生成头像占位符文本 + * @param name 用户名 + * @returns 占位符文本 + */ + static generatePlaceholderText(name) { + if (!name) + return '👤'; + // 如果是中文名字,取最后一个字符 + if (/[\u4e00-\u9fa5]/.test(name)) { + return name.charAt(name.length - 1); + } + // 如果是英文名字,取首字母 + return name.charAt(0).toUpperCase(); + } + /** + * 获取头像显示URL + * @param avatarUrl 原始头像URL + * @param size 目标尺寸 + * @param quality 图片质量 + * @returns 处理后的头像URL + */ + static getAvatarUrl(avatarUrl, _size = exports.AvatarSizes.MEDIUM, _quality = exports.AvatarQuality.STANDARD) { + if (!avatarUrl) { + return ''; + } + // 如果是本地临时文件,直接返回 + if (avatarUrl.startsWith('http://tmp/') || avatarUrl.startsWith('wxfile://')) { + return avatarUrl; + } + // 如果是网络图片,可以添加尺寸参数(如果后端支持) + if (avatarUrl.startsWith('http')) { + // 这里可以根据后端API添加尺寸参数 + // 例如:return `${avatarUrl}?width=${size}&quality=${quality}`; + return avatarUrl; + } + return avatarUrl; + } + /** + * 缩放头像图片 + * @param src 原始图片路径 + * @param width 目标宽度 + * @param height 目标高度 + * @returns Promise 缩放后的图片路径 + */ + static async resizeAvatar(src, width, height) { + return new Promise((resolve, reject) => { + if (!src) { + reject(new Error('图片路径不能为空')); + return; + } + wx.getImageInfo({ + src, + success: (_infoRes) => { + const canvasId = `resize-canvas-${Date.now()}`; + const ctx = wx.createCanvasContext(canvasId); + // 绘制缩放后的图片 + ctx.drawImage(src, 0, 0, width, height); + ctx.draw(false, () => { + wx.canvasToTempFilePath({ + canvasId, + destWidth: width, + destHeight: height, + success: (res) => { + resolve(res.tempFilePath); + }, + fail: reject + }); + }); + }, + fail: reject + }); + }); + } + /** + * 裁剪头像为圆形 + * @param src 原始图片路径 + * @param size 裁剪尺寸 + * @returns Promise 裁剪后的圆形头像路径 + */ + static async cropAvatarToCircle(src, size = exports.AvatarSizes.MEDIUM) { + return new Promise((resolve, reject) => { + if (!src) { + reject(new Error('图片路径不能为空')); + return; + } + wx.getImageInfo({ + src, + success: (_infoRes) => { + const canvasId = `circle-canvas-${Date.now()}`; + const ctx = wx.createCanvasContext(canvasId); + // 绘制圆形裁剪区域 + ctx.save(); + ctx.beginPath(); + ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); + ctx.clip(); + // 绘制图片 + ctx.drawImage(src, 0, 0, size, size); + ctx.restore(); + ctx.draw(false, () => { + wx.canvasToTempFilePath({ + canvasId, + destWidth: size, + destHeight: size, + success: (res) => { + resolve(res.tempFilePath); + }, + fail: reject + }); + }); + }, + fail: reject + }); + }); + } + /** + * 压缩头像图片 + * @param src 原始图片路径 + * @param quality 压缩质量 (0-100) + * @returns Promise 压缩后的图片路径 + */ + static async compressAvatar(src, quality = exports.AvatarQuality.STANDARD) { + return new Promise((resolve, reject) => { + wx.compressImage({ + src, + quality, + success: (res) => { + resolve(res.tempFilePath); + }, + fail: reject + }); + }); + } + /** + * 缓存头像图片 + * @param avatarUrl 头像URL + * @param cacheKey 缓存键 + * @returns Promise 缓存后的图片路径 + */ + static async cacheAvatar(avatarUrl, cacheKey) { + if (!avatarUrl) { + return ''; + } + // 如果是本地文件,直接返回 + if (avatarUrl.startsWith('http://tmp/') || avatarUrl.startsWith('wxfile://')) { + return avatarUrl; + } + const key = cacheKey || `avatar_${avatarUrl.replace(/[^a-zA-Z0-9]/g, '_')}`; + try { + // 检查缓存 + const cachedPath = wx.getStorageSync(key); + if (cachedPath) { + // 验证缓存文件是否存在 + try { + await this.checkFileExists(cachedPath); + return cachedPath; + } + catch (error) { + // 缓存文件不存在,重新下载 + console.log('缓存文件不存在,重新下载:', avatarUrl); + } + } + // 下载头像 + const downloadRes = await this.downloadAvatar(avatarUrl); + // 缓存头像路径 + wx.setStorageSync(key, downloadRes); + return downloadRes; + } + catch (error) { + console.error('缓存头像失败:', error); + return avatarUrl; + } + } + /** + * 下载头像图片 + * @param url 头像URL + * @returns Promise 下载后的临时文件路径 + */ + static downloadAvatar(url) { + return new Promise((resolve, reject) => { + wx.downloadFile({ + url, + success: (res) => { + if (res.statusCode === 200) { + resolve(res.tempFilePath); + } + else { + reject(new Error(`下载失败,状态码: ${res.statusCode}`)); + } + }, + fail: reject + }); + }); + } + /** + * 检查文件是否存在 + * @param filePath 文件路径 + * @returns Promise + */ + static checkFileExists(filePath) { + return new Promise((resolve) => { + wx.getFileInfo({ + filePath, + success: () => resolve(true), + fail: () => resolve(false) + }); + }); + } + /** + * 生成头像样式类名 + * @param size 尺寸 + * @param shape 形状 + * @param bordered 是否有边框 + * @param shadow 是否有阴影 + * @returns 样式类名字符串 + */ + static generateAvatarClass(size, shape = 'circle', bordered = false, shadow = false) { + const classes = []; + // 尺寸类 + if (size <= exports.AvatarSizes.SMALL) { + classes.push('avatar-small'); + } + else if (size <= exports.AvatarSizes.MEDIUM) { + classes.push('avatar-medium'); + } + else if (size <= exports.AvatarSizes.LARGE) { + classes.push('avatar-large'); + } + else { + classes.push('avatar-xlarge'); + } + // 形状类 + classes.push(shape === 'circle' ? 'avatar-circle' : 'avatar-square'); + // 边框类 + if (bordered) { + classes.push('avatar-bordered'); + } + // 阴影类 + if (shadow) { + classes.push('avatar-shadow'); + } + return classes.join(' '); + } + /** + * 清理头像缓存 + * @param cacheKey 缓存键(可选,不传则清理所有头像缓存) + */ + static clearAvatarCache(cacheKey) { + if (cacheKey) { + wx.removeStorageSync(cacheKey); + } + else { + // 清理所有以 avatar_ 开头的缓存 + const storageInfo = wx.getStorageInfoSync(); + storageInfo.keys.forEach(key => { + if (key.startsWith('avatar_')) { + wx.removeStorageSync(key); + } + }); + } + } +} +exports.AvatarUtils = AvatarUtils; +exports.default = AvatarUtils; diff --git a/dist/utils/helpers.js b/dist/utils/helpers.js new file mode 100644 index 0000000..cd72f60 --- /dev/null +++ b/dist/utils/helpers.js @@ -0,0 +1,164 @@ +"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/dist/utils/roleUtils.js b/dist/utils/roleUtils.js new file mode 100644 index 0000000..f1df818 --- /dev/null +++ b/dist/utils/roleUtils.js @@ -0,0 +1,85 @@ +"use strict"; +/** + * 角色工具类 - 处理角色枚举的转换和验证 + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoleMapping = exports.Role = void 0; +exports.normalizeRole = normalizeRole; +exports.getRoleText = getRoleText; +exports.isValidRole = isValidRole; +exports.getRoleOptions = getRoleOptions; +/** + * 角色枚举定义 + * 服务器可能返回数字角色值,前端使用字符串枚举 + */ +var Role; +(function (Role) { + Role["DELIVERY_PERSON"] = "DELIVERY_PERSON"; + Role["ADMIN"] = "ADMIN"; // 管理员 +})(Role || (exports.Role = Role = {})); +/** + * 角色值映射 + * 服务器返回的数字角色值到前端字符串枚举的映射 + */ +exports.RoleMapping = { + 0: Role.DELIVERY_PERSON, // 0对应配送员 + 1: Role.ADMIN // 1对应管理员 +}; +/** + * 将服务器返回的角色值转换为前端枚举 + * @param roleValue 服务器返回的角色值(可能是数字或字符串) + * @returns 标准化的角色枚举值 + */ +function normalizeRole(roleValue) { + if (typeof roleValue === 'number') { + // 如果是数字,使用映射表转换 + return exports.RoleMapping[roleValue] || Role.DELIVERY_PERSON; + } + // 如果是字符串,直接转换为大写进行比较 + const normalizedRole = roleValue.toUpperCase(); + // 检查是否是有效的角色值 + if (normalizedRole === Role.ADMIN) { + return Role.ADMIN; + } + // 默认返回配送员 + return Role.DELIVERY_PERSON; +} +/** + * 获取角色显示文本 + * @param role 角色枚举值 + * @returns 对应的中文显示文本 + */ +function getRoleText(role) { + switch (role) { + case Role.ADMIN: + return '管理员'; + case Role.DELIVERY_PERSON: + return '配送员'; + default: + return '未知角色'; + } +} +/** + * 验证角色值是否有效 + * @param roleValue 角色值 + * @returns 是否有效 + */ +function isValidRole(roleValue) { + try { + const normalized = normalizeRole(roleValue); + return normalized === Role.ADMIN || normalized === Role.DELIVERY_PERSON; + } + catch { + return false; + } +} +/** + * 获取角色选项列表(用于下拉选择器) + * @returns 角色选项数组 + */ +function getRoleOptions() { + return [ + { value: Role.DELIVERY_PERSON, label: '配送员' }, + { value: Role.ADMIN, label: '管理员' } + ]; +} diff --git a/dist/utils/util.js b/dist/utils/util.js new file mode 100644 index 0000000..131ded6 --- /dev/null +++ b/dist/utils/util.js @@ -0,0 +1,19 @@ +"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/miniprogram/app.json b/miniprogram/app.json index d76bcb4..6589e8f 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -11,7 +11,8 @@ "pages/staff/warehouse-management" ], "requiredPrivateInfos": [ - "getLocation" + "getLocation", + "chooseLocation" ], "permission": { "scope.userLocation": { diff --git a/miniprogram/components/avatar/avatar.js b/miniprogram/components/avatar/avatar.js new file mode 100644 index 0000000..9ce0cd4 --- /dev/null +++ b/miniprogram/components/avatar/avatar.js @@ -0,0 +1,257 @@ +// 头像组件逻辑 +Component({ + properties: { + // 头像URL + avatarUrl: { + type: String, + value: '' + }, + // 头像尺寸(rpx) + size: { + type: Number, + value: 120 + }, + // 占位符文本 + placeholderText: { + type: String, + value: '👤' + }, + // 占位符文本大小 + textSize: { + type: Number, + value: 40 + }, + // 是否可编辑 + editable: { + type: Boolean, + value: false + }, + // 头像形状:circle(圆形)或 square(方形) + shape: { + type: String, + value: 'circle' + }, + // 是否显示边框 + bordered: { + type: Boolean, + value: false + }, + // 是否显示阴影 + shadow: { + type: Boolean, + value: false + } + }, + + data: { + showAvatarModal: false // 是否显示头像选择弹窗 + }, + + methods: { + // 头像点击事件 + onAvatarTap() { + if (this.properties.editable) { + this.showAvatarModal(); + } + this.triggerEvent('tap', { + avatarUrl: this.properties.avatarUrl + }); + }, + + // 显示头像选择弹窗 + showAvatarModal() { + this.setData({ + showAvatarModal: true + }); + }, + + // 隐藏头像选择弹窗 + hideAvatarModal() { + this.setData({ + showAvatarModal: false + }); + }, + + // 拍照 + takePhoto() { + this.hideAvatarModal(); + + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['camera'], + camera: 'front', + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + this.processImage(res.tempFiles[0].tempFilePath); + } + }, + fail: (err) => { + console.error('拍照失败:', err); + wx.showToast({ + title: '拍照失败', + icon: 'none' + }); + } + }); + }, + + // 从相册选择 + chooseImage() { + this.hideAvatarModal(); + + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album'], + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + this.processImage(res.tempFiles[0].tempFilePath); + } + }, + fail: (err) => { + console.error('选择图片失败:', err); + wx.showToast({ + title: '选择图片失败', + icon: 'none' + }); + } + }); + }, + + // 使用默认头像 + useDefaultAvatar() { + this.hideAvatarModal(); + + // 触发默认头像选择事件 + this.triggerEvent('change', { + avatarUrl: '', + isDefault: true + }); + }, + + // 处理图片 + processImage(tempFilePath) { + wx.showLoading({ + title: '处理中...' + }); + + // 压缩图片 + wx.compressImage({ + src: tempFilePath, + quality: 80, + success: (res) => { + // 裁剪图片为正方形 + this.cropImage(res.tempFilePath); + }, + fail: (err) => { + console.error('图片压缩失败:', err); + wx.hideLoading(); + wx.showToast({ + title: '图片处理失败', + icon: 'none' + }); + } + }); + }, + + // 裁剪图片 + cropImage(tempFilePath) { + // 获取图片信息 + wx.getImageInfo({ + src: tempFilePath, + success: (infoRes) => { + const { width, height } = infoRes; + const size = Math.min(width, height); + const x = (width - size) / 2; + const y = (height - size) / 2; + + // 创建画布进行裁剪 + const ctx = wx.createCanvasContext('avatar-canvas'); + + // 绘制圆形裁剪区域 + ctx.save(); + ctx.beginPath(); + ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); + ctx.clip(); + + // 绘制图片 + ctx.drawImage(tempFilePath, x, y, size, size, 0, 0, size, size); + ctx.restore(); + + // 将画布内容导出为图片 + ctx.draw(false, () => { + wx.canvasToTempFilePath({ + canvasId: 'avatar-canvas', + success: (canvasRes) => { + wx.hideLoading(); + + // 触发头像改变事件 + this.triggerEvent('change', { + avatarUrl: canvasRes.tempFilePath, + isDefault: false + }); + }, + fail: (err) => { + console.error('画布导出失败:', err); + wx.hideLoading(); + wx.showToast({ + title: '头像处理失败', + icon: 'none' + }); + } + }); + }); + }, + fail: (err) => { + console.error('获取图片信息失败:', err); + wx.hideLoading(); + wx.showToast({ + title: '图片处理失败', + icon: 'none' + }); + } + }); + }, + + // 生成头像缩略图 + generateThumbnail(avatarUrl, size = 60) { + return new Promise((resolve, reject) => { + if (!avatarUrl) { + resolve(''); + return; + } + + wx.getImageInfo({ + src: avatarUrl, + success: (infoRes) => { + const ctx = wx.createCanvasContext('thumbnail-canvas'); + + // 绘制圆形缩略图 + ctx.save(); + ctx.beginPath(); + ctx.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI); + ctx.clip(); + ctx.drawImage(avatarUrl, 0, 0, size, size); + ctx.restore(); + + ctx.draw(false, () => { + wx.canvasToTempFilePath({ + canvasId: 'thumbnail-canvas', + destWidth: size, + destHeight: size, + success: (res) => { + resolve(res.tempFilePath); + }, + fail: (err) => { + reject(err); + } + }); + }); + }, + fail: reject + }); + }); + } + } +}); \ No newline at end of file diff --git a/miniprogram/components/avatar/avatar.json b/miniprogram/components/avatar/avatar.json new file mode 100644 index 0000000..e8cfaaf --- /dev/null +++ b/miniprogram/components/avatar/avatar.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/miniprogram/components/avatar/avatar.wxml b/miniprogram/components/avatar/avatar.wxml new file mode 100644 index 0000000..90f4f38 --- /dev/null +++ b/miniprogram/components/avatar/avatar.wxml @@ -0,0 +1,52 @@ + + + + + + + + + {{placeholderText}} + + + + ✏️ + + + + + + + + + 选择头像 + × + + + + + + 📷 + 拍照 + + + + + 🖼️ + 从相册选择 + + + + + 👤 + 使用默认头像 + + + + + \ No newline at end of file diff --git a/miniprogram/components/avatar/avatar.wxss b/miniprogram/components/avatar/avatar.wxss new file mode 100644 index 0000000..446f405 --- /dev/null +++ b/miniprogram/components/avatar/avatar.wxss @@ -0,0 +1,204 @@ +/* 头像组件样式 */ +.avatar-container { + position: relative; + display: inline-block; +} + +/* 头像显示区域 */ +.avatar-display { + position: relative; + border-radius: 50%; + overflow: hidden; + background-color: #f0f0f0; + border: 2px solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; +} + +.avatar-display:active { + transform: scale(0.95); + opacity: 0.8; +} + +/* 头像图片容器 */ +.avatar-image-wrapper { + width: 100%; + height: 100%; + border-radius: 50%; + overflow: hidden; +} + +/* 头像图片 */ +.avatar-image { + width: 100%; + height: 100%; + border-radius: 50%; +} + +/* 头像占位符 */ +.avatar-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 50%; +} + +.avatar-text { + font-weight: bold; + text-transform: uppercase; +} + +/* 编辑图标 */ +.avatar-edit-icon { + position: absolute; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.7); + border-radius: 50%; + width: 24rpx; + height: 24rpx; + display: flex; + align-items: center; + justify-content: center; + transform: translate(25%, 25%); +} + +.edit-icon { + font-size: 12rpx; + color: white; +} + +/* 头像选择弹窗 */ +.avatar-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 9999; +} + +.modal-mask { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); +} + +.modal-content { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: white; + border-radius: 20rpx 20rpx 0 0; + padding: 40rpx; + box-sizing: border-box; + animation: slideUp 0.3s ease; +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + to { + transform: translateY(0); + } +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 40rpx; +} + +.modal-title { + font-size: 32rpx; + font-weight: bold; + color: #333; +} + +.modal-close { + font-size: 40rpx; + color: #999; + padding: 10rpx; +} + +/* 头像选项 */ +.avatar-options { + display: flex; + flex-direction: column; + gap: 30rpx; +} + +.avatar-option { + display: flex; + align-items: center; + padding: 30rpx; + background: #f8f9fa; + border-radius: 15rpx; + transition: all 0.3s ease; +} + +.avatar-option:active { + background: #e9ecef; + transform: scale(0.98); +} + +.option-icon { + font-size: 40rpx; + margin-right: 20rpx; +} + +.option-text { + font-size: 28rpx; + color: #333; +} + +/* 不同尺寸的头像样式 */ +.avatar-small { + width: 80rpx; + height: 80rpx; +} + +.avatar-medium { + width: 120rpx; + height: 120rpx; +} + +.avatar-large { + width: 160rpx; + height: 160rpx; +} + +.avatar-xlarge { + width: 200rpx; + height: 200rpx; +} + +/* 圆形头像样式 */ +.avatar-circle { + border-radius: 50%; +} + +/* 方形头像样式 */ +.avatar-square { + border-radius: 15rpx; +} + +/* 带边框的头像 */ +.avatar-bordered { + border: 3rpx solid #667eea; +} + +/* 阴影效果 */ +.avatar-shadow { + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15); +} \ No newline at end of file diff --git a/miniprogram/pages/admin/admin.ts b/miniprogram/pages/admin/admin.ts index 76bca3f..a0a5662 100644 --- a/miniprogram/pages/admin/admin.ts +++ b/miniprogram/pages/admin/admin.ts @@ -4,6 +4,10 @@ import mapService from '../../services/mapService'; import warehouseService from '../../services/warehouseService'; import orderService from '../../services/orderService'; import deliveryService from '../../services/deliveryPersonService'; +import userService from '../../services/userService'; +import { avatarCache } from '../../utils/avatarCache'; +import { Role } from '../../utils/roleUtils'; +import { API_BASE_URL } from '../../services/apiService'; Page({ @@ -36,28 +40,54 @@ Page({ currentTab: 'main' }, - onLoad() { - // 模拟管理员登录状态 - this.setData({ - userInfo: { - id: 1, - name: '管理员', - role: 'ADMIN' as any, - phone: '13800138000' - } - }); + onLoad: function() { + // 获取用户信息 + this.getUserInfo(); // 初始化数据 this.initData(); }, - onShow() { + /** + * 获取用户信息 + */ + getUserInfo: async function() { + try { + const app = getApp(); + let userInfo = app.globalData.userInfo; + + if (!userInfo) { + userInfo = await userService.getUserInfo(); + } + + // 处理用户信息,添加完整的头像URL + const processedUserInfo = await this.processUserInfo(userInfo); + + this.setData({ + userInfo: processedUserInfo + }); + } catch (error) { + console.error('获取用户信息失败:', error); + + // 设置默认用户信息 + this.setData({ + userInfo: { + id: -1, + name: '管理员', + role: Role.ADMIN, + avatarPath: '/images/user-avatar.png' + } + }); + } + }, + + onShow: function() { // 页面显示时刷新数据 this.refreshData(); }, // 初始化数据 - initData() { + initData: function() { // 获取仓库数据 warehouseService.getWarehouses().then(warehouses => { this.setData({ @@ -66,22 +96,78 @@ Page({ }).catch(error => { console.error('获取仓库数据失败:', error); }); - // 刷新数据 this.refreshData(); }, + /** + * 获取完整的头像URL + */ + getFullAvatarUrl(avatarPath: string | undefined): string { + if (!avatarPath) { + return '/images/user-avatar.png'; + } + + // 如果已经是完整URL,直接返回 + if (avatarPath.startsWith('http')) { + return avatarPath; + } + + // 将相对路径转换为完整URL + return `${API_BASE_URL}${avatarPath}`; + }, + + /** + * 获取本地缓存的头像路径 + */ + async getLocalAvatarPath(avatarPath: string | undefined): Promise { + if (!avatarPath) { + return '/images/user-avatar.png'; + } + + try { + // 使用头像缓存获取本地文件路径 + const fullAvatarUrl = this.getFullAvatarUrl(avatarPath); + const localAvatarPath = await avatarCache.getAvatarPath(fullAvatarUrl); + return localAvatarPath; + } catch (error) { + console.error('获取头像缓存失败:', error); + // 如果缓存失败,回退到远程URL + return this.getFullAvatarUrl(avatarPath); + } + }, + + /** + * 处理用户信息,添加完整的头像URL + */ + async processUserInfo(userInfo: any): Promise { + if (!userInfo) { + return null; + } + + const processedUserInfo = { ...userInfo }; + processedUserInfo.fullAvatarUrl = await this.getLocalAvatarPath(userInfo.avatarPath); + + return processedUserInfo; + }, + // 刷新数据 - refreshData() { + refreshData: function() { // 获取订单数据 - 使用正确的getPendingOrders方法 orderService.getPendingOrders().then(orders => { // 获取员工数据 - 使用正确的getDeliveryPersons方法 deliveryService.getDeliveryPersons().then(deliveryPersons => { + // 处理员工数据,添加完整的头像URL + const processedDeliveryPersons = deliveryPersons.map(person => ({ + ...person, + fullAvatarUrl: person.avatarPath ? this.getFullAvatarUrl(person.avatarPath) : '/images/user-avatar.png' + })); + // 更新地图标记点 - const markers = this.generateMarkers(orders, deliveryPersons); + const markers = this.generateMarkers(orders, processedDeliveryPersons); this.setData({ orders, - deliveryPersons, + deliveryPersons: processedDeliveryPersons, markers }); diff --git a/miniprogram/pages/admin/admin.wxml b/miniprogram/pages/admin/admin.wxml index a902cb6..9671a79 100644 --- a/miniprogram/pages/admin/admin.wxml +++ b/miniprogram/pages/admin/admin.wxml @@ -62,7 +62,7 @@ - + {{item.name}} {{item.phone}} @@ -101,7 +101,7 @@ 请选择配送人员: - + {{item.name}} {{getStatusText(item.status)}} diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts index 5293bca..8564f90 100644 --- a/miniprogram/pages/index/index.ts +++ b/miniprogram/pages/index/index.ts @@ -3,6 +3,7 @@ // 引入服务和工具函数 import userService from '../../services/userService'; +import { avatarCache } from '../../utils/avatarCache'; // 引入主页面模块 import { MainPageModule } from './modules/mainPageModule'; @@ -41,7 +42,7 @@ Component({ showSignOutButton: false, showSignInButton: false, showRegisterButton: false, - showAuthButton: true, + showAuthButton: true // mainPageModule: null as MainPageModule | null }, @@ -78,6 +79,20 @@ Component({ methods: { + // 获取完整的头像URL(用于wxml显示) + getFullAvatarUrl(avatarPath: string | undefined): string { + if (!avatarPath) { + return '/images/truck.png'; + } + + // 如果已经是完整URL,直接返回 + if (avatarPath.startsWith('http')) { + return avatarPath; + } + + // 将相对路径转换为完整URL + return `${API_BASE_URL}${avatarPath}`; + }, // 处理货运人员底部弹窗关闭 @@ -169,9 +184,9 @@ Component({ // 本地已有登录状态,此时可以安全获取用户状态(因为有token) const userStatus = await loginModule.determineUserStatus(app.globalData.userInfo); - // 更新页面状态 + // 更新页面状态,使用头像缓存方法 + await this.updateUserInfoWithAvatarCache(app.globalData.userInfo); this.setData({ - userInfo: app.globalData.userInfo, 'authStatus.hasWxCode': true, 'authStatus.userStatus': userStatus }); @@ -191,15 +206,15 @@ Component({ const app = getApp(); // 登录成功,更新全局用户信息 - app.globalData.userInfo = loginResult.userInfo; - app.globalData.isLoggedIn = true; - + app.globalData.userInfo = loginResult.userInfo; + app.globalData.isLoggedIn = true; + // 登录成功后,此时已经有token,可以安全获取用户状态 const userStatus = await loginModule.determineUserStatus(loginResult.userInfo); - // 更新页面状态 + // 更新页面状态,使用头像缓存方法 + await this.updateUserInfoWithAvatarCache(loginResult.userInfo); this.setData({ - userInfo: loginResult.userInfo, 'authStatus.hasWxCode': true, 'authStatus.userStatus': userStatus }); @@ -250,6 +265,62 @@ Component({ console.log('✅ 按钮状态已更新到页面'); }, + + // 处理用户信息更新,包含头像URL预处理 + async updateUserInfoWithAvatarCache(userInfo: any) { + if (!userInfo) { + this.setData({ userInfo: null }); + return; + } + + // 预处理头像URL + const processedUserInfo = { ...userInfo }; + if (userInfo.avatarPath) { + try { + // 使用头像缓存获取本地文件路径 + const fullAvatarUrl = this.getFullAvatarUrl(userInfo.avatarPath); + const localAvatarPath = await avatarCache.getAvatarPath(fullAvatarUrl); + processedUserInfo.fullAvatarUrl = localAvatarPath; + } catch (error) { + console.error('获取用户头像缓存失败:', error); + // 如果缓存失败,使用默认头像 + processedUserInfo.fullAvatarUrl = '/images/truck.png'; + } + } else { + // 如果没有头像路径,使用默认头像 + processedUserInfo.fullAvatarUrl = '/images/truck.png'; + } + + this.setData({ userInfo: processedUserInfo }); + }, + + // 处理货运人员信息更新,包含头像URL预处理 + async updateDeliveryPersonWithAvatarCache(deliveryPerson: any) { + if (!deliveryPerson) { + this.setData({ currentDeliveryPerson: null }); + return; + } + + // 预处理头像URL + const processedDeliveryPerson = { ...deliveryPerson }; + if (deliveryPerson.avatarPath) { + try { + // 使用头像缓存获取本地文件路径,而不是远程URL + const fullAvatarUrl = this.getFullAvatarUrl(deliveryPerson.avatarPath); + const localAvatarPath = await avatarCache.getAvatarPath(fullAvatarUrl); + processedDeliveryPerson.fullAvatarUrl = localAvatarPath; + } catch (error) { + console.error('获取货运人员头像缓存失败:', error); + // 如果缓存失败,回退到远程URL + processedDeliveryPerson.fullAvatarUrl = this.getFullAvatarUrl(deliveryPerson.avatarPath); + } + } else { + // 如果没有头像路径,使用默认头像 + processedDeliveryPerson.fullAvatarUrl = '/images/truck.png'; + } + + this.setData({ currentDeliveryPerson: processedDeliveryPerson }); + }, // 登录成功后统一刷新页面 async refreshPageAfterLogin() { @@ -331,9 +402,11 @@ Component({ if (success) { const app = getApp(); - const userStatus = loginModule.determineUserStatus(app.globalData.userInfo); + const userStatus = await loginModule.determineUserStatus(app.globalData.userInfo); + + // 更新页面状态,使用头像缓存方法 + await this.updateUserInfoWithAvatarCache(app.globalData.userInfo); this.setData({ - userInfo: app.globalData.userInfo, 'authStatus.hasWxCode': true, 'authStatus.userStatus': userStatus }); diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index 0910d40..3def006 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -85,7 +85,7 @@ @@ -236,7 +236,7 @@ - + {{currentDeliveryPerson.name}} diff --git a/miniprogram/pages/index/modules/adminModule.ts b/miniprogram/pages/index/modules/adminModule.ts index b3b9f23..d90421f 100644 --- a/miniprogram/pages/index/modules/adminModule.ts +++ b/miniprogram/pages/index/modules/adminModule.ts @@ -1,13 +1,12 @@ // 管理员模块 - 专门处理管理员相关功能 -import { showToast } from '../../utils/helpers'; + import { DataModule } from './dataModule'; export class AdminModule { - private pageContext: any; + private dataModule: DataModule; - constructor(pageContext: any, dataModule: DataModule) { - this.pageContext = pageContext; + constructor(_pageContext: any, dataModule: DataModule) { this.dataModule = dataModule; } @@ -24,7 +23,7 @@ export class AdminModule { /** * 显示管理员详情面板 */ - private showAdminPanel(admin: any, position: { x: number, y: number }): void { + private showAdminPanel(admin: any, _position: { x: number, y: number }): void { console.log('显示管理员详情面板:', admin); // 设置当前管理员 diff --git a/miniprogram/pages/index/modules/dataModule.ts b/miniprogram/pages/index/modules/dataModule.ts index 93ff035..a9dad55 100644 --- a/miniprogram/pages/index/modules/dataModule.ts +++ b/miniprogram/pages/index/modules/dataModule.ts @@ -1,4 +1,5 @@ import { UserInfo, Marker } from '../../../types'; +import { avatarCache } from '../../../utils/avatarCache'; /** * 页面数据管理模块 @@ -80,16 +81,48 @@ export class DataModule { /** * 更新用户信息 */ - public updateUserInfo(userInfo: UserInfo | null): void { + public async updateUserInfo(userInfo: UserInfo | null): Promise { + if (!userInfo) { + this.pageContext.setData({ + userInfo: null, + isLoggedIn: false + }); + + // 更新按钮显示状态 + if (this.pageContext.updateButtonDisplayStatus) { + this.pageContext.updateButtonDisplayStatus(); + } + return; + } + + // 处理头像缓存 + const processedUserInfo = { ...userInfo }; + if (userInfo.avatarPath) { + try { + // 只有在用户已签到的情况下才使用头像缓存 + const app = getApp(); + const isSignedIn = app.globalData.userInfo && app.globalData.userInfo.status === 'signed_in'; + + if (isSignedIn) { + processedUserInfo.avatarPath = await avatarCache.getAvatarPath(userInfo.avatarPath); + } else { + // 未签到状态,直接使用原始路径 + processedUserInfo.avatarPath = userInfo.avatarPath; + } + } catch (error) { + console.error('处理用户头像缓存失败:', error); + } + } + this.pageContext.setData({ - userInfo, - isLoggedIn: !!userInfo + userInfo: processedUserInfo, + isLoggedIn: true }); // 更新按钮显示状态 - if (this.pageContext.updateButtonDisplayStatus) { - this.pageContext.updateButtonDisplayStatus(); - } + if (this.pageContext.updateButtonDisplayStatus) { + this.pageContext.updateButtonDisplayStatus(); + } } /** @@ -160,8 +193,32 @@ export class DataModule { /** * 设置当前选中货运人员 */ - public setCurrentDeliveryPerson(person: any): void { - this.pageContext.setData({ currentDeliveryPerson: person }); + public async setCurrentDeliveryPerson(person: any): Promise { + if (!person) { + this.pageContext.setData({ currentDeliveryPerson: null }); + return; + } + + // 处理头像缓存 + const processedPerson = { ...person }; + if (person.avatarPath) { + try { + // 只有在用户已签到的情况下才使用头像缓存 + const app = getApp(); + const isSignedIn = app.globalData.userInfo && app.globalData.userInfo.status === 'signed_in'; + + if (isSignedIn) { + processedPerson.avatarPath = await avatarCache.getAvatarPath(person.avatarPath); + } else { + // 未签到状态,直接使用原始路径 + processedPerson.avatarPath = person.avatarPath; + } + } catch (error) { + console.error('处理货运人员头像缓存失败:', error); + } + } + + this.pageContext.setData({ currentDeliveryPerson: processedPerson }); } /** diff --git a/miniprogram/pages/index/modules/deliveryPersonModule.ts b/miniprogram/pages/index/modules/deliveryPersonModule.ts index 4944888..2a68295 100644 --- a/miniprogram/pages/index/modules/deliveryPersonModule.ts +++ b/miniprogram/pages/index/modules/deliveryPersonModule.ts @@ -1,13 +1,12 @@ // 货运人员模块 - 专门处理货运人员相关功能 -import { showToast } from '../../utils/helpers'; + import { DataModule } from './dataModule'; export class DeliveryPersonModule { - private pageContext: any; + private dataModule: DataModule; - constructor(pageContext: any, dataModule: DataModule) { - this.pageContext = pageContext; + constructor(_pageContext: any, dataModule: DataModule) { this.dataModule = dataModule; } @@ -24,7 +23,7 @@ export class DeliveryPersonModule { /** * 显示货运人员详情面板 */ - private showDeliveryPersonPanel(deliveryPerson: any, position: { x: number, y: number }): void { + private showDeliveryPersonPanel(deliveryPerson: any, _position: { x: number, y: number }): void { console.log('显示货运人员详情面板:', deliveryPerson); // 设置当前货运人员 diff --git a/miniprogram/pages/index/modules/employeeModule.ts b/miniprogram/pages/index/modules/employeeModule.ts index 818c17e..fd0fa3d 100644 --- a/miniprogram/pages/index/modules/employeeModule.ts +++ b/miniprogram/pages/index/modules/employeeModule.ts @@ -1,21 +1,20 @@ // 员工模块 - 处理所有员工(管理员和货运人员)的通用功能 -import { showToast } from '../../../utils/helpers'; import { DataModule } from './dataModule'; import employeeService from '../../../services/employeeService'; +import { API_BASE_URL } from '../../../services/apiService'; export class EmployeeModule { - private pageContext: any; + private dataModule: DataModule; - constructor(pageContext: any, dataModule: DataModule) { - this.pageContext = pageContext; + constructor(_pageContext: any, dataModule: DataModule) { this.dataModule = dataModule; } /** * 处理员工标记点点击 - 通用处理逻辑 */ - onEmployeeMarkerClick(employee: any, position: { x: number, y: number }): void { + onEmployeeMarkerClick(employee: any, _position: { x: number, y: number }): void { console.log('员工被点击:', employee); // 设置当前员工 @@ -73,7 +72,31 @@ export class EmployeeModule { try { console.log('开始加载所有员工数据'); const employees = await employeeService.getEmployees(); - console.log('员工数据加载完成:', employees); + console.log('员工数据加载完成,共', employees.length, '名员工'); + + // 打印每个员工的基本信息 + console.log('=== 员工列表详细信息 ==='); + employees.forEach((employee, index) => { + console.log(`员工 ${index + 1}:`); + console.log(` - ID: ${employee.id}`); + console.log(` - 姓名: ${employee.name || '未设置'}`); + console.log(` - 电话: ${employee.phone || '未设置'}`); + console.log(` - 角色: ${employee.role || '未设置'}`); + + // 处理头像路径:使用avatarPath转换为完整URL + let avatarFullUrl = '未设置'; + if (employee.avatarPath) { + avatarFullUrl = `${API_BASE_URL}${employee.avatarPath}`; + console.log(` - 头像路径: ${employee.avatarPath}`); + console.log(` - 完整头像URL: ${avatarFullUrl}`); + } else { + console.log(` - 头像URL: 未设置`); + } + + console.log(` - 头像缩略图: ${employee.avatarThumbnail || '未设置'}`); + console.log('---'); + }); + console.log('=== 员工列表打印完成 ==='); // 这里可以添加员工数据处理逻辑,比如更新到dataModule // this.dataModule.updateEmployees(employees); diff --git a/miniprogram/pages/index/modules/locationModule.ts b/miniprogram/pages/index/modules/locationModule.ts index 9f4a7f6..743dd23 100644 --- a/miniprogram/pages/index/modules/locationModule.ts +++ b/miniprogram/pages/index/modules/locationModule.ts @@ -2,6 +2,8 @@ import { showToast } from '../../../utils/helpers'; import { DataModule } from './dataModule'; import locationTrackingService from '../../../services/locationTrackingService'; +import { avatarCache } from '../../../utils/avatarCache'; +import { API_BASE_URL } from '../../../services/apiService'; // 位置模块接口定义 export interface LocationModule { @@ -39,12 +41,16 @@ export class LocationModule { console.log('📝 [LocationModule] 设置位置更新回调'); // 注册位置更新回调 - locationTrackingService.subscribeToLocationUpdates((onlineUsers) => { + locationTrackingService.subscribeToLocationUpdates(async (onlineUsers) => { console.log('🔔 [LocationModule] 收到位置更新通知'); console.log('👥 [LocationModule] 在线用户数量:', onlineUsers.length); // 调用updateEmployeeMarkers方法更新地图标记 - this.updateEmployeeMarkers(onlineUsers); + try { + await this.updateEmployeeMarkers(onlineUsers); + } catch (error) { + console.error('更新员工标记点失败:', error); + } }); console.log('✅ [LocationModule] 位置更新回调设置完成'); @@ -118,7 +124,7 @@ export class LocationModule { /** * 更新员工标记点 */ - public updateEmployeeMarkers(onlineUsers: any[]): void { + public async updateEmployeeMarkers(onlineUsers: any[]): Promise { console.log('📍 [LocationModule] 开始更新员工标记点'); console.log('👥 [LocationModule] 传入用户数量:', onlineUsers.length); @@ -135,7 +141,7 @@ export class LocationModule { console.log('🗺️ [LocationModule] 当前地图上员工标记点数量:', currentEmployeeMarkers.length); // 为每个在线用户创建标记点 - const newEmployeeMarkers = onlineUsers.map((user, index) => { + const newEmployeeMarkers = await Promise.all(onlineUsers.map(async (user, index) => { console.log(`🔍 [LocationModule] 处理第 ${index + 1} 个用户:`, user.userId || '未知ID'); // 获取用户角色信息 @@ -149,25 +155,33 @@ export class LocationModule { console.log(`📡 [LocationModule] 用户 ${index + 1} 位置: ${latitude}, ${longitude}`); console.log(`👤 [LocationModule] 用户 ${index + 1} 信息: ${user.userName || '未知用户'} (${userRole})`); + // 获取员工图标 + const iconPath = await this.getEmployeeIcon(user.userId, userRole, user); + const employeeMarker = { id: 10000 + user.userId, // 避免ID冲突 type: 'employee', title: user.userName || `员工${user.userId}`, longitude: longitude, latitude: latitude, - iconPath: this.getEmployeeIcon(user.userId, userRole), + iconPath: iconPath, width: 32, height: 32, zIndex: 30, data: { userId: user.userId, role: userRole, - lastUpdateTime: lastUpdateTime + lastUpdateTime: lastUpdateTime, + currentLocation: { + address: user.address || '位置信息获取中...', + longitude: longitude, + latitude: latitude + } } }; return employeeMarker; - }); + })); console.log('📍 [LocationModule] 新生成标记点数量:', newEmployeeMarkers.length); @@ -274,14 +288,59 @@ export class LocationModule { /** * 获取员工图标 */ - private getEmployeeIcon(_userId: number, userRole: string): string { - // 根据用户角色返回不同的图标 + private async getEmployeeIcon(userId: number, userRole: string, userData?: any): Promise { + console.log(`🔍 [LocationModule] 获取用户 ${userId} (${userRole}) 的图标`); + + // 优先使用传入的用户数据中的头像信息 + if (userData) { + // 尝试使用avatarPath(相对路径) + if (userData.avatarPath) { + console.log(`📸 [LocationModule] 使用用户数据中的avatarPath: ${userData.avatarPath}`); + const fullAvatarUrl = `${API_BASE_URL}${userData.avatarPath}`; + return await avatarCache.getAvatarPath(fullAvatarUrl); + } + + // 尝试使用avatarUrl(完整URL) + if (userData.avatarUrl) { + console.log(`📸 [LocationModule] 使用用户数据中的avatarUrl: ${userData.avatarUrl}`); + return await avatarCache.getAvatarPath(userData.avatarUrl); + } + + // 尝试使用avatar(兼容字段) + if (userData.avatar) { + console.log(`📸 [LocationModule] 使用用户数据中的avatar: ${userData.avatar}`); + return await avatarCache.getAvatarPath(userData.avatar); + } + } + + // 其次尝试从全局数据获取当前用户的头像 + const app = getApp(); + if (app.globalData.userInfo && app.globalData.userInfo.id === userId) { + if (app.globalData.userInfo.avatarUrl) { + console.log(`📸 [LocationModule] 使用全局数据中的avatarPath: ${app.globalData.userInfo.avatarPath}`); + const fullAvatarUrl = `${API_BASE_URL}${app.globalData.userInfo.avatarPath}`; + return await avatarCache.getAvatarPath(fullAvatarUrl); + } + } + + // 最后尝试从页面数据获取用户信息 + const userInfo = this.dataModule.getData().userInfo; + if (userInfo && userInfo.id === userId) { + if (userInfo.avatarPath) { + console.log(`📸 [LocationModule] 使用页面数据中的avatarPath: ${userInfo.avatarPath}`); + const fullAvatarUrl = `${API_BASE_URL}${userInfo.avatarPath}`; + return await avatarCache.getAvatarPath(fullAvatarUrl); + } + } + + // 如果所有头像获取都失败,使用默认图标 + console.log(`⚠️ [LocationModule] 用户 ${userId} 没有头像数据,使用默认图标`); if (userRole === 'ADMIN') { - return '/images/crown.png'; // 管理员图标 + return '/images/crown.png'; // 使用现有的皇冠图标作为管理员图标 } else if (userRole === 'DRIVER') { - return '/images/truck.png'; // 司机图标 + return '/images/truck.png'; // 使用现有的卡车图标作为司机图标 } else { - return '/images/truck.png'; // 普通员工也使用货运图标 + return '/images/user-avatar.png'; // 使用现有的用户头像作为普通员工图标 } } } \ No newline at end of file diff --git a/miniprogram/pages/index/modules/loginModule.ts b/miniprogram/pages/index/modules/loginModule.ts index b41357f..c971ca3 100644 --- a/miniprogram/pages/index/modules/loginModule.ts +++ b/miniprogram/pages/index/modules/loginModule.ts @@ -3,7 +3,10 @@ import { showToast } from '../../../utils/helpers'; import { UserInfo } from '../../../types'; import userService from '../../../services/userService'; import locationTrackingService from '../../../services/locationTrackingService'; +import employeeService from '../../../services/employeeService'; +import { avatarCache } from '../../../utils/avatarCache'; import { DataModule } from './dataModule'; +import { API_BASE_URL } from '../../../services/apiService'; export class LoginModule { private dataModule: DataModule; @@ -25,8 +28,8 @@ export class LoginModule { /** * 登录成功后更新页面状态 */ - public updatePageAfterLogin(userInfo: UserInfo): void { - this.dataModule.updateUserInfo(userInfo); + public async updatePageAfterLogin(userInfo: UserInfo): Promise { + await this.dataModule.updateUserInfo(userInfo); } /** @@ -107,7 +110,7 @@ export class LoginModule { const result = await userService.wxLogin(); if (result.success && result.userInfo) { // 登录成功,更新页面状态 - this.updatePageAfterLogin(result.userInfo); + await this.updatePageAfterLogin(result.userInfo); console.log('手动登录成功'); return true; } @@ -297,7 +300,7 @@ export class LoginModule { name: signInResult.employeeInfo.name, phone: signInResult.employeeInfo.phone }; - this.updatePageAfterLogin(app.globalData.userInfo); + await this.updatePageAfterLogin(app.globalData.userInfo); } } @@ -313,15 +316,6 @@ export class LoginModule { } } - // 启动位置追踪服务(WebSocket连接) - try { - // 启动位置追踪服务(建立WebSocket连接) - await locationTrackingService.startTracking(); - console.log('位置追踪服务已启动'); - } catch (trackingError) { - console.warn('启动位置追踪失败,但不影响签到:', trackingError); - } - // 加载业务数据(所有登录用户) try { console.log('用户签到成功,开始加载业务数据'); @@ -350,6 +344,25 @@ export class LoginModule { } } + // 预加载所有必要的头像并等待完成 + try { + console.log('开始预加载所有必要的头像'); + await this.preloadAllAvatars(); + console.log('所有头像预加载完成'); + } catch (avatarError) { + console.warn('头像预加载失败,但不影响签到:', avatarError); + } + + // 启动位置追踪服务(WebSocket连接)- 在所有必要数据加载和头像预加载完成后启动 + try { + console.log('所有必要数据加载和头像预加载完成,开始启动位置追踪服务'); + // 启动位置追踪服务(建立WebSocket连接) + await locationTrackingService.startTracking(); + console.log('位置追踪服务已启动'); + } catch (trackingError) { + console.warn('启动位置追踪失败,但不影响签到:', trackingError); + } + return true; } else { console.warn('签到失败:', signInResult.message); @@ -510,4 +523,59 @@ export class LoginModule { return null; } } + + /** + * 预加载所有必要的头像 + */ + private async preloadAllAvatars(): Promise { + console.log('🔄 [LoginModule] 开始收集需要预加载的头像URL'); + + const avatarUrls: string[] = []; + + // 1. 当前用户的头像 + const app = getApp(); + if (app.globalData.userInfo && app.globalData.userInfo.avatarPath) { + const fullAvatarUrl = `${API_BASE_URL}${app.globalData.userInfo.avatarPath}`; + avatarUrls.push(fullAvatarUrl); + console.log(`👤 [LoginModule] 添加当前用户头像: ${fullAvatarUrl}`); + } + + // 2. 员工列表中的头像(如果是管理员) + if (app.globalData.userInfo && app.globalData.userInfo.role === 'ADMIN') { + try { + // 获取员工数据 + const mainPageModule = this.pageContext.data.mainPageModule; + if (mainPageModule && mainPageModule.getEmployeeModule) { + // 直接调用员工服务获取员工数据 + const employees = await employeeService.getEmployees(); + + console.log(`📋 [LoginModule] 获取到 ${employees.length} 名员工数据`); + + // 处理员工头像路径 + employees.forEach((employee, index) => { + if (employee.avatarPath) { + // 将相对路径转换为完整URL + const fullAvatarUrl = `${API_BASE_URL}${employee.avatarPath}`; + avatarUrls.push(fullAvatarUrl); + console.log(`👥 [LoginModule] 添加员工 ${index + 1} 头像: ${fullAvatarUrl}`); + } else { + console.log(`ℹ️ [LoginModule] 员工 ${index + 1} 没有设置头像`); + } + }); + } + } catch (error) { + console.warn('获取员工数据失败,跳过员工头像预加载:', error); + } + } + + console.log(`📊 [LoginModule] 总共需要预加载 ${avatarUrls.length} 个头像`); + + if (avatarUrls.length > 0) { + // 等待所有头像预加载完成 + await avatarCache.preloadAvatarsAndWait(avatarUrls); + console.log('✅ [LoginModule] 所有头像预加载完成'); + } else { + console.log('ℹ️ [LoginModule] 没有需要预加载的头像'); + } + } } \ No newline at end of file diff --git a/miniprogram/pages/staff/admin-dashboard.ts b/miniprogram/pages/staff/admin-dashboard.ts index 64d5731..8b186ee 100644 --- a/miniprogram/pages/staff/admin-dashboard.ts +++ b/miniprogram/pages/staff/admin-dashboard.ts @@ -1,6 +1,9 @@ // 管理员界面 - 全屏管理面板 import { UserInfo } from '../../types'; +import { Role } from '../../utils/roleUtils'; import userService from '../../services/userService'; +import statisticsService from '../../services/statisticsService'; +import { API_BASE_URL } from '../../services/apiService'; Page({ data: { @@ -60,7 +63,7 @@ Page({ currentTime: '' }, - onLoad() { + onLoad: function() { this.getUserInfo(); this.loadStatistics(); this.updateCurrentTime(); @@ -70,49 +73,143 @@ Page({ }, 1000); }, - onShow() { + onShow: function() { this.loadStatistics(); this.updateCurrentTime(); }, + /** + * 获取完整的头像URL + * @param avatarPath 头像相对路径 + * @returns 完整的头像URL + */ + getFullAvatarUrl: function(avatarPath: string | undefined): string { + if (!avatarPath) { + return '/images/user-avatar.png'; + } + + // 如果已经是完整URL,直接返回 + if (avatarPath.startsWith('http')) { + return avatarPath; + } + + // 将相对路径转换为完整URL + return `${API_BASE_URL}${avatarPath}`; + }, + + /** + * 处理用户信息,预先准备好完整的头像URL + * @param userInfo 原始用户信息 + * @returns 处理后的用户信息 + */ + processUserInfo: function(userInfo: any): any { + if (!userInfo) { + return { + id: 0, + role: Role.ADMIN, + name: '管理员', + fullAvatarUrl: '/images/user-avatar.png' + }; + } + + // 预先准备好完整的头像URL + const processedUserInfo = { ...userInfo }; + if (userInfo.avatarPath) { + processedUserInfo.fullAvatarUrl = this.getFullAvatarUrl(userInfo.avatarPath); + } else { + processedUserInfo.fullAvatarUrl = '/images/user-avatar.png'; + } + + return processedUserInfo; + }, + /** * 获取用户信息 */ - getUserInfo() { - const app = getApp(); - if (app.globalData.userInfo) { - const userInfo = app.globalData.userInfo; - this.setData({ userInfo }); + getUserInfo: async function() { + try { + const app = getApp(); - // 验证是否为管理员 - if (userInfo.role !== 'ADMIN') { - wx.showToast({ - title: '无权限访问', - icon: 'none' - }); - wx.navigateBack(); + // 如果全局数据中没有用户信息,尝试从用户服务获取 + if (!app.globalData.userInfo) { + const userInfo = await userService.getUserInfo(); + if (userInfo) { + app.globalData.userInfo = userInfo; + } } + + if (app.globalData.userInfo) { + const userInfo = this.processUserInfo(app.globalData.userInfo); + this.setData({ userInfo }); + + // 验证是否为管理员 + if (userInfo.role !== Role.ADMIN) { + wx.showToast({ + title: '无权限访问', + icon: 'none' + }); + wx.navigateBack(); + } + } else { + // 如果仍然没有用户信息,显示默认信息 + this.setData({ + userInfo: this.processUserInfo(null) + }); + } + } catch (error) { + console.error('获取用户信息失败:', error); + // 出错时显示默认信息 + this.setData({ + userInfo: this.processUserInfo(null) + }); } }, /** * 加载统计数据 */ - loadStatistics() { - // 模拟加载统计数据 - this.setData({ - stats: { - employeeCount: 25, - orderCount: 156, - warehouseCount: 8 - } - }); + loadStatistics: async function() { + try { + wx.showLoading({ + title: '加载数据中...' + }); + + // 使用统计服务获取真实数据 + const stats = await statisticsService.getSystemStats(); + + this.setData({ + stats: { + employeeCount: stats.employeeCount, + orderCount: stats.orderCount, + warehouseCount: stats.warehouseCount + } + }); + + wx.hideLoading(); + } catch (error) { + console.error('加载统计数据失败:', error); + wx.hideLoading(); + + // 出错时使用默认值 + this.setData({ + stats: { + employeeCount: 0, + orderCount: 0, + warehouseCount: 0 + } + }); + + wx.showToast({ + title: '数据加载失败', + icon: 'none' + }); + } }, /** * 菜单项点击事件 */ - onMenuItemTap(e: any) { + onMenuItemTap: function(e: any) { const itemId = e.currentTarget.dataset.id; switch (itemId) { @@ -152,14 +249,14 @@ Page({ /** * 返回首页 */ - goBack() { + goBack: function() { wx.navigateBack(); }, /** * 刷新数据 */ - onRefresh() { + onRefresh: function() { this.loadStatistics(); this.updateCurrentTime(); wx.showToast({ @@ -171,7 +268,7 @@ Page({ /** * 更新当前时间 */ - updateCurrentTime() { + updateCurrentTime: function() { const now = new Date(); const timeString = now.toLocaleString('zh-CN', { year: 'numeric', @@ -189,7 +286,7 @@ Page({ /** * 解绑微信处理 */ - async onUnbindWechat() { + onUnbindWechat: async function() { try { // 确认对话框 wx.showModal({ diff --git a/miniprogram/pages/staff/admin-dashboard.wxml b/miniprogram/pages/staff/admin-dashboard.wxml index 0ebe8e0..5258cd1 100644 --- a/miniprogram/pages/staff/admin-dashboard.wxml +++ b/miniprogram/pages/staff/admin-dashboard.wxml @@ -7,10 +7,14 @@ - 👑 + + + + {{userInfo && userInfo.name ? userInfo.name.charAt(0) : '管'}} + - {{userInfo.name || '管理员'}} - 系统管理员 + {{userInfo && userInfo.name ? userInfo.name : '管理员'}} + {{userInfo && userInfo.role ? (userInfo.role === 'ADMIN' ? '系统管理员' : userInfo.role === 'MANAGER' ? '经理' : '员工') : '系统管理员'}} diff --git a/miniprogram/pages/staff/admin-dashboard.wxss b/miniprogram/pages/staff/admin-dashboard.wxss index a024752..c450930 100644 --- a/miniprogram/pages/staff/admin-dashboard.wxss +++ b/miniprogram/pages/staff/admin-dashboard.wxss @@ -58,6 +58,23 @@ border-radius: 50%; margin-right: 20rpx; border: 3rpx solid #667eea; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: #f0f0f0; +} + +.avatar-image { + width: 100%; + height: 100%; + border-radius: 50%; +} + +.avatar-placeholder { + font-size: 32rpx; + font-weight: bold; + color: #667eea; } .admin-info { diff --git a/miniprogram/pages/staff/employee-management.ts b/miniprogram/pages/staff/employee-management.ts index 8cefb7b..0fbe58a 100644 --- a/miniprogram/pages/staff/employee-management.ts +++ b/miniprogram/pages/staff/employee-management.ts @@ -2,6 +2,8 @@ import { EmployeeInfo } from '../../types'; import employeeService from '../../services/employeeService'; import { Role, getRoleOptions } from '../../utils/roleUtils'; +import { avatarCache } from '../../utils/avatarCache'; +import { API_BASE_URL } from '../../services/apiService'; Page({ data: { @@ -10,7 +12,7 @@ Page({ filteredEmployees: [] as EmployeeInfo[], // 页面状态 - currentTab: 'list', // list: 列表页, add: 添加页 + currentTab: 'list', // list: 列表页, add: 添加页, edit: 编辑页 loading: false, // 添加员工表单数据 @@ -20,6 +22,15 @@ Page({ role: Role.DELIVERY_PERSON }, + // 编辑员工表单数据 + editForm: { + id: 0, + name: '', + phone: '', + role: Role.DELIVERY_PERSON, + avatarUrl: '' + }, + // 错误信息 errorMessage: '', successMessage: '', @@ -31,7 +42,10 @@ Page({ roleOptions: getRoleOptions(), // 用户信息 - userInfo: null as any + userInfo: null as any, + + // 临时头像路径(用于头像上传) + tempAvatarPath: '' }, onLoad() { @@ -40,7 +54,10 @@ Page({ }, onShow() { - this.loadEmployees(); + // 只在页面显示时检查是否需要刷新数据,避免频繁请求 + if (this.data.employees.length === 0) { + this.loadEmployees(); + } }, /** @@ -63,6 +80,45 @@ Page({ } }, + /** + * 获取完整的头像URL + * @param avatarPath 头像相对路径 + * @returns 完整的头像URL + */ + getFullAvatarUrl(avatarPath: string | undefined): string { + if (!avatarPath) { + return '/images/user-avatar.png'; + } + + // 如果已经是完整URL,直接返回 + if (avatarPath.startsWith('http')) { + return avatarPath; + } + + // 将相对路径转换为完整URL + return `${API_BASE_URL}${avatarPath}`; + }, + + /** + * 获取本地缓存的头像路径 + */ + async getLocalAvatarPath(avatarPath: string | undefined): Promise { + if (!avatarPath) { + return '/images/user-avatar.png'; + } + + try { + // 使用头像缓存获取本地文件路径 + const fullAvatarUrl = this.getFullAvatarUrl(avatarPath); + const localAvatarPath = await avatarCache.getAvatarPath(fullAvatarUrl); + return localAvatarPath; + } catch (error) { + console.error('获取头像缓存失败:', error); + // 如果缓存失败,回退到远程URL + return this.getFullAvatarUrl(avatarPath); + } + }, + /** * 加载员工列表 */ @@ -75,11 +131,24 @@ Page({ try { const employees = await employeeService.getEmployees(); + + // 为每个员工获取本地缓存的头像路径 + const employeesWithAvatars = await Promise.all( + employees.map(async (employee) => { + const localAvatarPath = await this.getLocalAvatarPath(employee.avatarPath); + + return { + ...employee, + fullAvatarUrl: localAvatarPath + }; + }) + ); + // 获取过滤后的员工列表 - const filteredEmployees = this.getFilteredEmployees(employees); + const filteredEmployees = this.getFilteredEmployees(employeesWithAvatars); this.setData({ - employees, + employees: employeesWithAvatars, filteredEmployees, loading: false }); @@ -103,7 +172,8 @@ Page({ successMessage: '' }); - if (tab === 'list') { + // 只在切换到列表页且没有数据时才加载,避免频繁请求 + if (tab === 'list' && this.data.employees.length === 0) { this.loadEmployees(); } }, @@ -215,6 +285,220 @@ Page({ } }, + /** + * 编辑员工 + */ + editEmployee(e: any) { + const employeeId = e.currentTarget.dataset.id; + const employee = this.data.employees.find(emp => emp.id === employeeId); + + if (employee) { + this.setData({ + editForm: { + id: employee.id || 0, + name: employee.name || '', + phone: employee.phone || '', + role: employee.role || Role.DELIVERY_PERSON, + avatarUrl: '' + }, + currentTab: 'edit' + }); + } + }, + + /** + * 提交编辑员工表单 + */ + async submitEditForm() { + if (!this.validateEditForm()) { + return; + } + + this.setData({ + loading: true, + errorMessage: '', + successMessage: '' + }); + + try { + // 调用更新员工的API方法 + await employeeService.updateEmployee(this.data.editForm.id, { + name: this.data.editForm.name, + phone: this.data.editForm.phone, + role: this.data.editForm.role + }); + + // 如果有新头像,上传头像 + if (this.data.tempAvatarPath) { + try { + const result = await employeeService.uploadAvatar(this.data.editForm.id, this.data.tempAvatarPath); + if (result.success && result.avatarUrl) { + // 更新头像URL + this.setData({ + 'editForm.avatarUrl': result.avatarUrl + }); + } + } catch (avatarError) { + console.error('上传头像失败:', avatarError); + wx.showToast({ + title: '头像上传失败', + icon: 'error' + }); + } + } + + this.setData({ + loading: false, + successMessage: '员工更新成功', + currentTab: 'list', + tempAvatarPath: '' + }); + + // 重新加载员工列表 + this.loadEmployees(); + + } catch (error) { + console.error('更新员工失败:', error); + this.setData({ + loading: false, + errorMessage: '更新员工失败,请重试' + }); + } + }, + + /** + * 选择头像 + */ + chooseAvatar() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['album'], + maxDuration: 30, + camera: 'back', + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFilePath = res.tempFiles[0].tempFilePath; + this.setData({ + 'editForm.avatarUrl': tempFilePath, + tempAvatarPath: tempFilePath + }); + } + }, + fail: (err) => { + console.error('选择头像失败:', err); + wx.showToast({ + title: '选择头像失败', + icon: 'error' + }); + } + }); + }, + + /** + * 拍照 + */ + takePhoto() { + wx.chooseMedia({ + count: 1, + mediaType: ['image'], + sourceType: ['camera'], + maxDuration: 30, + camera: 'back', + success: (res) => { + if (res.tempFiles && res.tempFiles.length > 0) { + const tempFilePath = res.tempFiles[0].tempFilePath; + this.setData({ + 'editForm.avatarUrl': tempFilePath, + tempAvatarPath: tempFilePath + }); + } + }, + fail: (err) => { + console.error('拍照失败:', err); + wx.showToast({ + title: '拍照失败', + icon: 'error' + }); + } + }); + }, + + /** + * 验证编辑表单 + */ + validateEditForm(): boolean { + const { name, phone, role } = this.data.editForm; + + if (!name.trim()) { + this.setData({ + errorMessage: '请输入员工姓名' + }); + return false; + } + + if (!phone.trim()) { + this.setData({ + errorMessage: '请输入员工工号' + }); + return false; + } + + // 简单的工号格式验证 + const phoneRegex = /^\d+$/; + if (!phoneRegex.test(phone)) { + this.setData({ + errorMessage: '工号只能包含数字' + }); + return false; + } + + if (!role) { + this.setData({ + errorMessage: '请选择员工角色' + }); + return false; + } + + return true; + }, + + /** + * 取消编辑 + */ + cancelEdit() { + this.setData({ + currentTab: 'list', + errorMessage: '', + successMessage: '' + }); + }, + + /** + * 编辑表单输入处理 + */ + onEditFormInput(e: any) { + const { field } = e.currentTarget.dataset; + const value = e.detail.value; + + this.setData({ + [`editForm.${field}`]: value, + errorMessage: '', + successMessage: '' + }); + }, + + /** + * 处理编辑角色选择 + */ + onEditRoleChange(e: any) { + const index = e.detail.value; + const selectedRole = this.data.roleOptions[index].value; + this.setData({ + 'editForm.role': selectedRole + }); + }, + /** * 删除员工 */ @@ -304,19 +588,8 @@ Page({ 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; - }); + // 显示所有员工,包括当前用户 + let filteredEmployees = employees; // 更严格的搜索关键词检查 if (!searchKeyword || typeof searchKeyword !== 'string' || !searchKeyword.trim()) { diff --git a/miniprogram/pages/staff/employee-management.wxml b/miniprogram/pages/staff/employee-management.wxml index 99a9c86..5c73512 100644 --- a/miniprogram/pages/staff/employee-management.wxml +++ b/miniprogram/pages/staff/employee-management.wxml @@ -30,6 +30,13 @@ > 添加员工 + + 编辑员工 + @@ -70,21 +77,32 @@ - {{item.name.charAt(0)}} + + + {{item.name.charAt(0)}} + {{item.name}} - - {{item.role === 'ADMIN' ? '👑' : '🚚'}} - {{getRoleText(item.role)}} - + + 👤 + {{item.role === 'ADMIN' ? '管理员' : '配送员'}} + - 工号: {{item.phone}} + {{item.phone}} ID: {{item.id}} + + + + + + + + 员工姓名 + + + + + 员工工号 + + + + + 员工角色 + + + + {{editForm.role === 'ADMIN' ? '管理员' : editForm.role === 'DELIVERY_PERSON' ? '配送员' : '请选择角色'}} + + + + + + + + + 提示:修改员工信息后,用户可以使用新的信息进行绑定 + + + + + + + + + \ No newline at end of file diff --git a/miniprogram/pages/staff/employee-management.wxss b/miniprogram/pages/staff/employee-management.wxss index 79eb5e5..44bb803 100644 --- a/miniprogram/pages/staff/employee-management.wxss +++ b/miniprogram/pages/staff/employee-management.wxss @@ -189,19 +189,22 @@ /* 员工列表样式 */ .employee-list { flex: 1; - padding: 0 40rpx; + padding: 0 30rpx; + box-sizing: border-box; } .employee-item { background: white; - padding: 30rpx; - margin: 20rpx 0; + padding: 25rpx; + margin: 15rpx 0; border-radius: 12rpx; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); display: flex; - justify-content: space-between; align-items: center; transition: transform 0.3s ease; + box-sizing: border-box; + max-width: 100%; + overflow: hidden; } .employee-item:active { @@ -212,45 +215,77 @@ display: flex; align-items: center; flex: 1; + min-width: 0; } .employee-avatar { - width: 80rpx; - height: 80rpx; + width: 70rpx; + height: 70rpx; border-radius: 50%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; align-items: center; justify-content: center; - margin-right: 25rpx; + margin-right: 20rpx; color: white; - font-size: 32rpx; + font-size: 28rpx; font-weight: bold; + flex-shrink: 0; + overflow: hidden; + position: relative; +} + +.employee-avatar .avatar-image { + width: 100%; + height: 100%; + object-fit: cover; +} + +.employee-avatar .avatar-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.employee-avatar .avatar-text { + font-size: 28rpx; + font-weight: bold; + color: white; } .employee-details { flex: 1; + min-width: 0; } .employee-name-row { display: flex; - justify-content: space-between; align-items: center; - margin-bottom: 10rpx; + margin-bottom: 8rpx; + gap: 15rpx; } .employee-name { - font-size: 30rpx; + font-size: 28rpx; font-weight: bold; color: #333; + flex-shrink: 0; + max-width: 200rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .employee-role { - font-size: 24rpx; + font-size: 22rpx; background: #f0f0f0; - padding: 8rpx 16rpx; - border-radius: 20rpx; + padding: 6rpx 12rpx; + border-radius: 16rpx; color: #666; + flex-shrink: 0; } .employee-role.admin-role { @@ -259,23 +294,30 @@ } .role-icon { - margin-right: 5rpx; + margin-right: 4rpx; } .employee-phone { display: block; - font-size: 26rpx; + font-size: 24rpx; color: #666; - margin-bottom: 5rpx; + margin-bottom: 4rpx; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .employee-id { - font-size: 22rpx; + font-size: 20rpx; color: #999; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .employee-actions { - margin-left: 20rpx; + margin-left: 15rpx; + flex-shrink: 0; } .delete-btn { @@ -392,4 +434,80 @@ .submit-btn:disabled { opacity: 0.6; +} + +/* 头像上传样式 */ +.avatar-upload-container { + display: flex; + align-items: center; + gap: 30rpx; +} + +.avatar-preview { + width: 120rpx; + height: 120rpx; + border-radius: 50%; + overflow: hidden; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + border: 2rpx solid #ddd; +} + +.avatar-image { + width: 100%; + height: 100%; +} + +.avatar-placeholder { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.avatar-text { + font-size: 48rpx; + font-weight: bold; + color: white; +} + +.avatar-actions { + display: flex; + flex-direction: column; + gap: 15rpx; +} + +.avatar-btn { + background: #f8f9fa; + border: 1rpx solid #ddd; + color: #333; + padding: 15rpx 20rpx; + border-radius: 8rpx; + font-size: 24rpx; +} + +.avatar-btn:active { + background: #e9ecef; +} + +/* 编辑页面样式 */ +.edit-container { + flex: 1; + padding: 40rpx; + background: white; +} + +.cancel-btn { + width: 100%; + background: #f8f9fa; + color: #666; + border: 1rpx solid #ddd; + padding: 30rpx; + border-radius: 12rpx; + font-size: 28rpx; + margin-top: 20rpx; } \ No newline at end of file diff --git a/miniprogram/pages/staff/profile.json b/miniprogram/pages/staff/profile.json new file mode 100644 index 0000000..6df1242 --- /dev/null +++ b/miniprogram/pages/staff/profile.json @@ -0,0 +1,7 @@ +{ + "usingComponents": {}, + "navigationBarTitleText": "个人信息", + "navigationStyle": "custom", + "enablePullDownRefresh": true, + "backgroundColor": "#f5f5f5" +} \ No newline at end of file diff --git a/miniprogram/pages/staff/profile.ts b/miniprogram/pages/staff/profile.ts new file mode 100644 index 0000000..457d228 --- /dev/null +++ b/miniprogram/pages/staff/profile.ts @@ -0,0 +1,218 @@ +// 个人信息页面 +import { UserInfo } from '../../types'; +import userService from '../../services/userService'; +import statisticsService from '../../services/statisticsService'; + +Page({ + data: { + userInfo: null as UserInfo | null, + // 个人统计数据 + personalStats: { + totalOrders: 0, // 总订单数 + completedOrders: 0, // 已完成订单数 + pendingOrders: 0, // 待处理订单数 + deliveryRate: 0, // 配送成功率 + averageTime: 0 // 平均配送时间 + }, + // 系统统计数据 + systemStats: { + employeeCount: 0, // 员工总数 + orderCount: 0, // 订单总数 + warehouseCount: 0 // 仓库总数 + }, + loading: true + }, + + onLoad() { + this.loadUserInfo(); + this.loadStatistics(); + }, + + onShow() { + // 页面显示时重新加载数据 + this.loadUserInfo(); + this.loadStatistics(); + }, + + /** + * 加载用户信息 + */ + async loadUserInfo() { + try { + const app = getApp(); + if (app.globalData.userInfo) { + this.setData({ + userInfo: app.globalData.userInfo + }); + } else { + // 如果没有全局用户信息,尝试从API获取 + const userInfo = await userService.getUserInfo(); + this.setData({ userInfo }); + } + } catch (error) { + console.error('加载用户信息失败:', error); + wx.showToast({ + title: '加载用户信息失败', + icon: 'none' + }); + } + }, + + /** + * 加载统计数据 + */ + async loadStatistics() { + this.setData({ loading: true }); + + try { + wx.showLoading({ + title: '加载统计数据...' + }); + + // 强制刷新统计数据,不使用缓存 + const systemStats = await statisticsService.getSystemStats(true); + + // 获取个人统计数据(这里需要根据用户ID获取个人统计数据) + const personalStats = await this.getPersonalStats(); + + this.setData({ + systemStats: { + employeeCount: systemStats.employeeCount, + orderCount: systemStats.orderCount, + warehouseCount: systemStats.warehouseCount + }, + personalStats, + loading: false + }); + + wx.hideLoading(); + } catch (error) { + console.error('加载统计数据失败:', error); + this.setData({ loading: false }); + wx.hideLoading(); + + wx.showToast({ + title: '加载统计数据失败', + icon: 'none' + }); + } + }, + + /** + * 获取个人统计数据 + */ + async getPersonalStats() { + try { + // 这里应该调用个人统计API + // 暂时使用模拟数据 + return { + totalOrders: 156, + completedOrders: 142, + pendingOrders: 14, + deliveryRate: 91.0, + averageTime: 45 + }; + } catch (error) { + console.error('获取个人统计数据失败:', error); + // 返回默认值 + return { + totalOrders: 0, + completedOrders: 0, + pendingOrders: 0, + deliveryRate: 0, + averageTime: 0 + }; + } + }, + + /** + * 刷新数据 + */ + onRefresh() { + this.loadStatistics(); + }, + + /** + * 返回上一页 + */ + goBack() { + wx.navigateBack(); + }, + + /** + * 编辑个人信息 + */ + onEditProfile() { + wx.showToast({ + title: '编辑功能开发中', + icon: 'none' + }); + }, + + /** + * 查看历史订单 + */ + onViewHistory() { + wx.navigateTo({ + url: '/pages/staff/order-management' + }); + }, + + /** + * 联系客服 + */ + onContactSupport() { + wx.makePhoneCall({ + phoneNumber: '400-123-4567' + }); + }, + + /** + * 退出登录 + */ + onLogout() { + wx.showModal({ + title: '确认退出', + content: '确定要退出登录吗?', + success: (res) => { + if (res.confirm) { + this.performLogout(); + } + } + }); + }, + + /** + * 执行退出登录操作 + */ + async performLogout() { + try { + wx.showLoading({ + title: '退出中...' + }); + + // 调用用户服务的退出登录方法 + const userService = require('../../services/userService').default; + await userService.logout(); + + // 清除全局用户信息 + const app = getApp(); + app.globalData.userInfo = null; + + wx.hideLoading(); + + // 跳转到首页 + wx.reLaunch({ + url: '/pages/index/index' + }); + + } catch (error) { + console.error('退出登录失败:', error); + wx.hideLoading(); + wx.showToast({ + title: '退出登录失败', + icon: 'none' + }); + } + } +}); \ No newline at end of file diff --git a/miniprogram/pages/staff/profile.wxml b/miniprogram/pages/staff/profile.wxml new file mode 100644 index 0000000..0e708ea --- /dev/null +++ b/miniprogram/pages/staff/profile.wxml @@ -0,0 +1,120 @@ + + + + + + + + + + 👤 + + {{userInfo.name || '用户'}} + {{userInfo.role === 'ADMIN' ? '管理员' : '员工'}} + + + + + + + + + + + 正在加载数据... + + + + + + + + 个人信息 + + + + + 姓名 + {{userInfo.name || '未设置'}} + + + 手机号 + {{userInfo.phone || '未设置'}} + + + 角色 + {{userInfo.role === 'ADMIN' ? '管理员' : '员工'}} + + + 用户ID + {{userInfo.id || '未知'}} + + + + + + + + 个人工作统计 + + + + + 📦 + {{personalStats.totalOrders}} + 总订单数 + + + + {{personalStats.completedOrders}} + 已完成 + + + + {{personalStats.pendingOrders}} + 待处理 + + + 📊 + {{personalStats.deliveryRate}}% + 成功率 + + + ⏱️ + {{personalStats.averageTime}}分钟 + 平均时间 + + + + + + + + 系统概览 + + + + 👥 + {{systemStats.employeeCount}} + 员工总数 + + + 📦 + {{systemStats.orderCount}} + 订单总数 + + + 🏭 + {{systemStats.warehouseCount}} + 仓库数量 + + + + + + + + + + + \ No newline at end of file diff --git a/miniprogram/pages/staff/profile.wxss b/miniprogram/pages/staff/profile.wxss new file mode 100644 index 0000000..678b208 --- /dev/null +++ b/miniprogram/pages/staff/profile.wxss @@ -0,0 +1,288 @@ +/* 个人信息页面样式 */ +.profile-container { + min-height: 100vh; + background: #f5f5f5; +} + +/* 顶部导航栏样式 */ +.profile-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 40rpx 30rpx; + background: white; + border-bottom: 1rpx solid #e0e0e0; +} + +.header-left { + display: flex; + align-items: center; + gap: 20rpx; +} + +.back-icon { + width: 60rpx; + height: 60rpx; + border-radius: 50%; + background: #f0f0f0; + display: flex; + align-items: center; + justify-content: center; + font-size: 36rpx; + color: #333; +} + +.back-arrow { + font-weight: bold; +} + +.profile-avatar { + width: 80rpx; + height: 80rpx; + border-radius: 50%; + background: #3498db; + display: flex; + align-items: center; + justify-content: center; + font-size: 40rpx; + color: white; +} + +.profile-info { + display: flex; + flex-direction: column; +} + +.profile-name { + font-size: 32rpx; + font-weight: bold; + color: #333; + margin-bottom: 5rpx; +} + +.profile-role { + font-size: 24rpx; + color: #666; + background: #f0f0f0; + padding: 4rpx 12rpx; + border-radius: 12rpx; + align-self: flex-start; +} + +.header-right { + display: flex; + gap: 20rpx; +} + +.refresh-btn { + padding: 15rpx 25rpx; + border: none; + border-radius: 8rpx; + font-size: 26rpx; + background: #3498db; + color: white; + cursor: pointer; + transition: all 0.3s ease; +} + +.refresh-btn:active { + transform: translateY(1rpx); +} + +/* 加载状态样式 */ +.loading-container { + 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 #3498db; + 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; +} + +/* 主内容区域样式 */ +.profile-content { + padding: 30rpx; +} + +/* 卡片通用样式 */ +.info-card, .stats-card { + background: white; + border-radius: 15rpx; + padding: 30rpx; + margin-bottom: 30rpx; + box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 30rpx; +} + +.card-title { + font-size: 32rpx; + font-weight: bold; + color: #333; +} + +.edit-btn, .history-btn { + padding: 10rpx 20rpx; + border: 1rpx solid #3498db; + border-radius: 6rpx; + font-size: 24rpx; + background: transparent; + color: #3498db; + cursor: pointer; +} + +.edit-btn:active, .history-btn:active { + background: #3498db; + color: white; +} + +/* 信息网格样式 */ +.info-grid { + display: grid; + grid-template-columns: 1fr; + gap: 20rpx; +} + +.info-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15rpx 0; + border-bottom: 1rpx solid #f0f0f0; +} + +.info-item:last-child { + border-bottom: none; +} + +.info-label { + font-size: 28rpx; + color: #666; +} + +.info-value { + font-size: 28rpx; + color: #333; + font-weight: 500; +} + +/* 统计网格样式 */ +.stats-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20rpx; +} + +.stat-item { + background: #f8f9fa; + padding: 25rpx; + border-radius: 10rpx; + text-align: center; + transition: transform 0.3s ease; +} + +.stat-item:active { + transform: translateY(-2rpx); +} + +.stat-icon { + font-size: 40rpx; + margin-bottom: 10rpx; +} + +.employee-icon { + color: #3498db; +} + +.order-icon { + color: #e74c3c; +} + +.warehouse-icon { + color: #f39c12; +} + +.stat-value { + display: block; + font-size: 32rpx; + font-weight: bold; + color: #333; + margin-bottom: 5rpx; +} + +.stat-label { + font-size: 24rpx; + color: #666; +} + +/* 功能按钮区域样式 */ +.action-buttons { + display: flex; + flex-direction: column; + gap: 20rpx; + margin-top: 40rpx; +} + +.action-btn { + padding: 25rpx; + border: none; + border-radius: 10rpx; + font-size: 30rpx; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; +} + +.contact-btn { + background: #27ae60; + color: white; +} + +.logout-btn { + background: #e74c3c; + color: white; +} + +.action-btn:active { + transform: translateY(1rpx); + opacity: 0.9; +} + +/* 响应式调整 */ +@media (max-width: 750rpx) { + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .profile-header { + padding: 30rpx 20rpx; + } + + .profile-content { + padding: 20rpx; + } +} \ No newline at end of file diff --git a/miniprogram/pages/staff/warehouse-management.ts b/miniprogram/pages/staff/warehouse-management.ts index b5d50be..274060a 100644 --- a/miniprogram/pages/staff/warehouse-management.ts +++ b/miniprogram/pages/staff/warehouse-management.ts @@ -10,7 +10,7 @@ Page({ filteredWarehouses: [] as WarehouseInfo[], // 页面状态 - currentTab: 'list', // list: 列表页, add: 添加页 + currentTab: 'list', // list: 列表页, add: 添加页, edit: 编辑页 loading: false, // 添加仓库表单数据 @@ -25,6 +25,20 @@ Page({ latitude: 24.880095 }, + // 编辑仓库表单数据 + editForm: { + id: 0, + name: '', + address: '', + contact: '', + phone: '', + description: '', + capacity: 500, + longitude: 102.833722, + latitude: 24.880095, + status: 'open' as 'open' | 'closed' | 'maintenance' + }, + // 错误信息 errorMessage: '', successMessage: '', @@ -41,8 +55,10 @@ Page({ }, onShow() { - // 页面显示时刷新数据 - this.loadWarehouses(); + // 只在页面显示时检查是否需要刷新数据,避免频繁请求 + if (this.data.warehouses.length === 0) { + this.loadWarehouses(); + } }, // 加载仓库列表 @@ -145,6 +161,51 @@ Page({ }); }, + // 经度输入处理 + onLongitudeInput(e: any) { + const value = parseFloat(e.detail.value) || 0; + this.setData({ + 'addForm.longitude': value + }); + }, + + // 纬度输入处理 + onLatitudeInput(e: any) { + const value = parseFloat(e.detail.value) || 0; + this.setData({ + 'addForm.latitude': value + }); + }, + + // 地图选点功能 + pickLocationFromMap() { + console.log('开始地图选点...'); + wx.chooseLocation({ + success: (res) => { + console.log('地图选点成功:', res); + if (res.longitude && res.latitude) { + console.log('更新表单数据 - 经度:', res.longitude, '纬度:', res.latitude); + this.setData({ + 'addForm.longitude': res.longitude, + 'addForm.latitude': res.latitude + }); + console.log('表单数据更新后:', this.data.addForm); + wx.showToast({ + title: '位置已选择', + icon: 'success' + }); + } + }, + fail: (error) => { + console.error('地图选点失败:', error); + wx.showToast({ + title: '选点失败,请重试', + icon: 'none' + }); + } + }); + }, + // 验证表单 validateForm(): boolean { const { name, address, contact, phone } = this.data.addForm; @@ -237,6 +298,168 @@ Page({ }); }, + // 取消编辑 + cancelEdit() { + this.setData({ + currentTab: 'list', + errorMessage: '', + successMessage: '' + }); + }, + + // 编辑表单输入处理 + onEditNameInput(e: any) { + this.setData({ + 'editForm.name': e.detail.value + }); + }, + + onEditAddressInput(e: any) { + this.setData({ + 'editForm.address': e.detail.value + }); + }, + + onEditContactInput(e: any) { + this.setData({ + 'editForm.contact': e.detail.value + }); + }, + + onEditPhoneInput(e: any) { + this.setData({ + 'editForm.phone': e.detail.value + }); + }, + + onEditDescriptionInput(e: any) { + this.setData({ + 'editForm.description': e.detail.value + }); + }, + + onEditCapacityInput(e: any) { + this.setData({ + 'editForm.capacity': parseInt(e.detail.value) || 500 + }); + }, + + onEditLongitudeInput(e: any) { + this.setData({ + 'editForm.longitude': parseFloat(e.detail.value) || 102.833722 + }); + }, + + onEditLatitudeInput(e: any) { + this.setData({ + 'editForm.latitude': parseFloat(e.detail.value) || 24.880095 + }); + }, + + onEditStatusChange(e: any) { + this.setData({ + 'editForm.status': e.detail.value + }); + }, + + // 编辑仓库 + editWarehouse(e: any) { + const warehouseId = e.currentTarget.dataset.id; + const warehouse = this.data.warehouses.find(w => w.id === warehouseId); + + if (warehouse) { + this.setData({ + editForm: { + id: warehouse.id || 0, + name: warehouse.name || '', + address: warehouse.address || '', + contact: warehouse.contact || '', + phone: warehouse.phone || '', + description: warehouse.description || '', + capacity: warehouse.capacity || 500, + longitude: warehouse.longitude || 102.833722, + latitude: warehouse.latitude || 24.880095, + status: warehouse.status || 'open' + }, + currentTab: 'edit' + }); + } + }, + + // 提交编辑仓库表单 + async submitEditForm() { + this.setData({ + errorMessage: '', + successMessage: '' + }); + + if (!this.validateEditForm()) { + return; + } + + this.setData({ + loading: true + }); + + try { + // 使用updateWarehouse更新仓库信息 + await warehouseService.updateWarehouse(this.data.editForm.id, this.data.editForm); + + this.setData({ + loading: false, + successMessage: '仓库更新成功', + currentTab: 'list' + }); + + // 重新加载仓库列表 + this.loadWarehouses(); + + showToast('仓库更新成功'); + + } catch (error) { + console.error('更新仓库失败:', error); + this.setData({ + loading: false, + errorMessage: '更新仓库失败,请重试' + }); + } + }, + + // 验证编辑表单 + validateEditForm(): boolean { + const { name, address, contact, phone } = this.data.editForm; + + if (!name.trim()) { + this.setData({ + errorMessage: '请输入仓库名称' + }); + return false; + } + + if (!address.trim()) { + this.setData({ + errorMessage: '请输入仓库地址' + }); + return false; + } + + if (!contact?.trim()) { + this.setData({ + errorMessage: '请输入联系人' + }); + return false; + } + + if (!phone?.trim()) { + this.setData({ + errorMessage: '请输入联系电话' + }); + return false; + } + + return true; + }, + // 删除仓库 async deleteWarehouse(e: any) { const warehouseId = e.currentTarget.dataset.id; diff --git a/miniprogram/pages/staff/warehouse-management.wxml b/miniprogram/pages/staff/warehouse-management.wxml index bb561d2..9750de2 100644 --- a/miniprogram/pages/staff/warehouse-management.wxml +++ b/miniprogram/pages/staff/warehouse-management.wxml @@ -49,6 +49,13 @@ > 添加仓库 + + 编辑仓库 + @@ -86,11 +93,18 @@ + @@ -192,6 +206,33 @@ type="number" /> + + + 坐标位置 + + + 经度: + + + + 纬度: + + + + + @@ -216,4 +257,127 @@ + + + + + + 编辑仓库信息 + + + 仓库名称 * + + + + + 仓库地址 * + + + + + 联系人 * + + + + + 联系电话 * + + + + + 仓库容量 + + + + + 坐标位置 + + + 经度: + + + + 纬度: + + + + + + + + 仓库状态 + + + {{editForm.status === 'open' ? '营业中' : editForm.status === 'closed' ? '已关闭' : '维护中'}} + + + + + + + 其他信息 + + + 仓库描述 +