Files
CloudHome-Ops/CHANGELOG.md

846 lines
67 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 云定制主页管理 (ops-console) — 变更清单
> 包名:`com.ainetv.aishicloud` · version:`1.0.0+1`
> 标记说明:✅ 已实现 / 已修复 | ⏳ 待后端 / 待验收 | ❌ 已知问题
> 所有 release APK 均通过 `flutter analyze --no-fatal-infos`(0 error)
---
## v6-fix68(2026-08-29)
> **修复两个功能性 Bug:① 设备列表「删除」按钮点击后返回「删除失败:未知异常,请联系管理员」;② 设备详情页修改房号后不生效,页面仍显示 Web 端原有房号。**
>
> **根因(通过静态分析 Web 前端 JS 逆向真实后端契约得出)**:
> 1. **删除失败**:后端 `POST /device/info/delete` 接收的是**裸 JSON 数组** `[id]`(Web 端 `Ao(t)` → `$.adornData(t,!1)` → `JSON.stringify([id,...])`)。旧实现误传 `{'ids':[id]}`(对象),后端无法解析请求体 → 抛出「未知异常」。
> 2. **房号不保存**:后端设备表房号字段名为 **`roomId`**(Web 端设备编辑表单 `prop:'roomId'`、列表列 `prop:'roomId'` 标签「房间号」)。旧实现发送 `roomNo`,后端忽略该未知字段 → 更新“成功”但房号未变,刷新后回显旧值。
>
> **改动**:
> 1. `lib/services/device_service.dart`:
> - `deleteDevice(id)`:body 由 `{'ids':[id]}` 改为裸数组 `[id]`,并在 id 可转为数字时按数字发送(复刻 Web 端 `Yo()` 的 `Number(id)` 行为),兼容数字/字符串两种主键;
> - `activateDeviceByScan`:扫码激活的房号字段 `roomNo` → `roomId`(update 与 save 两条路径)。
> 2. `lib/screens/home/device_detail_page.dart`:`DeviceGroupRoomSheet` 提交时房号字段 `roomNo` → `roomId`。
> 3. `lib/models/device.dart`:`toJson` 房号输出键 `roomNo` → `roomId`(`fromJson` 读取别名列表保留 `roomNo`/`roomId` 等,确保后端返回 `roomId` 时正确回显)。
> 4. 版本号 `v6-fix67` → `v6-fix68`。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备服务 | `deleteDevice(id)`:删除 body 由 `{'ids':[id]}` 改为裸数组 `[id]`,对齐 Web 端契约 |
| ✅ 已修复 | 设备/扫码 | 房号字段统一为后端 `roomId`(update/save/详情编辑/toJson) |
| ✅ 已更新 | 版本号 | 登录页版本号 `v6-fix67` → `v6-fix68` |
---
## v6-fix67(2026-08-31)
> **设备列表页「向左滑动显示删除按钮」手势。**
>
> 用户需求:在「设备 MAC 排序」列表中,为每个 MAC 项增加**向左滑动**手势,滑动后**露出删除按钮**(位于行右侧);**点击删除按钮**才弹出确认对话框,确认后执行删除、取消则保留。
>
> **实现选择**:原先用 `Dismissible` 会在滑动过阈值松手时**自动**弹确认框(红色背景仅为装饰、不可点击),不满足「先露按钮再点击」的交互。故改用 `flutter_slidable`(新增依赖 `flutter_slidable: ^3.1.0`):**向左滑**揭示一个**可点击**的红色「删除」动作(位于行右侧),点击动作才触发确认框。
>
> **改动**:
> 1. `pubspec.yaml`:新增 `flutter_slidable: ^3.1.0`(`flutter pub get` 已解析)。
> 2. `lib/services/device_service.dart`:新增 `deleteDevice(String id)`——`POST /device/info/delete`,body 为 `{ids: [id]}`,与分组/用户等模块的删除接口约定保持一致。
> 3. `lib/screens/home/device_list_page.dart`:
> - 列表 `itemBuilder` 每行用 `Slidable`(`endActionPane` + `ScrollMotion`)包裹,**向左滑动**揭示右侧红色 `SlidableAction`(删除图标 + `t.delete` 文案);`dismissible: null` 表示不自动滑出删除,仅作按钮;
> - `SlidableAction.onPressed` 调用 `_confirmDeleteDevice(device)`:弹出 `AlertDialog`(标题 `确认删除` / 内容 `是否确认删除该设备(设备名/MAC)?`,取消/删除按钮);
> - 取消 → 返回 `false`,行回弹保留;
> - 确认 → 先入 `_pendingDeleteIds` 本地隐藏该行(避免抖动),再调 `deleteDevice`,成功 `ref.invalidate(devicesByStoreProvider)` + SnackBar `已删除`,真正由列表刷新移除;异常则移出 `_pendingDeleteIds` 恢复显示并 SnackBar `删除失败:{原因}`;
> - 新增 `_rowKey(Device)`:`${id 或空}__${mac}` 作为行唯一键,避免 `device.id` 为空时 `ValueKey('')` 重复触发 Flutter 断言崩溃;`_filter` 与 `_pendingDeleteIds` 均改用该键。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 新增 | 依赖 | `pubspec.yaml`:`flutter_slidable: ^3.1.0` |
| ✅ 新增 | 设备服务 | `lib/services/device_service.dart`:`deleteDevice(id)` 调 `POST /device/info/delete`,body `{ids:[id]}` |
| ✅ 新增 | 设备列表 | `lib/screens/home/device_list_page.dart`:每行 `Slidable`(startToEnd)露可点击红色「删除」按钮,点击才弹确认框;确认删除、取消保留;删除中本地隐藏防抖、失败回滚并提示;`_rowKey` 防空 id 重复键 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix66` → `v6-fix67` |
---
## v6-fix66(2026-08-31)
> **修复 Web 端删除设备后,手机端设备列表仍显示已删除设备的问题。**
>
> 用户反馈:在 Web 后台删除设备后,手机端设备列表未同步,仍显示该设备 MAC(如截图中 `08:C3:ED:64:16:72`)。
>
> **根因**:设备列表页使用 `devicesByStoreProvider`(Riverpod `FutureProvider.family`)做缓存,但只在「下拉刷新」「切换门店筛选」「扫码成功」三种场景下刷新;日常从 Web 端删除设备后,手机端没有自动刷新机制,因此一直展示旧缓存。
>
> **改动**:
> 1. `lib/screens/home/device_list_page.dart`:
> - `_DeviceListPageState` 混入 `WidgetsBindingObserver`,在 `initState`/`dispose` 中注册/注销生命周期监听;
> - 新增 `didChangeAppLifecycleState` 处理:应用从后台切回前台(`AppLifecycleState.resumed`)且跳过首次启动事件后,调用 `ref.invalidate(devicesByStoreProvider)` 失效所有门店筛选参数下的设备列表缓存,并静默刷新门店列表,使手机端与 Web 端数据同步;
> - 合并原有的 `dispose` 方法,确保 `removeObserver` 与控制器释放同时执行。
> 2. `lib/screens/device/device_scan_page.dart`:
> - 将扫码查询命中后端设备、扫码绑定/激活成功后的 `ref.invalidate(devicesProvider)` 改为 `ref.invalidate(devicesByStoreProvider)`,失效门店筛选 Provider 族(而不是只失效无参数的 `devicesProvider`),确保返回列表页时看到最新数据。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备列表 | `lib/screens/home/device_list_page.dart`:App 切回前台自动刷新设备/门店列表,同步 Web 端变更 |
| ✅ 已修复 | 扫码页 | `lib/screens/device/device_scan_page.dart`:扫码成功后失效 `devicesByStoreProvider` 全族,而非仅 `devicesProvider` |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix65` → `v6-fix66` |
---
## v6-fix65(2026-08-31)
> **扫码绑定流程改造:扫码成功后改为页面内表单(门店分组下拉 + 房号输入),替代原先分步弹窗。**
>
> 新增需求:扫码成功后,在页面上直接提供「门店分组」选择控件(从分组列表选取)与「房号」输入框,并明确两者的校验规则,保证移动端布局清晰、操作便捷。
>
> **改动**:`lib/screens/device/device_scan_page.dart`
> 1. 移除原 `_confirmBind` / `_pickGroup` / `_pickStore` 三步弹窗流程。
> 2. 新增 `_enterBindForm`:扫码识别 MAC 后进入表单态,停止相机预览;
> - 激活模式(无预填门店):加载门店下拉,选择门店后级联加载其分组;
> - 绑定模式(路由已预填门店):门店固定展示,直接加载分组。
> 3. 新增 `_buildBindForm`(SingleChildScrollView + Card):已识别设备 MAC 卡片、门店分组 `DropdownButtonFormField`、房号 `TextFormField`、提交/重新扫码按钮;分组加载中显示进度。
> 4. 校验规则(`_validateForm`):
> - 门店:仅激活模式必填(未选门店 → `请先选择门店`);
> - 门店分组:必填(未选 → `请选择门店分组`;门店无分组 → `该门店暂无设备分组…`);
> - 房号:必填且格式校验(非空、长度 1~20、仅含中英文/数字/连字符/斜杠,否则 `房号格式不正确…`);错误信息以输入框 `errorText` 实时回显。
> 5. `_submitBind` 校验通过调 `DeviceService().activateDeviceByScan(mac, storeId, groupId, roomNo)`(后端 `roomNo` 字段已支持),写本地绑定缓存并 `ref.invalidate(devicesProvider)`,成功 SnackBar 后 `context.pop()`;登录失效仍兜底跳 `/login`。
> 6. i18n(`lib/utils/i10n.dart`)新增 `scanGroupLabel` / `scanStoreRequired` / `scanGroupRequired` / `scanRoomRequired` / `scanRoomInvalid` / `scanRoomHint` / `scanRescan` / `scanFormInvalid` / `scanFormDeviceTitle`。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 新增 | 扫码页 | 页面内绑定表单:门店分组下拉 + 房号输入 + 实时校验 + 提交/重扫 |
| ✅ 已删除 | 扫码页 | 移除 `_confirmBind` / `_pickGroup` / `_pickStore` 分步弹窗 |
| ✅ 新增 | i18n | 表单相关中文/英文文案 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix64` → `v6-fix65` |
---
## v6-fix60(2026-08-31)
> **修复设备详情页在线状态与 Web 端不一致的问题。**
>
> 用户截图:手机端设备详情页状态显示「离线」,但 Web 端同一设备显示「在线」。根因:移动端详情接口 `/device/info/info/{id}` 返回的 `status`/`lastSyncTime` 滞后于列表接口 `/device/info/list`;Web 端设备列表正是走列表接口并显示最新在线状态。修复后详情页先用 ID 取详情,再用设备 MAC 从列表接口拉一次最新状态并合并,保证手机端详情与 Web 端列表/心跳一致。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备模型 | `lib/models/device.dart`:新增 `copyWith` 方法;`lastSyncTime` 解析兼容 `heartbeatTime` / `heartbeat_time` / `lastHeartbeat` / `last_heartbeat` 等 Web 端心跳命名字段 |
| ✅ 已修复 | 设备服务 | `lib/services/device_service.dart`:`fetchDeviceById` 在拿到详情后调用 `_mergeWithFreshListStatus`:用 `detail.mac` 调 `/device/info/list?mac=...&limit=1` 取最新状态,合并 `status` 与 `lastSyncTime`;列表接口失败时不阻断详情展示 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix59` → `v6-fix60` |
---
## v6-fix61(2026-08-31)
> **修复「设备列表页扫码时弹出『查询失败:登录已过期,请重新登录』」的体验问题。**
>
> 用户操作:设备列表页右上角二维码(`mode=query` 扫码查设备,注意:真正「激活」是底部 + 按钮 `mode=bind&activate=1`)→ 触发 `_queryDevice` 读取 `devicesProvider.future`(`GET /device/info/list`)。当 token 过期:拦截器 `ApiInterceptor.onResponse` 识别到 `code==401` 或「token失效/登录」类文案 → 调 `_refreshAndRetry` 自动刷新令牌并重试一次;若刷新令牌也失败(refreshToken 失效),则 `_notifyUnauthorized()` 清空令牌并触发 `authProvider.onUnauthorized` 回调 → 路由 `redirect` 自动跳 `/login`。问题是拦截器最终 `handler.reject(ApiException(401,'登录已过期,请重新登录'))`,该异常冒泡到扫码页 catch,被 `formatApiError` 取出后以「查询失败:登录已过期,请重新登录」SnackBar 暴露给用户,与登录页跳转形成竞态/困惑。
>
> **修复**:新增 `isUnauthorizedError()` 统一识别登录失效异常;扫码页 4 个 catch(查设备 / 激活绑定 / 加载门店 / 加载分组)在捕获到登录失效时**不再展示底层错误**,直接 `context.go('/login')` 兜底跳转(与拦截器已触发的路由重定向一致)。自动刷新令牌能力保持不变(首次 401 静默重试成功则用户无感)。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 网络层 | `lib/services/api_client.dart`:新增顶层 `isUnauthorizedError(Object?)`,识别 `ApiException(401)` 或含「登录已过期」文案的登录失效异常,供 UI 层复用 |
| ✅ 已修复 | 扫码页 | `lib/screens/device/device_scan_page.dart`:`_queryDevice` / `_confirmBind` / `_pickGroup` / `_pickStore` 四处 catch 增加登录失效判断——命中时跳过错误 SnackBar、改为 `context.go('/login')` 引导重新登录 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix60` → `v6-fix61` |
---
## v6-fix64(2026-08-31)
> **设备列表页登录过期错误提示与扫码页保持一致。**
>
> 之前设备列表页加载失败时,若遇登录过期,会整屏显示「设备列表加载失败:登录已过期,请重新登录」+ 重试按钮(仍用 `error.toString()` 暴露底层异常)。拦截器已通过 `_notifyUnauthorized` 清空令牌并触发路由 `redirect` 跳 `/login`,此处应与之对齐——不再展示底层错误。
>
> **改动**:`lib/screens/home/device_list_page.dart` 的 `devicesAsync.when(error:)` 分支:
> - 命中登录失效(`isUnauthorizedError`)时不再展示错误文案与重试按钮,改为显示加载占位(路由重定向会接管跳登录页);
> - 其他错误改用 `formatApiError(error)` 输出友好文案(不再直接 `error.toString()`)。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备列表 | `lib/screens/home/device_list_page.dart`:error 分支登录失效走加载占位、其余错误用 `formatApiError`;补充 `api_client.dart` 导入 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix63` → `v6-fix64` |
---
## v6-fix63(2026-08-31)
> **精简设置页与设备列表页:移除运行环境切换、显示运行指标开关及设备列表右上角二维码按钮。**
>
> 用户反馈(截图):设置页中「运行环境」与「显示运行指标」两项不需要继续展示,要求直接删掉;设备列表页右上角二维码按钮也直接删掉。
>
> **改动**:
> 1. `lib/screens/profile/settings_page.dart`:
> - 移除「运行环境」`_AppSettingsTile`(含测试/预发切换、`AppConfig.switchEnvironment`、`ApiClient.instance.reconfigure` 等逻辑)。
> - 移除「显示运行指标」`SwitchListTile` 及其对应的 `showRuntimeMetricsProvider` watch。
> - 删除不再使用的导入:`metrics_visibility_provider`、`app_config`、`environment`、`staging_config`、`test_config`;删除不再使用的 `_envLabel` 私有方法。
> 2. `lib/screens/home/device_list_page.dart`:移除 AppBar `actions` 中的二维码 `IconButton`(`mode=query` 扫码入口)。底部的「扫码激活」浮动按钮(`mode=bind&activate=1`)保留。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已删除 | 设置页 | `lib/screens/profile/settings_page.dart`:删除运行环境切换与显示运行指标开关 |
| ✅ 已删除 | 设备列表 | `lib/screens/home/device_list_page.dart`:删除 AppBar 右上角二维码按钮 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix62` → `v6-fix63` |
---
## v6-fix62(2026-08-31)
> **修复扫码查询「未找到设备。请确认二维码内容」且无法识别/连接设备的问题,并优化错误提示。**
>
> 用户反馈:设备列表页右上角扫码后弹「未找到设备。请确认二维码内容」,未能跳转到对应设备。
>
> **根因**:`device_scan_page.dart` 的 `_queryDevice`(query 模式)把扫码 **原始字符串** 直接与 `device.mac`/`deviceSn`/`id` 做小写等值比较,但 bind 模式会用 `_extractMacFromPayload` 解析 JSON(`{"mac":"..."}`)或归一化 MAC。因此当二维码是 `{"mac":"..."}` JSON,或 MAC 分隔符/大小写与设备记录不一致时,query 模式永远匹配不到 → 误报「未找到设备」。此外原提示「请确认二维码内容」过于笼统,对「二维码有效但设备尚未联网/同步」的场景没有引导。
>
> **修复**:
> 1. query 模式新增 `_parseScanPayload`:解析纯 MAC(兼容 `:`/`-`/无分隔)与 JSON(`mac`/`id`/`sn`/`deviceUid` 等字段),并用 `_normalizeMac` 归一化比较,忽略分隔符与大小写差异。
> 2. 本地列表未命中时,按 MAC/SN 调后端 `/device/info/list` 兜底查询,覆盖「设备已激活但本地缓存尚未刷新」的场景,命中即刷新列表并跳转详情。
> 3. 错误提示分级:二维码无法识别(非 MAC 也非含标识的 JSON)→ 「无法识别该二维码,请扫描设备二维码或确认二维码内容」;二维码有效但设备暂未就绪 → 「未找到对应设备(MAC)。设备可能尚未联网或同步,请确认设备已开机联网、已激活并归属当前账号后重试」。
> 4. 登录失效(401)仍按既有逻辑兜底跳登录页。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 扫码页 | `lib/screens/device/device_scan_page.dart`:`_queryDevice` 改为先解析 payload(MAC/JSON)、归一化 MAC 比较;本地未命中时按 MAC/SN 后端兜底查询并跳转详情 |
| ✅ 已修复 | 扫码页 | 新增静态 `_parseScanPayload`(兼容纯 MAC 与 JSON)与 `_normalizeMac`(去分隔符转大写) |
| ✅ 已优化 | 文案 | `lib/utils/i10n.dart`:新增 `scanPayloadUnrecognized`(无法识别二维码)、`scanDeviceNotReady(identifier)`(设备暂未就绪引导);原 `scanDeviceNotFound` 不再使用 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix61` → `v6-fix62` |
---
## v6-fix59(2026-08-28)
> **修复环境切换后再操作导致的 `LateInitializationError`**。
>
> 用户截图显示报错:`LateInitializationError: Field '_dio' has already been initialized.`,调用栈指向 `ApiClient.reconfigure` → `SettingsPage` 的环境切换 SegmentedButton。根因:`ApiClient._dio` 声明为 `late final`,`reconfigure()` 重复调用时会二次赋值,触发 LateInitializationError。修复后支持重复重建,并在环境/语言切换入口增加 try-catch,避免崩溃。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 网络层 | `lib/services/api_client.dart`:`ApiClient._dio` 由 `late final` 改为 `late`,`reconfigure()` 改为返回 `({bool success, String? error})`,允许安全重复调用,内部捕获异常避免抛崩溃 |
| ✅ 已修复 | 设置页 | `lib/screens/profile/settings_page.dart`:环境切换 closure 加 try-catch,先切换环境、重建 Dio、提示「环境已切换,请重新登录」、再 logout;失败时 SnackBar 提示错误。语言切换也加 try-catch 防御 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix58` → `v6-fix59` |
---
## v6-fix58(2026-08-28)
> **移除登录页副标题「设备与系统运维监控」**。
>
> 用户截图显示登录页标题「云定制主页管理」下方仍显示「设备与系统运维监控」一行副标题文字,需移除该文本及相关 UI 元素,保持页面其余布局与功能不受影响。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已移除 | 登录页 | `lib/screens/login/login_page.dart` 删除副标题 `Text('设备与系统运维监控')` 及其上方的 `SizedBox(height: 8)`,登录页不再展示该文字 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix57` → `v6-fix58` |
---
## v6-fix57(2026-08-28)
> **优化 App「用户管理-新增用户」交互(依据 MuMu 录屏 `云定制主页管理(5).mp4`)**。
>
> 打开新增/编辑用户弹窗时,系统默认先弹出「所属组织」选择界面,打断了先填表单(用户名/账号/密码等)的正常流程。调整为:组织选择置于表单填写完成之后,仅用户主动点击「所属组织」字段才展开。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 用户管理-新增/编辑用户 | `lib/screens/admin/sys/user_page.dart` 的 `_UserForm` 中 `OrgPickerFormField` 显式设置 `autoOpen: false`,打开用户表单不再自动弹出组织选择器,需先填完必填信息、再主动点击字段触发 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix56` → `v6-fix57` |
---
## v6-fix56(2026-08-28)
> **修复 App 组织选择器交互问题(依据 MuMu 录屏 `云定制主页管理(2)(3)(4).mp4`)**。
>
> 视频2/3:门店管理 → 点击编辑门店时,「所属组织」选择器在表单打开后自动弹出,影响操作流程。视频4:用户管理 → 编辑用户 → 点击「所属组织」选择器后,列表未将当前已选组织置顶,导致需滑动查找。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 门店编辑页 | `lib/screens/store/store_list_page.dart` 中 `_StoreFormSheet` 的 `OrgPickerFormField` 显式设置 `autoOpen: false`,进入编辑/新增表单时不再自动弹出组织选择器,仅用户主动点击字段时才展开 |
| ✅ 已修复 | 组织选择器 | `lib/widgets/org_picker_sheet.dart` 中 `OrgPickerSheet` 在过滤后把当前 `_selectedId` 对应的选项置顶到列表最前面,保持其他项原有层级顺序;搜索关键字为空时当前选择一目了然,搜索时仍按名称过滤 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix55` → `v6-fix56` |
---
## v6-fix55(2026-08-27)
> **用户纠正**:v6-fix54 后加的"服务器地址"自由输入设置实为一条可切生产的入口(可手填生产 IP `58.220.83.58`),违反"仅测试环境"要求。本版本移除该入口,改为"强制防生产切换"机制:默认锁定测试环境,永不可进入生产模式。
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已重构 | 环境配置 | 新增 `lib/config/` 模块:`AppEnvironment` 枚举(仅 `test` / `staging`,**刻意不含 production**)、`TestConfig` / `StagingConfig`(测试/预发敏感配置分文件独立管理,禁止交叉引用生产)、`AppConfig`(按当前环境解析 origin/前缀/验证码开关,默认 `test`)、`ProductionGuard`(启动期强制守卫) |
| ✅ 已移除 | 可切生产入口 | 删除 `lib/utils/server_config.dart` 及设置页"服务器地址"自由输入对话框(原可手填任意 URL 含生产 IP);所有网络层(`api_client` / `auth_service` / `marquee_web_source`)改为读 `AppConfig` 当前环境地址 |
| ✅ 已新增 | 启动守卫 | `main.dart` 启动即调用 `ProductionGuard.enforce()`:① 编译期 `assert(!kAllowProduction)`(kAllowProduction 恒 false);② 运行期解析当前后端 host,若命中生产拒绝名单(`58.220.83.58` / `api.ainetv.com` / `ainetv.com` 或含 `prod`)则 `print` 清晰报错并 `exit(1)` 阻断启动,确保系统无法进入生产模式 |
| ✅ 已新增 | 环境切换函数 | `AppConfig.switchEnvironment(AppEnvironment)` 仅接受预定义 `test` / `staging` 枚举值,显式排除 production,禁止解析/接收任何生产标识字符串;设置页仅提供 TEST/预发 分段切换(无生产按钮) |
| ✅ 已清理 | 常量 | `AppConstants` 移除 `apiBaseUrl` / `apiFullBaseUrl` / `enableCaptcha`(原注释含生产 IP 兜底),仅保留 `apiPrefix`;默认配置不再含任何生产默认值/兜底/隐式生产配置 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix54` → `v6-fix55` |
| ✅ 已验证 | 构建 | `flutter analyze` 0 error;release APK `ops-console-release-v6-fix55.apk`(33.9MB)验鲜:`v6-fix55`=1、`ProductionGuard`=1、默认 origin=`ips-test.ainetv.com`(测试);prod-ip 仅出现在守卫拒绝名单中,非活动端点 |
---
## v6-fix54(2026-08-27)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | Web-公司介绍内容展示 | 修复 Web 端「公司介绍」选中大类后右侧小类列表偶发"暂无数据"的问题:`parseIntroSubcategoryList` / `parseIntroDetailList` 现在兼容后端 `{data: [...]}` 与 `{data: {list, totalCount}}` 两种返回;`loadChildren` 始终以当前 `treeData` 重新定位节点,避免 `selectedNode` 引用过期,并在 API 解析为空时使用树节点已携带的完整小类数据兜底展示 |
| ✅ 已优化 | App-公司介绍数据加载 | 门店内容页加载大类由 `getIntroCategoryAll()` + 客户端过滤改为 `getIntroCategoryList(page, limit, orgId)` 服务端过滤,与 Web 端 `store-company-intro.vue` 的查询方式完全一致,避免门店权限/分页差异导致数据不一致 |
| ✅ 已优化 | App-公司介绍保存字段 | 小类表单的 `categoryType` 预填父大类值;保存时若为空则不下发,与 Web 端保存行为对齐,避免空字符串覆盖后端默认值 |
| ✅ 已调整 | 文案 | 移除代码注释中的"门店维度"表述;管理首页入口、页面标题继续使用 `companyIntro`/`introCategoryTitle`(即"公司介绍") |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix53` → `v6-fix54` |
---
## v6-fix53(2026-08-27)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已重构 | 公司介绍-门店维度 | 将公司介绍入口从「大类→小类→详情」三级结构改为「门店维度」展示:新增 `IntroStoreListPage` 门店列表页(参考门店管理页布局,支持关键字搜索门店),点击进入 `IntroStoreContentPage` 门店介绍内容页 |
| ✅ 已新增 | 公司介绍-门店介绍内容页 | 按门店加载其下所有公司介绍大类,并按 `categoryType` 内容类型分组呈现;每个分类可展开查看小类,每个小类可展开查看详情内容;数据层级:门店 → 内容类型 → 分类 → 小类 → 详情 |
| ✅ 已保留 | 公司介绍-原管理入口 | 原 `/admin/intro/category` 三级管理页保留,可作为后台管理备用入口;管理首页公司介绍入口改为 `/admin/intro/store-list` |
| ✅ 已新增 | 公司介绍-快捷操作 | 门店内容页支持直接在分类/小类/详情层级新增、编辑、删除;新增分类时自动带入当前门店 `orgId` |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix52` → `v6-fix53` |
---
## v6-fix52(2026-08-27)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 公司介绍-小类/详情图片上传 | 根因:原 `UploadService.uploadImage` 走 `POST /sys/upload` 通用本地上传,与公司介绍模块(Web 端 `company-intro-subcategory-form.vue`)使用的腾讯云 COS STS 直传通道不一致,导致上传失败、无法保存。现改为对齐 Web 流程:计算文件 MD5 → `GET /theme/resource/check-md5` 秒传判断 → `GET /theme/upload/credential` 取 STS 临时凭证(bucket/region/key)→ 用临时密钥直传 COS(putObject,原生 q-sign-algorithm=sha1 签名)→ `POST /theme/resource/register` 注册 `tv_resource` → 返回 `https://ipscdn.ainetv.cn/{key}` 访问地址 |
| ✅ 已新增 | 依赖 | 新增 `crypto`(文件 MD5);腾讯云 COS 直传客户端 vendoring 到 `lib/services/cos/cos_client.dart`(基于 flutter_cos_client MIT 实现,修复签名有效期硬性 `now+3600` 与后端 STS `ExpiredTime` 冲突导致的 403,去掉未维护 0.0.1 包依赖与 mime 依赖) |
| ✅ 已完善 | 错误提示 | 上传各阶段(凭证不完整 / 云存储失败 / 资源注册失败)统一抛出 `ApiException`,message 为可展示中文;前端 `catch` 已能显示 `e.message`,便于定位失败原因 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix51` → `v6-fix52` |
---
## v6-fix51(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 公司介绍-新增/修改 | 修复「保存失败」提示不展示后端真实原因的问题:拦截器返回的是 `DioException`(其 `.error` 才是 `ApiException`),原代码 `e is ApiException ? e.message : t.saveFailed` 永远走兜底,导致只能看到「保存失败」。三个表单(大类/小类/详情)的保存与删除异常提示统一改为 `formatApiError(e)`,可正确显示后端 msg |
| ✅ 已修复 | 公司介绍-新增大类 | 与 Web 前端字段对齐:`saveIntroCategory` 请求体移除后端未使用的 `orgName`;`orgId` 在发送前 `int.tryParse` 转为数字,与 Web 一致 |
| ✅ 已新增 | 公司介绍-表单校验 | 新增大类/小类/详情保存前进行客户端校验:名称/标题/类型编码/媒体地址非空;类型编码禁止中文。减少无效请求,错误反馈更即时 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix50` → `v6-fix51` |
---
## v6-fix50(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 网络层(写操作失败) | `api_client.dart` 的 `ApiInterceptor.onRequest` 不再给 POST/PUT 等业务 body 注入防缓存字段 `t`:此前该字段会作为未知列进入后端 insert,导致「列表能读、新增/修改 500 失败」。防缓存 `t` 仅在 GET 的 query 上保留;写入请求 body 保持纯净 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix49` → `v6-fix50` |
---
## v6-fix49(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已更新 | 后端环境切换 | `constants.dart` 的 `apiBaseUrl` 由生产环境 `http://58.220.83.58` 切回测试环境 `https://ips-test.ainetv.com`(HTTPS);生产地址保留为注释便于后续切回 |
| ✅ 已恢复 | 登录验证码 | `enableCaptcha` 由 `false` 恢复为 `true`:测试环境 `ips-test.ainetv.com` 提供 `/api/captcha.jpg`(HTTP 200),登录页重新显示验证码输入与刷新 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix48` → `v6-fix49` |
---
## v6-fix48(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已更新 | 桌面/启动图标 | 将 `com_ainetv_ainetvcloud_xxxhdpi_640dpi_云管理.png` 生成并覆盖 Android 5 个密度(mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi)的 `mipmap-*/ic_launcher.png` |
| ✅ 已更新 | 登录页 logo | 登录页继续使用 `assets/images/logo.png`,已同步替换为同一源图 |
| ✅ 已更新 | 应用内图标 | 设置页「关于」弹窗新增 `applicationIcon`,统一使用 `assets/images/logo.png`(64×64 显示) |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix47` → `v6-fix48` |
---
## v6-fix47(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 登录页 logo | 将顶部蓝色 `Icons.monitor_heart` 图标替换为橙色云朵 logo 图片(`assets/images/logo.png`,文字已改为「云管理」,保留底部「ainetv」) |
| ✅ 已修复 | 验证码加载失败 | 新增 `AppConstants.enableCaptcha` 开关(默认 `false`):关闭时隐藏验证码输入区与刷新按钮,登录时 captcha/uuid 传空串,避免生产环境 `58.220.83.58` 未暴露 `/api/captcha.jpg` 导致登录页报错 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix46` → `v6-fix47` |
---
## v6-fix46(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已更新 | 后端环境切换 | `constants.dart` 的 `apiBaseUrl` 由测试环境 `https://ips-test.ainetv.com` 切换为生产环境 `http://58.220.83.58`(明文 HTTP,Android 已开启 `usesCleartextTraffic`);测试地址保留为注释便于回退 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix45` → `v6-fix46` |
---
## v6-fix45(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 手机端全屏显示 | 修复应用在手机上未全屏、四周出现黑边的问题:`main.dart` 启动时设置 `SystemUiMode.edgeToEdge` + 透明状态栏/导航栏;`styles.xml` / `styles-night.xml` 的 `NormalTheme` 启用 `windowDrawsSystemBarBackgrounds` 并将状态栏/导航栏颜色设为透明 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix44` → `v6-fix45` |
---
## v6-fix44(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已移除 | 公司介绍门店筛选 | 移除大类列表顶部「筛选门店」FilterChip 区域(含「全部门店」多选),保留大类/小类/详情三级列表、表单、树形组织选择器、图片/视频上传等其余所有功能与布局 |
| ✅ 已清理 | 冗余代码 | 移除 `_stores`、`_selectedOrgIds`、`_loadStores`、`_applyOrgFilter`、`_buildOrgFilter` 及 `StoreInfo` import;`_storeName` 改为仅从组织树回查;`initState` 改调用 `_loadOrgNodes()` 以支持列表门店名称回显 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix43` → `v6-fix44` |
---
## v6-fix43(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已调整 | 公司介绍门店选择器节点规则 | 树形组织选择器仅展示渠道(type=1)、代理商(type=2)与门店(type=3);渠道与代理商节点 `selectable=false`(禁用、灰色、不可点击);门店节点保持可选;平台(type=0)与设备分组(type=4)不展示 |
| ✅ 已新增 | 节点选择工具方法 | 新增 `_isOrgVisible`、`_isOrgSelectable`,统一控制选择器内组织节点的展示与可选状态 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix42` → `v6-fix43` |
---
## v6-fix42(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已改造 | 公司介绍大类门店选择器 | 「所属门店」从 DropdownButtonFormField 改为与用户管理一致的树形组织选择器:底部弹窗、实时搜索组织名称、按组织层级缩进展示;复用 `OrgPickerSheet`/`OrgPickerFormField` 组件,数据走 `OrgService.getOrgTree()`(组织树 / flatList / select 三阶回退) |
| ✅ 已实现 | 编辑态自动高亮当前节点 | 编辑大类时 `OrgPickerFormField` 自动展开选择器,并默认选中高亮当前 `orgId` 对应的门店组织节点;若当前 category 所属组织不在用户可见树中,自动补入选项保证可展示 |
| ✅ 已新增 | 组织树工具方法 | `intro_category_page` 新增 `_loadOrgNodes`、`_buildOrgPickerOptions`、`_buildHierarchicalOptions`、`_buildHierarchyFromFlatList`、`_findOrgByName`、`_findOrgById`,与用户管理 `_UserForm` 对齐 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix41` → `v6-fix42` |
---
## v6-fix41(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 公司介绍大类门店选择 | 修复新增/编辑大类时「所属门店」下拉为空、无法选择门店的问题:`intro_category_page._loadStores` 改用 `OrgService.getMyStores()`(组织树 / myStores / listByLoginOrg 三阶回退),覆盖当前用户可见的所有门店;表单打开前强制重新加载门店;编辑时若当前 category 所属门店不在返回列表中,自动补入下拉项;空字符串 `orgId` 统一视为未选择,避免收起态显示空白 |
| ✅ 已新增 | 国际化 | `i10n.dart` 新增 `introStoreLoadFailed`(门店列表加载失败提示)中英 key |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix40` → `v6-fix41` |
---
## v6-fix38(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 公司介绍多门店筛选 | 大类列表页门店筛选由单选下拉改为 `FilterChip` 多选,空选表示「全部门店」,数据按所选门店列表实时过滤 |
| ✅ 已实现 | 公司介绍图片上传 | 新增 `UploadService`,小类/详情表单支持从相册选择图片、本地即时预览、Dio `FormData` 上传至 `/sys/upload`,成功后自动回填 `mediaUrl` 并切换为网络预览;保留手动输入 `mediaUrl` 兜底 |
| ✅ 已对齐 | 大类表单 | 新增「所属门店」必填下拉(`orgId`/`orgName` 联动);移除 Web 后台不存在的 `status` 字段,保存字段与 Web 端一致 |
| ✅ 已对齐 | 小类/详情表单 | 新增「排序号」字段;`status` 语义与 Web 后台统一为 `1=启用/0=禁用`;小类新增可选「类型编码」字段(默认预填父大类编码) |
| ✅ 已改造 | 保存接口分流 | `IntroService` 三个 `save*` 方法按 `id` 是否存在分流到 `/save`(新增)或 `/update`(编辑),与 Web 端 `companyIntro.ts` 保持一致 |
| ✅ 已修正 | 状态语义 | `IntroCategory`/`IntroSubcategory`/`IntroDetail` 的 `_parseStatus` 改为后端原值映射(1=启用,0=禁用),列表 Chip 文案同步改为「启用/禁用」 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充 `introFilterStores`、`introSelectStoresHint`、`introCategoryStore`、`introStoreRequired`、`introSortOrder`、`introEnabled`、`introDisabled`、`introUploadImage`、`introPickImage`、`introImagePreview`、`introUploading`、`introUploadFailed`、`introInsertImage` 中英 key |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix37` → `v6-fix38` |
---
## v6-fix40(2026-08-26)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 滚动字幕速度档位化 | 数字速度输入改为 5 档下拉选择(慢/较慢/中/较快/快),新增 `MarqueeConfig`/`MarqueeSpeed` 配置结构,提供 `setSpeed`/`setGrade`/`copyWith` 接口与 `speedFromValue`/`gradeFromValue` 历史值吸附;落库仍为 Web 端原值(200/100/50/30/10),卡片与表单直观显示当前生效档位 |
| ✅ 已实现 | 滚动字幕级别选择 | 新增 3 级「级别」下拉(危险/紧急/普通),落库原值(50/40/20),与 Web 端 `scrolling-text.data.ts` 一致;列表与表单显示当前生效级别 |
| ✅ 已实现 | 滚动字幕字体颜色色板 | 「字体颜色 / 背景颜色」由手动输入 `#HEX` 改为对齐 Web 端 `el-color-picker` 的色板弹窗:饱和度/明度 2D 选择区 + 色相条 + HEX 输入框 + 实时预览 + 取消/确定,回填 `#RRGGBB` |
| ✅ 已修复 | 公司介绍大类门店名称 | 新增/编辑大类「所属门店」选中后正确展示门店组织名称:下拉增加 `selectedItemBuilder` 确保收起态渲染所选名称;列表卡片在接口未返回 `orgName` 时按 `orgId` 从本地门店列表回查名称 |
| ✅ 已对齐 | 公司介绍小类图片上传 | 小类素材固定为图片(与 Web 端一致,Web 端无媒体类型选择、始终 `mediaType=image`);新增图片类型(jpg/png/gif/webp)与大小(≤300KB,建议 1920×1080)校验,超限给出提示;上传仍走后端 `/sys/upload`,返回 URL 存入 `mediaUrl` |
| ✅ 已对齐 | 公司介绍详情图片/视频上传 | 详情表单保留「媒体类型」图片/视频切换;图片(≤3MB)/ 视频(≤30MB)类型与大小校验与 Web 端一致,视频走 `pickVideo`;上传路径与返回结构同小类 |
---
## v6-fix39(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 滚动字幕起止时间筛选 | 「滚动字幕」新增/编辑表单的开始时间、结束时间改为纯 `HH:mm:ss` 滚轮选择器,不再复用日期时间选择器,与 Web 端 `/theme/scrollingText` 时间字段格式(`HH:mm:ss`)一致 |
| ✅ 已实现 | 时间滚轮控件 | 新增 `_TimePickerSheet` 底部弹窗:三列 `ListWheelScrollView` 滚轮(时 00-23 / 分 00-59 / 秒 00-59),`FixedExtentScrollController` 定位初始值;底部「取消/确定」按钮,确定后回传 `DateTime(2000,1,1,h,m,s)`,与 `MarqueeMessage._parseDateTime` 基准日期一致 |
| ✅ 已对齐 | 默认值与范围 | 新增字幕默认起止时间为 `00:00:00` ~ `23:59:59`;编辑时回显既有 `startTime`/`endTime`;秒级精度,可选范围覆盖全天,与 Web 后台一致 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充 `marqueeHour`/`marqueeMinute`/`marqueeSecond`(时/分/秒)中英 key,供滚轮单位标签使用 |
| ✅ 已清理 | 死代码 | 移除 `_TimePickerSheet._buildWheel` 中的未使用 `selected` 局部变量,消除 analyze 警告 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix38` → `v6-fix39` |
---
## v6-fix37(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 组织选择器高亮 | `OrgPickerSheet` 列表项选中时立即通过本地 `_selectedId` 状态更新高亮背景、文字颜色与对勾图标,并延迟 120ms 后关闭弹窗,解决选中条目需要额外按一次才显示高亮的问题 |
| ✅ 已优化 | 选择器视觉反馈 | 已选项文字加粗并显示 `Icons.check_circle`,高亮底色透明度由 `0.24` 提升至 `0.38`,选中状态与视觉反馈同步呈现 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix36` → `v6-fix37` |
---
## v6-fix36(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 滚动字幕数据源 | 移动端 `MarqueeWebSource` 默认接口由 `/marquee/list` 改为与 Web 后台一致的 `/theme/scrollingText/list`;解析 `totalCount` 与 Web 端字段映射(`orgId`→`storeId`、`orgName`→`storeName`、`scrollSpeed`→`speed`、`bgColor`→`backgroundColor`),解决移动端列表为空的问题 |
| ✅ 已修复 | 滚动字幕新增/编辑 | 修复 `_MarqueeFormState` 中 `late String? _storeId` 未在 `initState` 初始化导致的 `LateInitializationError`;新增「状态」下拉框(启用/禁用),表单提交携带真实状态 |
| ✅ 已改造 | 滚动字幕服务 | `MarqueeService` 新增/编辑/删除/启用/禁用均优先调用 Web 后台 `/theme/scrollingText/save|update|delete`;失败时回退本地 `SharedPreferences` 缓存,保证编辑内容实时同步到桌面与移动端 |
| ✅ 已优化 | 列表卡片 | `_StatusChip` 改为「启用/禁用」;`_MarqueeCard` 操作按钮改为「启用/禁用」;时间范围展示改为 `HH:mm:ss`,与 Web 后台一致 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充 `marqueeStatus`、`marqueeStatusEnabled`、`marqueeStatusDisabled` 中英 key |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix35` → `v6-fix36` |
---
## v6-fix35(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已重构 | 设备详情页 | `DeviceDetailPage` 按需求分四个清晰区块:**基本信息**(设备名称、设备 ID、型号、状态)、**配置参数**(所属门店、分组、房号、位置、操作系统、设备序列号、设备 UID、创建时间)、**网络信息**(IP 地址、MAC 地址)、**运行时数据**(在线时长、最后活跃时间);原信息卡拆分为带标题的分区块卡片,缺口字段(设备名称 / 设备 ID / 最后活跃时间)已补齐并回显 |
| ✅ 已更新 | 详情头部 | 标题由「型号」改为「设备名称」(型号降级为副标题),与列表点击进入的语义一致 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充 `basicInfo`、`configParams`、`networkInfo`、`runtimeData`、`deviceName`、`deviceId`、`modelLabel`、`deviceSnLabel`、`deviceUidLabel`、`createTime`、`lastActiveTime` 等中英 key |
| ✅ 已保留 | 刷新同步 | 详情页下拉刷新(`RefreshIndicator`)刷新 `deviceDetailProvider`,与设备列表数据保持一致;「下发指令 / 修改分组」按钮保持可用 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix34` → `v6-fix35` |
---
## v6-fix34(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | Web 端滚动字幕数据源 | 新增 `lib/services/marquee_web_source.dart` 作为独立的 Web 端内容获取层;支持配置请求地址(`baseUrl` / `path`)与请求方式(`GET/POST` 等),内置超时、网络异常、HTTP 错误、业务码错误、解析失败等完善错误处理;将结果封装为结构化 `MarqueeFetchResult` |
| ✅ 已扩展 | 滚动字幕数据模型 | `MarqueeMessage` 新增 `fontColor`、`backgroundColor`、`fontSize`、`level`、`startTime`、`endTime` 字段,兼容 Web 端返回的 JSON 格式 |
| ✅ 已改造 | 字幕服务 | `MarqueeService.getMarqueeList` 优先调用 `MarqueeWebSource` 获取 Web 端数据,失败时自动回退到本地 `SharedPreferences` 缓存 |
| ✅ 已更新 | UI | `MarqueeManagementPage` 列表卡片展示字体颜色、背景颜色、字体大小、级别、生效时间;新增/编辑表单支持设置颜色、字号、级别与起止时间 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充字体颜色、背景颜色、字体大小、级别、开始/结束时间等中英 key |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix33` → `v6-fix34` |
---
## v6-fix33(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 滚动字幕管理 | 在「管理」界面新增「滚动字幕管理」入口;新增 `MarqueeManagementPage`(`/admin/marquee`),支持按门店查看、新增、编辑、删除、查询滚动字幕,并区分不同门店内容 |
| ✅ 已实现 | 下发/取消下发 | 字幕状态区分为「待下发」与「已下发」;点击「下发」调用设备指令 `marqueeUpdate` 按组织下发到门店设备,成功后状态更新为已下发;点击「取消下发」重置为待下发 |
| ✅ 已新增 | 数据模型 | `lib/models/marquee_message.dart` 定义滚动字幕实体;`lib/services/marquee_service.dart` 基于 `SharedPreferences` 实现字幕记录本地持久化与 CRUD,并提供下发/取消下发能力 |
| ✅ 已新增 | 国际化 | `lib/utils/i10n.dart` 补充滚动字幕管理相关 key(管理标题、新增/编辑、字幕内容、滚动速度、目标门店、状态、下发/取消下发等) |
| ✅ 已更新 | 管理首页 | `lib/screens/admin/admin_hub_page.dart` 增加「滚动字幕管理」入口卡片 |
| ✅ 已更新 | 路由 | `lib/app/router.dart` 增加 `/admin/marquee` 路由 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix32` → `v6-fix33` |
---
## v6-fix32(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 激活码管理 | 英文语言下激活码管理页所有中文硬编码文案(门店选择、当前激活码、复制、最大激活数、已激活数、创建时间、生成/重置激活码、生成弹窗等)全部替换为 `AppLocalizations` 国际化 key,切换英文后完整显示英文 |
| ✅ 已新增 | 国际化 | `i10n.dart` 补充 `copy`、`generate`、`generateActivationCodeHint`、`activationCodeCopied`、`activationCodeLoadFailed`、`generateFailedWithError` 等 key |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix31` → `v6-fix32` |
---
## v6-fix31(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 管理模块返回键 | 管理中心「设备管理」「激活码」入口原 `context.push('/device')` / `context.push('/device/activation')` 会把页面压入**设备分支**导航栈,导致底部导航错乱 + 左上角返回箭头显示/点击异常 → 在 `/admin` 分支下新增别名路由 `/admin/device`(复用 `DeviceListPage`)与 `/admin/activation`(复用 `DeviceActivationPage`),入口改为 `context.push('/admin/device')` / `context.push('/admin/activation')`;仍保留 `push` 方式,用户可通过系统返回键回到管理首页 |
| ✅ 已修复 | 设备列表 AppBar | `DeviceListPage` 的 `AppBar` 增加 `automaticallyImplyLeading: false`,从管理模块进入时不显示左上角返回箭头(`DeviceActivationPage` 此前已设置,保持生效) |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix30` → `v6-fix31` |
⚠️ 备注:
- 原 `/device` 与 `/device/activation` 路由**保留不变**(设备 tab 入口及设备详情内的跳转仍使用),本次仅新增 `/admin` 分支下的别名路由,无命名冲突。
---
## v6-fix30(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 角色选择器 | 用户编辑页「角色」字段由下拉框改为可视化 `ChoiceChip` 横向选择器;编辑时根据 `roleId`/`roleName`/`roleNames` 自动选中对应角色,选中项显示主题色高亮背景 + `Icons.check` 勾选图标;角色来自动态 `widget.roles`,新增用户默认选中当前登录用户角色 |
| ✅ 已删除 | 全局返回悬浮按钮 | 移除 `AppBottomNav` 中的全局返回 FAB(`_BackFabLocation`、`floatingActionButton`、`floatingActionButtonLocation`)及 `navigator_key.dart` 导入;同步删除不再被引用的 `lib/app/navigator_key.dart`,并移除 `router.dart` 中的 `navigatorKey` 引用 |
| ✅ 已删除 | AppBar 返回键 | 扫描 `lib/screens/**` 所有 `AppBar`,移除自定义 `leading` 返回按钮(`device_scan`/`device_group`/`store_config` 三页),并对全部子页面 `AppBar` 设置 `automaticallyImplyLeading: false`,统一移除左上角返回箭头,保留标题与 actions |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix29` → `v6-fix30` |
---
## v6-fix29(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 组织选择器 | 新建/编辑门店与用户编辑页「所属组织」字段进入页面时自动弹出选择弹窗,已选项自动高亮勾选;弹窗支持关键字实时搜索与层级缩进展示 |
| ✅ 已实现 | 返回悬浮按钮 | 在 `AppBottomNav` 外层 Scaffold 增加全局返回悬浮按钮(左上角、仅非首页显示),点击优先 `pop`,不可 pop 时回到首页 |
| ✅ 已删除 | AppBar 返回键 | 移除「设备管理」「用户管理」「激活码」「公司介绍」四页 AppBar 左上角返回箭头,统一由全局返回悬浮按钮负责返回 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix28` → `v6-fix29` |
---
## v6-fix28(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 默认选中 | 门店编辑页与用户管理编辑页进入时,已关联的组织/角色选项自动高亮选中;用户编辑时若后端未返回 ID,通过名称反向匹配并增加只读占位项兜底展示 |
| ✅ 已修复 | 门店组织搜索 | 编辑门店「所属组织」搜索框输入关键字后,新增实时搜索结果列表;点击结果立即更新 `_parentId`、清空搜索关键字并刷新页面,修复「选中无反应」问题 |
| ✅ 已优化 | 门店默认上级 | 编辑门店加载组织树后,若 `store.parentId` 为空,自动在组织树中按 `store.orgId` 查找门店节点并取其 `parentId` 作为默认上级;新增/仍为空时回退到当前用户所在组织 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix27` → `v6-fix28` |
---
## v6-fix27(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已优化 | 门店表单 | 编辑门店「所属组织」下拉选项仅渲染渠道(type=1)与代理商(type=2),门店类型(type=3)已过滤隐藏;新增组织名称关键字搜索框,输入时实时模糊匹配并动态筛选渠道/代理组织 |
| ✅ 已优化 | 默认选中 | 新增门店时,若未指定上级组织则默认选中当前登录用户所在组织;新增用户时,默认选中当前登录用户的所属组织与角色 |
| ✅ 已更新 | 用户模型 | `User` 模型新增 `orgId`/`roleId` 字段;`AuthService` 从登录响应与 `/sys/user/info` 中解析并持久化,供表单默认选中使用 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix26` → `v6-fix27` |
---
## v6-fix26(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 用户编辑 | 编辑用户时角色字段为空 → 当列表接口未返回角色信息时,自动调用 `/sys/user/info/{id}` 拉取详情;同时通过 `roleName`/`roleNames` 反向匹配 `roleId`,确保角色下拉与用户信息页/Web 端展示一致 |
| ✅ 已优化 | 用户表单 | 新增/编辑用户「所属组织」下拉改为按组织层级自上而下排序:优先调用 `/sys/org/tree`,递归扁平化为带深度的选项并逐层缩进展示;失败时回退到 `/sys/org/flatList` 并按 `parentId` 重建层级顺序 |
| ✅ 已修复 | 返回按钮 | 管理模块入口进入的「设备列表」「激活码管理」「用户管理」页面左上角返回箭头点击无响应 → 统一将返回目标改为 `/admin`,确保回到管理首页 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix25` → `v6-fix26` |
---
## v6-fix25(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已优化 | 门店表单 | 编辑门店「所属组织」下拉列表改为按组织层级自上而下排序:调用 `/sys/org/tree` 获取树形结构,递归扁平化为带深度的选项;列表中从最上级组织开始,逐层缩进展示其下级组织,层级关系清晰 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix24` → `v6-fix25` |
---
## v6-fix24(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 修改分组 | 分组下拉仍展示全部门店分组 → 修复 `/device/group/list` 接口参数:Web 端按 `orgId` 过滤门店,移动端此前只传 `storeId` 导致后端忽略门店过滤;现同时携带 `orgId` 与 `storeId`,确保仅返回当前设备所属门店的分组 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix23` → `v6-fix24` |
---
## v6-fix23(2026-08-25)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 修改分组 | 分组下拉报 `DioException [unknown]: null` → 改为调用已有接口 `/device/group/list?storeId=xxx`;同时增强 `Device.storeId` 解析,支持从 `store`/`storeInfo`/`org` 嵌套对象取门店 ID |
| ✅ 已修复 | 新建门店保存 | 保存仍提示「未知异常」→ `_save()` 先按 Web 端字段契约(`type='STORE'`/`status='ACTIVE'`)提交;失败时自动 fallback 到旧后端兼容格式(`type=3`/`status=1`),并展示后端返回的具体错误信息 |
| ✅ 已修复 | 首页快捷操作 | 「系统管理」文字/图标有误 → 改为「用户管理」并替换为 `Icons.people` |
| ✅ 已修复 | 返回按钮 | 管理模块下的「设备管理」「激活码」「用户管理」「公司介绍」页面左上角返回键无响应 → 统一为各页 AppBar 增加显式 `leading` 返回按钮(可 pop 时 pop,不可 pop 时回退到 `/admin`) |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix22` → `v6-fix23` |
---
## v6-fix22(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已调整 | 首页快捷操作 | 移除「媒资管理」入口;当前项目阶段暂不需要,后续如需可在 `HomePage._buildDashboard` 中恢复 `_QuickAction(mediaManagement)` |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix21` → `v6-fix22` |
---
## v6-fix21(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 修改分组 | 分组下拉改为仅展示当前设备所属门店的分组:调用门店专用接口 `/sys/store/deviceGroups/{storeId}`,避免 `/device/group/list` 跨门店返回全部分组;无门店上下文时保留「未分组」与当前分组可选 |
| ✅ 已修复 | 返回按钮 | 扫码激活页左上角返回键无响应 → AppBar 增加显式 `leading` 返回按钮;全面排查并把设备列表/扫码页/首页管理入口/管理中心的 `context.go` 改为 `context.push`,确保子页面返回正常;为设备分组页、门店配置页也增加显式返回兜底 |
| ✅ 已修复 | 门店保存 | 门店保存仍失败(提示「未知异常,请联系管理员」)→ 对齐 Web 端 `/sys/org/saveOrUpdate` 字段契约:`type` 改为字符串 `'STORE'`,新增 `orderNo`/`status`,补充 `storeProfile` 对象(storeName/phone/address/province/city);移除后端不识别字段;所属组织下拉仅展示代理商(type=2),与 Web 端一致 |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix20` → `v6-fix21` |
⚠️ 备注:
- 门店保存字段已按 Web 端「新增门店」payload 重构;若后端仍返回具体业务错误,弹窗会展示后端 msg,请把完整错误文字发我进一步对齐。
- 返回按钮修复后,从底部导航进入的 tab 根页(首页/设备/门店/管理/我的)仍保持 `go` 替换,子页面统一使用 `push`,返回行为与原生一致。
---
## v6-fix20(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 新增门店 | 门店新增保存时报错 → `_save()` 增加「所在地区」「所属组织」必选项校验;保存失败/成功/校验失败均通过页面中央 `AlertDialog` 提示,避免 SnackBar 被遮挡 |
| ✅ 已优化 | 门店表单 | 新增 i18n 词条 `regionRequired`/`parentOrgRequired`;硬编码中文接入国际化 |
| ✅ 已修复 | 激活码管理 | 「重置激活码」结果提示改为页面居中 `AlertDialog` 展示(成功/失败均居中),替代原左下角 SnackBar |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix19` → `v6-fix20` |
---
## v6-fix19(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备列表 | 顶部搜索框 hint 被截断(`搜索设备名称 / 房号`)→ 顶部改为两行布局:第一行放门店下拉 + 型号筛选,第二行放全宽搜索框,确保 hint 完整显示;i18n `searchDeviceHint` 中英文词条已确认同步 |
| ✅ 已修复 | 设备详情 | 遥测字段(位置 / 操作系统 / 运行时长)全显示空或 `0分` → `Device.fromJson` 新增详情响应兜底解包(`{device|info|data|result|record}` 嵌套),并对 location/os/uptime 等做常见字段别名兼容;详情卡空值统一回退为「无」,保证 UI 不崩溃 |
| ✅ 已修复 | 修改分组 | 弹窗原显示「所属分组 ID」文本输入框 → 改为 `DropdownButtonFormField` 分组名称下拉,进入时按设备所属门店拉取 `/device/group/list`,下拉显示分组名称(`groupName`)、提交 `groupId`,与 Web 端一致;无门店上下文时安全降级 |
| ✅ 已修复 | 公司介绍 | 左上角返回箭头点击无响应 → AppBar 增加显式 `leading` 返回按钮:`context.pop()` 回管理中心,内部层级逐级返回,无法 pop 时兜底跳转 `/admin` |
| ✅ 已修复 | 编译 | 修复 `Device.fromJson` 引用未定义的 `_firstNum` 导致的编译错误(原交付版本潜在隐患);补充 `num? _firstNum(...)` 静态辅助方法 |
| ✅ 已优化 | i18n | 新增 `back`(返回)、`groupListLoading`(加载分组列表…)等词条,弹窗内所有硬编码中文均接入 `lib/utils/i10n.dart` |
| ✅ 已更新 | 版本号 | 登录页底部版本号 `v6-fix18` → `v6-fix19` |
⚠️ 备注:
- 设备详情遥测依赖后端 `/device/info/info/{id}` 实际返回字段;已做 `device/info/data` 等多层包络与 `cpu_usage`/`os`/`uptime` 等常见别名兼容,若后端确无数据则统一显示「无」,不崩溃。
- 修改分组下拉依赖设备携带的门店 ID(`Device.storeId`);若该字段后端未返回,下拉仅含「未分组」与当前分组,仍可正常提交。
⏳ 待验收:4 项修复在真机/模拟器上的实际表现。
---
## v6-fix18(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 新增门店 | 激活码改为由系统依据 web 端已有规则自动生成:移除手动输入框,保存门店后若终端数>0 自动调用 `/device/activationCode/generate`;每次点击「创建激活码」仅生成一个唯一码 |
| ✅ 已实现 | 门店编辑 | 编辑页直接展示「最大终端数 / 已用终端数」及当前正在使用的激活码,并提供「创建激活码」按钮 |
| ✅ 已修复 | 门店详情 | 创建时间字段空白 → `StoreInfo` 兼容 `createTime`/`create_time`/`createdTime`/`createdAt`/`created_at` 等常见别名;列表项与详情弹窗均补充展示创建时间 |
| ✅ 已修复 | 语言切换 | 切换英文后启动报错「token 失效」→ `ApiInterceptor` 刷新 token 成功后同步持久化到 `CredentialStorage`,避免语言切换/进程重启后恢复到旧令牌 |
| ✅ 已修复 | 扫码页 | 英文模式下扫码页全部文案接入 i10n,包括标题、提示、权限、弹窗、成功/失败提示等 |
| ✅ 已优化 | 门店表单 | 省市区、详细地址、联系电话、所属组织、保存/创建按钮等剩余硬编码中文全部接入 i10n |
⏳ 待验收:新增门店自动生成激活码、编辑页终端数/激活码展示、英文切换不报错、扫码页英文文案。
---
## v6-fix17(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备模型 | 新增 `Device.model` 字段,按 `model`/`modelName`/`deviceModel`/`productModel`/`productName`/`board`/`hardware`/`brand`/`deviceType`/`type` 等常见别名解析真实型号 |
| ✅ 已修复 | 设备显示 | `DeviceListTile` / `DeviceDetailPage` 标题统一使用 `device.model`,避免把 MAC 地址误当型号展示 |
| ✅ 已修复 | 型号过滤 | 设备列表「型号」筛选框改为基于 `Device.model` 过滤 |
| ✅ 已优化 | 降级处理 | 型号解析不到时,依次降级为 `name`(若 name 不是 MAC)→ `deviceSn` → `mac`;并新增 `_isMacAddress` 识别 MAC 格式,确保 MAC 不会混入型号展示 |
⏳ 待验收:设备标题是否显示真实型号(非 MAC)。
---
## v6-fix16(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 设备列表 | 顶部搜索区新增「型号」输入框,按 `Device.name` / `deviceSn` 实时过滤设备列表 |
| ✅ 已优化 | 设备列表 | Riverpod `AsyncValue.when` 启用 `skipLoadingOnRefresh`/`skipLoadingOnReload`,刷新/重新加载时保留已有数据,避免全屏转圈覆盖已加载项 |
| ✅ 已优化 | 设备列表 | 门店下拉已加载过门店后不再重新显示转圈;状态 Chip 颜色与 `DeviceStatus` 严格对应(在线绿/离线灰/告警橙) |
⏳ 待验收:型号筛选是否生效、刷新是否不再全屏转圈。
---
## v6-fix15(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备列表 | 标题前增加「型号:」前缀(如「型号:rk3566_r」),并接入 i10n |
| ✅ 已修复 | 扫码页 | 移除 AppBar「相册识别」按钮,进入页面即直接调用摄像头扫描;说明当前代码无「实时截屏/摄像头」选择弹窗,若仍出现请确认版本/入口 |
| ✅ 已实现 | 新增门店 | 表单新增「终端数」与「激活码」字段;保存门店后若终端数>0,自动调用 `/device/activationCode/generate` 创建激活码;`saveOrUpdateOrg` 改为返回 orgId 供后续操作 |
⏳ 待验收:设备标题前缀、扫码页直接摄像头、新增门店激活码/终端数。
---
## v6-fix14(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 设备列表 | web 端有房间号但手机端显示「未设置」→ 扩大 `Device.fromJson` 房号字段兼容:`roomNo`/`room`/`roomNumber`/`room_id`/`roomNum`/`roomName`/`houseNumber`/`roomCode` 等常见别名 |
✅ 已验收:设备列表房号。
---
## v6-fix13(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 门店详情 | 激活码仍显示「—」→ 改从激活码专用接口 `/device/activationCode/info/{orgId}` 拉取 `code` 字段,作为真实激活码展示;接口无值时回退门店列表里的 `activationCode` |
| ✅ 已修复 | 设备列表 | 副标题改为用户指定格式:**门店:XXX门店 MAC:AA:BB:CC:DD:EE:FF 房号:V888**(缺失项显示「未设置」) |
✅ 已验收:门店详情激活码、设备副标题格式。
---
## v6-fix12(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 用户管理 | 角色/组织显示「未分配」→ 兼容后端嵌套对象/数组返回(`roles`/`org`/`dept` 等),并建立完整 orgId→name 映射 |
| ✅ 已修复 | 设备列表 | 副标题改为 `MAC · 所属门店 · 房号`(原为设备型号/MAC) |
| ✅ 已修复 | 门店管理 | 手机端新增/编辑门店**真实上报后端**(`/sys/org/saveOrUpdate`),不再本地占位 |
| ✅ 已修复 | 门店列表 | 按创建时间倒序,最新门店排最前面(与 web 一致) |
⏳ 待验收:用户管理角色/组织回显、新增门店 web 可见、门店列表排序。
---
## v6-fix11(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 国际化 i18n | 设备详情、下发指令、修改分组/房号、激活码管理、用户管理、公司介绍等页面接入英文翻译 |
| ✅ 已修复 | 编译 | 修复 8 个 analyze error(i10n 重复定义、store_list 误用 `t.normal`/`t.disabled`、device_list 漏传参、user_page 漏 `t` 声明) |
---
## v6-fix10(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 门店编辑-界面配置 | 按设备分组展示已启用/添加的主题(接入 `/theme/page/org/{orgId}/photos`,ThemeGroup→ThemePage[]) |
| ✅ 已修复 | 设备列表 | `Device.fromJson` 兼容 `deviceName`/`ipAddress`/`deviceIp` 等字段,正确回显设备名、房号、MAC、IP |
---
## v6-fix9(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已实现 | 国际化 i18n | 底部导航、首页、设备列表、门店列表、管理入口、设置页接入英文翻译(首批) |
---
## v6-fix8(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 门店详情 | 联系电话/激活码/到期时间/创建时间字段空白 → 调 `/sys/org/listByLoginOrg` 回填 |
| ✅ 已修复 | 扫码 | 路由冲突(`scan` 在 `:id` 后导致被当 deviceId)→ 路由前置 |
| ✅ 已修复 | 管理页 | 移除「门店配置」入口(后端 `/device/storeConfig/{orgId}` 500) |
---
## v6-fix7(2026-08-24)
| 类型 | 模块 | 内容 |
|------|------|------|
| ✅ 已修复 | 登录-记住密码 | `CredentialStorage` 由 `Directory.current`(Android 不可写)迁移到 `shared_preferences`,记住账号密码 + 免登录恢复均生效 |
---
## 历史(本会话早段)
| 版本 | 类型 | 模块 | 内容 |
|------|------|------|------|
| v6-fix6 | ✅ 已修复 | 门店编辑 | 移除扫码绑定按钮/启动图Logo区块;主题皮肤改接 web 模板;所属组织下拉过滤+透传 parentId |
| v6-fix5 | ✅ 已修复 | 登录灰块 | `localizationsDelegates` 漏默认 delegate 致 TextField 崩溃 → 补全三个默认 delegate |
| v6-fix3 | ✅ 已实现 | 排障 | `ErrorWidget.builder` 红色错误页 + DIAG 横幅,替代 release 灰屏 |
---
## ⏳ 已知待办 / 未改项
- ❌ **门店配置** 功能:后端 `/device/storeConfig/{orgId}` 持续 500,前端已移除入口并给友好提示,需后端修复后恢复。
- ⏳ 门店编辑「界面配置」目前为**展示型**(显示分组/主题/激活主题),移动端切换激活主题需后端 `/theme/page/active` 支持,可后续迭代。
- ⏳ 切换英文后如仍有个别深层弹窗/页面为中文,多为未接 i18n 的入口,截图可继续补。
---
## 交付物索引
| 版本 | APK 路径 | 大小 |
|------|----------|------|
| v6-fix35 | `E:/Project/ops-console/ops-console-release-v6-fix35.apk` | 33.5MB |
| v6-fix34 | `E:/Project/ops-console/ops-console-release-v6-fix34.apk` | 33.5MB |
| v6-fix33 | `E:/Project/ops-console/ops-console-release-v6-fix33.apk` | 33.5MB |
| v6-fix32 | `E:/Project/ops-console/ops-console-release-v6-fix32.apk` | 33.5MB |
| v6-fix31 | `E:/Project/ops-console/ops-console-release-v6-fix31.apk` | 33.5MB |
| v6-fix30 | `E:/Project/ops-console/ops-console-release-v6-fix30.apk` | 33.5MB |
| v6-fix12 | `E:/Project/ops-console/ops-console-release-v6-fix12.apk` | 33.4MB |
| v6-fix11 | `E:/Project/ops-console/ops-console-release-v6-fix11.apk` | 35.0MB |
| v6-fix7~10 | `E:/Project/ops-console/build/app/outputs/flutter-apk/app-release.apk` | 33.x MB |