import 'package:flutter/material.dart'; import '../api/oa_client.dart'; import '../nav/open_module.dart'; import '../ota/updater.dart'; import '../pages/approvals_page.dart'; import '../pages/attendance_page.dart'; import '../pages/calendar_page.dart'; import '../pages/flow_page.dart'; import '../pages/hr_apply_page.dart'; import '../pages/todos_page.dart'; import '../pages/work_assign_page.dart'; import '../session/session.dart'; import '../theme.dart'; import '../widgets/wecom.dart'; class WorkbenchPage extends StatefulWidget { const WorkbenchPage({super.key, required this.session, required this.api, this.embedded = true}); final SessionStore session; final OaClient api; final bool embedded; @override State createState() => _WorkbenchPageState(); } class _WorkbenchPageState extends State { Map _ov = {}; String _greet = ''; AppRelease? _rel; bool _loading = true; String _q = ''; @override void initState() { super.initState(); _load(); } Future _load() async { try { final ov = await widget.api.get('/office/overview'); String greet = ''; try { final w = await widget.api.get('/office/weather'); if (w is Map) greet = '${w['greeting'] ?? w['text'] ?? ''}'; } catch (_) {} AppRelease? rel; try { rel = await OtaUpdater(widget.api).fetch(); } catch (_) {} if (!mounted) return; setState(() { _ov = Map.from(ov as Map? ?? {}); _greet = greet; _rel = rel; _loading = false; }); } catch (_) { if (mounted) setState(() => _loading = false); } } bool _match(String name) => _q.isEmpty || name.contains(_q); bool _skipGroup(MenuNode m) { if (m.name == '工作台' || m.name == '个人办公' || m.code == 'office:overview') return true; if (m.name == '系统设置' || m.code.startsWith('system')) return true; return false; } bool _skipChild(MenuNode n) { const names = { '待办', '待审批', '我的申请', '日程', '公告', '公司公告', '工作安排', '人事申请', '工作台', '个人办公', '消息', '通讯录', '员工通讯', '发送短信', '人事设置', '数据字典', '权限设置', '编号规则', '短信服务', '消息通道', '手机端升级', '操作日志', '系统设置', }; if (n.code.startsWith('office:')) return true; if (n.code.startsWith('system')) return true; if (n.code.contains('sms') || n.name.contains('短信')) return true; if (n.name.contains('公告') || n.name == '员工通讯') return true; return names.contains(n.name); } @override Widget build(BuildContext context) { final body = RefreshIndicator( onRefresh: _load, child: ListView( padding: const EdgeInsets.only(bottom: 24), children: [ if (_loading) const LinearProgressIndicator(minHeight: 2, color: kBind), if (_rel != null && _rel!.newer) Padding( padding: const EdgeInsets.fromLTRB(12, 12, 12, 0), child: Material( color: const Color(0xFFE8F3FF), borderRadius: BorderRadius.circular(8), child: InkWell( onTap: () => OtaUpdater(widget.api).prompt(context, _rel!, manual: true), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.fromLTRB(14, 12, 14, 12), child: Row( children: [ const Icon(Icons.system_update_alt, color: kBind), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('发现新版本 ${_rel!.version}', style: const TextStyle(fontWeight: FontWeight.w600, color: kInk)), Text(_rel!.changelog.isEmpty ? '点击升级到最新手机版' : _rel!.changelog, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12, color: kMute)), ], ), ), const Text('升级', style: TextStyle(color: kBind, fontWeight: FontWeight.w600)), ], ), ), ), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_greet.isEmpty ? '你好,${widget.session.displayName}' : _greet, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w700, color: kInk)), const SizedBox(height: 4), const Text('工作台', style: TextStyle(color: kMute, fontSize: 13)), ], ), ), GroupCard( title: '待处理', child: Padding( padding: const EdgeInsets.fromLTRB(8, 4, 8, 14), child: Row( children: [ _stat('待办', '${_ov['pendingTodos'] ?? 0}', () => pushPage(context, TodosPage(api: widget.api))), _stat('待审批', '${_ov['pendingApprovals'] ?? 0}', () => pushPage(context, ApprovalsPage(api: widget.api))), _stat('我的申请', '${_ov['myPending'] ?? 0}', () => pushPage(context, FlowPage(api: widget.api))), _stat('逾期', '${_ov['overdueTodos'] ?? 0}', () => pushPage(context, TodosPage(api: widget.api))), ], ), ), ), if (_match('待办') || _match('审批') || _match('申请') || _match('日程') || _match('人事')) GroupCard( title: '个人办公', child: AppGrid( items: [ if (_match('待办')) AppGridItem(label: '待办', icon: Icons.task_alt, color: const Color(0xFFFA9D3B), onTap: () => pushPage(context, TodosPage(api: widget.api))), if (_match('审批')) AppGridItem(label: '待审批', icon: Icons.fact_check, color: kBind, onTap: () => pushPage(context, ApprovalsPage(api: widget.api))), if (_match('申请')) AppGridItem(label: '我的申请', icon: Icons.assignment_outlined, color: const Color(0xFF6267F2), onTap: () => pushPage(context, FlowPage(api: widget.api))), if (_match('日程')) AppGridItem(label: '日程', icon: Icons.calendar_month, color: const Color(0xFF10AEFF), onTap: () => pushPage(context, CalendarPage(api: widget.api))), if (_match('安排')) AppGridItem(label: '工作安排', icon: Icons.event_note, color: const Color(0xFF00B578), onTap: () => pushPage(context, WorkAssignPage(session: widget.session, api: widget.api))), if (_match('打卡') || _match('考勤')) AppGridItem(label: '考勤打卡', icon: Icons.access_time_filled, color: const Color(0xFF267EF0), onTap: () => pushPage(context, AttendancePage(api: widget.api))), if (_match('人事') || _match('请假') || _match('加班')) AppGridItem(label: '人事申请', icon: Icons.beach_access, color: const Color(0xFF8B5CF6), onTap: () => pushPage(context, HrApplyPage(api: widget.api))), ], ), ), ..._menuGroups(), ], ), ); if (!widget.embedded) { return Scaffold(appBar: AppBar(title: const Text('工作台')), body: body); } return ColoredBox( color: kPaper, child: Column( children: [ WxHeader( title: '工作台', actions: [ IconButton( onPressed: () async { final c = TextEditingController(text: _q); final v = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('搜索应用'), content: TextField(controller: c, autofocus: true, decoration: const InputDecoration(hintText: '待办、投标、报销…')), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, ''), child: const Text('清除')), FilledButton(onPressed: () => Navigator.pop(ctx, c.text.trim()), child: const Text('搜索')), ], ), ); if (v != null) setState(() => _q = v); }, icon: const Icon(Icons.search, color: kInk), ), ], ), Expanded(child: body), ], ), ); } List _menuGroups() { final out = []; for (final m in widget.session.menus) { if (_skipGroup(m)) continue; if (m.children.isNotEmpty) { final items = []; for (var i = 0; i < m.children.length; i++) { final c = m.children[i]; if (_skipChild(c)) continue; if (!(_q.isEmpty || c.name.contains(_q) || m.name.contains(_q))) continue; items.add(AppGridItem( label: c.name, icon: iconFor(c.code, c.name), color: colorFor(c.code, c.name, i), onTap: () => openMenuNode(context, widget.session, widget.api, c), )); } if (items.isEmpty) continue; out.add(GroupCard(title: m.name, child: AppGrid(items: items))); } else if (!_skipChild(m) && _match(m.name)) { out.add(GroupCard( title: m.name, child: AppGrid(items: [ AppGridItem( label: m.name, icon: iconFor(m.code, m.name), color: colorFor(m.code, m.name), onTap: () => openMenuNode(context, widget.session, widget.api, m), ), ]), )); } } return out; } Widget _stat(String label, String value, VoidCallback onTap) { return Expanded( child: InkWell( onTap: onTap, child: Column( children: [ Text(value, style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w700, color: kBind)), const SizedBox(height: 2), Text(label, style: const TextStyle(fontSize: 12, color: kMute)), ], ), ), ); } }