Commit 688b07fddfe114e1e603750d3a3137bb0cba3bf8

Authored by weixin_45529955
1 parent 82579a79

新建分支 语音播报 功能设置

  1 +{
  2 + "mcpServers": {
  3 + "figma-developer-mcp": {
  4 + "command": "/Users/wangdongni/nodejs/v20/bin/npx",
  5 + "args": [
  6 + "-y",
  7 + "figma-developer-mcp",
  8 + "--stdio"
  9 + ],
  10 + "env": {
  11 + "FIGMA_API_KEY": "figd_MhWE2nNs0P2wrczrUcPeFByg-M_1lME4AnH1XpQWs"
  12 + }
  13 + }
  14 + }
  15 +}
  1 +[mcp_servers.figma-developer-mcp]
  2 +command = "/Users/wangdongni/nodejs/v20/bin/npx"
  3 +args = [
  4 + "-y",
  5 + "figma-developer-mcp",
  6 + "--stdio",
  7 +]
  8 +
  9 +[mcp_servers.figma-developer-mcp.env]
  10 +FIGMA_API_KEY = "figd_MhWE2nNs0P2wrczrUcPeFByg-M_1lME4AnH1XpQWs"
  1 +{
  2 + "mcpServers": {
  3 + "figma-developer-mcp": {
  4 + "command": "/Users/wangdongni/nodejs/v20/bin/npx",
  5 + "args": [
  6 + "-y",
  7 + "figma-developer-mcp",
  8 + "--stdio"
  9 + ],
  10 + "env": {
  11 + "FIGMA_API_KEY": "figd_MhWE2nNs0P2wrczrUcPeFByg-M_1lME4AnH1XpQWs",
  12 + "PATH": "/Users/wangdongni/nodejs/v20/bin:/usr/bin:/bin"
  13 + }
  14 + }
  15 + }
  16 +}
  1 +# 周到安排商家(one_key)开发技术文档
  2 +
  3 +> 本文件供后续协助开发时快速理解项目结构、代码习惯与开发约定。
  4 +> 最后更新:2026-08-06
  5 +
  6 +## 0. 开发约定(重要,优先遵守)
  7 +
  8 +以下是后续开发必须沿用的约定:
  9 +
  10 +1. **屏幕适配**
  11 + - 新建页面一律使用 `w()` / `font()`(来自 `lib/utils/screen_util.dart`),不要直接写 `ScreenUtil().setWidth()` / `ScreenUtil().setSp()`。
  12 + - `w()` / `font()` 的入参就是 **375 设计稿的数值**:`w(375)` = 整屏宽,`font(14)` = 14 号字。
  13 + - **同一个文件内绝不混用**两种写法:新文件用 `w()/font()`;修改老文件时跟随该文件已有风格(老文件多为 `ScreenUtil().setWidth(x)`,其数值是 750 口径,不用改)。
  14 +2. **间距**:一律用 `VEmptyView(高度)` / `HEmptyView(宽度)`。
  15 + - 注意口径:这两个组件内部直接 `setWidth(数值)`(未 ×2),**传 750 口径数值 = 375 设计稿数值 ×2**。详见 §6.2。
  16 +3. **分割线**:一律用 `MyDivider(宽, 高, {color})`,默认色 `MyColours.color_dbe3e3`。
  17 +4. **颜色**:一律从 `MyColours`(`lib/res/colors.dart`)取,不要写裸 `Color(0x...)`。
  18 +5. **页面导航**:新页面三步注册(Routes 常量 → route_handles Handler → NavigatorUtil 方法),见 §7。
  19 +6. **状态管理**:`ChangeNotifier + provider`,页面数据放 Provider,路由级用 `ChangeNotifierProvider` 包裹,见 §8。
  20 +7. **网络请求**:接口地址加在 `HttpUrl`,请求统一走 `DioUtil().request<T>()`,返回 `BaseResp`,成功码 `code == 200`(`Constant.status_success`),见 §9。
  21 +
  22 +---
  23 +
  24 +## 1. 项目概览
  25 +
  26 +- 项目名:`one_key`,App 显示名「周到安排商家」,面向餐厅/商家的预订与经营管理系统。
  27 +- Android 包名:`com.naji.one_key`;iOS Bundle ID:`com.naji.reserveLine`。
  28 +- Flutter:`3.27.3`(通过 fvm 管理,见 `.fvmrc`);Dart SDK `>=3.0.0`。
  29 +- 服务端地址:`http://xt04.colorcun.com/`(`lib/net/http_url.dart`),请求统一 `Method.post`,参数作为 query 拼在 URL 上,接口名形如 `?service=Android.X.Y` / `?service=App.X.Y`。
  30 +- 双版本:正常版 + 极速版(`Constant.normalApp` 控制,当前为 `false` 即双版本共存;极速版页面在 `lib/pages_sim/`,路由名带 `_sim`)。
  31 +- 微信:AppId `wx4d105e36a36a6139`,UniversalLink `https://xt04.colorcun.com/link/OneKey/`。
  32 +- 文案:UI 文案基本硬编码中文,`lib/generated/l10n` 实际使用很少。
  33 +
  34 +## 2. 环境与常用命令
  35 +
  36 +```bash
  37 +fvm flutter run # 运行
  38 +fvm flutter pub run build_runner build --delete-conflicting-outputs # 生成 model 的 .g.dart
  39 +fvm flutter analyze # 静态检查
  40 +```
  41 +
  42 +## 3. 目录结构
  43 +
  44 +| 目录 | 说明 |
  45 +| --- | --- |
  46 +| `lib/main.dart` | 入口:初始化路由、SharedPreferences、全局 Provider、ScreenUtilInit、MaterialApp |
  47 +| `lib/app.dart` | `App` 全局静态类:`App.sp`(SharedPreferences)、`App.router`、`App.navigatorKey`、屏幕/状态栏信息 |
  48 +| `lib/pages/` | 业务页面,按模块分子目录(home、login、message、notebook、table、feast、my、detail、coupon、order_advance、create_store 等) |
  49 +| `lib/pages_sim/` | 极速版页面副本,与正常版同名页面需同步维护 |
  50 +| `lib/provider/` | 状态管理:`ChangeNotifier` 子类,命名 `XxxProvider` / `XxxModel` |
  51 +| `lib/models/` | 数据模型:`json_serializable` + `.g.dart`,常见命名 `XxxSunModel` |
  52 +| `lib/net/` | 网络层:`dio_util.dart`(DioUtil 单例)、`base_resp.dart`(BaseResp)、`http_url.dart`(接口地址常量) |
  53 +| `lib/route/` | fluro 路由:`routes.dart`(路径常量 + 注册)、`route_handles.dart`(Handler)、`navigator_util.dart`(跳转方法) |
  54 +| `lib/res/` | `colors.dart`(MyColours 色板)、`dimens.dart`(Dimens 字号/间距常量)、`styles.dart`、`strings.dart` |
  55 +| `lib/utils/` | `screen_util.dart`(w/h/font 等适配函数)、`utils.dart`(Utils 工具大集合)、`dialog_util.dart`、`event_bus.dart`、`misc.dart`(权限)等 |
  56 +| `lib/widgets/` | 公共组件:SystemAppBar、RoundButton、CommonButton、VEmptyView、HEmptyView、MyDivider、ExceptionPage 等 |
  57 +| `lib/constant/` | `constant.dart`(全局常量、权限配置)、`state.dart`(PageState 等枚举)、`error.dart`(MyError) |
  58 +| `assets/` | 图片与音频资源,已在 pubspec 全量声明 |
  59 +| `library/` | 本地依赖插件(webview_flutter 修改版、sendsms、table_calendar 等) |
  60 +
  61 +## 4. 启动流程(main.dart)
  62 +
  63 +1. `FluroRouter` 创建并调用 `Routes.configureRoutes(router)` 注册全部路由,赋给 `App.router`。
  64 +2. `App.initSp()` 初始化 `SharedPreferences`(全局通过 `App.sp` 访问)。
  65 +3. `runApp(MultiProvider(...))` 注册全局 Provider(MessageModel、UserModel、OrderFoodProvider、CouponProvider、CreateStoreModel 等)。
  66 +4. `ScreenUtilInit(designSize: Size(750, 1334), minTextAdapt: true, splitScreenMode: true)` 初始化屏幕适配,注意 **设计尺寸是 750×1334**。
  67 +5. `MaterialApp`:主题 `useMaterial3: false`,`primaryColor: Colors.white`,iOS 转场用 `PopScopePageTransitionsBuilder`,`home` 由本地状态决定:
  68 + - 首次打开(`isFirst`)→ `FirstPage`(初始化微信/网络等后跳登录);
  69 + - 已登录 → `HomePage(initialIndex: 1)`;
  70 + - 未登录 → `LoginPage`。
  71 +
  72 +## 5. 整体架构与数据流
  73 +
  74 +```
  75 +页面 (pages/) ── Provider.of / Consumer ──> Provider (provider/)
  76 +Provider ── DioUtil().request() ──> HttpUrl 接口 ──> 服务端 (xt04.colorcun.com)
  77 +页面状态:PageState 枚举 → ExceptionPage 统一渲染 加载中/网络错误/超时/空页面
  78 +```
  79 +
  80 +关键点:
  81 +
  82 +- 页面通过 `Provider.of<XxxProvider>(context, listen: true/false)` 或 `Consumer<XxxProvider>` 拿数据;Provider 内部 `notifyListeners()` 驱动刷新。
  83 +- 每个 Provider 通常维护若干 `PageState` 字段(`Loading / ConnectivityError / ConnectivityTimeOut / Empty / Success` 等,见 `lib/constant/state.dart`),页面据此渲染 `ExceptionPage` 或列表内容。
  84 +- 用户信息:`UserModel`(全局注册),登录后用户对象 JSON 存 `App.sp`(key:`user`),通过 `Provider.of<UserModel>(context, listen: false).user!` 获取 `sellerid` / `openid` / `token` 等。
  85 +- 权限控制:`Constant.authorityConfig`(模块 → 角色数组)+ `Utils.getAuthority(context, '模块名')` 判断当前角色是否有权。
  86 +
  87 +## 6. UI 规范(重点)
  88 +
  89 +### 6.1 屏幕适配:w() / font() 与 375 设计稿
  90 +
  91 +`lib/utils/screen_util.dart` 提供顶层函数(新页面直接 import 使用):
  92 +
  93 +```dart
  94 +double w(num width) => ScreenUtil().setWidth(width * 2); // 宽(宽度等比缩放)
  95 +double h(num height) => ScreenUtil().setWidth(height * 2); // 高(也是 setWidth!按宽度缩放)
  96 +double h2(num height) => ScreenUtil().setHeight(height * 2); // 高(按高度缩放,少用)
  97 +double r(num radius) => ScreenUtil().radius(radius * 2); // 圆角
  98 +double font(num size) => ScreenUtil().setSp(size * 2); // 字号
  99 +```
  100 +
  101 +由于 `ScreenUtilInit` 的设计尺寸是 **750×1334**,而上述函数内部把入参 ×2,所以:
  102 +
  103 +- **`w()` / `font()` 的入参即 375 设计稿数值**:`w(375)` = 整屏宽,`w(20)` = 20 逻辑像素,`font(16)` = 16 号字,`r(8)` = 8 圆角。
  104 +- 后续设计稿按 375 宽,新页面直接把稿子上的数值填进 `w()/font()/r()` 即可,**不需要自己换算**。
  105 +- `h()` 实际按宽度等比缩放(与 `w()` 一致),保证横向适配时高度不溢出;`h2()` 才是按高度缩放。
  106 +
  107 +> 老文件大量使用 `ScreenUtil().setWidth(40)`、`ScreenUtil().setSp(28)` 这种 750 口径写法(数值 ≈ 375 值 ×2)。**修改老文件时保持该文件原有写法,不要混入 `w()/font()`;新建文件则统一用 `w()/font()`。**
  108 +
  109 +### 6.2 间距:VEmptyView / HEmptyView
  110 +
  111 +- `VEmptyView(高度)` → `SizedBox(height: ScreenUtil().setWidth(高度))`
  112 +- `HEmptyView(宽度)` → `SizedBox(width: ScreenUtil().setWidth(宽度))`
  113 +
  114 +⚠️ **口径注意**:这两个组件内部直接 `setWidth(数值)`,**没有 ×2**,所以传的是 750 口径数值。换算关系:
  115 +
  116 +| 375 设计稿间距 | 代码写法 |
  117 +| --- | --- |
  118 +| 6px | `VEmptyView(12)` / `HEmptyView(12)` |
  119 +| 12px | `VEmptyView(24)` |
  120 +| 24px | `VEmptyView(48)` |
  121 +
  122 +也就是说:**375 设计稿上的间距数值 ×2 后传给 VEmptyView/HEmptyView**。已有代码都是这个口径(例如 `w()` 风格文件中 `VEmptyView(24)` 与 `w(12)` 实际渲染尺寸一致)。
  123 +
  124 +### 6.3 分割线:MyDivider
  125 +
  126 +```dart
  127 +MyDivider(宽, 高, {color});
  128 +// 例如:MyDivider(w(375), w(1)) 默认颜色 MyColours.color_dbe3e3
  129 +// 自定义颜色:MyDivider(w(375), w(1), color: MyColours.color_DBDBDB)
  130 +```
  131 +
  132 +新页面分割线一律用 `MyDivider`,不要用 `Divider()` / `Container(color:...)` 代替。
  133 +
  134 +### 6.4 颜色:MyColours
  135 +
  136 +色板集中在 `lib/res/colors.dart`(`MyColours` 类),命名 `color_<十六进制>`。常用色:
  137 +
  138 +| 用途 | 常量 | 值 |
  139 +| --- | --- | --- |
  140 +| 主色(App 主题青绿色) | `MyColours.app_main` | `0xFF24A4A8` |
  141 +| 主操作色(按钮/选中/链接) | `MyColours.color_199095` | `0xFF199095` |
  142 +| 辅助青绿(强调/标签) | `MyColours.color_438E93` | `0xFF438E93` |
  143 +| 正文深色 | `MyColours.color_333333` / `color_353535` / `color_515151` | `0xFF333333` 等 |
  144 +| 次要文字 | `MyColours.color_666666` / `color_777777` | `0xFF666666` 等 |
  145 +| 弱化文字/占位 | `MyColours.color_999999` / `color_bfbfbf` | `0xFF999999` 等 |
  146 +| 页面背景 | `MyColours.color_f5f5f5` / `color_f7f7f7` / `color_f8f9fd` | 浅灰 |
  147 +| 白色 | `MyColours.color_ffffff` | `0xFFFFFFFF` |
  148 +| 红色(错误/删除/未读角标) | `MyColours.color_E34D59` / `color_fa5151` / `color_E95750` | 红系 |
  149 +| 分割线默认 | `MyColours.color_dbe3e3` | `0xFFdbe3e3` |
  150 +
  151 +### 6.5 字号与文字
  152 +
  153 +- 新页面统一 `font(数值)`:常用 `font(10) / font(12) / font(14) / font(16) / font(18) / font(22)`。
  154 +- `lib/res/dimens.dart` 中有 `Dimens.font_sp10~24` 等常量,但页面里使用较少,新代码不强制使用。
  155 +- 文字样式一般直接 `TextStyle(fontSize: font(14), color: MyColours.color_333333)`;不需要 `ThemeData.textTheme`。
  156 +
  157 +### 6.6 图片资源
  158 +
  159 +- 图片在 `assets/`,pubspec 已声明 `- assets/`,新图直接放入即可。
  160 +- 加载方式:`Image.asset(Utils.getImgPath('xxx'))`(自动补 `assets/xxx.png`),或网络图 `CachedNetworkImage`。
  161 +
  162 +### 6.7 常用公共组件
  163 +
  164 +| 组件 | 文件 | 说明 |
  165 +| --- | --- | --- |
  166 +| `SystemAppBar` | `lib/widgets/system_app_bar.dart` | 标准导航栏(AppBar 实现),标题居中,返回键 `assets/icon_back.png`,可配右侧文字/图片按钮 |
  167 +| `MyAppBar` | `lib/widgets/app_bar.dart` | 自绘导航栏,可自定义背景/字体色/右侧按钮 |
  168 +| `RoundButton` | `lib/widgets/round_button.dart` | 圆角渐变按钮,常用参数 `text/width/height/radius/fontSize/onPressed`;w 风格文件里 `width: w(48), height: h(18), radius: r(7)` |
  169 +| `CommonButton` | `lib/widgets/common_button.dart` | 状态按钮,`state`:-1 灰禁、0 描边、1 实心主色、-3 红色 |
  170 +| `GradientButton` | `lib/widgets/gradient_button.dart` | 纯渐变容器按钮 |
  171 +| `ExceptionPage` | `lib/widgets/page_exception.dart` | 按 `PageState` 渲染 加载中/网络错误/超时/空页面,带重试回调 |
  172 +| `LoadingPage / EmptyPage / ErrorPage` | `lib/widgets/` | 状态页基础组件 |
  173 +| `Loading` | `lib/widgets/loading.dart` | 全局 loading 弹层:`Loading.showLoading(context)` / `hideLoading` |
  174 +| `MyItem` | `lib/widgets/my_item.dart` | 「标题 + 内容 + 右箭头」列表行 |
  175 +| `RowMultiText` | `lib/widgets/row_multi_text.dart` | 左右两段文字行 |
  176 +| `TextInputFieldWidget` | `lib/widgets/text_input_field_widget.dart` | 无边框输入框 |
  177 +| `FixedText` | `lib/widgets/fixed_text.dart` | 固定字号文本(`textScaleFactor: 1.0`) |
  178 +| `CustomTabIndicator` | `lib/widgets/custom_tab_indicator.dart` | 自定义 Tab 指示器 |
  179 +
  180 +### 6.8 页面骨架模板(新页面按此写)
  181 +
  182 +```dart
  183 +import 'package:flutter/material.dart';
  184 +import 'package:one_key/res/colors.dart';
  185 +import 'package:one_key/utils/screen_util.dart'; // w()/font()
  186 +import 'package:one_key/widgets/MyDivider.dart'; // 分割线
  187 +import 'package:one_key/widgets/h_empty_view.dart'; // 水平间距
  188 +import 'package:one_key/widgets/v_empty_view.dart'; // 垂直间距
  189 +import 'package:one_key/widgets/system_app_bar.dart'; // 导航栏
  190 +
  191 +class DemoPage extends StatefulWidget {
  192 + const DemoPage({super.key});
  193 +
  194 + @override
  195 + State<DemoPage> createState() => _DemoPageState();
  196 +}
  197 +
  198 +class _DemoPageState extends State<DemoPage> {
  199 + @override
  200 + void initState() {
  201 + super.initState();
  202 + WidgetsBinding.instance.addPostFrameCallback((_) {
  203 + // 初始化请求
  204 + });
  205 + }
  206 +
  207 + @override
  208 + Widget build(BuildContext context) {
  209 + return Scaffold(
  210 + backgroundColor: MyColours.color_f5f5f5,
  211 + appBar: SystemAppBar('页面标题'),
  212 + body: Column(
  213 + children: [
  214 + Container(
  215 + width: w(375), // 375 设计稿直接填
  216 + height: w(48),
  217 + padding: EdgeInsets.symmetric(horizontal: w(20)),
  218 + alignment: Alignment.centerLeft,
  219 + child: Text('内容',
  220 + style: TextStyle(
  221 + fontSize: font(16), color: MyColours.color_333333)),
  222 + ),
  223 + MyDivider(w(375), w(1)), // 分割线
  224 + VEmptyView(24), // 间距 = 375稿 12px
  225 + Row(children: [
  226 + HEmptyView(20), // 水平间距 = 375稿 10px
  227 + const Text('另一段'),
  228 + ]),
  229 + ],
  230 + ),
  231 + );
  232 + }
  233 +}
  234 +```
  235 +
  236 +### 6.9 首页 Tab 结构
  237 +
  238 +`HomePage`(`lib/pages/home/home_page.dart`):`PageView`(`NeverScrollableScrollPhysics`)+ `BottomNavigationBar`,共 5 个 Tab:
  239 +
  240 +| index | 页面 | 底部图标 |
  241 +| --- | --- | --- |
  242 +| 0 | 消息 `MessagePage` | `message_default/selected.png` |
  243 +| 1 | 预订本 `NoteBookPage`(默认页) | `notebook_default/selected.png` |
  244 +| 2 | 桌位 `TableManagerPage` | `tablemanage_default/selected.png` |
  245 +| 3 | 宴会 `FeastPage` | `y_feast_un/sel.png` |
  246 +| 4 | 我的 `MyPage` | `my_default/selected.png` |
  247 +
  248 +## 7. 路由规范(fluro)
  249 +
  250 +新增页面三步:
  251 +
  252 +1. `lib/route/routes.dart`:定义路径常量,如 `static String demo_page = "/demo_page";`。
  253 +2. `lib/route/route_handles.dart`:定义 `Handler`,构造页面并包上需要的 `ChangeNotifierProvider`;然后在 `Routes.configureRoutes` 里 `router.define(Routes.demo_page, handler: xxxHandler);`。
  254 +3. `lib/route/navigator_util.dart`:写 `static void goDemoPage(BuildContext context, {参数})`,内部 `App.router.navigateTo(context, "...")`。
  255 +
  256 +传参规则:
  257 +
  258 +- 简单参数拼在路径 query 上,如 `${Routes.demo_page}?id=$id`,Handler 里 `params["id"]!.first` 取。
  259 +- 复杂对象先 `json.encode` + `FluroConvertUtils.fluroCnParamsDecode` 编码,Handler 里 `FluroConvertUtils.fluroCnParamsDecode(...)` 解码后 `XxxModel.fromJson`。
  260 +- 跳转前 `NavigatorUtil` 内部会先 `Utils.changeStatusColor(color: MyColours.color_ffffff)`。
  261 +
  262 +## 8. 状态管理规范(provider)
  263 +
  264 +- 全局状态:`main.dart` 的 `MultiProvider` 注册(UserModel、MessageModel、OrderFoodProvider 等)。
  265 +- 页面级状态:路由 Handler 里用 `ChangeNotifierProvider<XxxProvider>(create: (_) => XxxProvider(), child: XxxPage())` 包裹(可多层嵌套)。
  266 +- 页面取数据:`Provider.of<XxxProvider>(context, listen: true)`(监听)或 `listen: false`(不监听,如请求方法内);列表用 `Consumer<XxxProvider>` 更清晰。
  267 +- Provider 内请求完成后 `notifyListeners()`;页面 `setState` 一般只在页面自身状态变化时调用。
  268 +- 页面初始化请求放在 `initState` + `WidgetsBinding.instance.addPostFrameCallback` 中(避免 build 前请求)。
  269 +
  270 +## 9. 网络请求规范
  271 +
  272 +### 9.1 接口定义
  273 +
  274 +`lib/net/http_url.dart`:`static const String xxx = "?service=App.X.Y"; // 注释`。
  275 +基础地址 `HttpUrl.server_address = "http://xt04.colorcun.com/"`,请求统一 `Method.post`,`data` 参数会作为 query 拼在 URL 上(`DioUtil` 内部 `queryParameters: data`)。
  276 +
  277 +### 9.2 发起请求
  278 +
  279 +```dart
  280 +BaseResp<dynamic> baseResp = await DioUtil().request<dynamic>(
  281 + context, Method.post, HttpUrl.xxx,
  282 + data: params, // Map<String, String?>,会作为 queryParameters
  283 + showLoading: true, // 是否显示全局 loading
  284 +);
  285 +if (baseResp.code == Constant.status_success) { // 200
  286 + // baseResp.data / baseResp.responseData
  287 +} else {
  288 + Utils.showToast(baseResp.msg ?? "");
  289 +}
  290 +```
  291 +
  292 +要点:
  293 +
  294 +- `BaseResp` 字段:`status / code / msg / data / responseData`;响应结构 `{ret, msg, data}`,`ret == 200` 为成功(`HttpConfig` 在 `main.dart` 的 `initDio()` 里配置:`status: "ret", code: "ret", msg: "msg", data: "data"`)。
  295 +- 常见参数:`sellerid` / `openid` 从 `Provider.of<UserModel>(context, listen: false).user!` 取;部分接口要 `sid`、`uid`、`token`。
  296 +- 错误处理:请求抛 `MyError`(`lib/constant/error.dart`),`type: 1` = 网络错误,`type: 2` = 超时;Provider 里通常据此把 `pageState` 置为 `PageState.ConnectivityError / ConnectivityTimeOut`,再 `notifyListeners()`。
  297 +- 断网时 `DioUtil` 内部会 `Utils.showToast("网络错误,请稍后重试")`。
  298 +- 上传文件(FormData)可参考 `FinanceProvider.upLoadApplyments` / `UploadImageProvider`(七牛上传)。
  299 +
  300 +## 10. 数据模型规范
  301 +
  302 +- 文件在 `lib/models/`,类名多为 `Xxx` / `XxxSunModel`。
  303 +- 使用 `json_serializable`:类上加 `@JsonSerializable()`,文件末尾 `part 'xxx.g.dart';`,提供 `factory Xxx.fromJson(...)` 与 `toJson()`。
  304 +- 修改模型字段后运行 build_runner 重新生成 `.g.dart`:
  305 + `fvm flutter pub run build_runner build --delete-conflicting-outputs`
  306 +
  307 +## 11. 常用工具速查
  308 +
  309 +- `Utils`(`lib/utils/utils.dart`):
  310 + - `Utils.showToast(msg)`:Toast 提示;
  311 + - `Utils.changeStatusColor({color})`:状态栏颜色;
  312 + - `Utils.getImgPath(name)`:拼 `assets/name.png`;
  313 + - `Utils.getAuthority(context, key)`:角色权限判断;
  314 + - `Utils.keySort(map)`:参数签名排序(部分接口需要);
  315 + - `Utils.compressImage / compressImageToFile`:图片压缩;
  316 + - 时间/农历/正则/手机号脱敏等大量工具方法。
  317 +- `DialogUtil`(`lib/utils/dialog_util.dart`):标签选择、滚轮选择器、消息弹窗、日历选择等。
  318 +- `EventBus`(`lib/utils/event_bus.dart`):全局 `bus.on(事件名, cb)` / `bus.emit(事件名, arg)`。
  319 +- 权限:`lib/utils/misc.dart` 的 `requestPermission(Permission.xxx)`(带使用目的说明弹窗)。
  320 +- 全局存储:`App.sp!.getString / setString / getBool ...`(SharedPreferences 单例)。
  321 +
  322 +## 12. 注意事项与常见坑
  323 +
  324 +1. **适配写法不要混用**:`w()` 是 375 口径、`ScreenUtil().setWidth()` 是 750 口径,混用会直接导致尺寸差一倍。同一文件保持一种风格。
  325 +2. **VEmptyView/HEmptyView 是 750 口径**:按 375 稿子写间距要 ×2(§6.2 有对照表)。
  326 +3. **`h()` 是宽度等比缩放**:不要用它做需要按屏幕高度伸缩的布局;需要高度适配用 `h2()`。
  327 +4. **极速版同步**:`lib/pages_sim/` 是极速版副本,修改正常版核心页面时注意是否需要在 sim 版同步;路由常量带 `_sim` 后缀。
  328 +5. **老页面是状态机**:列表页先判 `PageState` 再渲染 `ExceptionPage` / `ListView`,不要绕过直接写列表。
  329 +6. **接口地址都在 HttpUrl**:不要在新代码里硬编码 URL。
  330 +7. **模型改了记得跑 build_runner**,否则 `fromJson` 不会更新。
  331 +8. **打包注意事项**(`lib/constant/constant.dart` 注释):`Constant.normalApp` 控制极速版是否打包;测试账号已由 `0001` 换成 `0019`;微信小程序类型 `Constant.wxMiniProgramType` 分正式/体验/开发。
  332 +9. **新建图片记得放 `assets/`**(目录已全量声明,无需改 pubspec,除非新增音频 mp3 需单独声明)。
  333 +10. **请求里传 `sellerid/openid` 前判空**,UserModel 未登录时 `user` 可能为 null。
  334 +
  335 +## 13. 新页面开发 Checklist
  336 +
  337 +- [ ] 页面文件放对应业务目录(`lib/pages/模块/xxx_page.dart`),类名 `XxxPage`;
  338 +- [ ] import `utils/screen_util.dart`,全程用 `w()/font()/r()`,不写 `ScreenUtil().setWidth`;
  339 +- [ ] 间距用 `VEmptyView/HEmptyView`(375 稿数值 ×2),分割线用 `MyDivider`;
  340 +- [ ] 颜色只从 `MyColours` 取;
  341 +- [ ] 导航栏用 `SystemAppBar`(普通页)/ `MyAppBar`(自定义样式);
  342 +- [ ] 请求方法放对应 Provider,`HttpUrl` 加接口,成功判断 `code == Constant.status_success`;
  343 +- [ ] 页面状态用 `PageState + ExceptionPage`;
  344 +- [ ] 在 `routes.dart`、`route_handles.dart`、`navigator_util.dart` 完成三步注册;
  345 +- [ ] 需要权限的入口用 `Utils.getAuthority(context, key)` 过滤;
  346 +- [ ] 涉及正常版/极速版双端时,检查 `pages_sim` 是否需要同步。
  1 +../../../../ohos/oh_modules/.ohpm/bugly@0.6.1/oh_modules/bugly