Showing
5 changed files
with
388 additions
and
0 deletions
.claude/mcp.json
0 → 100644
.codex/config.toml
0 → 100644
.vscode/mcp.json
0 → 100644
| 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 | +} |
docs/技术文档.md
0 → 100644
| 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` 是否需要同步。 |
library/TPNS-Flutter-Plugin-a5349371525462b1d1b6cfa4cf4d2a59503f1435_new/ohos/oh_modules/bugly
0 → 120000
| 1 | +../../../../ohos/oh_modules/.ohpm/bugly@0.6.1/oh_modules/bugly |
-
Please register or login to post a comment