feat: add Flutter mobile clients and staff delivery API

This commit is contained in:
david
2026-07-30 21:47:41 +08:00
parent 550efb3812
commit 36a5ced1c0
228 changed files with 17159 additions and 22 deletions

View File

@@ -0,0 +1,149 @@
import '../../domain/models/client_models.dart';
import '../services/api_client.dart';
class ClientRepository {
ClientRepository(this._api);
static const root = '/heqi/client/v1/user';
final ApiClient _api;
Future<List<ClientRecord>> contents() async {
final values = jsonList(await _api.get('$root/public/contents', authenticated: false));
return values
.map(
(value) => ClientRecord(
identity: value['identity'] as String? ?? '',
title: value['title'] as String? ?? '安全公告',
subtitle: value['summary'] as String? ?? value['content_type'] as String? ?? '',
raw: value,
),
)
.toList();
}
Future<List<ClientRecord>> products() async {
final values = jsonList(await _api.get('$root/public/products', authenticated: false));
return values
.map(
(value) => ClientRecord(
identity: value['identity'] as String? ?? '',
title: value['name'] as String? ?? '商品',
subtitle:
'${moneyText((value['price_amount'] as num?)?.toInt() ?? 0)} · 库存 ${(value['stock_quantity'] as num?)?.toInt() ?? 0}',
raw: value,
),
)
.toList();
}
Future<UserProfile> profile() async =>
UserProfile.fromJson(jsonMap(await _api.get('$root/auth/profile')));
Future<WalletSummary> wallet() async =>
WalletSummary.fromJson(jsonMap(await _api.get('$root/wallet')));
Future<List<ClientRecord>> addresses() =>
_records('$root/addresses', titleKeys: const ['address'], subtitleKeys: const ['is_default']);
Future<List<ClientRecord>> shopOrders() => _records(
'$root/shop/orders',
titleKeys: const ['order_no'],
subtitleKeys: const ['payable_amount', 'logistics_company'],
statusKey: 'order_status',
);
Future<List<ClientRecord>> gasOrders() => _records(
'$root/gas/orders',
titleKeys: const ['order_no'],
subtitleKeys: const ['address'],
statusKey: 'order_status',
);
Future<List<ClientRecord>> contracts() => _records(
'$root/gas/contracts',
titleKeys: const ['contract_no'],
subtitleKeys: const ['title'],
statusKey: 'contract_status',
);
Future<List<ClientRecord>> tickets() => _records(
'$root/tickets',
titleKeys: const ['ticket_no'],
subtitleKeys: const ['description', 'category'],
statusKey: 'ticket_status',
);
Future<List<ClientRecord>> walletRecords() => _records(
'$root/wallet/records',
titleKeys: const ['trade_type', 'record_no'],
subtitleKeys: const ['amount', 'direction'],
);
Future<Map<String, Object?>?> serviceRelation() async {
final value = await _api.get('$root/service-relation');
if (value == null || value == '') return null;
return jsonMap(value);
}
Future<void> addAddress(String address, {bool isDefault = false}) async {
await _api.post('$root/addresses', body: {'address': address, 'is_default': isDefault});
}
Future<void> createTicket({
required String requestNo,
required String category,
required String description,
}) async {
await _api.post(
'$root/tickets',
body: {'request_no': requestNo, 'category': category, 'description': description},
);
}
Future<void> createShopOrder({
required String requestNo,
required String productIdentity,
required String addressIdentity,
required String contactName,
required String contactPhone,
}) async {
await _api.post(
'$root/shop/orders',
body: {
'request_no': requestNo,
'address_identity': addressIdentity,
'contact_name': contactName,
'contact_phone': contactPhone,
'items': [
{'product_identity': productIdentity, 'quantity': 1},
],
},
);
}
Future<List<ClientRecord>> _records(
String path, {
required List<String> titleKeys,
required List<String> subtitleKeys,
String? statusKey,
}) async {
final values = jsonList(await _api.get(path));
return values.map((value) {
String pick(List<String> keys) {
for (final key in keys) {
final item = value[key];
if (item != null && item.toString().isNotEmpty) return item.toString();
}
return '';
}
return ClientRecord(
identity: value['identity'] as String? ?? '',
title: pick(titleKeys),
subtitle: pick(subtitleKeys),
status: statusKey == null ? null : (value[statusKey] as num?)?.toInt(),
raw: value,
);
}).toList();
}
}

View File

@@ -0,0 +1,89 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
class ApiException implements Exception {
const ApiException(this.code, this.message);
final int code;
final String message;
@override
String toString() => message;
}
class ApiClient {
ApiClient(
this._tokenProvider, {
http.Client? client,
String? baseUrl,
}) : _client = client ?? http.Client(),
baseUrl =
baseUrl ??
const String.fromEnvironment(
'API_BASE_URL',
defaultValue: 'http://10.0.2.2:12426',
);
final String baseUrl;
final String Function() _tokenProvider;
final http.Client _client;
Future<Object?> get(String path, {bool authenticated = true}) =>
_send('GET', path, authenticated: authenticated);
Future<Object?> post(
String path, {
Map<String, Object?>? body,
bool authenticated = true,
}) => _send('POST', path, body: body, authenticated: authenticated);
Future<Object?> put(String path, {Map<String, Object?>? body}) => _send('PUT', path, body: body);
Future<Object?> delete(String path, {Map<String, Object?>? body}) =>
_send('DELETE', path, body: body);
Future<Object?> _send(
String method,
String path, {
Map<String, Object?>? body,
bool authenticated = true,
}) async {
final request = http.Request(method, Uri.parse('$baseUrl$path'));
request.headers[HttpHeaders.acceptHeader] = 'application/json';
if (authenticated) {
final token = _tokenProvider();
if (token.isNotEmpty) request.headers[HttpHeaders.authorizationHeader] = token;
}
if (body != null) {
request.headers[HttpHeaders.contentTypeHeader] = 'application/json; charset=UTF-8';
request.body = jsonEncode(body);
}
final streamed = await _client.send(request);
final response = await http.Response.fromStream(streamed);
if (response.statusCode < 200 || response.statusCode >= 300) {
throw ApiException(response.statusCode, '网络请求失败(${response.statusCode}');
}
final decoded = jsonDecode(response.body);
if (decoded is! Map<String, Object?>) {
throw const ApiException(500, '服务端响应格式错误');
}
final code = (decoded['code'] as num?)?.toInt() ?? 500;
if (code != 0) {
throw ApiException(code, decoded['message'] as String? ?? '操作失败');
}
return decoded['details'];
}
}
Map<String, Object?> jsonMap(Object? value) {
if (value is Map<String, Object?>) return value;
if (value is Map) return value.map((key, item) => MapEntry(key.toString(), item));
throw const ApiException(500, '服务端数据格式错误');
}
List<Map<String, Object?>> jsonList(Object? value) {
if (value is! List) return const [];
return value.map<Map<String, Object?>>(jsonMap).toList(growable: false);
}

View File

@@ -0,0 +1,15 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class SecureSessionStore {
SecureSessionStore({FlutterSecureStorage? storage})
: _storage = storage ?? const FlutterSecureStorage();
static const _tokenKey = 'user_app_access_token';
final FlutterSecureStorage _storage;
Future<String?> readToken() => _storage.read(key: _tokenKey);
Future<void> writeToken(String token) => _storage.write(key: _tokenKey, value: token);
Future<void> clear() => _storage.delete(key: _tokenKey);
}