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
@@ -0,0 +1,112 @@
import 'package:flutter/material.dart';
import '../desk_theme.dart';
/// 桌面端通用数据表格,替代移动端 KvTile 列表。
class DeskDataTable extends StatelessWidget {
const DeskDataTable({
super.key,
required this.columns,
required this.rows,
this.onRowTap,
this.emptyHint = '暂无数据',
});
final List<String> columns;
final List<List<String>> rows;
final void Function(int index)? onRowTap;
final String emptyHint;
@override
Widget build(BuildContext context) {
if (rows.isEmpty) {
return Center(child: Text(emptyHint, style: const TextStyle(color: kDeskMute)));
}
return Material(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.antiAlias,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
headingRowHeight: 40,
dataRowMinHeight: 44,
dataRowMaxHeight: 56,
columnSpacing: 24,
horizontalMargin: 16,
columns: [for (final c in columns) DataColumn(label: Text(c))],
rows: [
for (var i = 0; i < rows.length; i++)
DataRow(
onSelectChanged: onRowTap == null ? null : (_) => onRowTap!(i),
cells: [for (final cell in rows[i]) DataCell(Text(cell, maxLines: 2, overflow: TextOverflow.ellipsis))],
),
],
),
),
);
}
}
class DeskListScaffold extends StatelessWidget {
const DeskListScaffold({
super.key,
required this.title,
required this.body,
this.filters,
this.actions = const [],
this.loading = false,
});
final String title;
final Widget body;
final Widget? filters;
final List<Widget> actions;
final bool loading;
@override
Widget build(BuildContext context) {
return ColoredBox(
color: kDeskBg,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ColoredBox(
color: kDeskPane,
child: Column(
children: [
SizedBox(
height: 48,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 16),
child: Text(title, style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600)),
),
const Spacer(),
...actions,
const SizedBox(width: 8),
],
),
),
if (filters != null)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 10),
child: filters!,
),
const Divider(height: 1, color: kDeskLine),
],
),
),
if (loading) const LinearProgressIndicator(minHeight: 2, color: kDeskBind),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: body,
),
),
],
),
);
}
}