Files
Public/apps/native/lib/pages/mine_page.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

268 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import '../api/oa_client.dart';
import '../device/push_bridge.dart';
import '../app_config.dart';
import '../labels.dart';
import '../ota/updater.dart';
import '../pages/legal_page.dart';
import '../pages/profile_page.dart';
import '../session/session.dart';
import '../theme.dart';
import '../widgets/beian_footer.dart';
import '../widgets/desktop_ui.dart';
import '../widgets/wecom.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key, required this.session, required this.api, this.desktopStyle = false});
final SessionStore session;
final OaClient api;
final bool desktopStyle;
@override
State<MinePage> createState() => _MinePageState();
}
class _MinePageState extends State<MinePage> {
AppRelease? _rel;
bool _checking = false;
@override
void initState() {
super.initState();
widget.session.addListener(_onSession);
_peek();
}
@override
void dispose() {
widget.session.removeListener(_onSession);
super.dispose();
}
void _onSession() {
if (mounted) setState(() {});
}
Future<void> _peek() async {
final rel = await OtaUpdater(widget.api).fetch();
if (mounted) setState(() => _rel = rel);
}
Future<void> _logout() async {
try {
await widget.api
.post('/auth/logout', {'refreshToken': widget.session.refreshToken});
} catch (_) {}
await widget.session.clear();
}
Future<void> _checkUpdate() async {
setState(() => _checking = true);
try {
final rel = await OtaUpdater(widget.api).fetch();
if (!mounted) return;
setState(() => _rel = rel);
if (rel == null) {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(content: Text('暂时无法检查更新')));
return;
}
await OtaUpdater(widget.api).prompt(context, rel, manual: true);
} finally {
if (mounted) setState(() => _checking = false);
}
}
@override
Widget build(BuildContext context) {
final s = widget.session;
final avatarId = '${s.user['avatarFileId'] ?? ''}';
return ColoredBox(
color: widget.desktopStyle ? kDeskBg : kPaper,
child: Column(
children: [
widget.desktopStyle ? const DesktopPaneHeader(title: '我') : const WxHeader(title: '我'),
Expanded(
child: widget.desktopStyle
? Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 720),
child: ListView(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 24),
children: _mineItems(s, avatarId),
),
),
)
: ListView(
children: _mineItems(s, avatarId),
),
),
],
),
);
}
List<Widget> _mineItems(SessionStore s, String avatarId) {
return [
const SizedBox(height: 12),
Material(
color: Colors.white,
child: InkWell(
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ProfilePage(session: s, api: widget.api),
)),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Row(
children: [
SquareAvatar(
label: s.displayName,
size: 64,
fileId: avatarId.isEmpty ? null : avatarId,
api: widget.api,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(s.displayName,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600)),
const SizedBox(height: 4),
Text('账号 ${s.user['username'] ?? ''}',
style: const TextStyle(
color: kMute, fontSize: 13)),
Text(s.roles.map(zh).join(' · '),
style: const TextStyle(
color: kMute, fontSize: 12)),
],
),
),
const Icon(Icons.chevron_right,
color: Color(0xFFC0C0C0)),
],
),
),
),
),
const SizedBox(height: 12),
CellGroup(
children: [
if (!widget.desktopStyle)
Cell(
title: '后台运行与通知',
subtitle: '允许自启动和后台,消息才能及时送达',
leading: const SquareAvatar(
label: '通',
color: kBind,
icon: Icons.notifications_active_outlined,
size: 32),
onTap: () async {
await PushBridge.requestBattery();
await PushBridge.openOemKeepAlive();
},
),
if (!widget.desktopStyle)
Cell(
title: '检查更新',
subtitle: _rel != null && _rel!.newer
? '有新版本 ${_rel!.version}'
: '当前 ${AppConfig.version} (${AppConfig.build})',
leading: const SquareAvatar(
label: '升',
color: kBind,
icon: Icons.system_update_alt,
size: 32),
trailing: _checking
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: (_rel != null && _rel!.newer
? const UnreadBadge(1, dot: true)
: null),
onTap: _checkUpdate,
),
if (widget.desktopStyle)
Cell(
title: '检查更新',
subtitle: _rel != null && _rel!.newer
? '有新版本 ${_rel!.version}'
: '当前 ${AppConfig.version} (${AppConfig.build})',
leading: const SquareAvatar(
label: '升',
color: kBind,
icon: Icons.system_update_alt,
size: 32),
trailing: _checking
? const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2))
: (_rel != null && _rel!.newer
? const UnreadBadge(1, dot: true)
: null),
onTap: _checkUpdate,
),
Cell(
title: '用户协议',
leading: const SquareAvatar(
label: '协',
color: kBind,
icon: Icons.article_outlined,
size: 32),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => LegalPage(
api: widget.api, kind: 'user-agreement')),
),
),
Cell(
title: '隐私政策',
leading: const SquareAvatar(
label: '私',
color: kBind,
icon: Icons.privacy_tip_outlined,
size: 32),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
LegalPage(api: widget.api, kind: 'privacy')),
),
showLine: false,
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
child: Material(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: _logout,
borderRadius: BorderRadius.circular(8),
child: const SizedBox(
height: 48,
child: Center(
child: Text('退出登录',
style:
TextStyle(fontSize: 16, color: kDanger))),
),
),
),
),
const SizedBox(height: 8),
Text(
'${AppConfig.brand}\n${AppConfig.version} (${AppConfig.build})',
textAlign: TextAlign.center,
style:
const TextStyle(color: kMute, fontSize: 12, height: 1.5),
),
const BeianFooter(),
];
}
}