hailin
7aa81bc5ab
fix(mobile-app): 初始化遥测时传入已登录用户ID,避免session_start丢失userId
...
从SecureStorage读取已存储的userSerialNum,在TelemetryService初始化时
传入,确保老token登录用户的session事件携带正确的user_id。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 20:19:57 -08:00
hailin
5e6ab25199
fix(mining-app): 前台回来时也上传遥测队列(大厂标准做法)
...
- 进入后台(paused)时立即 flush:session_end + 设备信息不丢失
- 回到前台(resumed)时立即 flush:上传被强杀遗留事件 + 新 session_start
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 20:07:11 -08:00
hailin
be415c1eb6
fix(mobile-app): 前台回来时也上传遥测队列(大厂标准做法)
...
进入前台时 flush 覆盖两种场景:
1. 上次 paused flush 因网络失败未上传的事件
2. 上次被强杀(force-kill)遗留在本地队列的事件
app_session_start 事件(含设备信息)也在此时立即上传,
无需等待10条阈值或30秒定时器。
参考:Amplitude/Mixpanel 均在 session start 时触发 flush。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 19:14:16 -08:00
hailin
9e90294d0e
fix(mobile-app): App进入后台时立即上传遥测队列
...
问题:TelemetryService.dispose()从未被调用,导致forceUploadAll()
永远不执行;uploadIfNeeded()有10条最小阈值,单次会话只有2-3条
事件无法触发上传,设备信息永远留在本地队列。
修复:进入后台(paused)结束session后,立即调用uploadBatch()
绕过10条阈值,确保每次App切换到后台时都能上报事件。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 19:11:25 -08:00
hailin
6bca65e434
feat(telemetry): 设备字段提升为顶层结构化列(Amplitude 风格)
...
将 device_brand/device_model/device_os/app_version/locale 从 JSONB properties
提升为 analytics_event_log 表的独立列,并建立索引,支持亿级数据量下的高效
按设备维度查询和分组统计。
前端 (mining-app + mobile-app):
- toServerJson() 从 properties 中提取设备字段,以顶层字段发送给服务端
- 本地存储格式不变(properties 仍保留设备字段,便于离线队列完整性)
后端 (presence-service):
- Prisma schema: EventLog 新增 deviceBrand/deviceModel/deviceOs/appVersion/locale 列
- Migration: ALTER TABLE 添加 5 列 + 2 个索引
- DTO/Command: EventItemDto 接收顶层设备字段
- Entity: EventLog 新增 5 个字段及 getter
- Mapper: toDomain/toPersistence 映射新字段
- Handler: toEventLog 从 DTO 读取设备字段;SessionStartedEvent 优先使用顶层字段
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:50:48 -08:00
hailin
482df12f91
fix(telemetry): 将设备信息合并到每个事件的 properties 中上报服务端
...
设备信息(品牌/型号/OS/版本/语言)此前只存储在本地,对服务端分析毫无价值。
现在在 logEvent() 中将 deviceProps 合并到每个事件的 properties 字段,
使服务端 analytics 表能按设备维度进行统计分析。同时修复 _deviceContext 为
null 时的空指针异常(deviceContextId 使用 ?? '' 安全降级)。
适用于 mining-app 和 mobile-app 两端。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:42:14 -08:00
hailin
033d1cde42
feat(mining-app): 完善 telemetry 埋点 - 页面访问 + 用户行为
...
- 新增 TelemetryRouteObserver,挂载到 GoRouter 自动追踪全部页面访问
无需每个页面手动调用 logPageView(),覆盖 27 个路由
- user_providers: login_success/login_failed(区分密码/短信)、logout
- trading_page: buy_shares、sell_shares(含价格/数量/结果)、transfer_shares(含方向/金额/结果)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:36:04 -08:00
hailin
a2a318e24c
feat(mining-app): 集成 telemetry 遥测功能
...
- TelemetryService 添加 _accessToken 字段和 setAccessToken() 方法
- _getAuthHeaders() 现在返回 Bearer token 供心跳上报使用
- splash_page.dart: 启动时初始化 TelemetryService,已登录用户自动注入 userId 和 token
- user_providers.dart: 登录成功后注入 userId/token 并恢复上报;退出时暂停上报并清除 token;token 刷新时同步更新 telemetry token
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:29:06 -08:00
hailin
34603aac8e
fix(security): 修复修改登录密码页面加载失败时静默降级的 bug
...
与 change_payment_password_page 同款问题:加载失败时原本默默设为
hasPassword=true(注册时必须设置的假设),改为显示错误状态+重试按钮。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:17:53 -08:00
hailin
5df9c97794
fix(account): 修复 verifyLoginPassword/verifyPaymentPassword 未读取 TransformInterceptor 包装层的 bug
...
后端 TransformInterceptor 将所有响应包装为 { success, data: <实际数据> },
但两个 verify 方法直接读 response.data['valid'],导致始终得到 null == true → false,
用户输入正确密码也显示"密码错误"。修复为读取 response.data['data']['valid']。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 08:47:17 -08:00
hailin
405e7e407e
fix(security): 修复支付密码页面加载失败时静默降级为设置模式的 bug
...
加载状态失败时改为显示错误提示和重试按钮,避免误导用户认为未设置支付密码。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 08:19:44 -08:00
hailin
a1d284b6b5
fix(password): 统一登录密码和支付密码状态查询为服务端 API
...
问题:
- isPasswordSet() 读 SecureStorage,重装/换设备后丢失 → 页面误判未设密码
- isPaymentPasswordSet() 读 response.data['isSet'],但实际格式为
{ success, data: { isSet } },取到 null → 始终返回 false
修复:
- 后端新增 GET /user/password-status 接口(isLoginPasswordSet 方法)
- 前端 isPasswordSet() 改为调用服务端 API
- 两个方法均使用正确的 response.data['data']['isSet'] 解包
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 08:14:24 -08:00
hailin
728b11c2aa
fix(payment-password): 修复 isPaymentPasswordSet 响应解析错误
...
API 返回 { success: true, data: { isSet: true } },
但代码读的是 response.data['isSet'](顶层),
实际应该读 response.data['data']['isSet'](嵌套在 data 下)。
导致 isSet 永远为 null == true → false,
支付密码页面始终显示「设置」模式而非「修改」模式。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 08:12:46 -08:00
hailin
5752f7b545
fix(telemetry): 冷启动会话恢复时注入 access token 到 TelemetryService
...
checkAuthStatus() 从 SecureStorage 读取 token 后,只设置了 userId
而未调用 setAccessToken,导致 App 冷启动后心跳一直返回 401。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 07:43:19 -08:00
hailin
37b11a3db6
feat(telemetry): 接通心跳服务的 JWT 认证,启用实时在线统计
...
- TelemetryService: 添加 _accessToken 缓存字段,实现 setAccessToken/clearAccessToken
- _getAuthHeaders(): 返回 Bearer token(原为空 {},导致心跳 401)
- AccountService: 3 处登录成功后同步调用 setAccessToken(response.accessToken)
- MultiAccountService: 账号切换后从 SecureStorage 读取恢复的 token 并注入;
账号删除时同步调用 clearAccessToken()
presence-service 后端待部署后,心跳将开始正常工作,实时在线数和 DAU 数据可用。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 07:20:05 -08:00
hailin
6f912b1232
feat(payment-password): 添加忘记支付密码功能(全栈)
...
后端(identity-service,纯新增):
- user-application.service 添加 2 个方法:
sendResetPaymentPasswordSmsCode(userId) —— 发送验证码到已绑定手机(Redis key 独立)
resetPaymentPassword(userId, smsCode, newPassword) —— 验证码校验 + 格式校验 + bcrypt 更新
- user-account.controller 新增 2 个端点(均为 @ApiBearerAuth):
POST /user/send-reset-payment-password-sms
POST /user/reset-payment-password
前端(mobile-app,纯新增):
- account_service 新增 sendResetPaymentPasswordSmsCode / resetPaymentPassword 两个方法
- 新建 reset_payment_password_page.dart:验证码 + 新6位PIN,重置成功后自动返回
- 路由:RoutePaths / RouteNames / AppRouter 各新增 resetPaymentPassword 条目
- change_payment_password_page:旧密码输入框下方添加「忘记支付密码?」入口链接
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 07:04:20 -08:00
hailin
71774f301d
fix(payment-password): 优化未设置支付密码时的错误提示
...
- 识别后端"尚未设置支付密码"异常,显示明确引导语
"请先前往「我的」→「支付密码」完成设置",替代误导性的"请检查网络"
- 简化空输入提示逻辑(直接使用 widget.hint)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 06:21:25 -08:00
hailin
cad7ebe832
feat(payment-password): 添加支付密码功能(全栈)
...
后端(identity-service):
- Prisma schema 新增 paymentPasswordHash 字段及手动迁移脚本
- user-application.service 添加 4 个方法:isPaymentPasswordSet / setPaymentPassword /
changePaymentPassword / verifyPaymentPassword(纯新增,不修改已有逻辑)
- user-account.controller 新增 4 个端点:
GET /user/payment-password-status
POST /user/set-payment-password
POST /user/change-payment-password
POST /user/verify-payment-password → { valid: bool }
前端(mobile-app):
- account_service 新增 4 个方法对应后端 4 个接口
- 新建 change_payment_password_page.dart:6 位数字支付密码设置/修改页面
- 路由:RoutePaths / RouteNames / AppRouter 注册 /security/payment-password
- profile_page:'修改登录密码' 改为 '修改密码',下方新增 '支付密码' 入口
- PasswordVerifyDialog:新增 title / subtitle / hint 可选参数,支持登录/支付双模式
- planting_location_page:认种确认改为验证支付密码(verifyPaymentPassword)
- pre_planting_purchase_page:预种确认后追加支付密码验证步骤
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 06:18:40 -08:00
hailin
d91ff7b83a
docs(mobile-app): 完善认种密码校验相关代码注释
...
- PasswordVerifyDialog: 补充类级文档、onVerify 字段说明、_handleConfirm 流程注释
- account_service.verifyLoginPassword: 补充参数、响应格式、行为说明
- planting_location_page._verifyPasswordThenSubmit: 说明在认种流程中的插入位置与作用
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:55:07 -08:00
hailin
3a84315b64
fix(mobile-app): verifyLoginPassword 读取响应体 valid 字段
...
后端 POST /user/verify-password 返回 { valid: bool },不用 HTTP 状态码区分,
修正响应解析逻辑。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:53:16 -08:00
hailin
41fa6349bd
feat(mobile-app): 认种确认前新增登录密码校验弹窗
...
- 新增 PasswordVerifyDialog 弹窗,风格与主体 App 一致
- account_service 新增 verifyLoginPassword() 调用 POST /user/verify-password
- planting_location_page 在 PlantingConfirmDialog 确认后插入密码校验步骤
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:51:47 -08:00
hailin
8de92f2511
fix(mining-app): 已解锁上 null 时显示 0
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:41:00 -08:00
hailin
98c898769f
fix(mining-app): 已解锁上层数 = unlockedBonusTiers × 5
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:40:00 -08:00
hailin
353299fd75
fix(mining-app): 已解锁上层数改为动态数据,替换硬编码的 15
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 05:13:10 -08:00
hailin
b67dfa0f4c
fix(planting-service+app): 合并列表补充 totalPortions,前端显示实际份数而非订单数
...
- getMerges 批量查来源订单 portionCount,按合并分组求和后返回 totalPortions
- 预种明细合并卡片改用 totalPortions 显示份数,fallback 才用订单数
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:58:45 -08:00
hailin
2b07219046
fix(mining-app): sellRestrictionProvider 绑定用户身份,避免跨账号缓存污染
...
[问题] sellRestrictionProvider 未绑定用户身份。若先用预种账号(isRestricted=true)
测试,再切换为正式认种账号,Riverpod keepAlive 缓存会在 2 分钟内残留上一个用户
的 true 值,导致正式认种用户错误看到预种卖出限制提示。
[修复] 在 provider 内 watch userNotifierProvider.select((s) => s.accountSequence),
accountSequence 变化(登录/切换账号)时 provider 自动失效并重新请求。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 07:29:12 -08:00
hailin
59097203ae
fix(app): 预种计划入口移至自助申请授权页市团队下方
...
## 问题
上一版错误将「申请预种计划」入口放在「我」页面主区域,
实际需求是放在「自助申请授权」页的「申请市团队」卡片下方。
## 改动内容
### authorization_apply_page.dart
- import route_paths.dart
- `_buildAuthorizationTypes()` 将 `.map()` 展开为 for 循环,
在 AuthorizationType.cityTeam item 渲染完后插入 `_buildPrePlantingEntry()`
- 新增 `_buildPrePlantingEntry()` 方法:
· 样式:金色(0xFFD4AF37)卡片,左侧图标圆角背景 + 右侧箭头,
副标题「购买预种份额 · 查看明细 · 团队预种统计」
· 点击跳转 RoutePaths.prePlantingHub(预种计划 Hub 页)
### profile_page.dart(撤销上一版在主页的改动)
- 移除 `bool _isPrePlantingActive` 状态变量
- 移除 `_loadPrePlantingConfig()` 方法及 initState 调用
- 移除布局中的预种入口按钮区块
- 移除 `_buildPrePlantingEntryButton()` 方法
Hub 页面(pre_planting_hub_page.dart)和路由定义保持不变,
入口位置从「我」主页改为自助申请授权页的市团队下方。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 07:03:59 -08:00
hailin
8b48d80cd4
feat(app): 预种计划三按钮合并为单入口 Hub 页面
...
## 背景
「我」页面原有三个并排的预种按钮(预种购买 / 预种明细 / 团队预种),
三等分宽度导致文字拥挤,视觉层级也不清晰。
## 改动内容
### 新增文件
- `pre_planting_hub_page.dart`
预种计划 Hub 汇总页,竖排展示三个功能入口卡片:
· 预种购买(金色)→ /pre-planting/purchase
· 预种明细(棕色)→ /pre-planting/position
· 团队预种(绿色)→ /pre-planting/team
页面为纯导航页,无需加载数据;风格与 pre_planting_purchase_page
一致(渐变背景、金色「← 返回」Header、Inter 字体)。
### 修改文件
- `route_paths.dart` / `route_names.dart`
新增路由常量 prePlantingHub = '/pre-planting/hub'
- `app_router.dart`
注册 GoRoute(path: prePlantingHub) → PrePlantingHubPage
新增对应 import
- `profile_page.dart`
· 删除 `_buildPrePlantingButtons()`(三按钮并排 Widget)
· 删除已无用的三个导航方法:
_goToPrePlantingPurchase / _goToPrePlantingPosition / _goToTeamPrePlanting
· 新增 `_buildPrePlantingEntryButton()`:
满宽 48px 按钮,文字「申请预种计划」,点击跳转 Hub 页
· 布局调用处改为 _buildPrePlantingEntryButton()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 06:42:50 -08:00
hailin
979ba379c1
fix(text): 预种合并份数从5份改为10份
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 06:18:12 -08:00
hailin
4904337e97
revert(app): 回滚 C2C 页面的卖出限制
...
C2C 卖出的是积分值,不是积分股,不受预种卖出限制约束。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 05:57:24 -08:00
hailin
ee734fb7b9
feat(trading+app): 预种卖出限制 — 前端 UI 禁用 + 后端查询端点
...
trading-service:
- asset.controller.ts: 新增 GET /asset/sell-restriction,供 mobile app 查询当前用户限制状态
- application.module.ts: 导出 TradingSellRestrictionService
mining-app:
- api_endpoints.dart: 新增 sellRestriction 端点常量
- trading_remote_datasource.dart: 新增 getSellRestriction()(fail-open)
- trading_repository.dart/impl: 新增接口与实现
- trading_providers.dart: 新增 sellRestrictionProvider(2分钟缓存,fail-open)
- trading_page.dart: 卖出限制时显示红色提示文字并禁用"确认交易"按钮
- c2c_publish_page.dart: 发布卖出广告时显示红色提示文字并禁用发布按钮
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 05:51:56 -08:00
hailin
ac3adfc90a
feat(pre-planting): 新增预种积分股卖出限制(方案B纯新增)
...
限制仅有预种份数(未合并成棵)的用户卖出积分股,
直到用户完成首次预种合并后方可卖出。
=== 改动范围(全部 2.0 系统,纯新增)===
contribution-service:
- prisma/pre-planting/schema.prisma: 新增 PrePlantingSellRestrictionOverride 模型
- migrations/20260304000000: 对应建表 SQL
- src/pre-planting/application/services/sell-restriction.service.ts: 核心判断逻辑
isRestricted = has_pre_planting_marker AND !has_real_tree AND !admin_override
- src/api/controllers/pre-planting-restriction.controller.ts: 暴露内部接口
GET /api/v2/pre-planting/sell-restriction/:accountSequence (@Public)
POST /api/v2/pre-planting/sell-restriction/:accountSequence/unlock (@Public)
- src/api/api.module.ts: 注册新 controller 和 SellRestrictionService
trading-service:
- src/application/services/sell-restriction.service.ts: HTTP + Redis 缓存(TTL 60s)
fail-open:contribution-service 不可用时允许卖出,保障业务连续性
- src/application/services/order.service.ts: 卖单前增加限制检查(4行)
- src/application/application.module.ts: 注册 TradingSellRestrictionService
mining-admin-service:
- src/application/services/pre-planting-restriction.service.ts: 代理接口 + 审计日志
每次管理员解除操作均写入 AuditLog,保证严格可追溯性
- src/api/controllers/pre-planting-restriction.controller.ts:
GET /pre-planting-restriction/:accountSequence
POST /pre-planting-restriction/:accountSequence/unlock
- api.module.ts / application.module.ts: 注册新服务和接口
mining-admin-web:
- users.api.ts: 新增 getPrePlantingRestriction / unlockPrePlantingRestriction
- use-users.ts: 新增 usePrePlantingRestriction / useUnlockPrePlantingRestriction hooks
- users/[accountSequence]/page.tsx: 受限时在基本信息卡显示红色警告 + 解除按钮
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 05:04:57 -08:00
hailin
f8f37a2e33
fix(admin-web): 推荐树节点「团队预种」显示时减去本人份数
...
后端返回的 teamPrePlantingPortions 包含该节点自身的 selfPrePlantingPortions,
导致管理后台推荐树中「团队预种」数量比实际多出本人的份数。
修复:推荐树展示层(祖先节点 + 直推/展开节点两处)统一改为:
团队预种显示值 = teamPrePlantingPortions - selfPrePlantingPortions
用户详情卡片顶部的原始统计数字保持不变(供管理员核查原始数据)。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 22:19:05 -08:00
hailin
85c20adb0b
fix: add libc6-compat to builder stage for SWC binary
...
The Next.js SWC binary requires libc6-compat on Alpine Linux.
It was only installed in the deps stage but not the builder stage,
causing build failures on fresh (no-cache) Docker builds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:34:46 -08:00
hailin
54e22b4709
feat: add IT0 API URL to mobile-upgrade production config
...
Add NEXT_PUBLIC_IT0_API_URL=https://it0api.szaiai.com to enable
the IT0 App version management in the mobile-upgrade admin panel.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:19:21 -08:00
hailin
33ae08c90f
feat(mobile-upgrade): add IT0 App version management support
...
Add IT0 App as the third application in the mobile-upgrade frontend,
connecting to IT0's new version-service backend.
Changes:
- api-client.ts: added 'it0' to AppType union, APP_CONFIGS entry
pointing to NEXT_PUBLIC_IT0_API_URL (default: it0api.szaiai.com),
exported it0ApiClient instance
- version-repository-impl.ts: added it0VersionRepository instance,
updated constructor client selection for it0, updated
getVersionRepository() factory with switch/case
- page.tsx: added IT0 App to APP_LABELS, added emerald green toggle
button in app selector
- upload-modal.tsx: fixed existing bug where parsePackage() was
hardcoded to use mobile repository regardless of selected app;
now uses getVersionRepository(appType) dynamically
Backend: IT0 version-service at it0api.szaiai.com/api/v1/versions
Env var: NEXT_PUBLIC_IT0_API_URL
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:49:37 -08:00
hailin
92c305c749
fix(mobile-app): 待签署合同页文案改为"10份预种份额已合并为1棵树"
...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 03:40:49 -08:00
hailin
2f78899ceb
fix(mobile-app): 修复待签署合同页和持仓页硬编码"5份"的问题
...
预种方案已从5份/棵调整为10份/棵,但以下两处文案仍硬编码为5:
1. 待签署合同页 (pending_contracts_page.dart)
- 原: "5 份预种份额已合并,请签署合同以开启挖矿"
- 改: "{订单数} 笔预种订单已合并为 {树数} 棵树,请签署合同以开启挖矿"
- 使用 merge.sourceOrderNos.length 和 merge.treeCount 动态显示
2. 持仓页空状态 (pre_planting_position_page.dart)
- 原: "累计 5 份后将自动合成 1 棵树"
- 改: "累计 $_portionsPerTree 份后将自动合成 1 棵树"
- 使用已有常量 _portionsPerTree=10
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 03:30:42 -08:00
hailin
55f81ff329
fix(mobile-app): 修复多个页面时间显示为UTC而非北京时间的问题
...
后端数据库统一存储UTC时间,前端展示时需调用 .toLocal() 转换为
设备本地时区(中国地区即 UTC+8 北京时间)。
以下5个页面的 _formatDateTime() 方法缺少 .toLocal() 转换,
导致页面显示的时间比北京时间慢8小时:
- 预种合并详情页 (pre_planting_merge_detail_page.dart)
→ 合并时间、签署时间、挖矿开启时间
- 预种持仓页 (pre_planting_position_page.dart)
→ 购买时间、合并时间
- 合同签署页 (contract_signing_page.dart)
→ 合同签署时间
- 转让列表页 (transfer_list_page.dart)
→ 转让创建时间
- 转让详情页 (transfer_detail_page.dart)
→ 转让各状态时间
修复方式:在格式化前统一调用 dt.toLocal() 将UTC转为本地时区。
后端和数据库保持UTC不变,仅前端展示层做时区转换。
注:ledger_detail_page.dart 已使用 DateTimeUtils.formatDateTime()
(内含 .toLocal()),无需修改。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 03:11:48 -08:00
hailin
532be9a561
fix(pre-planting): 合并详情显示实际份数和金额,不再硬编码
...
问题:合并详情页"合并份数"显示订单条数(7)而非实际份数(10),
"总价值"硬编码 订单数×1887,每笔订单金额也硬编码 1,887。
修复:
后端 getMergeDetail:
- 新增 sourceOrders[] 含每笔订单的 portionCount + totalAmount
- 新增 totalPortions(总份数)和 totalAmount(总金额)
前端 PrePlantingMerge model:
- 新增 MergeSourceOrder 类
- 新增 sourceOrders/totalPortions/totalAmount 字段
前端合并详情页:
- "合并份数"用 totalPortions 替代 sourceOrderNos.length
- "总价值"用 totalAmount 替代硬编码计算
- 来源订单列表显示每笔实际金额和份数(多份时显示"N份"前缀)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:58:34 -08:00
hailin
d7f7d7082d
fix(pricing): 预种定价API常量同步 — 3566→1887, /5→/10, 正式认种15831不变
...
admin-service tree-pricing.service.ts:
- BASE_PORTION_PRICE: 3566 → 1887
- supplement 除数: /5 → /PORTIONS_PER_TREE(10)
- BASE_PRICE 保持 15831(正式认种价格不变)
- 移除 updateSupplement 中重复声明的 BASE_PRICE 局部变量
planting-service tree-pricing-admin.client.ts:
- fallback basePortionPrice/totalPortionPrice: 3566 → 1887
mobile-app tree_pricing_service.dart:
- 修正上次commit误改的 basePrice/totalPrice fallback: 18870 → 15831
- basePortionPrice/totalPortionPrice 保持 1887
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 01:56:21 -08:00
hailin
1e31d6d863
feat(mining-app): 市场数据3个小数字段显示后端返回的完整精度
...
原先 formatCompact/formatWithCommas 内部用 double 运算,double 有效精度
仅约15-16位,对大数值(如98亿)除以1e8后小数部分会丢失精度。
后端 3 个字段均为 Decimal(30,8),通过 toFixed(8) 返回 8 位小数。
新增 formatCompactFull / formatWithCommasFull 函数:
- _shiftDecimalLeft: 纯字符串小数点移位,不经 double 转换,零精度损失
- _addCommasFullPrecision: 整数部分加千位逗号 + 小数部分原样保留
- formatCompactFull: 万/亿缩写 + 完整后端精度(替代 formatCompact)
- formatWithCommasFull: 逗号分隔 + 完整后端精度(替代 formatWithCommas)
兑换页 (trading_page.dart) 修改:
- 剩余积分股: formatCompact(precision:4) → formatCompactFull (8位小数)
- 已分配积分股: formatCompact(precision:2) → formatCompactFull (8位小数)
- 积分股池: formatWithCommas → formatWithCommasFull (8位小数)
- 已销毁量: 保持 formatIntWithCommas 整数显示不变
贡献值页 (contribution_page.dart) 修改:
- 100亿销毁剩余量: formatAmount(4位) → formatCompactFull (完整精度)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:58:58 -08:00
hailin
af2afeda56
fix(admin-web): 修复申请照片页面响应解包多余一层导致数据为空
...
apiClient 响应拦截器已返回 response.data,页面不应再取 .data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 23:15:30 -08:00
hailin
0576733579
fix(mining-app): 统一贡献值页与兑换页的剩余积分股计算方式
...
贡献值页原先使用 sharePoolBalance API (Pool A + Pool B 余额) 显示剩余积分股,
兑换页使用公式 totalShares - totalMined - blackHoleAmount 计算。
两者显示结果不一致 (差异约 228,343 积分股)。
根本原因调查:
- mining-wallet-service 的 Kafka 消费者在服务重启期间丢失了部分事件
- Pool B 仅处理了 15.7% 的挖矿分配事件 (28,261/180,497)
- Pool A 遗漏了 66,591 笔销毁扣减事件
- 池账户余额是通过 Kafka 事件维护的记账台账,存在消费遗漏
- totalShares - totalMined - blackHoleAmount 公式基于实际挖矿和销毁数据计算,是数学上的 ground truth
- 实际用户分配和销毁均 100% 准确,仅池账户记账有偏差
修复方案:
- 贡献值页改用 marketOverviewProvider (同兑换页)
- 使用公式 totalShares - totalMined - blackHoleAmount 计算剩余积分股
- 两个页面现在显示完全一致的数据
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:28:55 -08:00
hailin
a7dd926877
fix(mining-app): 修复分配记录 distributionMinute 时间仍显示UTC的问题
...
上次修复使用了 DateTime.parse(t).toLocal(),但 distributionMinute
字符串不带 Z 后缀(如 "2026-03-03 05:09:00"),Dart 的 DateTime.parse
在无时区标识时默认当作本地时间处理,导致 .toLocal() 无效。
修复:解析前追加 Z 后缀,强制标记为 UTC,使 .toLocal() 正确转换
为北京时间(UTC+8)。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 21:26:47 -08:00
hailin
1621b75a47
feat(admin): 引荐关系树节点增加个人/团队预种份数展示
...
后端:
- ReferralNodeDto 新增 selfPrePlantingPortions, teamPrePlantingPortions
- user-detail.controller: getReferralTree 中并行调用
ReferralProxyService 批量获取所有节点的预种统计
(当前用户用 getPrePlantingStats,祖先+下级用 batchGetPrePlantingStats)
前端:
- ReferralNode 类型新增两个预种字段
- 引荐关系树节点(祖先链 + 递归展开节点)在"本人认种/团队认种"
下方新增一行"个人预种: X份 / 团队预种: Y份"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 19:16:02 -08:00
hailin
b59d5bda2d
fix(mining-app): 分配记录 distributionMinute 时间转换为北京时间
...
_shortTime 中 DateTime.parse() 后未调用 .toLocal(),
导致第一行的分配时间仍显示 UTC 而非北京时间(与第三行 createdAt 不一致)。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 18:52:03 -08:00
hailin
40731c08ea
fix(mining-app): 分配记录时间压缩防溢出 + 剩余积分股扣除销毁量
...
1. 分配记录页面:
- 时间格式从完整时间戳压缩为 "YY/MM/DD HH:mm:ss",节省空间
- 积分股字号从18缩至13,字体改为monospace便于对齐
- 外层加 Flexible 防止超出屏幕右边界
2. 兑换页面剩余积分股:
- 公式从 totalShares - totalMined
改为 totalShares - totalMined - blackHoleAmount
- 即:总量 - 已分配 - 已销毁 = 真正的剩余可分配量
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:27:05 -08:00
hailin
789a703ec8
fix(mining-app): 修复剩余积分股显示精度不足导致与总量看起来一样
...
问题:totalShares=100.02亿,totalMined≈18万,相减后≈100.0182亿,
但 formatCompact 在亿级别只保留2位小数,四舍五入后仍显示"100.02亿",
与总量无法区分。
修复:
- formatCompact 新增可选 precision 参数(默认2,向后兼容)
- 剩余积分股使用 precision=4,显示为"100.0182亿",可见差异
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:25:31 -08:00
hailin
ab78086f1e
feat(mining-app+admin): 挖矿记录积分股小数精度统一提升至13位
...
需求:每次预种叠加后的积分股变化极小,原有精度(APP 4位/后台 8位)
无法有效区分,需提升至13位小数以便查看细微变化。
修改范围:
- mining-admin-web: 分配记录 shareAmount 8→13 位小数
- mining-admin-web: 补发记录 amount 8→13 位小数
- mining-app: 补发记录 amount 4→13 位小数
(mining-app 分配记录已在上一次提交中修改)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 09:09:20 -08:00