Files
flutter_wisetronic/TODO.md

251 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# TODO — Flutter Wisetronic Dart 3 空安全迁移
> ## ✅ Web 构建已成功2026-08-01
> **`flutter build web --no-tree-shake-icons` → ✓ Built build/web/main.dart.js 4.5M**
> - `dart analyze lib`: 仅剩 **6 error**,全部在 `utils/util_io.dart`原生路径Web 已排除,见 §5 延后)。
> - Web 可达错误: **0**。
> - 峰值 2374 → 6全部 util_io 原生。0 语法损坏。分支 `dev2`。
> - 构建脚本: `./build_web.sh`(构建)/ `./build_web.sh run`Chrome 运行)。
> - **关键修复**: `cross_file` override 到 `0.3.4+2`(旧版 0.3.1+4 用了 Dart3 移除的 `UnmodifiableUint8ListView`0.3.5+ 需 Dart 3.10)。
---
## 0. 快速恢复Resume 起点)
```bash
cd /Users/peima/StudioProjects/flutter_wisetronic
git log --oneline -1 # 确认在最新提交
dart analyze lib | tail -5 # 当前: 仅 6 error(util_io.dart)
./build_web.sh # 构建: ✓ Built build/web/
./build_web.sh run # Chrome 运行调试
# 重跑自动修复脚本(安全,带损坏自检,可随时跑)—— 已无 web 可达错误,通常无需再跑
python3 tools/nullfix.py --max-rounds 12
```
**核心原则**:每次改动后用 `dart analyze lib | grep -cE '\s+error\s+-'` 数错误;脚本若报"检测到语法损坏"会自动中止,`git checkout -- lib` 回滚。
---
## 1. 剩余错误分布118 个)
| 规则 | 数量 | 说明 |
|------|------|------|
| `unchecked_use_of_nullable_value` | 44 | 可空值当非空用(方法调用/条件/迭代器,多为多行) |
| `invalid_assignment` | 34 | 赋值类型不匹配(复杂表达式) |
| `argument_type_not_assignable` | 22 | 参数类型不匹配(三元/比较/Map字面量 |
| `return_of_invalid_type_from_closure` | 4 | 闭包返回类型 |
| `undefined_getter` | 3 | 多为 `.buttonColor`Flutter 3 移除) |
| `return_of_invalid_type` | 3 | 函数 `return null` 但返回类型非空 |
| `undefined_named_parameter` | 2 | |
| `undefined_method` | 2 | |
| `creation_with_non_type` | 2 | 类名拼错util_io 内) |
| `extra_positional_arguments_could_be_named` | 1 | |
| `body_might_complete_normally` | 1 | |
**错误最多的文件**(详细见 `dart analyze lib`
```
6 utils/util_io.dart ← 延后仅原生Web 构建已排除)
5 widgets/desktop/desktop_edit_address.dart ← .trim() 多行(见 §3已知修法
5 widgets/mobile/mobile_edit_address.dart ← 同上
5 widgets/general/add_remove_button.dart
5 widgets/general/parabolic_animation_widget.dart
5 widgets/general/sliding_up_panel.dart
4 widgets/desktop/desktop_me.dart
4 widgets/mobile/mobile_attribute_selection.dart
4 widgets/mobile/mobile_order_detail.dart
3 widgets/desktop/desktop_coupons.dart / desktop_my_support / desktop_order_detail / shop_products
3 widgets/general/animation_point_manager / popup_animation_widget
3 widgets/mobile/mobile_coupons / mobile_me / mobile_my_support / mobile_orders / shopping_cart_bar
2 约 12 个文件blog/orders/product_item/shopping_cart 等)
1 约 25 个文件(单点错误)
```
---
## 2. 推荐处理顺序
1. **edit_address5+5** — 已知修法,见 §3最快。
2. **parabolic/popup_animation5+3**`late` 字段已修,可能还有 unchecked_useoffset 计算方法调用),手动看。
3. **sliding_up_panel5** — 第三方面板 widget 适配,手动。
4. **add_remove_button5** — badges 相关,手动。
5. **散落的 2-error / 1-error 文件** — 用 `dart analyze lib/<file>` 逐个看,多为加 `!`
6. **util_io.dart6** — 最后处理(见 §5不影响 Web 构建。
**目标**:先清掉 Web 可达错误(`flutter build web` 的 112让 Web 能跑;`util_io` 原生路径可延后。
---
## 3. 已知具体修法(直接套用)
### 3.1 edit_address `.trim()` 多行desktop + mobile各 5 处)
错误:`A nullable expression can't be used to invoke 'trim'`。代码结构:
```dart
validator: (String? value) {
if (value // ← value 是 String?
.trim()
.isEmpty) {
```
**修法**:把 `if (value` 行改成 `if (value!`
```python
# 上次脚本 bug用了 rstrip() 应该用 strip()。正确版:
for p in ['lib/widgets/desktop/desktop_edit_address.dart','lib/widgets/mobile/mobile_edit_address.dart']:
lines=open(p).read().split('\n'); n=0
for i in range(len(lines)):
if lines[i].strip()=='if (value' and lines[i+1].lstrip().startswith('.trim()'):
lines[i]=lines[i].rstrip()+'!'; n+=1 # 注意 rstrip() 保留前导缩进
open(p,'w').write('\n'.join(lines))
```
`.buttonColor``.colorScheme.primary` 已在两个文件修好。)
### 3.2 通用模式(手动加 `!` / `?? 默认` / `== true`
- `String? → String` 参数:`field``field!`(确认运行时非空)。
- `num?/int?/double?` 比较:`subtotal > minPrice``subtotal > minPrice!``(minPrice ?? 0)`
- `bool?` 当条件:`if (x)``if (x == true)``if (!x)``if (x != true)`
- `return null;` 但返回类型非空:把函数返回类型加 `?`(如 `CategoryProducts? foo()`)。
- 局部变量赋值 nullable声明加 `?`(如 `Map<String,dynamic>? x = ...`),下游 nullfix 会补 `!`
- 多行方法调用 `.toUpperCase()` / `.trim()`:接收者在上一行末尾,在该行末尾加 `!`
### 3.3 可复用的定向脚本(一次性,贴进 bash 跑)
**a) 条件修复analyzer 驱动,只修简单 `if (chain)`** — 已用过,安全:
```python
python3 -c "
import re,subprocess
ERR=re.compile(r'\s+error\s+-\s+([\w/.\-]+):(\d+):(\d+)\s+-\s+(.*?)\s+-\s+([a-z_]+)\s*\$')
pat=re.compile(r'^(\s*if \()(!?)([\w.!\[\]]+)(\)\s*\{?\s*)\$')
out=subprocess.run(['dart','analyze','lib'],capture_output=True,text=True).stdout
tg={}
for ln in out.splitlines():
m=ERR.match(ln)
if m and 'used as a condition' in m.group(4): tg.setdefault('lib/'+m.group(1),set()).add(int(m.group(2)))
n=0
for p,ls in tg.items():
L=open(p).read().split('\n');ch=False
for i,x in enumerate(L,1):
if i in ls:
m=pat.match(x.rstrip('\n'))
if m: L[i-1]=m.group(1)+m.group(3)+(' != true' if m.group(2) else ' == true')+m.group(4);ch=True;n+=1
if ch: open(p,'w').write('\n'.join(L))
print('fixed',n)
"
```
**b) 局部变量改可空(严格类型,已验证安全)** — 跑完再 `python3 tools/nullfix.py` 吃级联:
```python
python3 -c "
import re,subprocess
ERR=re.compile(r'\s+error\s+-\s+([\w/.\-]+):(\d+):(\d+)\s+-\s+(.*?)\s+-\s+([a-z_]+)\s*\$')
NAMEQ=re.compile(r\"'([^']+)'\"); TYPE=r'(?:[A-Z]\w*(?:<[^;]*>)?|int|double|String|bool|num|dynamic|List<[^;]*>|Map<[^;]*>|Set<[^;]*>)'
out=subprocess.run(['dart','analyze','lib'],capture_output=True,text=True).stdout;tg={}
for ln in out.splitlines():
m=ERR.match(ln)
if m and m.group(5)=='not_assigned_potentially_non_nullable_local_variable':
nm=NAMEQ.search(m.group(4))
if nm: tg.setdefault('lib/'+m.group(1),[]).append((int(m.group(2)),nm.group(1)))
dp=re.compile(r'^(\s+(?:final\s+|const\s+)?)('+TYPE+r')\s+(\w+)\s*;\s*\$');n=0
for p,it in tg.items():
L=open(p).read().split('\n');ch=False;done=set()
for ul,nm in it:
if (ul,nm) in done: continue
done.add((ul,nm))
for i in range(ul-2,-1,-1):
m=dp.match(L[i])
if m and m.group(3)==nm and '?' not in m.group(2): L[i]=m.group(1)+m.group(2)+'? '+nm+';';ch=True;n+=1;break
if ch: open(p,'w').write('\n'.join(L))
print('fixed',n)
"
```
---
## 4. 工具脚本说明(`tools/`
### `tools/nullfix.py` — 主力自动修复
```bash
python3 tools/nullfix.py # 修整个 lib默认 5 轮
python3 tools/nullfix.py --max-rounds 12
python3 tools/nullfix.py lib/widgets/mobile/shop.dart # 只修单文件
```
- 每轮重新 `dart analyze lib`,按 error 的 (file,line,col) 精确定位修改。
- **带腐败自检**:每轮后检测 `expected_token`/`missing_identifier`,一旦 >0 立即中止并提示 `git diff`
- 已实现的 handler
- `insert_bang`:成员访问/方法调用/迭代器/return 加 `!`(按 analyzer 分类)
- `insert_arg_bang``argument_type`/`invalid_assignment` 在参数值末尾加 `!`(值必须是简单标识符链 `[\w.\[\]()!]`,含运算符/空格跳过)
- `late` 实例字段、参数空安全、field-formal 字段空安全
- `missing_default_value_for_parameter``@required``required` 已并入
- **已禁用的危险 handler**:基于正则的 `if(x)``if(x==true)`(曾破坏 89 文件)。条件修复改用 §3.3a 的 analyzer 驱动版。
- **已知边界**:多行表达式(接收者跨行)、含空格/逗号的函数调用参数、三元/比较表达式 → 跳过,需手动。
### `tools/badge_migrate.py` — 未验证,**已不需要**
badges 3.x 迁移已手动完成(`badgeStyle`/`badgeAnimation`),此脚本可忽略/删除。
---
## 5. 延后项
### `lib/utils/util_io.dart`6 error仅原生
- 通过 `dart.library.html` 条件导入从 **Web 构建中排除**,不影响 `flutter build web`
- 错误来自 `flutter_local_notifications` v17 API 变更:
- `IOSInitializationSettings` / `IOSNotificationDetails` → v17 改名(`DarwinInitializationSettings` / `DarwinNotificationDetails`
- `onSelectNotification` 参数已移除
- `extra_positional_arguments`(构造参数变化)
- `body_might_complete_normally``Future<void>` 缺 return
- 原生Android/iOS构建时才需要修。Web 不阻塞。
---
## 6. 关键上下文 / 决策(避免踩坑)
- **`late` 用于实例字段**:避免模型 `fromJson` 初始化列表 + State 类的级联错误。模型字段置为可空,调用点加 `!`
- **脚本必须 analyzer 驱动**:盲目正则会损坏代码(如 `return? item;``this.?field`)。`nullfix.py` 用 analyzer 的列/列定位 + 腐败自检是关键。
- **badges 实际解析 3.2.0**(不是 2.0.3):参数是 `badgeStyle:`/`badgeAnimation:`(不是 `badgeColor`/`animationType`/`animation`)。
- **pinput 实际 5.0.2**:类名 `Pinput`(非 `PinPut`),导入 `package:pinput/pinput.dart``length`/`onCompleted`/`defaultPinDecoration`
- **youtube_player_iframe 5.2.2**`YoutubePlayerController.fromVideoId(videoId:, startSeconds:, params:)`
- **BreadCrumb.route 已改可空**`String? route`,调用点 `BreadCrumb(X, null)` 合法。
- **e-Transfer**Stripe 已全移除,付款走 `payment@wisetronic.com`,备注写 `R#<receipt.id>`
- **搜索框**:自写轻量 `lib/widgets/general/search_bar.dart`(非第三方包)。
- **不升级全局 Flutter SDK**:用本地 Flutter 3.29.3 / Dart 3.7.2。
## 7. 提交历史(基线 f8a90ad 之后)
```
最新 phase3: mobile/shop clean ... 131->118 ← resume 起点
phase3: checkout files clean ... 142->131
phase3: fix attribute widgets ... ~30 fixed
phase3: FIX arg-bang regex (missing \]) ... 235->183 ← 关键 bug 修复
...(共约 28 个 phase3 提交)
f8a90ad 基线
```
## 8. 最终验收
```bash
flutter build web # 目标:无 error生成 build/web
flutter run -d chrome # 目标:能打开、首页/商品/购物车/结账流程跑通
```
---
## 9. 运行时问题(应用已能运行,以下为后续优化,非阻塞)
应用 `flutter run -d chrome` 已成功启动并加载首页/API 数据/数据库。控制台有以下运行时警告/异常:
### 9.1 Scrollbar 缺 ScrollPosition真实影响滚动条显示
4 个 `Scrollbar(` 未配 `controller`,新版 Flutter 的 Scrollbar 找不到 ScrollPosition
- `lib/pages/home.dart:101` — ✅ 已修:给 `SingleChildScrollView``primary: true`
- `lib/pages/blog.dart:24` — 子是 `DesktopBlog`(自定义组件)
- `lib/pages/download.dart:63` — 子是 `DesktopDownloadApps`(自定义组件)
- `lib/widgets/desktop/shop.dart:137` — 包裹 `NotificationListener`
后 3 处的子是自定义组件,需让内部 ListView/ScrollView 暴露 controller`primary: true`)或 Scrollbar 改用 `NotificationListener` 关联。**仅影响滚动条渲染,不影响内容加载/显示。**
### 9.2 CustomersLogo setState-after-dispose小 bug
`lib/widgets/desktop/desktop_customers_logo.dart``Timer.periodic`(每 10s 刷新 logo未在 `dispose()``cancel()`,组件卸载后 timer 仍触发 `setState`。建议在 `dispose``timer?.cancel()`。**热重载时易触发,干净启动偶发。**
### 9.3 `_business` LateInitializationError热重载假象
`_business` 是店铺页面组件(`shop_promote.dart`/`shop.dart` 等)的 `late Business` 字段。热重载重建 widget 树时字段状态丢失触发,**干净启动(浏览器刷新 Cmd+R不会出现**。
> **验证方式**:在浏览器里 Cmd+R 刷新页面而非热重载9.2/9.3 类异常应消失,仅剩 9.1 的滚动条警告。