Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)

含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。
排除 node_modules、构建产物、安装包与 .env 密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 10:04:03 +00:00
commit 76f266645d
507 changed files with 99891 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
import 'package:shared_preferences/shared_preferences.dart';
/// 会话级本地偏好:免打扰、置顶、背景、本地清空记录。
class ChatPrefs {
ChatPrefs._();
static SharedPreferences? _p;
static Future<void> ensure() async {
_p ??= await SharedPreferences.getInstance();
// 旧版本曾在滑动过程中误写 hidden,导致会话读完后看似“自动删除”。
// 升级时清理一次旧标记,之后只有确认“不显示/删除”才会再次隐藏。
if (_p!.getBool('im.hidden.migration.v3') != true) {
for (final key
in _p!.getKeys().where((k) => k.startsWith('im.hidden.'))) {
await _p!.remove(key);
}
await _p!.setBool('im.hidden.migration.v3', true);
}
}
static bool muted(String id) =>
id.isEmpty ? false : (_p?.getBool('im.mute.$id') ?? false);
static bool pinned(String id) =>
id.isEmpty ? false : (_p?.getBool('im.pin.$id') ?? false);
static bool hidden(String id) =>
id.isEmpty ? false : (_p?.getBool('im.hidden.$id') ?? false);
static int? bgColor(String id) => id.isEmpty ? null : _p?.getInt('im.bg.$id');
static DateTime? clearedAt(String id) {
if (id.isEmpty) return null;
final ms = _p?.getInt('im.cleared.$id');
if (ms == null || ms <= 0) return null;
return DateTime.fromMillisecondsSinceEpoch(ms);
}
static Future<void> setMuted(String id, bool v) async {
await ensure();
await _p!.setBool('im.mute.$id', v);
}
static Future<void> setPinned(String id, bool v) async {
await ensure();
await _p!.setBool('im.pin.$id', v);
}
static Future<void> setHidden(String id, bool v) async {
await ensure();
await _p!.setBool('im.hidden.$id', v);
}
static Future<void> setBg(String id, int color) async {
await ensure();
await _p!.setInt('im.bg.$id', color);
}
static Future<void> clearHistory(String id) async {
await ensure();
await _p!.setInt('im.cleared.$id', DateTime.now().millisecondsSinceEpoch);
}
static int fakeUnread(String id) =>
id.isEmpty ? 0 : (_p?.getInt('im.fakeUnread.$id') ?? 0);
static Future<void> setFakeUnread(String id, int n) async {
await ensure();
if (n <= 0) {
await _p!.remove('im.fakeUnread.$id');
} else {
await _p!.setInt('im.fakeUnread.$id', n);
}
}
/// 语音转文字结果,按消息 id 或 fileId 缓存。
static String? voiceText(String key) {
if (key.isEmpty) return null;
final v = _p?.getString('im.voiceText.$key');
if (v == null || v.isEmpty) return null;
return v;
}
static Future<void> setVoiceText(String key, String text) async {
if (key.isEmpty) return;
await ensure();
final t = text.trim();
if (t.isEmpty) {
await _p!.remove('im.voiceText.$key');
} else {
await _p!.setString('im.voiceText.$key', t);
}
}
}