Files
Public/apps/native/lib/session/session.dart
T
daiyongkang 76f266645d Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 10:04:03 +00:00

151 lines
4.7 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../app_config.dart';
import '../im/im_bridge.dart';
import '../im/tcp_client.dart';
class MenuNode {
MenuNode({required this.name, required this.code, this.path, this.icon, this.children = const []});
final String name;
final String code;
final String? path;
final String? icon;
final List<MenuNode> children;
factory MenuNode.fromJson(Map<String, dynamic> j) {
final kids = (j['children'] as List? ?? []).whereType<Map>().map((e) => MenuNode.fromJson(Map<String, dynamic>.from(e))).toList();
return MenuNode(
name: '${j['name'] ?? ''}',
code: '${j['code'] ?? ''}',
path: j['path']?.toString(),
icon: j['icon']?.toString(),
children: kids,
);
}
}
class SessionStore extends ChangeNotifier {
String apiBase = AppConfig.defaultApiBase;
String imHost = AppConfig.defaultImHost;
int imPort = AppConfig.defaultImPort;
String accessToken = '';
String refreshToken = '';
Map<String, dynamic> user = {};
List<MenuNode> menus = [];
bool loaded = false;
final ImTcpClient im = ImTcpClient();
final ImBridge bridge = ImBridge();
String get userId => '${user['id'] ?? ''}';
String get displayName => '${user['displayName'] ?? user['username'] ?? ''}';
List<String> get roles => (user['roles'] as List? ?? []).map((e) => '$e').toList();
List<String> get permissions => (user['permissions'] as List? ?? []).map((e) => '$e').toList();
bool get signedIn => accessToken.isNotEmpty && userId.isNotEmpty;
bool canSee(String code) {
if (roles.contains('admin') || roles.contains('owner')) return true;
return permissions.contains(code) || permissions.any((p) => p.startsWith('$code:'));
}
Future<void> load() async {
final p = await SharedPreferences.getInstance();
apiBase = p.getString('apiBase') ?? AppConfig.defaultApiBase;
imHost = p.getString('imHost') ?? AppConfig.defaultImHost;
imPort = p.getInt('imPort') ?? AppConfig.defaultImPort;
accessToken = p.getString('accessToken') ?? '';
refreshToken = p.getString('refreshToken') ?? '';
final u = p.getString('user');
if (u != null && u.isNotEmpty) {
try {
user = Map<String, dynamic>.from(jsonDecode(u) as Map);
} catch (_) {
user = {};
}
}
loaded = true;
notifyListeners();
}
Future<void> saveEndpoints({required String api, required String host, required int port}) async {
apiBase = api.trim().isEmpty ? AppConfig.defaultApiBase : api.trim();
imHost = host.trim().isEmpty ? AppConfig.defaultImHost : host.trim();
imPort = port <= 0 ? AppConfig.defaultImPort : port;
final p = await SharedPreferences.getInstance();
await p.setString('apiBase', apiBase);
await p.setString('imHost', imHost);
await p.setInt('imPort', imPort);
notifyListeners();
}
Future<void> applyLogin(Map<String, dynamic> data) async {
accessToken = '${data['accessToken'] ?? ''}';
refreshToken = '${data['refreshToken'] ?? ''}';
user = Map<String, dynamic>.from(data['user'] as Map? ?? {});
final p = await SharedPreferences.getInstance();
await p.setString('accessToken', accessToken);
await p.setString('refreshToken', refreshToken);
await p.setString('user', jsonEncode(user));
notifyListeners();
await connectIm();
}
Future<void> setTokens(String access, String refresh) async {
final changed = access != accessToken;
accessToken = access;
refreshToken = refresh;
final p = await SharedPreferences.getInstance();
await p.setString('accessToken', access);
await p.setString('refreshToken', refresh);
notifyListeners();
if (changed && signedIn) await connectIm(force: true);
}
Future<void> patchUser(Map<String, dynamic> next) async {
user = next;
final p = await SharedPreferences.getInstance();
await p.setString('user', jsonEncode(user));
notifyListeners();
}
Future<void> setMenus(List<MenuNode> items) async {
menus = items;
notifyListeners();
}
Future<void> connectIm({bool force = false}) async {
if (!signedIn) return;
if (force) im.disconnect();
await bridge.init();
await im.connect(
host: imHost,
port: imPort,
userId: userId,
token: accessToken,
);
}
Future<void> clear() async {
im.disconnect();
await bridge.release();
accessToken = '';
refreshToken = '';
user = {};
menus = [];
final p = await SharedPreferences.getInstance();
await p.remove('accessToken');
await p.remove('refreshToken');
await p.remove('user');
notifyListeners();
}
@override
void dispose() {
im.disconnect();
im.dispose();
super.dispose();
}
}