更新于 2026-07-29 13:50 · v1.5 · Token 鉴权版 · 基地址 http://49.232.222.161:3000
**版本**: v1.5
**最后更新**: 2026-07-29
**访问地址**: https://k-xx.cn/docs/api(密码: kxx-admin-2026)
**用途**: 前端(小程序 / 管理后台)与后端之间的接口协议
**原则**: 本文档是前后端之间的「合同」——接口定义一旦确认,双方按文档各自开发,互不阻塞
**给新人**: 拿到这份文档 + Mock Server 示例数据 = 可以独立开发小程序和后台,不需要碰协议网关
| 项 | 值 |
|---|---|
| 协议 | HTTPS |
| 域名 | `https://api.lkc.com` (开发期用 `http://localhost:3000`) |
| 请求格式 | `application/json` |
| 响应格式 | `application/json` |
| 字符编码 | UTF-8 |
{
"code": 0,
"message": "ok",
"data": { ... },
"timestamp": 1717200000
}
| 字段 | 类型 | 说明 |
|---|---|---|
| code | int | 0=成功, 非0=错误(见错误码表) |
| message | string | 提示信息 |
| data | object/array/null | 业务数据 |
| timestamp | int | 响应时间戳(秒) |
| code | 说明 |
|---|---|
| 0 | 成功 |
| 1001 | 参数错误 |
| 1002 | 签名校验失败 |
| 1003 | Token 过期 |
| 1004 | Token 无效 |
| 2001 | 用户不存在 |
| 2002 | 余额不足 |
| 3001 | 充电桩不在线 |
| 3002 | 充电桩已占用 |
| 3003 | 充电桩故障 |
| 3004 | 充电订单不存在 |
| 4001 | 费率不存在 |
| 5000 | 服务器内部错误 |
// 请求参数(Query String)
?page=1&pageSize=20
// 响应格式
{
"code": 0,
"message": "ok",
"data": {
"list": [ ... ],
"total": 156,
"page": 1,
"pageSize": 20,
"totalPages": 8
}
}
POST /api/auth/login (或 POST /api/users/login,完全等价)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | code/userId 二选一 | wx.login() 返回的临时 code(微信登录) |
| userId | int | code/userId 二选一 | 用户ID(管理后台/测试用,无需微信授权) |
| nickName | string | 否 | 微信昵称(code 模式下新用户自动存入) |
| avatarUrl | string | 否 | 微信头像URL(code 模式下新用户自动存入) |
请求示例(微信登录):
{
"code": "0a3XyY0w1kabcDeFGH",
"nickName": "孔师傅",
"avatarUrl": "https://thirdwx.qlogo.cn/xxx"
}
💡 wx.getUserProfile 已废弃(基础库 2.27.1+ 返回默认值)。新方案:
响应:
{
"code": 0,
"message": "ok",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 86400,
"user": {
"id": 1001,
"nickname": "孔师傅",
"avatar": "https://example.com/avatar.png",
"phone": "137****0417",
"balance": 12850,
"createdAt": "2026-03-15T10:30:00Z"
}
}
}
`balance` 单位为分(12850 = ¥128.50),前端自行除100显示
GET /api/user/profile
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应:
{
"code": 0,
"message": "ok",
"data": {
"id": 1001,
"nickname": "孔师傅",
"avatar": "https://example.com/avatar.png",
"phone": "137****0417",
"balance": 12850,
"totalCharged": 320500,
"chargeCount": 48,
"createdAt": "2026-03-15T10:30:00Z"
}
}
| 字段 | 说明 |
|---|---|
| balance | 余额(分) |
| totalCharged | 累计充电金额(分) |
| chargeCount | 累计充电次数 |
POST /api/user/recharge
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amount | int | 是 | 充值金额(分),如 5000 = ¥50 |
| payMethod | string | 是 | 固定值 `"wechat"` |
请求示例:
{
"amount": 5000,
"payMethod": "wechat"
}
响应:
{
"code": 0,
"message": "ok",
"data": {
"rechargeNo": "R20260601123456001",
"amount": 5000,
"payInfo": {
"appId": "wx1234567890",
"timeStamp": "1717200000",
"nonceStr": "abc123",
"package": "prepay_id=wx123456789",
"signType": "RSA",
"paySign": "xxx"
}
}
}
前端拿到 `payInfo` 后调用 `wx.requestPayment()` 拉起微信支付。
支付结果前端不要自行判断——需调 `GET /api/user/profile` 刷新余额确认。
**三种录入方式**:
1. **手动输入** — 用户在小程序「我的车辆」手动输入VIN码
2. **行驶证OCR** — 扫描上传行驶证,自动识别VIN及车辆信息
3. **首次充电触发** — 桩读取VIN后平台通知用户确认绑定
POST /api/users/vin/bind
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| vin | string | 是 | 车架号(17位) |
| vehicleBrand | string | 否 | 车辆品牌,如 "比亚迪" |
| vehicleModel | string | 否 | 车辆型号,如 "汉EV" |
| plateNo | string | 否 | 车牌号 |
| isDefault | bool | 否 | 设为默认车辆,默认 true |
| bindSource | string | 否 | 绑定来源,默认 `"manual"`。可选 `"car_app_authorize"` |
请求:
{
"vin": "LSVAU2A38J2150114",
"vehicleBrand": "比亚迪",
"vehicleModel": "汉EV",
"plateNo": "京A12345",
"bindSource": "manual"
}
响应:
{ "code": 0, "data": { "id": 1, "vin": "LSVAU2A38J2150114", "vehicleBrand": "比亚迪", "vehicleModel": "汉EV", "plateNo": "京A12345", "isDefault": true, "bindSource": "manual" } }
POST /api/users/vin/ocr
⚠️ 生产环境需接入腾讯云OCR/百度OCR API识别行驶证。开发模式下,前端调用此接口提交OCR识别结果(JSON),平台自动绑定。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ocrResult.vin | string | 是 | OCR识别的车架号 |
| ocrResult.brand | string | 否 | 识别的品牌 |
| ocrResult.model | string | 否 | 识别的型号 |
| ocrResult.plateNo | string | 否 | 识别的车牌号 |
| imageUrl | string | 否 | 行驶证原图URL(存证) |
| autoBind | bool | 否 | 是否自动绑定,默认 true |
| isDefault | bool | 否 | 是否设为默认车辆,默认 true |
请求:
{
"ocrResult": {
"vin": "LSVAU2A38J2150114",
"brand": "比亚迪",
"model": "汉EV",
"plateNo": "京A12345"
},
"imageUrl": "https://cdn.lkc.com/ocr/driving_license_123.jpg",
"autoBind": true
}
响应(自动绑定):
{ "code": 0, "data": { "id": 2, "vin": "LSVAU2A38J2150114", "brand": "比亚迪", "model": "汉EV", "plateNo": "京A12345", "autoBound": true, "bindSource": "ocr" }, "message": "行驶证识别成功,车架号已自动绑定" }
响应(需确认) — 传 autoBind: false:
{ "code": 0, "data": { "vin": "...", "brand": "...", "model": "...", "plateNo": "...", "autoBound": false }, "message": "OCR识别成功,请确认后绑定" }
POST /api/users/vin/confirm-from-charge
**场景**:用户在充电桩首次充电后,小程序推送 VIN 绑定确认弹窗。
此 API 返回待确认的 VIN 信息,用户确认后还需调 `POST /api/users/vin/bind` 完成绑定。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| vin | string | 是 | 桩读取到的车架号 |
| pileSn | string | 否 | 充电桩编号(前端展示) |
| gunNo | int | 否 | 枪号 |
请求:
{ "vin": "LSVAU2A38J2150114", "pileSn": "LKC-P001-001", "gunNo": 1 }
响应:
{
"code": 0,
"data": {
"vin": "LSVAU2A38J2150114",
"chargingAt": { "pileSn": "LKC-P001-001", "gunNo": 1 },
"status": "pending_confirm",
"message": "检测到新车辆,请在弹窗中确认是否绑定此车架号并开通即插即充",
"action": "请调用 POST /api/users/vin/bind 完成绑定"
}
}
部分车企(比亚迪、蔚来等)与充电平台合作支持授权同步。用户在车企 App 内授权后,车企回调至平台,调用 `POST /api/users/vin/bind` 并传 `bindSource: "car_app_authorize"`。
GET /api/users/vin/list
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应:
{
"code": 0,
"data": {
"list": [
{ "id": 1, "vin": "LSVAU2A38J2150114", "vehicleBrand": "比亚迪", "vehicleModel": "汉EV", "plateNo": "京A12345", "isDefault": true, "status": "active" }
],
"total": 1
}
}
DELETE /api/users/vin/:id
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应: { "code": 0, "data": { "vin": "LSVAU2A38J2150114" } }
POST /api/users/pay-mode
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payMode | string | 是 | `"prepaid"` 预付费 / `"postpaid"` 后付费(信用支付) |
请求:
{ "payMode": "prepaid" }
响应:
{ "code": 0, "data": { "payMode": "prepaid" }, "message": "支付模式已切换为: 预付费(先充值后充电)" }
POST /api/users/credit/authorize
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| provider | string | 是 | `"wechat_pay_score"` 微信支付分 / `"sesame_credit"` 芝麻信用 |
| authCode | string | 是 | 微信/支付宝返回的信用授权码 |
请求:
{
"provider": "wechat_pay_score",
"authCode": "abc123def456"
}
响应:
{
"code": 0,
"data": { "creditEnabled": true, "provider": "wechat_pay_score", "payMode": "postpaid" },
"message": "信用支付已开通"
}
GET /api/users/credit/status
响应:
{ "code": 0, "data": { "creditEnabled": true, "payMode": "postpaid" } }
GET /api/stations
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| lng | float | 是 | 当前经度 |
| lat | float | 是 | 当前纬度 |
| radius | int | 否 | 搜索半径(米),默认5000 |
| keyword | string | 否 | 站名模糊搜索 |
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 1,
"name": "望京充电站",
"address": "北京市朝阳区望京SOHO B1停车场",
"lng": 116.4809,
"lat": 40.0015,
"distance": 1200,
"image": "https://cdn.lkc.com/station/1.jpg",
"openTime": "00:00-24:00",
"totalPiles": 4,
"idlePiles": 2,
"minFee": 80,
"tags": ["24小时", "有地锁"]
}
],
"total": 3
}
}
| 字段 | 说明 |
|---|---|
| distance | 距离(米) |
| totalPiles | 总桩数 |
| idlePiles | 空闲枪数 |
| minFee | 计费参考最低价(分/度) |
GET /api/stations/:id
响应:
{
"code": 0,
"message": "ok",
"data": {
"id": 1,
"name": "望京充电站",
"address": "北京市朝阳区望京SOHO B1停车场",
"lng": 116.4809,
"lat": 40.0015,
"openTime": "00:00-24:00",
"phone": "400-xxx-xxxx",
"images": ["https://cdn.lkc.com/station/1_1.jpg"],
"piles": [
{
"id": 101,
"sn": "LKC-P001-001",
"gunCount": 2,
"guns": [
{
"no": 1,
"status": "idle",
"power": 60,
"voltage": 380,
"rateFee": 80,
"rateService": 50
},
{
"no": 2,
"status": "charging",
"power": 60,
"voltage": 380,
"rateFee": 80,
"rateService": 50
}
]
}
]
}
}
枪状态枚举:
| status | 说明 | 小程序行为 |
|---|---|---|
| `idle` | 空闲 | 显示绿色,"可充电",可扫码 |
| `charging` | 充电中 | 显示红色,不可选 |
| `offline` | 离线 | 显示灰色,"维护中" |
| `fault` | 故障 | 显示灰色,"故障" |
| `reserved` | 已预约 | 显示橙色 |
费率说明:
| 字段 | 含义 | 单位 |
|---|---|---|
| rateFee | 电费单价 | 分/度 (80 = 0.80元/度) |
| rateService | 服务费单价 | 分/度 (50 = 0.50元/度) |
| **合计** | rateFee + rateService | 用户看到的单价 |
GET /api/piles/:sn
响应:
{
"code": 0,
"message": "ok",
"data": {
"sn": "LKC-P001-001",
"status": "charging",
"gunStatus": {
"gun1": "charging",
"gun2": "idle"
},
"currentOrder": {
"orderNo": "LC202606011200001",
"gunNo": 1,
"startTime": "2026-06-01T11:55:00Z",
"duration": 300,
"energy": 2.35,
"fee": 306
},
"lastOnline": "2026-06-01T12:00:05Z"
}
}
小程序扫码后可以用这个接口轮询获取桩最新状态,配合扫码页展示。
充电桩支持三种启动方式:**扫码充电**、**VIN充电**、**密码充电**。
扣款逻辑分两种:**预付费**(先充值后充电)、**后付费**(信用支付,先充后付)。
**扫码充电两种入口路径**:
>
**路径A — 小程序内扫码(已登录态)**
用户已在 APP/小程序内保持登录态 → 点击扫码 → 识别桩编号 → 调用 `POST /api/charge/start` → 进入充电页。
⚠️ 如果 Token 已过期(Bearer 头存在但验证失败),返回 `401 needLogin: true`,前端应弹出重新授权弹窗。
>
**路径B — 微信「扫一扫」扫描桩码(跳转场景)**
用户用系统相机或微信扫一扫扫描充电桩二维码 → 自动跳转小程序 → 弹出微信授权登录弹窗 → 用户点击「允许」→ 小程序拿到 `code` → 调用 `POST /api/charge/start-with-auth` → **一次请求完成:授权登录 + 自动注册 + 创建订单**。
新用户无感注册(自动分配 openid、账号密码、注册积分),老用户静默登录,无需额外跳转应用商店。
POST /api/charge/start
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pileSn | string | 是 | 充电桩编号 |
| gunNo | int | 是 | 枪号(1或2) |
| payMode | string | 否 | `"prepaid"` / `"postpaid"`,不传则使用用户偏好 |
请求示例:
{
"pileSn": "LKC-P001-001",
"gunNo": 1
}
响应:
{
"code": 0,
"data": {
"orderNo": "LClz2k3fWxP9",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"startTime": "2026-07-14T09:10:00Z",
"rateFee": 80,
"rateService": 50,
"totalFeeRate": 130,
"status": "pending",
"mode": "scan",
"user": { "id": 1001, "nickname": "孔师傅", "balance": 12850, "payMode": "prepaid", "creditEnabled": false },
"message": "订单已创建,请在桩上启动充电"
}
}
Token 过期错误(Header 有 Authorization 但 Token 失效):
{
"code": 401,
"message": "登录已过期,请重新授权登录",
"data": {
"needLogin": true,
"action": "请在弹窗中重新授权登录,或使用微信扫一扫重新进入"
}
}
POST /api/charge/start-with-auth
**一体式接口**:一次请求完成「微信授权 → 自动登录/注册 → 创建订单」。
前端无需先调 `/api/auth/login`,本接口内部走完全流程。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | `wx.login()` 返回的 code 凭证 |
| pileSn | string | 是 | 充电桩编号(扫码解析) |
| gunNo | int | 是 | 枪号 |
| userInfo.nickname | string | 否 | 微信头像昵称(新用户注册用) |
| userInfo.avatar | string | 否 | 微信头像URL |
请求示例:
{
"code": "0b1cZf000xyz123abc",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"userInfo": { "nickname": "孔师傅", "avatar": "https://thirdwx.xxx/avatar.jpg" }
}
响应(老用户/静默登录):
{
"code": 0,
"data": {
"token": "eyJhbG...",
"user": {
"id": 1001, "nickname": "孔师傅", "avatar": "https://...", "phone": "137****0417",
"balance": 12850, "payMode": "prepaid", "creditEnabled": false, "isNew": false
},
"order": {
"orderNo": "LClz2khWxQ1", "pileSn": "LKC-P001-001", "gunNo": 1,
"startTime": "2026-07-14T09:11:00Z", "rateFee": 80, "rateService": 50,
"totalFeeRate": 130, "status": "pending", "mode": "scan"
},
"message": "欢迎回来 孔师傅,请在桩上启动充电"
}
}
响应(新用户/无感注册):
{
"code": 0,
"data": {
"token": "eyJhbG...",
"user": {
"id": 1002, "nickname": "新用户9527", "avatar": null, "phone": null,
"balance": 0, "points": 20, "payMode": "prepaid", "creditEnabled": false, "isNew": true
},
"order": { "orderNo": "LClz2kkWxQ2", "pileSn": "LKC-P001-001", "gunNo": 1, "status": "pending", "mode": "scan" },
"message": "欢迎 新用户9527!账户已自动创建,请在桩上启动充电"
}
}
**注册逻辑**:首次扫码自动创建 `charge_user` → 分配充电账号 `CK0001` → 密码同账号 → 奖励 20 积分 → 下发 Token → 创建订单。全程无需用户手动填表单。
小程序扫码后可以用这个接口轮询获取桩最新状态,配合扫码页展示。
POST /api/charge/start-by-vin
**两种鉴权模式**:
- **mode=default(旧版明文)**:平台侧匹配 VIN 绑定 → 创建订单 → 发远程启机帧。适用于不支持 ISO 15118 的旧桩。
- **mode=challenge(ISO 15118 挑战-应答)**:平台下发挑战码给桩 → 桩结合车端 VIN 回传 → 平台校验后返回认证结果。安全等级更高。
>
**完整流程**:充电桩显示屏点击 VIN 充电模式 → 插枪 → 桩通过 CAN/BMS 读取 VIN → 上报平台(0xA9 明文或 0xAD 安全上报) → 平台匹配绑定信息 → 自动启机充电扣款。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| vin | string | mode=default 时必填 | 车架号(17位),mode=challenge 时桩自动读取 |
| pileSn | string | 是 | 充电桩编号 |
| gunNo | int | 否 | 枪号,默认 1 |
| mode | string | 否 | `"default"` 直接匹配 / `"challenge"` 下发挑战码 / `"status"` 查询挑战码状态 |
请求示例(default 模式):
{
"vin": "LSVAU2A38J2150114",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"mode": "default"
}
请求示例(challenge 模式 — 下发挑战码):
{
"pileSn": "LKC-P001-001",
"gunNo": 1,
"mode": "challenge"
}
请求示例(status 模式 — 查询挑战码):
{
"pileSn": "LKC-P001-001",
"mode": "status"
}
响应(default 模式 — VIN 匹配成功):
{
"code": 0,
"message": "ok",
"data": {
"orderNo": "VIly2k3fWxP7",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"vin": "LSVAU2A38J2150114",
"startTime": "2026-07-20T08:45:00Z",
"rateFee": 76,
"rateService": 500,
"status": "pending",
"mode": "VIN",
"user": {
"id": 1001,
"nickname": "孔师傅",
"balance": 12850,
"vehicle": "比亚迪 汉EV",
"plateNo": "京A12345",
"payMode": "prepaid",
"creditEnabled": false
},
"message": "VIN匹配成功: 孔师傅,请在桩上启动充电"
}
}
响应(challenge 模式 — 挑战码已下发):
{
"code": 0,
"data": {
"pileSn": "LKC-P001-001",
"challenge": "a1b2c3d4e5f6...",
"operatorId": "CN-TH-001",
"ttl": 60,
"message": "挑战码已下发,等待桩端安全上报(60秒有效)"
}
}
响应(status 模式 — 挑战码有效):
{
"code": 0,
"data": {
"pileSn": "LKC-P001-001",
"challengeExists": true,
"message": "挑战码有效,等待桩端上报"
}
}
错误场景:
{ "code": 4008, "message": "该车架号未绑定用户,请先在小程序中绑定" }
{ "code": 2002, "message": "余额不足(当前1.28元),请先充值" }
{ "code": 3001, "message": "充电桩不在线" }
{ "code": 1001, "message": "缺少车架号 VIN(mode=default时必填)" }
POST /api/charge/start-by-password
**流程**:商家预设充电密码 → 用户在桩显示屏输入密码 → 验证通过后直接启动 → **跳过账户验证和计费逻辑**。
⚠️ 密码充电为商家内部使用,不扣费。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| password | string | 是 | 充电密码 |
| pileSn | string | 是 | 充电桩编号 |
| gunNo | int | 是 | 枪号 |
请求示例:
{
"password": "admin123",
"pileSn": "LKC-P001-001",
"gunNo": 2
}
响应:
{
"code": 0,
"message": "ok",
"data": {
"orderNo": "PWly2k3fWxP8",
"pileSn": "LKC-P001-001",
"gunNo": 2,
"startTime": "2026-07-14T08:46:00Z",
"rateFee": 80,
"rateService": 50,
"status": "pending",
"mode": "password",
"message": "密码验证通过,请在桩上启动充电"
}
}
错误场景:
{ "code": 4000, "message": "未配置充电密码,请联系管理员" }
{ "code": 4003, "message": "密码错误" }
GET /api/charge/:orderNo
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应:
{
"code": 0,
"message": "ok",
"data": {
"orderNo": "LC202606011200001",
"status": "charging",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"startTime": "2026-06-01T12:00:00Z",
"duration": 480,
"energy": 3.85,
"fee": 501,
"realTime": {
"voltage": 385.2,
"current": 28.6,
"power": 11.02,
"soc": 62,
"temperature": 35
}
}
}
| 字段 | 类型 | 单位 | 说明 |
|---|---|---|---|
| duration | int | 秒 | 已充时长 |
| energy | float | kWh | 已充电量 |
| fee | int | 分 | 累计费用 |
| voltage | float | V | 实时电压 |
| current | float | A | 实时电流 |
| power | float | kW | 实时功率 |
| soc | int | % | 当前电池SOC |
| temperature | int | ℃ | 电池温度 |
**轮询建议**: 小程序每 5 秒轮询一次。充电中页面用这些数据画实时曲线和进度条。
status 状态流:
charging → finished (正常结束)
charging → stopped (用户主动停止)
charging → error (异常中断)
POST /api/charge/stop
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| orderNo | string | 是 | 订单编号 |
请求示例:
{
"orderNo": "LC202606011200001"
}
响应:
{
"code": 0,
"message": "ok",
"data": {
"orderNo": "LC202606011200001",
"stopTime": "2026-06-01T12:45:00Z",
"status": "finished"
}
}
前端收到此响应后,跳转到充电账单页。
GET /api/charge/:orderNo/bill
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应:
{
"code": 0,
"message": "ok",
"data": {
"orderNo": "LC202606011200001",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"stationName": "望京充电站",
"startTime": "2026-06-01T12:00:00Z",
"endTime": "2026-06-01T12:45:00Z",
"duration": 2700,
"startSoc": 20,
"endSoc": 85,
"energy": 19.50,
"feeDetail": {
"elecFee": 1560,
"serviceFee": 975,
"totalFee": 2535
},
"payStatus": "paid",
"payTime": "2026-06-01T12:45:05Z",
"balanceBefore": 12850,
"balanceAfter": 10315
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| duration | int | 充电时长(秒),前端显示 "45分00秒" |
| energy | float | 充电度数(kWh) |
| elecFee | int | 电费(分) |
| serviceFee | int | 服务费(分) |
| totalFee | int | 合计(分) |
| payStatus | string | `paid` / `unpaid` |
| balanceBefore | int | 扣款前余额(分) |
| balanceAfter | int | 扣款后余额(分) |
GET /api/orders
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 默认1 |
| pageSize | int | 否 | 默认20 |
| status | string | 否 | `charging` / `finished` / `error` |
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"orderNo": "LC202606011200001",
"stationName": "望京充电站",
"pileSn": "LKC-P001-001",
"gunNo": 1,
"startTime": "2026-06-01T12:00:00Z",
"endTime": "2026-06-01T12:45:00Z",
"energy": 19.50,
"totalFee": 2535,
"status": "finished"
}
],
"total": 48,
"page": 1,
"pageSize": 20,
"totalPages": 3
}
}
GET /api/orders/:orderNo
| Header | 值 |
|---|---|
| Authorization | Bearer {token} |
响应: 与 4.4 充电账单 结构完全一致,可直接复用。
**认证方式**: 后台使用独立的用户名+密码登录,与小程序用户体系分开。
Token 同样放在 Header: `Authorization: Bearer {token}`
所有管理后台接口路径前缀为 `/api/admin/`
POST /api/admin/login
⚡ **v1.1 更新**: 响应增加 `stationId`、`permission`、`realName` 字段,支持站长角色。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| username | string | 是 | 账号 |
| password | string | 是 | 密码 |
请求:
{
"username": "admin",
"password": "123456"
}
响应:
{
"code": 0,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"admin": {
"id": 1,
"username": "admin",
"role": "super",
"stationId": null,
"realName": "超级管理员"
},
"permission": "all_stations"
}
}
角色与权限:
| role | stationId | permission | 说明 |
|---|---|---|---|
| `super` | null | `all_stations` | 超级管理员,全平台 |
| `admin` | null | `all_stations` | 管理员,全平台 |
| `station_master` | 具体站点ID | `single_station` | 站长,仅归属站点 |
| `operator` | null | 无管理后台权限 | 运营人员 |
GET /api/admin/me
响应:
{
"code": 0,
"data": {
"id": 2,
"username": "yt_station",
"role": "station_master",
"stationId": 1,
"realName": "盐田站长",
"permission": "single_station"
}
}
前端用 `permission` 字段决定是否显示全站数据选择器;`station_master` 角色应隐藏站点切换功能。
GET /api/admin/piles
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 默认1 |
| pageSize | int | 否 | 默认20 |
| status | string | 否 | `online` / `offline` / `fault` / `all` |
| keyword | string | 否 | 桩编号/站名模糊搜索 |
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 1,
"sn": "LKC-P001-001",
"imei": "860123456789012",
"model": "LKC-AC60-D",
"stationName": "望京充电站",
"status": "online",
"gunCount": 2,
"firmwareVer": "v2.1.3",
"lastOnline": "2026-06-01T12:00:05Z",
"todayEnergy": 156.8,
"todayFee": 20384,
"totalEnergy": 15230.5,
"totalFee": 1980065
}
],
"total": 12,
"page": 1,
"pageSize": 20,
"totalPages": 1
}
}
POST /api/admin/piles
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sn | string | 是 | 桩编号 |
| imei | string | 是 | IMEI |
| model | string | 是 | 型号 |
| stationId | int | 是 | 所属充电站ID |
| gunCount | int | 是 | 枪数(1或2) |
请求:
{
"sn": "LKC-P002-001",
"imei": "860123456789013",
"model": "LKC-AC60-D",
"stationId": 1,
"gunCount": 2
}
响应:
{
"code": 0,
"message": "ok",
"data": {
"id": 13,
"sn": "LKC-P002-001"
}
}
PUT /api/admin/piles/:id
| 参数 | 类型 | 说明 |
|---|---|---|
| stationId | int | 更换所属电站 |
| model | string | 型号 |
| gunCount | int | 枪数 |
| firmwareVer | string | 固件版本(手动更新记录) |
请求:
{
"stationId": 2,
"firmwareVer": "v2.2.0"
}
响应:
{
"code": 0,
"message": "ok",
"data": null
}
DELETE /api/admin/piles/:id
响应:
{
"code": 0,
"message": "充电桩已删除",
"data": null
}
POST /api/admin/pile/restart
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pileId | int | 是 | 桩ID |
请求:
{
"pileId": 1
}
响应:
{
"code": 0,
"message": "重启指令已下发",
"data": {
"commandId": "cmd-abc123"
}
}
对应协议帧: `0x92 远程重启`
下发后桩会返回 `0x91 远程重启应答`,后台通过 WebSocket 推送执行结果。
POST /api/admin/pile/update
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| pileId | int | 是 | 桩ID |
| firmwareUrl | string | 是 | 固件下载地址 |
| firmwareVer | string | 是 | 新版本号 |
| md5 | string | 是 | 固件包MD5 |
请求:
{
"pileId": 1,
"firmwareUrl": "https://cdn.lkc.com/firmware/v2.2.0.bin",
"firmwareVer": "v2.2.0",
"md5": "d41d8cd98f00b204e9800998ecf8427e"
}
响应:
{
"code": 0,
"message": "升级指令已下发",
"data": {
"commandId": "cmd-abc456"
}
}
对应协议帧: `0x94 远程更新`
GET /api/admin/rates
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 1,
"name": "标准费率",
"elecFee": 80,
"serviceFee": 50,
"desc": "全天统一",
"isDefault": true,
"pileCount": 8
},
{
"id": 2,
"name": "峰谷费率-峰时",
"elecFee": 120,
"serviceFee": 50,
"timeRange": "08:00-22:00",
"pileCount": 4
},
{
"id": 3,
"name": "峰谷费率-谷时",
"elecFee": 40,
"serviceFee": 50,
"timeRange": "22:00-08:00",
"pileCount": 4
}
]
}
}
POST /api/admin/rates
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 是 | 费率名称 |
| elecFee | int | 是 | 电费单价(分/度) |
| serviceFee | int | 是 | 服务费单价(分/度) |
| timeRange | string | 否 | 时段范围 "08:00-22:00",不填=全天 |
| isDefault | bool | 否 | 是否默认费率 |
请求:
{
"name": "标准费率",
"elecFee": 80,
"serviceFee": 50,
"isDefault": true
}
响应:
{
"code": 0,
"message": "ok",
"data": {
"id": 34
}
}
PUT /api/admin/rates/:id
参数同创建。已有交易记录的费率不可删除,只能停用或修改。
POST /api/admin/piles/:id/bind-rate
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| rateId | int | 是 | 费率ID |
请求:
{
"rateId": 2
}
响应:
{
"code": 0,
"message": "费率已绑定,将在下一个心跳周期同步到充电桩",
"data": null
}
对应协议帧: `0x58 计费模型设置`。绑定后系统通过心跳应答自动下发最新费率到桩。
GET /api/admin/transactions
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| page | int | 否 | 默认1 |
| pageSize | int | 否 | 默认20 |
| startDate | string | 否 | 开始日期 "2026-06-01" |
| endDate | string | 否 | 结束日期 |
| type | string | 否 | `recharge` / `charge` / `refund` |
| userId | int | 否 | 按用户筛选 |
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 1,
"userId": 1001,
"userName": "孔师傅",
"type": "charge",
"orderNo": "LC202606011200001",
"amount": -2535,
"balanceBefore": 12850,
"balanceAfter": 10315,
"createdAt": "2026-06-01T12:45:05Z"
}
],
"total": 562,
"page": 1,
"pageSize": 20,
"totalPages": 29
}
}
GET /api/admin/report/daily
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| date | string | 否 | 日期 "2026-06-01",不填=今天 |
| stationId | int | 否 | 按电站筛选 |
响应:
{
"code": 0,
"message": "ok",
"data": {
"date": "2026-06-01",
"totalOrders": 42,
"totalEnergy": 680.5,
"totalElecFee": 54440,
"totalServiceFee": 34025,
"totalFee": 88465,
"rechargeTotal": 50000,
"hourlyBreakdown": [
{ "hour": 0, "orders": 2, "energy": 30.2, "fee": 3926 },
{ "hour": 1, "orders": 1, "energy": 15.0, "fee": 1950 }
],
"pileBreakdown": [
{ "pileSn": "LKC-P001-001", "orders": 8, "energy": 120.5, "fee": 15665 }
]
}
}
GET /api/admin/report/monthly
| 参数 | 类型 | 说明 |
|---|---|---|
| month | string | "2026-06" |
响应: 结构同日,多一个 dailyTrend 数组用于折线图。
{
"code": 0,
"message": "ok",
"data": {
"month": "2026-06",
"totalOrders": 1200,
"totalEnergy": 18500,
"totalFee": 2405000,
"dailyTrend": [
{ "date": "2026-06-01", "orders": 42, "fee": 88465 },
{ "date": "2026-06-02", "orders": 38, "fee": 79200 }
]
}
}
GET /api/admin/stations
响应:
{
"code": 0,
"message": "ok",
"data": {
"list": [
{
"id": 1,
"name": "望京充电站",
"address": "北京市朝阳区望京SOHO B1停车场",
"lng": 116.4809,
"lat": 40.0015,
"pileCount": 4,
"totalGuns": 8,
"onlineCount": 3,
"status": "open",
"createdAt": "2026-03-01T00:00:00Z"
}
]
}
}
POST /api/admin/stations
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| name | string | 是 | 站名 |
| address | string | 是 | 地址 |
| lng | float | 是 | 经度 |
| lat | float | 是 | 纬度 |
| openTime | string | 否 | 营业时间,默认 "00:00-24:00" |
| phone | string | 否 | 联系电话 |
| images | array | 否 | 图片URL数组 |
PUT /api/admin/stations/:id
参数同创建,全部可选填。
POST /api/admin/vin/batch
**场景**:企业车队管理员批量导入车辆VIN码。支持同时指定用户账号关联。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| batchName | string | 是 | 批次名称,如"盐田港2026Q3车队" |
| vehicles | array | 是 | 车辆列表 |
| vehicles[].vin | string | 是 | 车架号(17位) |
| vehicles[].brand | string | 否 | 品牌 |
| vehicles[].model | string | 否 | 型号 |
| vehicles[].plateNo | string | 否 | 车牌 |
| vehicles[].userAccount | string | 否 | 用户手机号/充电账号,存在则直接绑定 |
| overwriteExisting | bool | 否 | 是否覆盖已存在的VIN绑定,默认 false |
请求示例:
{
"batchName": "盐田港2026Q3车队",
"vehicles": [
{ "vin": "LSVAU2A38J2150114", "brand": "比亚迪", "model": "汉EV", "plateNo": "京A12345", "userAccount": "13712345678" },
{ "vin": "LNBMDBAH4FU055511", "brand": "北汽", "model": "EU5", "plateNo": "京B56789", "userAccount": "qc001" }
],
"overwriteExisting": false
}
响应:
{
"code": 0,
"data": {
"batchId": 1,
"batchName": "盐田港2026Q3车队",
"total": 2,
"success": 1,
"fail": 1,
"failDetail": [
{ "row": 3, "vin": "LNBMDBAH4FU055511", "reason": "VIN已被绑定,跳过" }
],
"status": "partial_ok"
}
}
GET /api/admin/vin/batch/list
响应:
{
"code": 0,
"data": {
"list": [
{ "id": 1, "batch_name": "盐田港2026Q3车队", "total_count": 100, "success_count": 97, "fail_count": 3, "status": "partial_ok", "operator_name": "admin", "created_at": "2026-07-14T00:30:00Z" }
]
}
}
GET /api/admin/vin/batch/:id
响应: 完整批次对象 + failDetail 数组(每笔失败的 row/vin/reason)。
POST /api/admin/password-preset 创建密码预设
PUT /api/admin/password-preset/:id 更新密码预设
DELETE /api/admin/password-preset/:id 删除密码预设
GET /api/admin/password-presets 密码预设列表
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| password | string | 是(创建/更新时) | 明文密码,服务端bcrypt加密 |
| stationId | int | 否 | 站点ID,NULL=全局密码 |
| remark | string | 否 | 备注说明 |
| status | string | 否(更新时) | active / disabled |
每个站点的服务费由站长/管理通过规格词条独立设置,支持分时段差异化定价。
充电计费链路:`rate-selector` 根据当前时段从 `station_service_fee_spec` 匹配对应服务费。
POST /api/stations/service-fee
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| stationId | int | 是 | 站点ID |
| specName | string | 否 | 规格名称,默认"标准服务费" |
| serviceFee | int | 是 | 服务费(分/度) |
| timeStart | string | 否 | 时段开始 HH:MM,如"22:00",null=全天 |
| timeEnd | string | 否 | 时段结束 HH:MM,如"06:00",null=全天 |
| isDefault | bool | 否 | 是否默认规格,默认true |
创建默认规格时自动清除该站点旧默认。
站长只能操作自己归属站点,跨站操作返回 403。
请求:
{
"stationId": 2,
"specName": "龙华夜间特惠",
"serviceFee": 30,
"timeStart": "22:00",
"timeEnd": "06:00",
"isDefault": false
}
响应:
{
"code": 0,
"data": {
"id": 3,
"station_id": 2,
"spec_name": "龙华夜间特惠",
"service_fee": 30,
"service_feeYuan": "0.30",
"time_start": "22:00",
"time_end": "06:00",
"is_default": 0,
"status": "active"
}
}
GET /api/stations/service-fee/list?stationId=1
响应:
{
"code": 0,
"data": {
"stationId": 1,
"specs": [
{
"id": 1,
"spec_name": "标准服务费",
"service_fee": 50,
"service_feeYuan": "0.50",
"time_start": null,
"time_end": null,
"is_default": 1,
"status": "active"
}
]
}
}
DELETE /api/stations/service-fee/:id
软禁用(status=disabled),不会物理删除。需 admin token。
按站点+按天汇总订单、电量、电费、服务费。
管理员看全站,站长仅看归属站点数据(自动隔离)。
GET /api/dashboard
GET /api/stats
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| date | string | 否 | 指定日期 YYYY-MM-DD |
| from | string | 否 | 开始日期 YYYY-MM-DD |
| to | string | 否 | 结束日期 YYYY-MM-DD |
| month | string | 否 | 整月 YYYY-MM(自动计算月初到月末) |
| stationId | int | 否 | 站点ID(管理员可用,站长忽略此参数自动限定归属站) |
`date` / `from+to` / `month` 三选一。
不带任何日期参数默认查当天。
响应 (/api/dashboard):
{
"code": 0,
"data": {
"dateFrom": "2026-07-14",
"dateTo": "2026-07-14",
"stationFilter": "all",
"summary": {
"orderCnt": 2,
"energy": 46.2,
"totalFee": 8963,
"totalFeeYuan": "89.63",
"elecFee": 5960,
"elecFeeYuan": "59.60",
"serviceFee": 3003,
"serviceFeeYuan": "30.03",
"paidFee": 8963,
"paidFeeYuan": "89.63",
"finishedCnt": 2
},
"daily": [
{
"date": "2026-07-14",
"orderCnt": 2,
"energy": 46.2,
"totalFee": 8963,
"totalFeeYuan": "89.63",
"elecFee": 5960,
"elecFeeYuan": "59.60",
"serviceFee": 3003,
"serviceFeeYuan": "30.03",
"paidFee": 8963,
"paidFeeYuan": "89.63",
"vinCnt": 1,
"pwdCnt": 0,
"scanCnt": 1
}
],
"byStation": [
{
"stationId": 2,
"stationName": "龙华工业园充电站",
"orderCnt": 1,
"energy": 23.1,
"totalFee": 4828,
"totalFeeYuan": "48.28",
"serviceFee": 1848,
"serviceFeeYuan": "18.48"
},
{
"stationId": 1,
"stationName": "盐田港充电站",
"orderCnt": 1,
"energy": 23.1,
"totalFee": 4135,
"totalFeeYuan": "41.35",
"serviceFee": 1155,
"serviceFeeYuan": "11.55"
}
]
}
}
响应 (/api/stats):
{
"code": 0,
"data": {
"from": "2026-07-01",
"to": "2026-07-14",
"stationFilter": "1",
"monthly": { "orderCnt": 45, "energy": 1024.5, "totalFee": 183200, "totalFeeYuan": "1832.00", "elecFee": 124800, "elecFeeYuan": "1248.00", "serviceFee": 58400, "serviceFeeYuan": "584.00", "finishedCnt": 42, "paidFee": 180000, "paidFeeYuan": "1800.00" },
"daily": [ { "date": "2026-07-14", "cnt": 2, "energy": 46.2, "totalFee": 8963, "totalFeeYuan": "89.63", "elecFee": 5960, "elecFeeYuan": "59.60", "serviceFee": 3003, "serviceFeeYuan": "30.03", "paidFee": 8963, "paidFeeYuan": "89.63" } ],
"byStation": [ { "id": 1, "name": "盐田港充电站", "cnt": 45, "energy": 1024.5, "totalFee": 183200, "totalFeeYuan": "1832.00", "serviceFee": 58400, "serviceFeeYuan": "584.00" } ],
"byPile": [ { "sn": "YT-P001-001", "model": "LKC-AC60-D", "cnt": 22, "energy": 512.3, "totalFee": 91600, "totalFeeYuan": "916.00", "serviceFee": 29200, "serviceFeeYuan": "292.00" } ],
"total": { "orderCnt": 1560, "energy": 32000.5, "totalFee": 5769000, "totalFeeYuan": "57690.00", "elecFee": 3920000, "elecFeeYuan": "39200.00", "serviceFee": 1849000, "serviceFeeYuan": "18490.00", "paidFee": 5705000, "paidFeeYuan": "57050.00" }
}
}
数据库全量以**分**(BIGINT 整数)存储,API 响应同时提供分字段和元字段。
| 分字段 | 对应元字段 | 示例 |
|---|---|---|
| `totalFee` | `totalFeeYuan` | `8963` → `"89.63"` |
| `elecFee` | `elecFeeYuan` | `5960` → `"59.60"` |
| `serviceFee` | `serviceFeeYuan` | `3003` → `"30.03"` |
| `balance` | `balanceYuan` | `12850` → `"128.50"` |
| `amount` | `amountYuan` | `-4135` → `"-41.35"` |
| `paidFee` | `paidFeeYuan` | `5705000` → `"57050.00"` |
前端展示统一用 `*Yuan` 字段,分字段仅用于精度计算。
费率(电费/服务费单价)同样是分/度存储 + `*Yuan` 元/度显示。
| 值 | 含义 |
|---|---|
| `online` | 在线(TCP连接正常) |
| `offline` | 离线(心跳超时) |
| `fault` | 故障(桩主动上报错误) |
| `maintenance` | 维护中(管理员手动标记) |
| 值 | 含义 |
|---|---|
| `idle` | 空闲 |
| `charging` | 充电中 |
| `reserved` | 已预约 |
| `offline` | 离线 |
| `fault` | 故障 |
| 值 | 含义 |
|---|---|
| `pending` | 等待充电桩确认启机 |
| `charging` | 充电中 |
| `finished` | 正常结束 |
| `stopped` | 用户主动停止 |
| `error` | 异常中断 |
| 值 | 含义 |
|---|---|
| `paid` | 已扣费 |
| `unpaid` | 未扣费(预付费余额不足或后付费待扣款) |
| `overdue` | 欠费(预付费余额不足,已记录欠费流水) |
| `failed` | 后付费扣款失败 |
| `pending` | 后付费等待回调确认 |
| 值 | 含义 |
|---|---|
| `scan` | 扫码充电 |
| `VIN` | VIN码充电(插枪自动识别) |
| `password` | 密码充电 |
| `account` | 账号登录充电 |
| `card` | 刷卡充电 |
| 值 | 含义 |
|---|---|
| `prepaid` | 预付费(余额实时扣款) |
| `postpaid` | 后付费(信用支付,充电后异步扣款) |
| 值 | 含义 |
|---|---|
| `recharge` | 充值 |
| `charge` | 充电扣费 |
| `refund` | 退款 |
| role | stationId | permission | 说明 |
|---|---|---|---|
| `super` | null | `all_stations` | 超级管理员,全平台数据 |
| `admin` | null | `all_stations` | 管理员,全平台 |
| `station_master` | 站点ID | `single_station` | 站长,仅归属站点 |
| `operator` | null | — | 运营人员(无管理后台权限) |
**给新人**: 建议用 Express + JSON 文件搭建 Mock Server,整个搭建不超过半天。
mock-server/
├── package.json
├── server.js # 主入口
├── routes/
│ ├── user.js # 用户模块 mock
│ ├── station.js # 充电站模块 mock
│ ├── charge.js # 充电流程 mock
│ ├── order.js # 订单模块 mock
│ └── admin.js # 管理后台 mock
└── data/ # Mock 数据文件
├── user.json
├── stations.json
├── piles.json
├── orders.json
└── rates.json
const express = require('express');
const app = express();
app.use(express.json());
// 注入mock token校验(真实开发时删掉这行,接真实auth)
app.use((req, res, next) => {
if (req.path === '/api/user/login' || req.path === '/api/admin/login') return next();
if (!req.headers.authorization) return res.status(401).json({ code: 1004, message: 'Token无效' });
next();
});
// 统一响应包装
const ok = (data) => ({ code: 0, message: 'ok', data, timestamp: Math.floor(Date.now()/1000) });
const fail = (code, msg) => ({ code, message: msg, data: null, timestamp: Math.floor(Date.now()/1000) });
app.use('/api/user', require('./routes/user'));
app.use('/api/stations', require('./routes/station'));
app.use('/api/charge', require('./routes/charge'));
app.use('/api/orders', require('./routes/order'));
app.use('/api/admin', require('./routes/admin'));
app.listen(3000, () => console.log('Mock Server running on :3000'));
充电中页面的 GET /api/charge/:orderNo 需要返回每5秒变化的数据,让老板看起来像"真的在充电"。Mock 实现:
// routes/charge.js
const chargingOrders = {}; // 内存存储充电中订单
router.post('/start', (req, res) => {
const orderNo = 'LC' + Date.now().toString(36).toUpperCase();
chargingOrders[orderNo] = {
orderNo,
pileSn: req.body.pileSn,
gunNo: req.body.gunNo,
status: 'charging',
startTime: new Date().toISOString(),
startSoc: 20,
energy: 0,
fee: 0,
voltage: 380,
current: 32,
power: 12.1,
soc: 20,
temperature: 30
};
res.json(ok({ orderNo, ...chargingOrders[orderNo] }));
});
router.get('/:orderNo', (req, res) => {
const order = chargingOrders[req.params.orderNo];
if (!order) return res.json(fail(3004, '订单不存在'));
// 模拟递增 —— 每次调用都增长,页面看起来像真的在充电
order.energy += 0.15;
order.fee = Math.round(order.energy * 130); // 1.3元/度
order.soc = Math.min(order.soc + 1, 99);
order.voltage = 380 + Math.random() * 5;
order.current = 28 + Math.random() * 8;
order.power = (order.voltage * order.current / 1000);
order.temperature = 30 + Math.random() * 10;
order.duration = Math.floor((Date.now() - new Date(order.startTime).getTime()) / 1000);
const resp = { ...order, realTime: {
voltage: +order.voltage.toFixed(1),
current: +order.current.toFixed(1),
power: +order.power.toFixed(2),
soc: order.soc,
temperature: Math.round(order.temperature)
}};
res.json(ok(resp));
});
这段 Mock 代码的效果:小程序充电中页面打开后,电量、费用、电流电压持续跳动——**非技术人员看就是"真的在充"**。第2周演示的核心就是这一段。
**文档版本**: v1.5
**最后更新**: 2026-07-29
**v1.5 变更摘要**:
- 🔧 /api/auth/login 新增 nickName、avatarUrl 参数(新用户自动存昵称/头像)
- 🔧 /api/users/login 升级支持 code + userId 双模式,与 /api/auth/login 完全等价
- 🔧 文档下载文件名 v1.2→v1.5 修正
- 📋 多份文档副本同步至 v1.5
**v1.4 变更**:
- 新增管理员角色体系(super/admin/station_master/operator)与站长站点隔离
- 新增 `POST /api/stations/service-fee` 站点服务费规格管理 CRUD
- 新增 `GET /api/dashboard` + `GET /api/stats` 按站+按天看板
- 新增 `GET /api/admin/me` 管理员信息接口
- 全部费用字段增加 `*Yuan` 元格式
- 新增 `start_mode` / `pay_mode` 数据字典
- 新增信用支付回调 `POST /api/callback/credit-pay`
- 补充支付状态 `overdue` / `failed` / `pending`
**维护者**: 克里斯提娜