Files
TtxPageAdmin/lib/studio_app_shell_workspace.dart
T
2026-05-16 01:10:06 +08:00

355 lines
11 KiB
Dart

part of 'studio_app.dart';
class StudioWorkspaceView extends StatelessWidget {
const StudioWorkspaceView({
super.key,
required this.runtime,
required this.user,
required this.selectedTab,
required this.tabs,
required this.activities,
required this.rootNodes,
required this.refreshToken,
required this.onSelectTab,
required this.onCloseTab,
required this.onRefresh,
required this.onLogout,
required this.themeMode,
required this.onToggleTheme,
required this.onPageAction,
required this.onOpenNode,
});
final StudioRuntime runtime;
final StudioUser user;
final StudioTab selectedTab;
final List<StudioTab> tabs;
final List<StudioActivity> activities;
final List<StudioNavNode> rootNodes;
final int refreshToken;
final ValueChanged<String> onSelectTab;
final ValueChanged<String> onCloseTab;
final VoidCallback onRefresh;
final VoidCallback onLogout;
final ThemeMode themeMode;
final VoidCallback onToggleTheme;
final void Function(String title, String detail) onPageAction;
final ValueChanged<StudioNavNode> onOpenNode;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_WorkspaceHeader(
user: user,
selectedTab: selectedTab,
onRefresh: onRefresh,
onLogout: onLogout,
themeMode: themeMode,
onToggleTheme: onToggleTheme,
),
const SizedBox(height: 14),
_TabStrip(
tabs: tabs,
selectedTabId: selectedTab.id,
onSelectTab: onSelectTab,
onCloseTab: onCloseTab,
),
const SizedBox(height: 14),
Expanded(
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 260),
child: _buildPage(
key: ValueKey('${selectedTab.id}:$refreshToken'),
tab: selectedTab,
activities: activities,
rootNodes: rootNodes,
runtime: runtime,
onPageAction: onPageAction,
onOpenNode: onOpenNode,
),
),
),
const SizedBox(height: 14),
_StudioStatusBar(
user: user,
selectedTab: selectedTab,
tabsCount: tabs.length,
activitiesCount: activities.length,
),
],
),
);
}
Widget _buildPage({
required Key key,
required StudioTab tab,
required List<StudioActivity> activities,
required List<StudioNavNode> rootNodes,
required StudioRuntime runtime,
required void Function(String title, String detail) onPageAction,
required ValueChanged<StudioNavNode> onOpenNode,
}) {
switch (tab.pageType) {
case StudioPageType.dashboard:
return StudioDashboardPage(
key: key,
tab: tab,
user: user,
activities: activities,
rootNodes: rootNodes,
onOpenNode: onOpenNode,
);
case StudioPageType.history:
return StudioHistoryPage(
key: key,
tab: tab,
runtime: runtime,
onAction: onPageAction,
);
case StudioPageType.sync:
case StudioPageType.repair:
return StudioOperationsPage(
key: key,
tab: tab,
runtime: runtime,
onAction: onPageAction,
);
case StudioPageType.sqlTemplate:
case StudioPageType.sqlRunner:
return StudioSqlPage(
key: key,
tab: tab,
runtime: runtime,
onAction: onPageAction,
);
case StudioPageType.portalMaintain:
return StudioPortalMaintainPage(
key: key,
tab: tab,
runtime: runtime,
onAction: onPageAction,
);
default:
return StudioFormEditorPage(
key: key,
tab: tab,
runtime: runtime,
onAction: onPageAction,
);
}
}
}
class _WorkspaceHeader extends StatelessWidget {
const _WorkspaceHeader({
required this.user,
required this.selectedTab,
required this.onRefresh,
required this.onLogout,
required this.themeMode,
required this.onToggleTheme,
});
final StudioUser user;
final StudioTab selectedTab;
final VoidCallback onRefresh;
final VoidCallback onLogout;
final ThemeMode themeMode;
final VoidCallback onToggleTheme;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(18),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
selectedTab.title,
style: Theme.of(context).textTheme.headlineSmall
?.copyWith(fontWeight: FontWeight.w700),
),
const SizedBox(height: 6),
Text(
selectedTab.summary,
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(color: _mutedColor(context)),
),
const SizedBox(height: 12),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
_MiniBadge(text: user.workspace),
_MiniBadge(text: user.displayName),
if (user.backendAppName.isNotEmpty)
_MiniBadge(text: user.backendAppName),
if (user.organizationName.isNotEmpty)
_MiniBadge(text: user.organizationName),
_MiniBadge(text: selectedTab.pageType.name),
],
),
],
),
),
const SizedBox(width: 16),
Wrap(
spacing: 10,
runSpacing: 10,
alignment: WrapAlignment.end,
children: [
_ThemeToggleButton(
themeMode: themeMode,
onPressed: onToggleTheme,
),
FilledButton.tonalIcon(
onPressed: onRefresh,
icon: const Icon(Icons.refresh_rounded),
label: const Text('刷新'),
),
FilledButton.tonalIcon(
onPressed: onLogout,
icon: const Icon(Icons.logout_rounded),
label: const Text('退出'),
),
],
),
],
),
),
);
}
}
class _TabStrip extends StatelessWidget {
const _TabStrip({
required this.tabs,
required this.selectedTabId,
required this.onSelectTab,
required this.onCloseTab,
});
final List<StudioTab> tabs;
final String selectedTabId;
final ValueChanged<String> onSelectTab;
final ValueChanged<String> onCloseTab;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 58,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: tabs.length,
separatorBuilder: (_, __) => const SizedBox(width: 10),
itemBuilder: (context, index) {
final tab = tabs[index];
final selected = tab.id == selectedTabId;
final scheme = Theme.of(context).colorScheme;
return InkWell(
borderRadius: BorderRadius.circular(18),
onTap: () => onSelectTab(tab.id),
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
width: 210,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: selected
? scheme.primary.withOpacity(0.18)
: _softPanelColor(context),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: selected
? scheme.primary.withOpacity(0.55)
: scheme.outlineVariant.withOpacity(0.8),
),
),
child: Row(
children: [
Icon(tab.icon, size: 18),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
tab.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
tab.pageType.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall
?.copyWith(color: _mutedColor(context, 0.62)),
),
],
),
),
if (tab.id != StudioTab.dashboardId)
IconButton(
tooltip: '关闭',
onPressed: () => onCloseTab(tab.id),
icon: const Icon(Icons.close_rounded, size: 16),
),
],
),
),
);
},
),
);
}
}
class _StudioStatusBar extends StatelessWidget {
const _StudioStatusBar({
required this.user,
required this.selectedTab,
required this.tabsCount,
required this.activitiesCount,
});
final StudioUser user;
final StudioTab selectedTab;
final int tabsCount;
final int activitiesCount;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
child: Wrap(
spacing: 16,
runSpacing: 10,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
_MiniBadge(text: '用户 ${user.displayName}'),
_MiniBadge(text: '工作区 ${user.workspace}'),
if (user.backendAppName.isNotEmpty)
_MiniBadge(text: '应用 ${user.backendAppName}'),
if (user.organizationName.isNotEmpty)
_MiniBadge(text: '机构 ${user.organizationName}'),
_MiniBadge(text: '当前页 ${selectedTab.title}'),
_MiniBadge(text: '标签 ${tabsCount}'),
_MiniBadge(text: '活动 ${activitiesCount}'),
],
),
),
);
}
}