优化配置,修复 demo bug
1,network 框架完善 2,websocket 机制完善 3,设计文档整理到架构文档 4,脚本,配置完善
This commit is contained in:
@@ -883,13 +883,16 @@ flowchart TD
|
||||
│ ├── data/
|
||||
│ │ ├── datasources/
|
||||
│ │ │ ├── http/
|
||||
│ │ │ │ ├── api_client.dart # Dio REST 客户端
|
||||
│ │ │ │ ├── api_client.dart # Dio REST 客户端
|
||||
│ │ │ │ ├── token_refresh_manager.dart # Token 刷新管理(竞态安全 + 超时 + 时间窗口复用)
|
||||
│ │ │ │ └── interceptor/
|
||||
│ │ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
|
||||
│ │ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试
|
||||
│ │ │ │ └── logging_interceptor.dart # 请求/响应日志
|
||||
│ │ │ └── socket/
|
||||
│ │ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream)
|
||||
│ │ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
|
||||
│ │ │ │ ├── encryption_interceptor.dart # 加密拦截器(预留给 cipher_guard_sdk)
|
||||
│ │ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试 + 业务错误钩子
|
||||
│ │ │ │ └── logging_interceptor.dart # 请求/响应日志
|
||||
│ │ │ ├── socket/
|
||||
│ │ │ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream/加密钩子)
|
||||
│ │ │ └── networks_sdk_method_channel_datasource.dart # 统一执行入口
|
||||
│ │ ├── dto/
|
||||
│ │ │ ├── api_requestable.dart # 请求基类 + fromJson 注册表
|
||||
│ │ │ └── api_response_wrapper.dart # { code, message/msg, data } 信封解析
|
||||
@@ -898,11 +901,12 @@ flowchart TD
|
||||
│ │ └── networks_messaging_repository_impl.dart
|
||||
│ ├── domain/
|
||||
│ │ ├── entities/
|
||||
│ │ │ ├── api_error.dart # @freezed HTTP 错误联合类型
|
||||
│ │ │ ├── api_error.dart # @freezed HTTP 错误联合类型(7 变体)
|
||||
│ │ │ ├── encrypted_request.dart # 加密请求结果数据类
|
||||
│ │ │ ├── socket_error.dart # @freezed WebSocket 错误联合类型
|
||||
│ │ │ ├── socket_connection_state.dart # 连接状态 enum
|
||||
│ │ │ ├── http_method.dart # GET/POST/PUT/DELETE/PATCH
|
||||
│ │ │ └── api_request_type.dart # request/login/upload
|
||||
│ │ │ └── api_request_type.dart # request/login/upload/stream/download
|
||||
│ │ └── repositories/
|
||||
│ │ ├── networks_sdk_repository.dart
|
||||
│ │ └── networks_messaging_repository.dart
|
||||
@@ -911,9 +915,9 @@ flowchart TD
|
||||
│ │ ├── networks_sdk_api.dart # HTTP 公开 API 接口
|
||||
│ │ └── networks_messaging_api.dart # WebSocket 公开 API 接口
|
||||
│ └── wiring/
|
||||
│ ├── api_config.dart # HTTP 配置(baseURL/Token/回调)
|
||||
│ ├── socket_config.dart # WebSocket 配置(心跳/重连策略)
|
||||
│ ├── network_callbacks.dart # 回调类型定义
|
||||
│ ├── api_config.dart # HTTP 配置(baseURL/Token/回调/加密/重试)
|
||||
│ ├── socket_config.dart # WebSocket 配置(心跳/重连/加密钩子)
|
||||
│ ├── network_callbacks.dart # 回调类型定义(认证/加密/业务错误/下载/WS)
|
||||
│ ├── networks_sdk_core.dart
|
||||
│ ├── networks_sdk_api_impl.dart
|
||||
│ ├── networks_messaging_api_impl.dart
|
||||
@@ -1670,7 +1674,7 @@ final viewModel = ref.read(chatViewModelProvider.notifier); // viewModel 类型
|
||||
<pre><code class="language-dart">// Riverpod DevTools 可以看到完整的依赖图
|
||||
ChatViewModel
|
||||
├─ chatRepositoryProvider
|
||||
│ ├─ apiClientProvider
|
||||
│ ├─ networkSdkApiProvider
|
||||
│ └─ messageLocalDataSourceProvider
|
||||
└─ sendMessageUseCaseProvider
|
||||
└─ chatRepositoryProvider
|
||||
@@ -2187,8 +2191,8 @@ extension APIRequestableDefaults<T> on APIRequestable<T> {
|
||||
|
||||
<pre><code class="language-dart">/// 执行 API 请求 - 唯一的请求入口
|
||||
Future<T?> executeRequest<T>(Ref ref, APIRequestable<T> request) async {
|
||||
final dio = ref.read(apiClientProvider);
|
||||
final config = ref.read(aPIConfigurationProvider);
|
||||
final client = ref.read(networkSdkApiProvider);
|
||||
final config = ref.read(apiConfigProvider);
|
||||
|
||||
// 1. 检查网络连接
|
||||
if (!networkManager.isNetworkAvailable) {
|
||||
@@ -2646,18 +2650,20 @@ final apiConfigProvider = Provider<ApiConfig>((ref) {
|
||||
);
|
||||
});
|
||||
|
||||
/// 2. API 客户端(内部自动挂载 Auth / Retry / Logging 拦截器)
|
||||
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
return ApiClient(config: ref.read(apiConfigProvider));
|
||||
/// 2. Networks SDK API Provider(全局单例,Facade 接口)
|
||||
/// 内部自动挂载 AuthInterceptor / EncryptionInterceptor / RetryInterceptor / LoggingInterceptor
|
||||
final networkSdkApiProvider = Provider<NetworksSdkApi>((ref) {
|
||||
final config = ref.read(apiConfigProvider);
|
||||
return NetworksSdkWiring.build(config: config);
|
||||
});
|
||||
|
||||
// ── features/auth/di/auth_providers.dart ── (Auth 模块完整 DI 链路)
|
||||
|
||||
/// 3. Repository(注入 domain 接口类型,ViewModel 不感知具体实现)
|
||||
/// 3. Repository(注入 Facade 接口类型,ViewModel 不感知具体实现)
|
||||
final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||
final apiConfig = ref.read(apiConfigProvider);
|
||||
return AuthRepositoryImpl(
|
||||
client: ref.read(apiClientProvider), // 直接注入 ApiClient
|
||||
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
onTokenUpdate: (token) {
|
||||
apiConfig.updateToken(token); // 内存(networks_sdk)
|
||||
// secureStorage.saveToken(token); // 持久化(storage_sdk,待接入)
|
||||
@@ -2707,8 +2713,9 @@ class LoginViewModel extends _$LoginViewModel {
|
||||
→ Repository: _client.executeRequest(LoginRequest(...))
|
||||
→ ApiClient.executeRequest() ← networks_sdk 内部
|
||||
→ AuthInterceptor ← 注入 token + headers
|
||||
→ EncryptionInterceptor ← 加密请求体(预留)
|
||||
→ Dio.request(baseURL + path, data) ← 实际 HTTP 请求
|
||||
→ RetryInterceptor ← token 过期自动刷新重试
|
||||
→ RetryInterceptor ← token 过期自动刷新重试 + 业务错误钩子
|
||||
→ LoggingInterceptor ← 请求/响应日志
|
||||
← request.decodeResponse(response) ← 自动解码
|
||||
← ApiResponseWrapper.fromJson ← 拆 { code, msg, data }
|
||||
@@ -2987,8 +2994,9 @@ flowchart TD
|
||||
│ │ └── auth_guard.dart # 登录守卫(switch AppRouteName,穷举防漏路由)
|
||||
│ │
|
||||
│ └── di/ # 全局 DI — 手动装配的 Provider
|
||||
│ ├── network_provider.dart # NetworkMonitor + ApiConfig + ApiClient + SocketConfig + SocketClient + SocketManager
|
||||
│ └── app_providers.dart # 全局共享状态(themeModeProvider + AuthNotifier)
|
||||
│ ├── network_provider.dart # NetworkMonitor + ApiConfig + NetworksSdkApi + SocketConfig + SocketClient + SocketManager
|
||||
│ ├── db_provider.dart # StorageSdkApi(注入 AppDatabase factory)
|
||||
│ └── app_providers.dart # AppInitializer + ThemeModeNotifier + AuthNotifier
|
||||
│
|
||||
├── features/ # 功能模块(垂直切片):Feature 间禁止直接 import
|
||||
│ │
|
||||
@@ -3022,6 +3030,7 @@ flowchart TD
|
||||
│ ├── di/
|
||||
│ │ └── settings_providers.dart # settingsRepositoryProvider(待 storage_sdk 接入)
|
||||
│ ├── presentation/
|
||||
│ │ ├── settings_view_model.dart # @riverpod ViewModel(设置页导航)
|
||||
│ │ └── theme_view_model.dart # @riverpod ViewModel(生成 theme_view_model.g.dart)
|
||||
│ ├── usecases/
|
||||
│ │ └── set_theme_usecase.dart # 主题切换用例
|
||||
@@ -3081,6 +3090,8 @@ flowchart TD
|
||||
│
|
||||
└── ui/ # Core UI(设计系统 + 可复用组件)
|
||||
├── base/ # 设计 Token
|
||||
│ ├── assets.dart # 静态资源路径常量(AppAssets:logo / 占位图)
|
||||
│ ├── icons.dart # 图标常量(AppIcons:导航 / 操作 / 聊天 / 用户 / 状态)
|
||||
│ ├── app_theme.dart # ThemeData 组装(Light / Dark)
|
||||
│ ├── colors.dart # 颜色体系(品牌色 / 语义色 / 灰阶)
|
||||
│ ├── context_theme_ext.dart # BuildContext 主题扩展(context.theme / context.colors)
|
||||
@@ -3172,7 +3183,7 @@ flowchart LR
|
||||
direction TB
|
||||
Step1["① 用户点击发送按钮"]
|
||||
Step2["② ref.read(chatVM.notifier)<br/>.sendMessage(content)"]
|
||||
Step3["③ ViewModel 调用 UseCase<br/>→ Repository → ApiClient"]
|
||||
Step3["③ ViewModel 调用 UseCase<br/>→ Repository → NetworksSdkApi"]
|
||||
Step4["④ state = state.copyWith(<br/>messages: [..., newMsg])"]
|
||||
Step5["⑤ ref.watch(chatVM) 检测变化<br/>→ ConsumerWidget 自动 rebuild"]
|
||||
Step6["⑥ UI 展示最新消息列表"]
|
||||
@@ -3356,7 +3367,7 @@ abstract class ChatRepository {
|
||||
|
||||
// Data 层实现接口
|
||||
class ChatRepositoryImpl implements ChatRepository {
|
||||
final ApiClient _client;
|
||||
final NetworksSdkApi _client;
|
||||
final MessageLocalDataSource _localDataSource;
|
||||
|
||||
@override
|
||||
@@ -5623,7 +5634,7 @@ flowchart TD
|
||||
│ ├── file_storage.dart # 文件存储管理
|
||||
│ └── image_cache.dart # 图片缓存
|
||||
│
|
||||
├── remote/ # Request 文件(一个端点一个文件,Repository 直接调 ApiClient)
|
||||
├── remote/ # Request 文件(一个端点一个文件,Repository 直接调 NetworksSdkApi)
|
||||
│ ├── login_request.dart # 登录端点
|
||||
│ ├── logout_request.dart # 登出端点
|
||||
│ ├── send_message_request.dart # 发消息端点
|
||||
@@ -5648,17 +5659,17 @@ flowchart TD
|
||||
Domain[Domain Layer<br/>domain/repositories/<br/>Repository 接口] -.实现.-> Repo[Data Layer<br/>data/repositories/<br/>Repository 实现]
|
||||
|
||||
Repo -->|读取| LocalDS[Local DataSource<br/>data/local/]
|
||||
Repo -->|请求| ApiClient[ApiClient<br/>networks_sdk]
|
||||
Repo -->|请求| SdkApi[NetworksSdkApi<br/>networks_sdk]
|
||||
Repo -->|缓存| Cache[Cache Manager<br/>data/cache/]
|
||||
|
||||
LocalDS -->|Drift| DB[(Database)]
|
||||
ApiClient -->|HTTP/WebSocket| API[API Server]
|
||||
SdkApi -->|HTTP/WebSocket| API[API Server]
|
||||
Cache -->|内存| Memory[Memory Cache]
|
||||
|
||||
style Domain fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
|
||||
style Repo fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
|
||||
style LocalDS fill:#c8e6c9,stroke:#388e3c
|
||||
style ApiClient fill:#c8e6c9,stroke:#388e3c
|
||||
style SdkApi fill:#c8e6c9,stroke:#388e3c
|
||||
style Cache fill:#c8e6c9,stroke:#388e3c
|
||||
</div>
|
||||
|
||||
@@ -5686,14 +5697,14 @@ class MessageLocalDataSource {
|
||||
|
||||
<ul>
|
||||
<li><strong>一个端点 = 一个文件</strong>:Response DTO + Request 类放在同一文件中</li>
|
||||
<li><strong>Repository 直接调 ApiClient</strong>:无需 RemoteDataSource 中间层</li>
|
||||
<li><strong>Repository 直接调 NetworksSdkApi</strong>:无需 RemoteDataSource 中间层</li>
|
||||
<li><strong>@ApiRequest 注解 + 代码生成</strong>:自动实现 path / method / fromJson 注册</li>
|
||||
</ul>
|
||||
|
||||
<pre><code>// 示例:Repository 直接调用 Request
|
||||
// data/repositories/message_repository_impl.dart
|
||||
class MessageRepositoryImpl implements MessageRepository {
|
||||
final ApiClient _client;
|
||||
final NetworksSdkApi _client;
|
||||
|
||||
Future<SendMessageData?> sendMessage({
|
||||
required String chatId,
|
||||
@@ -5758,9 +5769,9 @@ flowchart LR
|
||||
|
||||
RepoImpl -->|1. 检查缓存| Cache[Cache]
|
||||
RepoImpl -->|2. 读取本地| LocalDS[Local DS]
|
||||
RepoImpl -->|3. 请求远程| ApiClient2[ApiClient]
|
||||
RepoImpl -->|3. 请求远程| SdkApi2[NetworksSdkApi]
|
||||
|
||||
ApiClient2 -->|DTO| RepoImpl
|
||||
SdkApi2 -->|DTO| RepoImpl
|
||||
RepoImpl -->|转换| Entity[Entity]
|
||||
Entity -->|返回| UC
|
||||
|
||||
@@ -5884,13 +5895,16 @@ flowchart LR
|
||||
├── data/
|
||||
│ ├── datasources/
|
||||
│ │ ├── http/
|
||||
│ │ │ ├── api_client.dart # Dio REST 客户端(executeRequest<T> 唯一入口)
|
||||
│ │ │ ├── api_client.dart # Dio REST 客户端
|
||||
│ │ │ ├── token_refresh_manager.dart # Token 刷新管理(竞态安全 + 超时 + 时间窗口复用 + 主动刷新)
|
||||
│ │ │ └── interceptor/
|
||||
│ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
|
||||
│ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试
|
||||
│ │ │ └── logging_interceptor.dart # 请求/响应日志
|
||||
│ │ └── socket/
|
||||
│ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream 输出)
|
||||
│ │ │ ├── auth_interceptor.dart # Token + 默认 header 注入
|
||||
│ │ │ ├── encryption_interceptor.dart # 请求加密 / 响应解密(预留给 cipher_guard_sdk)
|
||||
│ │ │ ├── retry_interceptor.dart # Token 刷新 + 瞬态错误重试 + 业务错误钩子
|
||||
│ │ │ └── logging_interceptor.dart # 请求/响应日志
|
||||
│ │ ├── socket/
|
||||
│ │ │ └── socket_client.dart # WebSocket 长连接(心跳/重连/Stream/Token 热更新/加密钩子)
|
||||
│ │ └── networks_sdk_method_channel_datasource.dart # 统一执行入口(executeRequest / executeDownload)
|
||||
│ ├── dto/
|
||||
│ │ ├── api_requestable.dart # 请求基类 + fromJson 注册表 + 解码扩展
|
||||
│ │ └── api_response_wrapper.dart # { code, message/msg, data } 信封解析
|
||||
@@ -5899,22 +5913,23 @@ flowchart LR
|
||||
│ └── networks_messaging_repository_impl.dart
|
||||
├── domain/
|
||||
│ ├── entities/
|
||||
│ │ ├── api_error.dart # @freezed HTTP 错误联合类型
|
||||
│ │ ├── api_error.dart # @freezed HTTP 错误联合类型(7 变体,含 cancelled)
|
||||
│ │ ├── encrypted_request.dart # 加密请求结果数据类(path / headers / body 覆盖)
|
||||
│ │ ├── socket_error.dart # @freezed WebSocket 错误联合类型
|
||||
│ │ ├── socket_connection_state.dart # 连接状态 enum
|
||||
│ │ ├── http_method.dart # GET / POST / PUT / DELETE / PATCH
|
||||
│ │ └── api_request_type.dart # request / login / upload
|
||||
│ │ └── api_request_type.dart # request / login / upload / stream / download
|
||||
│ └── repositories/
|
||||
│ ├── networks_sdk_repository.dart
|
||||
│ └── networks_messaging_repository.dart
|
||||
└── presentation/
|
||||
├── facade/
|
||||
│ ├── networks_sdk_api.dart # HTTP 公开 API 接口
|
||||
│ └── networks_messaging_api.dart # WebSocket 公开 API 接口
|
||||
│ ├── networks_sdk_api.dart # HTTP 公开 API 接口(含 executeDownload)
|
||||
│ └── networks_messaging_api.dart # WebSocket 公开 API 接口(含 updateToken / sendBytes)
|
||||
└── wiring/
|
||||
├── api_config.dart # HTTP 配置(baseURL / token / 回调)
|
||||
├── socket_config.dart # WebSocket 配置(心跳 / 重连策略)
|
||||
├── network_callbacks.dart # 回调类型定义(OnTokenRefresh 等)
|
||||
├── api_config.dart # HTTP 配置(baseURL / token / 回调 / 加密 / 重试 / 主动刷新)
|
||||
├── socket_config.dart # WebSocket 配置(心跳 / 重连 / 加密钩子 / 压缩)
|
||||
├── network_callbacks.dart # 回调类型定义(认证 / 加密 / 业务错误 / 下载进度 / WS 加密)
|
||||
├── networks_sdk_core.dart
|
||||
├── networks_sdk_api_impl.dart
|
||||
├── networks_messaging_api_impl.dart
|
||||
@@ -5928,7 +5943,7 @@ flowchart LR
|
||||
<tr><th>职责</th><th>SDK (networks_sdk)</th><th>App 层 (im_app)</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Dio 管理</td><td>ApiClient 内部创建管理</td><td>构造 ApiClient 传入 config</td></tr>
|
||||
<tr><td>Dio 管理</td><td>ApiClient 内部创建管理</td><td>通过 NetworksSdkWiring.build(config:) 创建</td></tr>
|
||||
<tr><td>baseURL</td><td>ApiConfig.baseURL</td><td>AppConfig.apiBaseUrl 提供初始值</td></tr>
|
||||
<tr><td>Token 存储</td><td>ApiConfig.token(内存)</td><td>安全存储、持久化</td></tr>
|
||||
<tr><td>Token 刷新</td><td>检测过期 → 调 onTokenRefresh</td><td>提供回调实现</td></tr>
|
||||
@@ -5941,7 +5956,7 @@ flowchart LR
|
||||
<tr><td>WebSocket 重连</td><td>指数退避自动重连(1s→2s→4s→8s→16s→30s)</td><td>无需关心</td></tr>
|
||||
<tr><td>WebSocket 生命周期</td><td>提供 onEnterForeground/Background</td><td>App 层调用(AppLifecycleListener)</td></tr>
|
||||
<tr><td>WebSocket 消息解析</td><td>JSON.decode → Stream 输出</td><td>App 层按 type 过滤 + DTO 解析</td></tr>
|
||||
<tr><td>Riverpod</td><td>无依赖</td><td>Provider 包装 ApiClient / SocketClient</td></tr>
|
||||
<tr><td>Riverpod</td><td>无依赖</td><td>Provider 包装 NetworksSdkApi / SocketClient</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -5968,7 +5983,7 @@ flowchart LR
|
||||
<li><strong>一个端点 = 一个 Request 文件</strong>:Response DTO + Request 类放在同一文件中</li>
|
||||
<li><strong>Response DTO 必须有 <code>toEntity()</code></strong>:统一 DTO → Domain Entity 的转换入口</li>
|
||||
<li><strong>持久化 DTO 和 Response DTO 分开</strong>:Response DTO(<code>XxxData</code>)在 request 文件中,持久化 DTO(<code>XxxDto</code>)在 <code>data/models/</code></li>
|
||||
<li><strong>禁止跳层</strong>:ViewModel → Repository(→ UseCase 按需)→ ApiClient,每层职责明确</li>
|
||||
<li><strong>禁止跳层</strong>:ViewModel → Repository(→ UseCase 按需)→ NetworksSdkApi,每层职责明确</li>
|
||||
</ul>
|
||||
|
||||
<h5>傻瓜式教程:从零开始定义并发送一个接口</h5>
|
||||
@@ -6112,10 +6127,10 @@ class LoginRequest extends ApiRequestable<LoginData> // ← 固定写法
|
||||
|
||||
<!-- ────────── 第 2 步 ────────── -->
|
||||
|
||||
<h6>第 2 步:在 Repository 中调用 ApiClient,转为 Domain Entity</h6>
|
||||
<h6>第 2 步:在 Repository 中调用 NetworksSdkApi,转为 Domain Entity</h6>
|
||||
|
||||
<p><strong>在哪写</strong>:<code>lib/data/repositories/auth_repository_impl.dart</code></p>
|
||||
<p><strong>做什么</strong>:直接调 ApiClient.executeRequest → 拿到 DTO → 回调写 Token → 转为 Domain Entity → 返回。</p>
|
||||
<p><strong>做什么</strong>:调 NetworksSdkApi.executeRequest → 拿到 DTO → 回调写 Token → 转为 Domain Entity → 返回。</p>
|
||||
|
||||
<pre><code class="language-dart">import 'package:networks_sdk/networks_sdk.dart';
|
||||
import '../../domain/entities/user.dart';
|
||||
@@ -6123,11 +6138,11 @@ import '../../domain/repositories/auth_repository.dart';
|
||||
import '../remote/login_request.dart';
|
||||
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final ApiClient _client; // ← 直接注入 ApiClient
|
||||
final NetworksSdkApi _client; // ← 注入 Facade 接口
|
||||
final void Function(String?) _onTokenUpdate; // ← 回调,由 Provider 层组合
|
||||
|
||||
AuthRepositoryImpl({
|
||||
required ApiClient client,
|
||||
required NetworksSdkApi client,
|
||||
required void Function(String?) onTokenUpdate,
|
||||
}) : _client = client,
|
||||
_onTokenUpdate = onTokenUpdate;
|
||||
@@ -6137,7 +6152,7 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
required String email,
|
||||
required String password,
|
||||
}) async {
|
||||
// 1. 直接调 ApiClient,构造请求 → 发 HTTP → 自动解码 → 返回 DTO
|
||||
// 1. 调 NetworksSdkApi,构造请求 → 发 HTTP → 自动解码 → 返回 DTO
|
||||
final LoginData? loginData = await _client.executeRequest(
|
||||
LoginRequest(email: email, password: password),
|
||||
);
|
||||
@@ -6162,15 +6177,15 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
<p><strong>3.1 注册 Provider(DI 装配)</strong></p>
|
||||
|
||||
<p><strong>在哪写</strong>:<code>lib/features/{模块}/di/{模块}_providers.dart</code></p>
|
||||
<p><strong>做什么</strong>:在 Feature 目录下创建 Provider 文件,注册该模块的 DI 链路(Repository → UseCase 按需)。<code>app/di/</code> 只提供 SDK 基础设施(ApiConfig / ApiClient),业务模块的 Provider 内聚在 Feature 目录下。</p>
|
||||
<p><strong>做什么</strong>:在 Feature 目录下创建 Provider 文件,注册该模块的 DI 链路(Repository → UseCase 按需)。<code>app/di/</code> 只提供 SDK 基础设施(ApiConfig / NetworksSdkApi),业务模块的 Provider 内聚在 Feature 目录下。</p>
|
||||
|
||||
<pre><code class="language-dart">// ── features/auth/di/auth_providers.dart ──
|
||||
|
||||
// Repository(直接注入 ApiClient + 回调组合多个 SDK 能力)
|
||||
// Repository(注入 Facade 接口 + 回调组合多个 SDK 能力)
|
||||
final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||
final apiConfig = ref.read(apiConfigProvider);
|
||||
return AuthRepositoryImpl(
|
||||
client: ref.read(apiClientProvider), // 直接注入 ApiClient
|
||||
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
onTokenUpdate: (token) {
|
||||
apiConfig.updateToken(token); // 内存(networks_sdk)
|
||||
// secureStorage.saveToken(token); // 持久化(storage_sdk,待接入)
|
||||
@@ -6199,7 +6214,7 @@ import '../../../domain/repositories/message_repository.dart';
|
||||
// ── Repository ──
|
||||
final messageRepositoryProvider = Provider<MessageRepository>((ref) {
|
||||
return MessageRepositoryImpl(
|
||||
client: ref.read(apiClientProvider), // 直接注入 ApiClient
|
||||
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
);
|
||||
});
|
||||
|
||||
@@ -6207,7 +6222,7 @@ final messageRepositoryProvider = Provider<MessageRepository>((ref) {
|
||||
// 如需 UseCase(多步编排、跨模块协调),参考 auth_providers.dart 中的 loginUseCaseProvider。
|
||||
</code></pre>
|
||||
|
||||
<p style="margin-bottom: 0;"><strong>原则</strong>:<code>app/di/</code> 只放 SDK 基础设施(ApiConfig / ApiClient),业务模块的 DI 链路(Repository → UseCase 按需)内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中。</p>
|
||||
<p style="margin-bottom: 0;"><strong>原则</strong>:<code>app/di/</code> 只放 SDK 基础设施(ApiConfig / NetworksSdkApi),业务模块的 DI 链路(Repository → UseCase 按需)内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中。</p>
|
||||
</div>
|
||||
|
||||
<p><strong>3.2 编写 ViewModel</strong></p>
|
||||
@@ -6280,7 +6295,7 @@ class LoginViewModel extends _$LoginViewModel {
|
||||
→ View: vm.doSomething(...)
|
||||
→ ViewModel: ref.read(xxxRepositoryProvider).doSomething(...)
|
||||
→ RepositoryImpl.doSomething() // data/repositories/
|
||||
→ _client.executeRequest(XxxRequest) // 直接调 ApiClient
|
||||
→ _client.executeRequest(XxxRequest) // 调 NetworksSdkApi
|
||||
→ 自动注入 header → HTTP 请求 → 自动解码 → DTO
|
||||
→ dto.toEntity() → Domain Entity
|
||||
← state = state.copyWith(...) // 更新状态
|
||||
@@ -6295,8 +6310,8 @@ class LoginViewModel extends _$LoginViewModel {
|
||||
→ LoginUseCase: 格式校验(邮箱 + 密码) // features/auth/usecases/
|
||||
→ LoginUseCase: authRepository.login(...)
|
||||
→ AuthRepositoryImpl.login() // data/repositories/
|
||||
→ _client.executeRequest(LoginRequest) // 直接调 ApiClient
|
||||
→ AuthInterceptor → Dio.request → RetryInterceptor // 自动处理
|
||||
→ _client.executeRequest(LoginRequest) // 调 NetworksSdkApi
|
||||
→ Auth → Encryption → Dio.request → Retry → Logging // 拦截器链自动处理
|
||||
← request.decodeResponse → LoginData.fromJson // 自动解码
|
||||
← LoginData(DTO)
|
||||
→ onTokenUpdate(token) // 回调:内存写入 + 持久化
|
||||
@@ -6360,7 +6375,7 @@ class SendMessageRequest extends ApiRequestable<SendMessageData>
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<p>保存 → 自动生成 → 然后在 Repository 中直接调 ApiClient 就完了:</p>
|
||||
<p>保存 → 自动生成 → 然后在 Repository 中调 NetworksSdkApi 就完了:</p>
|
||||
|
||||
<pre><code class="language-dart">// 在 MessageRepositoryImpl 中添加
|
||||
Future<SendMessageData?> sendMessage({
|
||||
@@ -6568,18 +6583,18 @@ final apiConfigProvider = Provider<ApiConfig>((ref) {
|
||||
);
|
||||
});
|
||||
|
||||
/// API 客户端 Provider(全局单例)
|
||||
/// 内部自动挂载 AuthInterceptor / RetryInterceptor / LoggingInterceptor
|
||||
final apiClientProvider = Provider<ApiClient>((ref) {
|
||||
/// Networks SDK API Provider(全局单例,Facade 接口)
|
||||
/// 内部自动挂载 AuthInterceptor / EncryptionInterceptor / RetryInterceptor / LoggingInterceptor
|
||||
final networkSdkApiProvider = Provider<NetworksSdkApi>((ref) {
|
||||
final config = ref.read(apiConfigProvider);
|
||||
return ApiClient(config: config);
|
||||
return NetworksSdkWiring.build(config: config);
|
||||
});
|
||||
</code></pre>
|
||||
|
||||
<h5>DI 装配总览</h5>
|
||||
|
||||
<pre><code>app/di/ ← 手动装配:SDK 基础设施
|
||||
└── network_provider.dart → apiConfigProvider + apiClientProvider
|
||||
└── network_provider.dart → apiConfigProvider + networkSdkApiProvider
|
||||
|
||||
features/{模块}/di/ ← 手动装配:业务模块 DI 链路(Repository → UseCase 按需)
|
||||
├── auth/di/auth_providers.dart → authRepositoryProvider
|
||||
@@ -6593,7 +6608,7 @@ features/{模块}/presentation/ ← @riverpod 自动生成:ViewModel
|
||||
</code></pre>
|
||||
|
||||
<p><strong>di/ 目录的定位</strong>:只放<strong>需要手动装配的 Provider</strong>(构造注入、回调组合等)。ViewModel Provider 由 <code>@riverpod</code> 注解自动生成(写在 <code>presentation/</code> 下),不在 <code>di/</code> 中。</p>
|
||||
<p><strong>最小化原则</strong>:<code>app/di/</code> 只提供 SDK 能力(ApiConfig / ApiClient),不放业务模块的 Provider。每个业务模块的手动装配 Provider 内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中,需要时才创建。</p>
|
||||
<p><strong>最小化原则</strong>:<code>app/di/</code> 只提供 SDK 能力(ApiConfig / NetworksSdkApi),不放业务模块的 Provider。每个业务模块的手动装配 Provider 内聚在 <code>features/{模块}/di/{模块}_providers.dart</code> 中,需要时才创建。</p>
|
||||
|
||||
<h5>SDK 间解耦:回调注入模式</h5>
|
||||
|
||||
@@ -6605,7 +6620,7 @@ final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||
// final secureStorage = ref.read(secureStorageProvider); // storage_sdk(待接入)
|
||||
|
||||
return AuthRepositoryImpl(
|
||||
client: ref.read(apiClientProvider), // 直接注入 ApiClient
|
||||
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
onTokenUpdate: (token) {
|
||||
apiConfig.updateToken(token); // 内存(networks_sdk)
|
||||
// secureStorage.saveToken(token); // 持久化(storage_sdk,待接入)
|
||||
@@ -6635,6 +6650,7 @@ final authRepositoryProvider = Provider<AuthRepository>((ref) {
|
||||
networkError: (msg) => showToast('网络错误: $msg'),
|
||||
decodingError: (msg) => showToast('数据解析失败'),
|
||||
apiError: (code, msg) => showToast('服务端错误[$code]: $msg'),
|
||||
cancelled: () => {}, // 用户主动取消,通常不提示
|
||||
unknown: (msg) => showToast('未知错误'),
|
||||
);
|
||||
}
|
||||
@@ -7137,9 +7153,9 @@ abstract class ProfileRepository {
|
||||
|
||||
<pre><code>// data/repositories/profile_repository_impl.dart
|
||||
class ProfileRepositoryImpl implements ProfileRepository {
|
||||
final ApiClient _client;
|
||||
final NetworksSdkApi _client;
|
||||
|
||||
ProfileRepositoryImpl({required ApiClient client})
|
||||
ProfileRepositoryImpl({required NetworksSdkApi client})
|
||||
: _client = client;
|
||||
|
||||
@override
|
||||
@@ -7166,7 +7182,7 @@ import '../../../app/di/network_provider.dart';
|
||||
// ── Repository ──
|
||||
final profileRepositoryProvider = Provider<ProfileRepository>((ref) {
|
||||
return ProfileRepositoryImpl(
|
||||
client: ref.read(apiClientProvider), // 直接注入 ApiClient
|
||||
client: ref.read(networkSdkApiProvider), // 注入 Facade 接口
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7174,7 +7190,7 @@ final profileRepositoryProvider = Provider<ProfileRepository>((ref) {
|
||||
// ViewModel 通过 @riverpod 注解自动生成 Provider,无需额外注册。
|
||||
</code></pre>
|
||||
|
||||
<p><strong>说明</strong>:Profile 属于简单模板,ViewModel 直接调 Repository,无需 UseCase 中间层。<code>app/di/</code> 只提供 SDK 基础设施(ApiConfig / ApiClient),业务模块的 DI 链路内聚在 Feature 目录下。</p>
|
||||
<p><strong>说明</strong>:Profile 属于简单模板,ViewModel 直接调 Repository,无需 UseCase 中间层。<code>app/di/</code> 只提供 SDK 基础设施(ApiConfig / NetworksSdkApi),业务模块的 DI 链路内聚在 Feature 目录下。</p>
|
||||
|
||||
<h4>Feature 结构图示</h4>
|
||||
|
||||
@@ -7714,7 +7730,7 @@ sequenceDiagram
|
||||
participant Repo as domain/repositories/<br/>message_repository.dart
|
||||
participant RepoImpl as data/repositories/<br/>message_repository_impl.dart
|
||||
participant LocalDS as data/local/<br/>message_local_ds.dart
|
||||
participant SDK as networks_sdk/<br/>ApiClient / SocketClient
|
||||
participant SDK as networks_sdk/<br/>NetworksSdkApi / SocketClient
|
||||
participant WS as WebSocket Server
|
||||
|
||||
UI->>VM: 1. 用户点击发送按钮
|
||||
@@ -7746,7 +7762,7 @@ sequenceDiagram
|
||||
<li><strong>Repository 接口</strong>:UseCase 通过 <code>domain/repositories/message_repository.dart</code> 接口调用</li>
|
||||
<li><strong>Repository 实现</strong>:<code>data/repositories/message_repository_impl.dart</code> 实现具体逻辑</li>
|
||||
<li><strong>本地优先</strong>:先保存到 <code>data/local/message_local_ds.dart</code></li>
|
||||
<li><strong>网络发送</strong>:Repository 直接调 SDK(ApiClient / SocketClient)发送</li>
|
||||
<li><strong>网络发送</strong>:Repository 调 SDK(NetworksSdkApi / SocketClient)发送</li>
|
||||
<li><strong>服务器确认</strong>:WebSocket 服务器确认接收</li>
|
||||
<li><strong>状态更新</strong>:更新本地数据库中的消息状态</li>
|
||||
<li><strong>数据返回</strong>:层层返回,最终更新 UI</li>
|
||||
@@ -7830,7 +7846,7 @@ abstract class MessageRepository {
|
||||
<pre><code>// data/repositories/message_repository_impl.dart
|
||||
class MessageRepositoryImpl implements MessageRepository {
|
||||
final MessageLocalDataSource _localDS;
|
||||
final ApiClient _client; // 直接注入 ApiClient / SocketClient
|
||||
final NetworksSdkApi _client; // 注入 Facade 接口
|
||||
|
||||
MessageRepositoryImpl(this._localDS, this._client);
|
||||
|
||||
@@ -7861,7 +7877,7 @@ class MessageRepositoryImpl implements MessageRepository {
|
||||
MessageRepository messageRepository(MessageRepositoryRef ref) {
|
||||
return MessageRepositoryImpl(
|
||||
ref.watch(messageLocalDataSourceProvider),
|
||||
ref.watch(apiClientProvider), // 直接注入 ApiClient
|
||||
ref.watch(networkSdkApiProvider), // 注入 Facade 接口
|
||||
);
|
||||
}
|
||||
</code></pre>
|
||||
@@ -7895,7 +7911,7 @@ sequenceDiagram
|
||||
participant RepoImpl as data/repositories/<br/>chat_repository_impl.dart
|
||||
participant Cache as data/cache/<br/>cache_manager.dart
|
||||
participant LocalDS as data/local/<br/>chat_local_ds.dart
|
||||
participant SDK as networks_sdk/<br/>ApiClient
|
||||
participant SDK as networks_sdk/<br/>NetworksSdkApi
|
||||
|
||||
UI->>VM: 1. 页面初始化
|
||||
VM->>UC: 2. 调用 LoadChatListUseCase
|
||||
@@ -7907,7 +7923,7 @@ sequenceDiagram
|
||||
else 缓存未命中
|
||||
RepoImpl->>LocalDS: 6b. 读取本地数据库
|
||||
LocalDS-->>RepoImpl: 7. 返回本地数据
|
||||
RepoImpl->>SDK: 8. 直接调 ApiClient 请求远程数据
|
||||
RepoImpl->>SDK: 8. 调 NetworksSdkApi 请求远程数据
|
||||
SDK-->>RepoImpl: 9. 返回最新数据
|
||||
RepoImpl->>LocalDS: 10. 更新本地数据库
|
||||
RepoImpl->>Cache: 11. 更新缓存
|
||||
@@ -8583,9 +8599,9 @@ abstract class ChatRepository {
|
||||
Future<void> sendMessage(Message message);
|
||||
}
|
||||
|
||||
// 2. Repository 实现层(直接注入 ApiClient)
|
||||
// 2. Repository 实现层(注入 Facade 接口)
|
||||
class ChatRepositoryImpl implements ChatRepository {
|
||||
final ApiClient client;
|
||||
final NetworksSdkApi client;
|
||||
final LocalDataSource localDataSource;
|
||||
final MessageMapper mapper;
|
||||
|
||||
@@ -8603,7 +8619,7 @@ class ChatRepositoryImpl implements ChatRepository {
|
||||
return localDTOs.map(mapper.toEntity).toList();
|
||||
}
|
||||
|
||||
// 直接调 ApiClient 从远程获取
|
||||
// 调 NetworksSdkApi 从远程获取
|
||||
final response = await client.executeRequest(
|
||||
GetMessagesRequest(chatId: chatId),
|
||||
);
|
||||
@@ -9770,6 +9786,146 @@ flowchart TD
|
||||
<p><strong>单一职责</strong>:每个模块只做一件事,UseCase/ViewModel/Repository 各司其职</p>
|
||||
</blockquote>
|
||||
|
||||
<!-- ═══════════════════════ 第八部分:UI 设计规范 ═══════════════════════ -->
|
||||
|
||||
<h2 id="part8-ui-design" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px;">第八部分:UI 设计规范</h2>
|
||||
|
||||
<p>本章定义颜色、字体、组件、弹框、图标的命名与使用规则,明确设计与研发的协作约定。Figma 按此命名,代码按此封装,两端名称一一对应。</p>
|
||||
|
||||
<h3 id="8-0-核心约定">8.0 核心约定</h3>
|
||||
|
||||
<div style="background: #e3f2fd; padding: 20px; border-radius: 8px; border-left: 4px solid #1565c0; margin: 20px 0;">
|
||||
<p style="margin-top: 0; font-weight: 700; color: #1565c0;">全局只有一份</p>
|
||||
<p>颜色、字体、基础组件、业务弹框、图片、图标——Figma 里每种元素只有一个定义。没有"备用版本",没有"临时副本",不允许两个"差不多一样"的组件并存。</p>
|
||||
</div>
|
||||
|
||||
<ol>
|
||||
<li><strong>Figma 命名是重中之重</strong>:点中任何元素都必须看到抽象名称。六类无例外:
|
||||
<ul>
|
||||
<li><code>颜色</code> — 如 <code>primary</code>、<code>surface</code></li>
|
||||
<li><code>字体</code> — 如 <code>Body/Medium</code>、<code>Label/Small</code></li>
|
||||
<li><code>基础组件</code> — 如 <code>Button/Primary</code>、<code>Input/Default</code>,全局只有一个版本</li>
|
||||
<li><code>业务弹框</code> — 如 <code>Dialog/Confirm</code></li>
|
||||
<li><code>图片</code> — Figma 统一导出,代码侧 <code>AppAssets</code> 注册,不硬编码路径</li>
|
||||
<li><code>图标</code> — 如 <code>send</code>、<code>more_options</code>,代码侧 <code>AppIcons</code> 调用</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>基础组件定稿后不随意改动</strong>:需改动时必须先告知研发,评估影响范围,双方同步后再执行</li>
|
||||
<li><strong>UI 团队自主维护 UI 基建体系</strong>:研发照着 Figma 名字封装,名称必须完全一致</li>
|
||||
<li><strong>所有元素遵循同一套命名规则</strong>:新增任何元素先在 Figma 定名,研发用相同名字注册</li>
|
||||
</ol>
|
||||
|
||||
<div style="background: #FFF8E6; border-left: 3px solid #F2C94C; border-radius: 0 8px 8px 0; padding: 12px 16px; margin: 16px 0; font-size: 14px; color: #5F4B00;">
|
||||
<strong>图片和组件是重灾区:</strong>没有统一来源时,不同研发各自导出同一张图,文件名不同、尺寸不同,最终项目里堆满重复文件。Figma 统一命名、代码统一注册,才能从源头堵住。
|
||||
</div>
|
||||
|
||||
<h3 id="8-1-颜色体系">8.1 颜色体系</h3>
|
||||
|
||||
<p>所有颜色通过抽象名称引用。抽象名在亮色 / 暗色两套主题下对应不同色值,修改主题只需改映射表,不需逐个找组件。</p>
|
||||
|
||||
<h4>语义色(随主题变化)</h4>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>抽象名</th><th>Figma 名</th><th>亮色</th><th>暗色</th><th>用途</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>primary</code></td><td>Primary</td><td>#2F80ED</td><td>#5BA3F5</td><td>主操作、链接、选中态</td></tr>
|
||||
<tr><td><code>background</code></td><td>Background</td><td>#F8F9FA</td><td>#202124</td><td>页面底色</td></tr>
|
||||
<tr><td><code>surface</code></td><td>Surface</td><td>#FFFFFF</td><td>#3C4043</td><td>卡片、弹框、输入框</td></tr>
|
||||
<tr><td><code>onSurface</code></td><td>On Surface</td><td>#202124</td><td>#FFFFFF</td><td>surface 上的文字</td></tr>
|
||||
<tr><td><code>error</code></td><td>Error</td><td colspan="2">#EB5757</td><td>错误状态</td></tr>
|
||||
<tr><td><code>success</code></td><td>Success</td><td colspan="2">#27AE60</td><td>成功状态</td></tr>
|
||||
<tr><td><code>warning</code></td><td>Warning</td><td colspan="2">#F2C94C</td><td>警告状态</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>灰阶(固定值,不随主题变化)</h4>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>名称</th><th>色值</th><th>名称</th><th>色值</th><th>名称</th><th>色值</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>white</td><td>#FFFFFF</td><td>gray50</td><td>#F8F9FA</td><td>gray100</td><td>#F1F3F4</td></tr>
|
||||
<tr><td>gray200</td><td>#E8EAED</td><td>gray400</td><td>#BDC1C6</td><td>gray600</td><td>#80868B</td></tr>
|
||||
<tr><td>gray800</td><td>#3C4043</td><td>gray900</td><td>#202124</td><td>black</td><td>#000000</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><strong>使用原则</strong>:需随主题切换 → 用语义色(<code>primary</code>、<code>surface</code>);亮暗保持不变 → 用灰阶固定值。</p>
|
||||
|
||||
<h3 id="8-2-字体体系">8.2 字体体系</h3>
|
||||
|
||||
<p>字体按层级分五档:Display、Headline、Title、Body、Label,每档三个尺寸。Figma 中按 <strong>层级/尺寸</strong> 格式命名(如 <code>Body/Large</code>),开发用同名变量调用。</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Figma 名称</th><th>字号</th><th>字重</th><th>行高</th><th>字距</th><th>典型用途</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">DISPLAY</td></tr>
|
||||
<tr><td>Display/Large</td><td>57</td><td>400</td><td>64</td><td>-0.25</td><td>启动页大标题、空状态</td></tr>
|
||||
<tr><td>Display/Medium</td><td>45</td><td>400</td><td>52</td><td>—</td><td>—</td></tr>
|
||||
<tr><td>Display/Small</td><td>36</td><td>400</td><td>44</td><td>—</td><td>—</td></tr>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">HEADLINE</td></tr>
|
||||
<tr><td>Headline/Large</td><td>32</td><td>400</td><td>40</td><td>—</td><td>页面主标题、导航栏</td></tr>
|
||||
<tr><td>Headline/Medium</td><td>28</td><td>400</td><td>36</td><td>—</td><td>—</td></tr>
|
||||
<tr><td>Headline/Small</td><td>24</td><td>400</td><td>32</td><td>—</td><td>—</td></tr>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">TITLE</td></tr>
|
||||
<tr><td>Title/Large</td><td>22</td><td>500</td><td>28</td><td>—</td><td>会话列表名称、设置项标题</td></tr>
|
||||
<tr><td>Title/Medium</td><td>16</td><td>500</td><td>24</td><td>0.15</td><td>卡片标题、列表主行</td></tr>
|
||||
<tr><td>Title/Small</td><td>14</td><td>500</td><td>20</td><td>0.1</td><td>—</td></tr>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">BODY</td></tr>
|
||||
<tr><td>Body/Large</td><td>16</td><td>400</td><td>24</td><td>0.5</td><td>聊天气泡、表单输入</td></tr>
|
||||
<tr><td>Body/Medium</td><td>14</td><td>400</td><td>20</td><td>0.25</td><td>正文说明、列表副行</td></tr>
|
||||
<tr><td>Body/Small</td><td>12</td><td>400</td><td>16</td><td>0.4</td><td>辅助信息、提示文字</td></tr>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">LABEL</td></tr>
|
||||
<tr><td>Label/Large</td><td>14</td><td>500</td><td>20</td><td>0.1</td><td>按钮文字、Tab 标签、Badge</td></tr>
|
||||
<tr><td>Label/Medium</td><td>12</td><td>500</td><td>16</td><td>0.5</td><td>次要标签、徽标文字</td></tr>
|
||||
<tr><td>Label/Small</td><td>11</td><td>500</td><td>16</td><td>0.5</td><td>最小粒度标签</td></tr>
|
||||
<tr><td colspan="6" style="font-weight:700;color:#80868B;font-size:12px;">语义样式</td></tr>
|
||||
<tr><td>Section Label</td><td>13</td><td>600</td><td>—</td><td>0.5</td><td>列表分组标题、设置分区</td></tr>
|
||||
<tr><td>Body/Muted</td><td>12</td><td>400</td><td>16</td><td>—</td><td>说明文字(灰色,低对比度)</td></tr>
|
||||
<tr><td>Body/Error</td><td>12</td><td>400</td><td>16</td><td>—</td><td>表单错误提示(红色)</td></tr>
|
||||
<tr><td>Label/Muted</td><td>12</td><td>500</td><td>16</td><td>—</td><td>时间戳、元数据(低对比度)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="8-3-组件-button">8.3 组件 — Button</h3>
|
||||
|
||||
<p>按钮共四种变体,每种有明确使用场景和 Figma 组件名。每个页面上主操作只用一个 Primary。</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Figma 组件名</th><th>用途</th><th>亮色样式</th><th>暗色样式</th><th>状态</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>Button/Primary</code></td><td>主操作(登录、发送、确认),每屏最多一次</td><td>背景 #2F80ED,白字</td><td>背景 #5BA3F5,白字</td><td>默认 / Loading / 禁用(#BDC1C6)</td></tr>
|
||||
<tr><td><code>Button/Secondary</code></td><td>次要操作(注册、稍后再说),描边样式</td><td>描边 #2F80ED,蓝字</td><td>描边 #5BA3F5,蓝字</td><td>默认 / 禁用</td></tr>
|
||||
<tr><td><code>Button/Text</code></td><td>辅助链接(忘记密码、查看全部、弹框取消)</td><td>无背景,蓝字</td><td>无背景,蓝字</td><td>默认</td></tr>
|
||||
<tr><td><code>Button/Inverse</code></td><td>反色按钮(深色背景高亮),支持左侧图标</td><td>背景 #202124,白字</td><td>背景 #FFFFFF,黑字</td><td>默认</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="8-4-业务弹框-dialog">8.4 业务弹框 — Dialog</h3>
|
||||
|
||||
<p>当前封装一种通用确认弹框 <code>Dialog/Confirm</code>,后续新增先在 Figma 以 <code>Dialog/</code> 前缀命名。</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>项目</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>结构</td><td>标题(不超 15 字) + 内容 + 操作区(取消 Text 样式 | 确认 Primary 样式)</td></tr>
|
||||
<tr><td>可配置</td><td>标题文字、内容文字、确认/取消标签、点击背景是否可关闭</td></tr>
|
||||
<tr><td>返回值</td><td>确认 / 取消 / 关闭(点击背景)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3 id="8-5-图标规范">8.5 图标规范</h3>
|
||||
|
||||
<ol>
|
||||
<li><strong>UI 先命名,开发跟随</strong>:Figma 确定名称,开发用完全相同的名称封装到 <code>AppIcons</code></li>
|
||||
<li><strong>名称有实际语义</strong>:全小写下划线,如 <code>send</code>、<code>add_contact</code>、<code>more_options</code>。不用拼音,不缩写</li>
|
||||
<li><strong>统一用 AppIcons 调用</strong>:不允许直接用裸 icon 库变量,替换时改一处全局生效</li>
|
||||
<li><strong>同义图标只保留一个</strong>:同功能图标在整个产品内只存在一种</li>
|
||||
</ol>
|
||||
|
||||
<p><strong>新增图标流程</strong>:设计师 Figma 确认名称 → 告知开发 → 开发用相同名称在 AppIcons 注册。两端名称必须完全一致。</p>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user