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:
@@ -0,0 +1,166 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../api/oa_client.dart';
|
||||
import '../../app_config.dart';
|
||||
import '../../ota/updater.dart';
|
||||
import 'desk_legal_page.dart';
|
||||
import 'desk_profile_edit_page.dart';
|
||||
import '../../session/session.dart';
|
||||
import '../desk_theme.dart';
|
||||
import '../widgets/desk_widgets.dart';
|
||||
|
||||
class DeskProfilePage extends StatefulWidget {
|
||||
const DeskProfilePage({super.key, required this.session, required this.api});
|
||||
|
||||
final SessionStore session;
|
||||
final OaClient api;
|
||||
|
||||
@override
|
||||
State<DeskProfilePage> createState() => _DeskProfilePageState();
|
||||
}
|
||||
|
||||
class _DeskProfilePageState extends State<DeskProfilePage> {
|
||||
AppRelease? _rel;
|
||||
bool _checking = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.session.addListener(() {
|
||||
if (mounted) setState(() {});
|
||||
});
|
||||
_peek();
|
||||
}
|
||||
|
||||
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) await OtaUpdater(widget.api).prompt(context, rel, manual: true);
|
||||
} finally {
|
||||
if (mounted) setState(() => _checking = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = widget.session;
|
||||
return ColoredBox(
|
||||
color: kDeskBg,
|
||||
child: Column(
|
||||
children: [
|
||||
const DeskPaneHeader(title: '我'),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 560),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: InkWell(
|
||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(
|
||||
builder: (_) => DeskProfileEditPage(session: s, api: widget.api),
|
||||
)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
children: [
|
||||
DeskAvatar(
|
||||
label: s.displayName,
|
||||
fileId: '${s.user['avatarFileId'] ?? ''}',
|
||||
api: widget.api,
|
||||
size: 56,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(s.displayName, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 4),
|
||||
Text('${s.user['department'] ?? ''} ${s.user['title'] ?? ''}'.trim(),
|
||||
style: const TextStyle(color: kDeskMute, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right, color: kDeskMute),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_section([
|
||||
_row('检查更新', trailing: _rel?.newer == true ? '有新版本' : '已是最新', onTap: _checking ? null : _checkUpdate),
|
||||
_row('用户协议', onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => DeskLegalPage(api: widget.api, kind: 'terms')))),
|
||||
_row('隐私政策', onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => DeskLegalPage(api: widget.api, kind: 'privacy')))),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
_section([
|
||||
_row('版本', trailing: 'v${AppConfig.version}'),
|
||||
]),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: OutlinedButton(
|
||||
style: OutlinedButton.styleFrom(minimumSize: const Size.fromHeight(40)),
|
||||
onPressed: _logout,
|
||||
child: const Text('退出登录', style: TextStyle(color: Color(0xFFFA5151))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _section(List<Widget> children) {
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _row(String title, {String? trailing, VoidCallback? onTap}) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(title, style: const TextStyle(fontSize: 14))),
|
||||
if (trailing != null) Text(trailing, style: const TextStyle(fontSize: 13, color: kDeskMute)),
|
||||
if (onTap != null) const Icon(Icons.chevron_right, size: 18, color: kDeskMute),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user