5 changed files with 727 additions and 188 deletions
-
16app/src/main/java/com/ouxuan/oxface/orderOX/OrderSelectionActivity.java
-
317app/src/main/java/com/ouxuan/oxface/orderOX/OrderVerificationResultActivity.java
-
135app/src/main/res/layout/activity_order_verification_result.xml
-
214app/src/test/java/com/ouxuan/oxface/OrderVerificationResultTest.java
-
213订单核销结果页面重构说明.md
@ -0,0 +1,214 @@ |
|||||
|
package com.ouxuan.oxface; |
||||
|
|
||||
|
import org.junit.Test; |
||||
|
import static org.junit.Assert.*; |
||||
|
|
||||
|
import com.google.gson.Gson; |
||||
|
import com.google.gson.JsonParser; |
||||
|
import com.google.gson.JsonObject; |
||||
|
import com.google.gson.JsonElement; |
||||
|
import com.ouxuan.oxface.network.api.PadApiService; |
||||
|
|
||||
|
/** |
||||
|
* 订单核销结果测试 |
||||
|
* 测试不同订单类型的数据解析和显示逻辑 |
||||
|
*/ |
||||
|
public class OrderVerificationResultTest { |
||||
|
|
||||
|
private Gson gson = new Gson(); |
||||
|
|
||||
|
/** |
||||
|
* 测试次卡核销数据解析(order_type=1) |
||||
|
* info 字段为字符串类型 |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testPeopleCardVerification() { |
||||
|
// 模拟次卡核销的服务器响应 |
||||
|
String jsonResponse = "{\n" + |
||||
|
" \"code\": 0,\n" + |
||||
|
" \"data\": {\n" + |
||||
|
" \"avatar_url\": \"https://imgcdn.ouxuanzhineng.cn/upload/user_avatar_img/aa2c59a8922fdd00a9909156a7265d3d.jpeg\",\n" + |
||||
|
" \"nickname\": \"mingtao\",\n" + |
||||
|
" \"result\": {\n" + |
||||
|
" \"order_no\": \"RC20250910155115457419\",\n" + |
||||
|
" \"start_time\": \"2025-09-10 15:51:16\",\n" + |
||||
|
" \"end_time\": \"4763-08-07 15:51:16\",\n" + |
||||
|
" \"order_type\": 1,\n" + |
||||
|
" \"project\": \"20230727测试\",\n" + |
||||
|
" \"number\": 1,\n" + |
||||
|
" \"v_code\": [\"250910159819\"],\n" + |
||||
|
" \"info\": \"平时\",\n" + |
||||
|
" \"success\": 0,\n" + |
||||
|
" \"pv_usage_duration\": 1,\n" + |
||||
|
" \"many_enter\": false\n" + |
||||
|
" }\n" + |
||||
|
" },\n" + |
||||
|
" \"message\": \"\",\n" + |
||||
|
" \"extension\": \"extension_fixed\"\n" + |
||||
|
"}"; |
||||
|
|
||||
|
try { |
||||
|
PadApiService.VerifyOrderResponse response = gson.fromJson(jsonResponse, PadApiService.VerifyOrderResponse.class); |
||||
|
|
||||
|
assertNotNull("响应不应该为空", response); |
||||
|
assertNotNull("结果不应该为空", response.getResult()); |
||||
|
assertEquals("订单类型应该是1", 1, response.getResult().getOrderType()); |
||||
|
assertEquals("订单号应该匹配", "RC20250910155115457419", response.getResult().getOrderNo()); |
||||
|
|
||||
|
// 验证info字段可以被正确解析为字符串 |
||||
|
String infoJson = gson.toJson(response.getResult().getInfo()); |
||||
|
JsonElement infoElement = JsonParser.parseString(infoJson); |
||||
|
|
||||
|
// 对于次卡类型,info应该是字符串 |
||||
|
if (response.getResult().getOrderType() == 1 && infoElement.isJsonPrimitive()) { |
||||
|
String bookingInfo = infoElement.getAsString(); |
||||
|
assertEquals("预订信息应该是'平时'", "平时", bookingInfo); |
||||
|
} |
||||
|
|
||||
|
System.out.println("次卡核销测试通过: " + response.getResult().getOrderNo()); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
fail("次卡核销数据解析失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 测试年月卡核销数据解析(order_type=3) |
||||
|
* info 字段为对象类型 |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testMonthlyCardVerification() { |
||||
|
// 模拟年月卡核销的服务器响应 |
||||
|
String jsonResponse = "{\n" + |
||||
|
" \"code\": 0,\n" + |
||||
|
" \"data\": {\n" + |
||||
|
" \"avatar_url\": \"https://imgcdn.ouxuanzhineng.cn/upload/user_avatar_img/aa2c59a8922fdd00a9909156a7265d3d.jpeg\",\n" + |
||||
|
" \"nickname\": \"mingtao\",\n" + |
||||
|
" \"result\": {\n" + |
||||
|
" \"order_no\": \"MC20250908145253448493\",\n" + |
||||
|
" \"start_time\": \"2025-09-08 14:52:59\",\n" + |
||||
|
" \"end_time\": \"2025-10-08 14:52:59\",\n" + |
||||
|
" \"order_type\": 3,\n" + |
||||
|
" \"project\": \"可多次进出\",\n" + |
||||
|
" \"number\": 7,\n" + |
||||
|
" \"v_code\": [\"2509087910\"],\n" + |
||||
|
" \"info\": {\n" + |
||||
|
" \"card_no\": \"2509085314\",\n" + |
||||
|
" \"rest_number\": 5,\n" + |
||||
|
" \"status\": 1,\n" + |
||||
|
" \"verify_desc\": \"人脸验证(pad: 乐动退回pad(第6批)+pad_ab)\",\n" + |
||||
|
" \"verify_time\": \"2025-09-10 11:26:36\",\n" + |
||||
|
" \"verify_type\": 2\n" + |
||||
|
" },\n" + |
||||
|
" \"success\": 2,\n" + |
||||
|
" \"pv_usage_duration\": 0,\n" + |
||||
|
" \"many_enter\": false\n" + |
||||
|
" }\n" + |
||||
|
" },\n" + |
||||
|
" \"message\": \"\",\n" + |
||||
|
" \"extension\": \"过滤后端调试extension\"\n" + |
||||
|
"}"; |
||||
|
|
||||
|
try { |
||||
|
PadApiService.VerifyOrderResponse response = gson.fromJson(jsonResponse, PadApiService.VerifyOrderResponse.class); |
||||
|
|
||||
|
assertNotNull("响应不应该为空", response); |
||||
|
assertNotNull("结果不应该为空", response.getResult()); |
||||
|
assertEquals("订单类型应该是3", 3, response.getResult().getOrderType()); |
||||
|
assertEquals("订单号应该匹配", "MC20250908145253448493", response.getResult().getOrderNo()); |
||||
|
|
||||
|
// 验证info字段可以被正确解析为对象 |
||||
|
String infoJson = gson.toJson(response.getResult().getInfo()); |
||||
|
JsonElement infoElement = JsonParser.parseString(infoJson); |
||||
|
|
||||
|
// 对于年月卡类型,info应该是对象 |
||||
|
if (response.getResult().getOrderType() == 3 && infoElement.isJsonObject()) { |
||||
|
JsonObject cardInfo = infoElement.getAsJsonObject(); |
||||
|
|
||||
|
assertTrue("应该包含card_no字段", cardInfo.has("card_no")); |
||||
|
assertTrue("应该包含verify_desc字段", cardInfo.has("verify_desc")); |
||||
|
assertTrue("应该包含verify_time字段", cardInfo.has("verify_time")); |
||||
|
|
||||
|
assertEquals("卡号应该匹配", "2509085314", cardInfo.get("card_no").getAsString()); |
||||
|
assertEquals("剩余次数应该是5", 5, cardInfo.get("rest_number").getAsInt()); |
||||
|
} |
||||
|
|
||||
|
System.out.println("年月卡核销测试通过: " + response.getResult().getOrderNo()); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
fail("年月卡核销数据解析失败: " + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 测试订单类型名称转换 |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testOrderTypeNameConversion() { |
||||
|
assertEquals("场次", getOrderTypeName(0)); |
||||
|
assertEquals("人次", getOrderTypeName(1)); |
||||
|
assertEquals("年月卡", getOrderTypeName(3)); |
||||
|
assertEquals("课程", getOrderTypeName(5)); |
||||
|
assertEquals("orderType:99", getOrderTypeName(99)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 模拟OrderVerificationResultActivity中的getOrderTypeName方法 |
||||
|
*/ |
||||
|
private String getOrderTypeName(int orderType) { |
||||
|
switch (orderType) { |
||||
|
case 0: |
||||
|
return "场次"; |
||||
|
case 1: |
||||
|
return "人次"; |
||||
|
case 3: |
||||
|
return "年月卡"; |
||||
|
case 5: |
||||
|
return "课程"; |
||||
|
default: |
||||
|
return "orderType:" + orderType; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 测试验证码格式化 |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testVerificationCodeFormatting() { |
||||
|
assertEquals("-", formatVerificationCode(null)); |
||||
|
assertEquals("-", formatVerificationCode("")); |
||||
|
assertEquals("123456", formatVerificationCode("123456")); |
||||
|
assertEquals("250910159819", formatVerificationCode("250910159819")); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 模拟OrderVerificationResultActivity中的formatVerificationCode方法 |
||||
|
*/ |
||||
|
private String formatVerificationCode(String code) { |
||||
|
if (code == null || code.isEmpty()) { |
||||
|
return "-"; |
||||
|
} |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 测试使用时长格式化 |
||||
|
*/ |
||||
|
@Test |
||||
|
public void testUsageDurationFormatting() { |
||||
|
assertEquals("无限制", formatUsageDuration(0)); |
||||
|
assertEquals("无限制", formatUsageDuration(-1)); |
||||
|
assertEquals("1小时到期", formatUsageDuration(1)); |
||||
|
assertEquals("24小时到期", formatUsageDuration(24)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 模拟OrderVerificationResultActivity中的formatUsageDuration方法 |
||||
|
*/ |
||||
|
private String formatUsageDuration(int duration) { |
||||
|
if (duration <= 0) { |
||||
|
return "无限制"; |
||||
|
} |
||||
|
return duration + "小时到期"; |
||||
|
} |
||||
|
} |
@ -0,0 +1,213 @@ |
|||||
|
# 订单核销结果页面重构说明 |
||||
|
|
||||
|
## 问题分析 |
||||
|
|
||||
|
### 原始问题 |
||||
|
在执行次卡核销(order_type=1)时出现错误: |
||||
|
``` |
||||
|
Expected BEGIN_OBJECT but was STRING at line 1 column 345 path $.data.result.info |
||||
|
``` |
||||
|
|
||||
|
### 根本原因 |
||||
|
服务端对于不同订单类型返回的 `info` 字段数据结构不一致: |
||||
|
|
||||
|
- **次卡核销 (order_type=1)**: `info` 字段为字符串类型 |
||||
|
```json |
||||
|
"info": "平时" |
||||
|
``` |
||||
|
|
||||
|
- **年月卡核销 (order_type=3)**: `info` 字段为对象类型 |
||||
|
```json |
||||
|
"info": { |
||||
|
"card_no": "2509085314", |
||||
|
"rest_number": 5, |
||||
|
"status": 1, |
||||
|
"verify_desc": "人脸验证...", |
||||
|
"verify_time": "2025-09-10 11:26:36", |
||||
|
"verify_type": 2 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
原有代码没有根据 `order_type` 正确处理这种数据结构差异。 |
||||
|
|
||||
|
## 解决方案 |
||||
|
|
||||
|
### 1. 重构 OrderVerificationResultActivity |
||||
|
|
||||
|
#### 1.1 数据结构优化 |
||||
|
```java |
||||
|
// 原始字段 |
||||
|
private TextView tvTitle, tvOrderNo, tvVerificationCode, tvOrderType, tvCardNo, tvStatus, tvMessage, tvProject; |
||||
|
private String orderNo, verificationCode, orderType, cardNo, status, message, project; |
||||
|
|
||||
|
// 重构后字段 |
||||
|
private TextView tvTitle, tvStatus, tvMessage; |
||||
|
private ScrollView scrollContent; |
||||
|
private LinearLayout layoutContent; |
||||
|
private int orderType; // 改为int类型 |
||||
|
private JsonElement orderInfo; // 支持多种数据类型 |
||||
|
private PadApiService.VerifyOrderResult verifyResult; // 完整核销结果 |
||||
|
``` |
||||
|
|
||||
|
#### 1.2 布局文件重构 |
||||
|
```xml |
||||
|
<!-- 原始静态布局 --> |
||||
|
<LinearLayout android:id="@+id/ll_order_info"> |
||||
|
<!-- 固定的订单号、验证码、订单类型等字段 --> |
||||
|
</LinearLayout> |
||||
|
|
||||
|
<!-- 重构后动态布局 --> |
||||
|
<ScrollView android:id="@+id/scroll_content"> |
||||
|
<LinearLayout android:id="@+id/layout_content" /> |
||||
|
</ScrollView> |
||||
|
``` |
||||
|
|
||||
|
#### 1.3 动态内容生成 |
||||
|
根据订单类型动态生成不同的显示内容: |
||||
|
|
||||
|
```java |
||||
|
private void buildOrderTypeSpecificContent() { |
||||
|
switch (orderType) { |
||||
|
case 0: // 场次核销 |
||||
|
buildSiteContent(); |
||||
|
break; |
||||
|
case 1: // 人次核销 |
||||
|
buildPeopleContent(); |
||||
|
break; |
||||
|
case 3: // 年月卡 |
||||
|
buildCardContent(); |
||||
|
break; |
||||
|
case 5: // 课程 |
||||
|
buildCourseContent(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. 根据 uni 端逻辑实现不同订单类型展示 |
||||
|
|
||||
|
#### 2.1 场次核销 (order_type=0) |
||||
|
显示场次信息列表: |
||||
|
- 场馆名称 |
||||
|
- 开始时间 |
||||
|
|
||||
|
#### 2.2 人次核销 (order_type=1) |
||||
|
显示人次相关信息: |
||||
|
- 验证码 |
||||
|
- 订单编号 |
||||
|
- 有效时间 |
||||
|
- 预订信息(info 字符串) |
||||
|
|
||||
|
#### 2.3 年月卡 (order_type=3) |
||||
|
显示年月卡信息: |
||||
|
- 验证码 |
||||
|
- 名称(项目) |
||||
|
- 卡号(NO.xxx) |
||||
|
- 核销方式 |
||||
|
- 核销时间 |
||||
|
|
||||
|
#### 2.4 课程 (order_type=5) |
||||
|
显示课程信息: |
||||
|
- 上课时间 |
||||
|
- 上课教练 |
||||
|
- 上课地点 |
||||
|
- 课程名称 |
||||
|
- 订单编号 |
||||
|
- 签到方式 |
||||
|
- 签到时间 |
||||
|
|
||||
|
### 3. 数据传递优化 |
||||
|
|
||||
|
#### 3.1 传递完整核销结果 |
||||
|
```java |
||||
|
// 在 OrderSelectionActivity 中 |
||||
|
if (data != null) { |
||||
|
intent.putExtra("verify_result", new Gson().toJson(data)); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 3.2 解析完整数据结构 |
||||
|
```java |
||||
|
// 在 OrderVerificationResultActivity 中 |
||||
|
String verifyResultJson = intent.getStringExtra("verify_result"); |
||||
|
if (verifyResultJson != null) { |
||||
|
verifyResult = gson.fromJson(verifyResultJson, PadApiService.VerifyOrderResponse.class).getResult(); |
||||
|
orderInfo = JsonParser.parseString(gson.toJson(verifyResult.getInfo())); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 4. 兼容性处理 |
||||
|
|
||||
|
#### 4.1 JsonElement 处理 |
||||
|
```java |
||||
|
private String getJsonString(JsonObject jsonObject, String key, String defaultValue) { |
||||
|
if (jsonObject.has(key) && !jsonObject.get(key).isJsonNull()) { |
||||
|
return jsonObject.get(key).getAsString(); |
||||
|
} |
||||
|
return defaultValue; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 4.2 不同数据类型检查 |
||||
|
```java |
||||
|
// 字符串类型处理 |
||||
|
if (orderInfo != null && orderInfo.isJsonPrimitive()) { |
||||
|
String bookingInfo = orderInfo.getAsString(); |
||||
|
} |
||||
|
|
||||
|
// 对象类型处理 |
||||
|
if (orderInfo != null && orderInfo.isJsonObject()) { |
||||
|
JsonObject cardInfo = orderInfo.getAsJsonObject(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 优势分析 |
||||
|
|
||||
|
### 1. 数据结构兼容性 |
||||
|
- ✅ 支持 info 字段的字符串和对象两种类型 |
||||
|
- ✅ 根据 order_type 智能选择处理方式 |
||||
|
- ✅ 避免 JSON 解析异常 |
||||
|
|
||||
|
### 2. 界面显示灵活性 |
||||
|
- ✅ 动态生成内容,支持不同订单类型 |
||||
|
- ✅ 滚动视图支持长内容显示 |
||||
|
- ✅ 统一的界面风格和交互 |
||||
|
|
||||
|
### 3. 代码可维护性 |
||||
|
- ✅ 模块化的内容构建方法 |
||||
|
- ✅ 清晰的数据传递流程 |
||||
|
- ✅ 完善的错误处理机制 |
||||
|
|
||||
|
### 4. 用户体验 |
||||
|
- ✅ 针对不同订单类型优化信息展示 |
||||
|
- ✅ 保持与 uni 端一致的信息结构 |
||||
|
- ✅ 友好的错误提示和状态显示 |
||||
|
|
||||
|
## 测试验证 |
||||
|
|
||||
|
创建了完整的单元测试来验证: |
||||
|
- 次卡核销数据解析 |
||||
|
- 年月卡核销数据解析 |
||||
|
- 订单类型名称转换 |
||||
|
- 验证码格式化 |
||||
|
- 使用时长格式化 |
||||
|
|
||||
|
## 使用说明 |
||||
|
|
||||
|
### 1. 次卡核销场景 |
||||
|
系统会自动检测 order_type=1,将 info 字段作为字符串处理,显示预订信息。 |
||||
|
|
||||
|
### 2. 年月卡核销场景 |
||||
|
系统会自动检测 order_type=3,将 info 字段作为对象处理,提取卡号、核销方式等详细信息。 |
||||
|
|
||||
|
### 3. 其他订单类型 |
||||
|
支持场次(order_type=0)和课程(order_type=5)核销,根据相应的数据结构显示内容。 |
||||
|
|
||||
|
## 注意事项 |
||||
|
|
||||
|
1. **数据完整性**: 确保从上级页面传递完整的 verify_result 数据 |
||||
|
2. **错误处理**: 当数据解析失败时,会降级显示基本订单信息 |
||||
|
3. **向后兼容**: 保持与现有调用方式的兼容性 |
||||
|
4. **性能优化**: 使用动态布局生成,避免冗余的视图创建 |
||||
|
|
||||
|
通过这次重构,彻底解决了不同订单类型核销时的数据结构不兼容问题,提升了用户体验和系统稳定性。 |
Write
Preview
Loading…
Cancel
Save
Reference in new issue