SHA256
init
This commit is contained in:
@@ -535,6 +535,7 @@ class StudioBackendClient {
|
|||||||
responseType: ResponseType.bytes,
|
responseType: ResponseType.bytes,
|
||||||
followRedirects: true,
|
followRedirects: true,
|
||||||
receiveTimeout: const Duration(seconds: 60),
|
receiveTimeout: const Duration(seconds: 60),
|
||||||
|
extra: const <String, dynamic>{'withCredentials': true},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_captureCookies(response);
|
_captureCookies(response);
|
||||||
@@ -594,6 +595,8 @@ class StudioBackendClient {
|
|||||||
session: session,
|
session: session,
|
||||||
includeSessionCookies: includeSessionCookies,
|
includeSessionCookies: includeSessionCookies,
|
||||||
),
|
),
|
||||||
|
// Web 端需要浏览器带上 cookie,避免登录态和会话丢失。
|
||||||
|
extra: const <String, dynamic>{'withCredentials': true},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_captureCookies(response);
|
_captureCookies(response);
|
||||||
@@ -623,6 +626,7 @@ class StudioBackendClient {
|
|||||||
? (request, options) => utf8.encode(request)
|
? (request, options) => utf8.encode(request)
|
||||||
: null,
|
: null,
|
||||||
followRedirects: followRedirects,
|
followRedirects: followRedirects,
|
||||||
|
extra: const <String, dynamic>{'withCredentials': true},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_captureCookies(response);
|
_captureCookies(response);
|
||||||
@@ -668,6 +672,7 @@ class StudioBackendClient {
|
|||||||
includeSessionCookies: true,
|
includeSessionCookies: true,
|
||||||
),
|
),
|
||||||
followRedirects: true,
|
followRedirects: true,
|
||||||
|
extra: const <String, dynamic>{'withCredentials': true},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_captureCookies(response);
|
_captureCookies(response);
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_highlight/themes/atom-one-dark.dart';
|
import 'package:flutter_highlight/themes/atom-one-dark.dart';
|
||||||
import 'package:flutter_highlight/themes/atom-one-light.dart';
|
import 'package:flutter_highlight/themes/atom-one-light.dart';
|
||||||
import 'package:code_text_field/code_text_field.dart';
|
import 'package:code_text_field/code_text_field.dart';
|
||||||
import 'package:highlight/languages/json.dart' as json_highlight;
|
import 'package:json_field_editor/json_field_editor.dart';
|
||||||
|
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
||||||
import 'package:highlight/languages/sql.dart' as sql_highlight;
|
import 'package:highlight/languages/sql.dart' as sql_highlight;
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class _StudioLoginViewState extends State<StudioLoginView> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
// 登录页的配置读取只做“增强体验”;就算后端暂时不可达,用户仍然可以手动输入地址继续登录。
|
||||||
_errorMessage = '后端配置暂时不可用,仍可继续尝试登录';
|
_errorMessage = '后端配置暂时不可用,仍可继续尝试登录';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,43 +161,6 @@ final List<StudioNavNode> studioNavTree = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
StudioNavNode(
|
|
||||||
id: 'json-center',
|
|
||||||
title: '模型 JSON',
|
|
||||||
summary: '直写 JSON 与脚本化编辑',
|
|
||||||
icon: Icons.data_object_rounded,
|
|
||||||
pageType: StudioPageType.dashboard,
|
|
||||||
children: [
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'TableJsonEditor',
|
|
||||||
title: '数据模型 Json',
|
|
||||||
summary: 'JSON 结构和字段定义',
|
|
||||||
icon: Icons.data_object_rounded,
|
|
||||||
pageType: StudioPageType.jsonEditor,
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'BillJsonEditor',
|
|
||||||
title: '单据模型 Json',
|
|
||||||
summary: '单据配置的 JSON 编辑器',
|
|
||||||
icon: Icons.code_rounded,
|
|
||||||
pageType: StudioPageType.jsonEditor,
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'ViewJsonEditor',
|
|
||||||
title: '界面模型 Json',
|
|
||||||
summary: '界面配置的 JSON 编辑器',
|
|
||||||
icon: Icons.article_rounded,
|
|
||||||
pageType: StudioPageType.jsonEditor,
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'NavigatorEditor',
|
|
||||||
title: '菜单维护 Json',
|
|
||||||
summary: '导航树的 JSON 配置',
|
|
||||||
icon: Icons.list_alt_rounded,
|
|
||||||
pageType: StudioPageType.navigationEditor,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
StudioNavNode(
|
||||||
id: 'history',
|
id: 'history',
|
||||||
title: '模型日志',
|
title: '模型日志',
|
||||||
@@ -235,36 +198,6 @@ final List<StudioNavNode> studioNavTree = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
StudioNavNode(
|
|
||||||
id: 'manage',
|
|
||||||
title: '模型管理',
|
|
||||||
summary: '在线维护与发布',
|
|
||||||
icon: Icons.tune_rounded,
|
|
||||||
pageType: StudioPageType.dashboard,
|
|
||||||
children: [
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'WsoDataManage',
|
|
||||||
title: '界面模型管理',
|
|
||||||
summary: '界面模型维护',
|
|
||||||
icon: Icons.preview_rounded,
|
|
||||||
pageType: StudioPageType.manage,
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'BillModelManage',
|
|
||||||
title: '单据模型管理',
|
|
||||||
summary: '单据模型维护',
|
|
||||||
icon: Icons.inventory_2_rounded,
|
|
||||||
pageType: StudioPageType.manage,
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
|
||||||
id: 'TableModelManage',
|
|
||||||
title: '数据模型管理',
|
|
||||||
summary: '数据模型维护',
|
|
||||||
icon: Icons.storage_rounded,
|
|
||||||
pageType: StudioPageType.manage,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
StudioNavNode(
|
StudioNavNode(
|
||||||
id: 'ModelSync',
|
id: 'ModelSync',
|
||||||
title: '配置下载',
|
title: '配置下载',
|
||||||
|
|||||||
+44
-145
@@ -6,15 +6,11 @@ class StudioDashboardPage extends StatelessWidget {
|
|||||||
required this.tab,
|
required this.tab,
|
||||||
required this.user,
|
required this.user,
|
||||||
required this.activities,
|
required this.activities,
|
||||||
required this.rootNodes,
|
|
||||||
required this.onOpenNode,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final StudioTab tab;
|
final StudioTab tab;
|
||||||
final StudioUser user;
|
final StudioUser user;
|
||||||
final List<StudioActivity> activities;
|
final List<StudioActivity> activities;
|
||||||
final List<StudioNavNode> rootNodes;
|
|
||||||
final ValueChanged<StudioNavNode> onOpenNode;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -126,58 +122,12 @@ class StudioDashboardPage extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
LayoutBuilder(
|
_ActivityPanel(activities: activities),
|
||||||
builder: (context, constraints) {
|
|
||||||
final leftWidth = constraints.maxWidth >= 980
|
|
||||||
? (constraints.maxWidth - 16) * 0.58
|
|
||||||
: constraints.maxWidth;
|
|
||||||
final rightWidth = constraints.maxWidth >= 980
|
|
||||||
? (constraints.maxWidth - 16) * 0.42
|
|
||||||
: constraints.maxWidth;
|
|
||||||
|
|
||||||
if (constraints.maxWidth >= 980) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: leftWidth,
|
|
||||||
child: _ActivityPanel(activities: activities),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
SizedBox(
|
|
||||||
width: rightWidth,
|
|
||||||
child: _FeaturePanel(onOpenNode: onOpenNode),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
_ActivityPanel(activities: activities),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
_FeaturePanel(onOpenNode: onOpenNode),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<StudioNavNode> _allLeaves(List<StudioNavNode> nodes) {
|
|
||||||
final result = <StudioNavNode>[];
|
|
||||||
for (final node in nodes) {
|
|
||||||
if (node.children.isEmpty) {
|
|
||||||
result.add(node);
|
|
||||||
} else {
|
|
||||||
result.addAll(_allLeaves(node.children));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InfoPanel extends StatelessWidget {
|
class _InfoPanel extends StatelessWidget {
|
||||||
@@ -216,20 +166,23 @@ class _InfoPanel extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
title,
|
title,
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
?.copyWith(color: _mutedColor(context, 0.68)),
|
color: _mutedColor(context, 0.68),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
value,
|
value,
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
color: _mutedColor(context, 0.72),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -256,102 +209,48 @@ class _ActivityPanel extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'最近活动',
|
'最近活动',
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
style: Theme.of(
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
context,
|
||||||
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
...activities.take(8).map(
|
...activities
|
||||||
(item) => Padding(
|
.take(8)
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
.map(
|
||||||
child: Row(
|
(item) => Padding(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
children: [
|
child: Row(
|
||||||
const Icon(Icons.bolt_rounded, size: 16),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
const SizedBox(width: 8),
|
children: [
|
||||||
Expanded(
|
const Icon(Icons.bolt_rounded, size: 16),
|
||||||
child: Column(
|
const SizedBox(width: 8),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
item.title,
|
children: [
|
||||||
style: Theme.of(context).textTheme.bodyMedium
|
Text(
|
||||||
?.copyWith(fontWeight: FontWeight.w600),
|
item.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium
|
||||||
|
?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
item.detail,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall
|
||||||
|
?.copyWith(
|
||||||
|
color: _mutedColor(context, 0.70),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
),
|
||||||
Text(
|
],
|
||||||
item.detail,
|
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
|
||||||
?.copyWith(color: _mutedColor(context, 0.70)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FeaturePanel extends StatelessWidget {
|
|
||||||
const _FeaturePanel({required this.onOpenNode});
|
|
||||||
|
|
||||||
final ValueChanged<StudioNavNode> onOpenNode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final nodes = _allLeaves(studioNavTree);
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'功能覆盖',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium
|
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
'当前 Flutter 桌面端实现了 Studio 页面的核心交互:登录、导航、标签页、JSON/SQL 编辑、日志和运维任务。',
|
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
|
||||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 8,
|
|
||||||
children: nodes
|
|
||||||
.take(5)
|
|
||||||
.map(
|
|
||||||
(node) => FilledButton.tonal(
|
|
||||||
onPressed: () => onOpenNode(node),
|
|
||||||
child: Text(node.title),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<StudioNavNode> _allLeaves(List<StudioNavNode> nodes) {
|
|
||||||
final result = <StudioNavNode>[];
|
|
||||||
for (final node in nodes) {
|
|
||||||
if (node.children.isEmpty) {
|
|
||||||
result.add(node);
|
|
||||||
} else {
|
|
||||||
result.addAll(_allLeaves(node.children));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1105
-377
File diff suppressed because it is too large
Load Diff
@@ -6,11 +6,13 @@ class StudioPortalMaintainPage extends StatefulWidget {
|
|||||||
required this.tab,
|
required this.tab,
|
||||||
required this.runtime,
|
required this.runtime,
|
||||||
required this.onAction,
|
required this.onAction,
|
||||||
|
required this.onOpenMenu,
|
||||||
});
|
});
|
||||||
|
|
||||||
final StudioTab tab;
|
final StudioTab tab;
|
||||||
final StudioRuntime runtime;
|
final StudioRuntime runtime;
|
||||||
final void Function(String title, String detail) onAction;
|
final void Function(String title, String detail) onAction;
|
||||||
|
final VoidCallback onOpenMenu;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StudioPortalMaintainPage> createState() => _StudioPortalMaintainPageState();
|
State<StudioPortalMaintainPage> createState() => _StudioPortalMaintainPageState();
|
||||||
@@ -108,6 +110,12 @@ class _StudioPortalMaintainPageState extends State<StudioPortalMaintainPage> {
|
|||||||
icon: const Icon(Icons.refresh_rounded),
|
icon: const Icon(Icons.refresh_rounded),
|
||||||
label: const Text('刷新'),
|
label: const Text('刷新'),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: widget.onOpenMenu,
|
||||||
|
icon: const Icon(Icons.menu_rounded),
|
||||||
|
label: const Text('设置菜单'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|||||||
+267
-154
@@ -39,8 +39,7 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_sqlController = CodeController(
|
_sqlController = CodeController(
|
||||||
text:
|
text: 'select id, code, name from item order by id desc limit 20',
|
||||||
'select id, code, name from item order by id desc limit 20',
|
|
||||||
language: sql_highlight.sql,
|
language: sql_highlight.sql,
|
||||||
);
|
);
|
||||||
_limitController = TextEditingController(text: '100');
|
_limitController = TextEditingController(text: '100');
|
||||||
@@ -157,10 +156,20 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_templateLoaded = true;
|
_templateLoaded = true;
|
||||||
_sqlController.text = _readValue(template, const ['value', 'sql']);
|
_sqlController.text = _readValue(template, const ['value', 'sql']);
|
||||||
_countSqlController.text = _readValue(template, const ['countSql', 'count']);
|
_countSqlController.text = _readValue(template, const [
|
||||||
_summarySqlController.text = _readValue(template, const ['summarySql', 'summary']);
|
'countSql',
|
||||||
_criteriaController.text = _readValue(template, const ['criteria'], '{}');
|
'count',
|
||||||
_extConfigController.text = _readValue(template, const ['extConfig'], '{}');
|
]);
|
||||||
|
_summarySqlController.text = _readValue(template, const [
|
||||||
|
'summarySql',
|
||||||
|
'summary',
|
||||||
|
]);
|
||||||
|
_criteriaController.text = _readValue(template, const [
|
||||||
|
'criteria',
|
||||||
|
], '{}');
|
||||||
|
_extConfigController.text = _readValue(template, const [
|
||||||
|
'extConfig',
|
||||||
|
], '{}');
|
||||||
_status = '模板已加载';
|
_status = '模板已加载';
|
||||||
_detail = '$tid / $oid';
|
_detail = '$tid / $oid';
|
||||||
});
|
});
|
||||||
@@ -319,7 +328,11 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _readValue(Map<String, dynamic> map, List<String> keys, [String fallback = '']) {
|
String _readValue(
|
||||||
|
Map<String, dynamic> map,
|
||||||
|
List<String> keys, [
|
||||||
|
String fallback = '',
|
||||||
|
]) {
|
||||||
for (final key in keys) {
|
for (final key in keys) {
|
||||||
final value = map[key];
|
final value = map[key];
|
||||||
if (value != null && value.toString().trim().isNotEmpty) {
|
if (value != null && value.toString().trim().isNotEmpty) {
|
||||||
@@ -354,58 +367,40 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatusGrid(BuildContext context) {
|
Widget _buildStatusGrid(BuildContext context) {
|
||||||
final rows = <DataRow>[
|
final items = <_StatusItem>[
|
||||||
DataRow(
|
_StatusItem('状态', _status),
|
||||||
cells: [
|
_StatusItem('详情', _detail.isEmpty ? '-' : _detail),
|
||||||
const DataCell(Text('状态')),
|
_StatusItem(
|
||||||
DataCell(SelectableText(_status)),
|
'结果',
|
||||||
],
|
_resultRows.isEmpty
|
||||||
),
|
? '无结果'
|
||||||
DataRow(
|
: '共 ${_resultRows.length} 行,${_resultColumns.length} 列',
|
||||||
cells: [
|
|
||||||
const DataCell(Text('详情')),
|
|
||||||
DataCell(
|
|
||||||
SelectableText(
|
|
||||||
_detail.isEmpty ? '-' : _detail,
|
|
||||||
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
DataRow(
|
|
||||||
cells: [
|
|
||||||
const DataCell(Text('结果')),
|
|
||||||
DataCell(
|
|
||||||
SelectableText(
|
|
||||||
_resultRows.isEmpty
|
|
||||||
? '无结果'
|
|
||||||
: '共 ${_resultRows.length} 行,${_resultColumns.length} 列',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
if (_isTemplateMode)
|
||||||
|
_StatusItem('模板', '${_selectedTid()} / ${_selectedOid()}'),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (_isTemplateMode) {
|
return LayoutBuilder(
|
||||||
rows.add(
|
builder: (context, constraints) {
|
||||||
DataRow(
|
final cardWidth = constraints.maxWidth >= 920
|
||||||
cells: [
|
? (constraints.maxWidth - 12) / 2
|
||||||
const DataCell(Text('模板')),
|
: constraints.maxWidth;
|
||||||
DataCell(SelectableText('${_selectedTid()} / ${_selectedOid()}')),
|
return Wrap(
|
||||||
],
|
spacing: 12,
|
||||||
),
|
runSpacing: 12,
|
||||||
);
|
children: items
|
||||||
}
|
.map(
|
||||||
|
(item) => SizedBox(
|
||||||
return SingleChildScrollView(
|
width: cardWidth,
|
||||||
scrollDirection: Axis.horizontal,
|
child: _StatusTile(
|
||||||
child: DataTable(
|
title: item.title,
|
||||||
columns: const [
|
content: item.content,
|
||||||
DataColumn(label: Text('字段')),
|
),
|
||||||
DataColumn(label: Text('内容')),
|
),
|
||||||
],
|
)
|
||||||
rows: rows,
|
.toList(),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +423,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
value: tids.contains(selectedTid) ? selectedTid : null,
|
value: tids.contains(selectedTid) ? selectedTid : null,
|
||||||
decoration: const InputDecoration(labelText: 'tid'),
|
decoration: const InputDecoration(labelText: 'tid'),
|
||||||
items: tids
|
items: tids
|
||||||
.map((tid) => DropdownMenuItem(value: tid, child: Text(tid)))
|
.map(
|
||||||
|
(tid) => DropdownMenuItem(value: tid, child: Text(tid)),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -436,7 +433,8 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedTidValue = value;
|
_selectedTidValue = value;
|
||||||
_selectedOidValue = (_templateIndex[value] ?? const <String>[]).isNotEmpty
|
_selectedOidValue =
|
||||||
|
(_templateIndex[value] ?? const <String>[]).isNotEmpty
|
||||||
? _templateIndex[value]!.first
|
? _templateIndex[value]!.first
|
||||||
: '';
|
: '';
|
||||||
});
|
});
|
||||||
@@ -449,7 +447,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
value: oids.contains(_selectedOid()) ? _selectedOid() : null,
|
value: oids.contains(_selectedOid()) ? _selectedOid() : null,
|
||||||
decoration: const InputDecoration(labelText: 'oid'),
|
decoration: const InputDecoration(labelText: 'oid'),
|
||||||
items: oids
|
items: oids
|
||||||
.map((oid) => DropdownMenuItem(value: oid, child: Text(oid)))
|
.map(
|
||||||
|
(oid) => DropdownMenuItem(value: oid, child: Text(oid)),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
@@ -511,114 +511,188 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isTemplateMode && _selectedTidValue.isEmpty && _templateIndex.isNotEmpty) {
|
if (_isTemplateMode &&
|
||||||
|
_selectedTidValue.isEmpty &&
|
||||||
|
_templateIndex.isNotEmpty) {
|
||||||
_selectedTidValue = _templateIndex.keys.first;
|
_selectedTidValue = _templateIndex.keys.first;
|
||||||
_selectedOidValue = _templateIndex[_selectedTidValue]!.first;
|
_selectedOidValue = _templateIndex[_selectedTidValue]!.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return LayoutBuilder(
|
||||||
child: Column(
|
builder: (context, constraints) {
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
final wide = constraints.maxWidth >= 1120;
|
||||||
children: [
|
final content = _loading
|
||||||
if (_loading)
|
? const Padding(
|
||||||
const Padding(
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
child: Center(child: CircularProgressIndicator()),
|
||||||
child: Center(child: CircularProgressIndicator()),
|
)
|
||||||
)
|
: _isTemplateMode
|
||||||
else if (_isTemplateMode)
|
? _buildTemplateBody(context)
|
||||||
_buildTemplateBody(context)
|
: _buildSqlEditors(context, includeTemplateFields: false);
|
||||||
else
|
|
||||||
_buildSqlEditors(context, includeTemplateFields: false),
|
if (!wide) {
|
||||||
const SizedBox(height: 12),
|
return SingleChildScrollView(
|
||||||
Card(
|
child: Column(
|
||||||
child: Padding(
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
padding: const EdgeInsets.all(14),
|
children: [
|
||||||
child: Column(
|
_buildSqlHeader(context),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
const SizedBox(height: 12),
|
||||||
|
content,
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildExecutionStatusCard(context),
|
||||||
|
if (_resultRows.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildExecutionResultCard(context),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_buildSqlHeader(context),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Expanded(
|
||||||
children: [
|
flex: 3,
|
||||||
Expanded(
|
child: SingleChildScrollView(child: content),
|
||||||
child: Text(
|
),
|
||||||
'执行状态',
|
const SizedBox(width: 12),
|
||||||
style: Theme.of(context).textTheme.titleLarge
|
SizedBox(
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
width: 400,
|
||||||
),
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
_buildExecutionStatusCard(context),
|
||||||
|
if (_resultRows.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildExecutionResultCard(context),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (!_isTemplateMode)
|
),
|
||||||
FilledButton.tonalIcon(
|
|
||||||
onPressed: _loading ? null : _runSql,
|
|
||||||
icon: const Icon(Icons.play_arrow_rounded),
|
|
||||||
label: const Text('执行 SQL'),
|
|
||||||
),
|
|
||||||
if (!_isTemplateMode) const SizedBox(width: 10),
|
|
||||||
FilledButton.tonalIcon(
|
|
||||||
onPressed: _loading ? null : _formatSql,
|
|
||||||
icon: const Icon(Icons.format_align_left_rounded),
|
|
||||||
label: const Text('格式化'),
|
|
||||||
),
|
|
||||||
if (_isTemplateMode) ...[
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
FilledButton.tonalIcon(
|
|
||||||
onPressed: _loading ? null : _saveTemplate,
|
|
||||||
icon: const Icon(Icons.save_rounded),
|
|
||||||
label: const Text('保存模板'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildStatusGrid(context),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
if (_resultRows.isNotEmpty) ...[
|
);
|
||||||
const SizedBox(height: 16),
|
},
|
||||||
Card(
|
);
|
||||||
child: Padding(
|
}
|
||||||
padding: const EdgeInsets.all(18),
|
|
||||||
child: Column(
|
Widget _buildSqlHeader(BuildContext context) {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return Card(
|
||||||
children: [
|
child: Padding(
|
||||||
Text(
|
padding: const EdgeInsets.all(14),
|
||||||
'执行结果',
|
child: Row(
|
||||||
style: Theme.of(context).textTheme.titleLarge
|
children: [
|
||||||
?.copyWith(fontWeight: FontWeight.w700),
|
Expanded(
|
||||||
),
|
child: Text(
|
||||||
const SizedBox(height: 12),
|
_isTemplateMode ? 'SQL 模板执行' : 'SQL 执行',
|
||||||
SingleChildScrollView(
|
style: Theme.of(
|
||||||
scrollDirection: Axis.horizontal,
|
context,
|
||||||
child: DataTable(
|
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||||
columns: _resultColumns
|
|
||||||
.map((column) => DataColumn(label: Text(column)))
|
|
||||||
.toList(),
|
|
||||||
rows: _resultRows
|
|
||||||
.map(
|
|
||||||
(row) => DataRow(
|
|
||||||
cells: _resultColumns
|
|
||||||
.map(
|
|
||||||
(column) => DataCell(
|
|
||||||
SelectableText(_formatCellValue(row[column])),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (!_isTemplateMode)
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: _loading ? null : _runSql,
|
||||||
|
icon: const Icon(Icons.play_arrow_rounded),
|
||||||
|
label: const Text('执行 SQL'),
|
||||||
|
),
|
||||||
|
if (!_isTemplateMode) const SizedBox(width: 10),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: _loading ? null : _formatSql,
|
||||||
|
icon: const Icon(Icons.format_align_left_rounded),
|
||||||
|
label: const Text('格式化'),
|
||||||
|
),
|
||||||
|
if (_isTemplateMode) ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: _loading ? null : _saveTemplate,
|
||||||
|
icon: const Icon(Icons.save_rounded),
|
||||||
|
label: const Text('保存模板'),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSqlEditors(BuildContext context, {required bool includeTemplateFields}) {
|
Widget _buildExecutionStatusCard(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'执行状态',
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildStatusGrid(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildExecutionResultCard(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'执行结果',
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DataTable(
|
||||||
|
columns: _resultColumns
|
||||||
|
.map((column) => DataColumn(label: Text(column)))
|
||||||
|
.toList(),
|
||||||
|
rows: _resultRows
|
||||||
|
.map(
|
||||||
|
(row) => DataRow(
|
||||||
|
cells: _resultColumns
|
||||||
|
.map(
|
||||||
|
(column) => DataCell(
|
||||||
|
SelectableText(_formatCellValue(row[column])),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSqlEditors(
|
||||||
|
BuildContext context, {
|
||||||
|
required bool includeTemplateFields,
|
||||||
|
}) {
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.all(14),
|
||||||
@@ -642,7 +716,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
width: 200,
|
width: 200,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _summarySqlController,
|
controller: _summarySqlController,
|
||||||
decoration: const InputDecoration(labelText: 'summary SQL'),
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'summary SQL',
|
||||||
|
),
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -671,9 +747,7 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 240,
|
height: 240,
|
||||||
child: CodeTheme(
|
child: CodeTheme(
|
||||||
data: CodeThemeData(
|
data: CodeThemeData(styles: _sqlCodeTheme(context)),
|
||||||
styles: _sqlCodeTheme(context),
|
|
||||||
),
|
|
||||||
child: CodeField(
|
child: CodeField(
|
||||||
controller: _sqlController,
|
controller: _sqlController,
|
||||||
expands: true,
|
expands: true,
|
||||||
@@ -743,3 +817,42 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _StatusItem {
|
||||||
|
const _StatusItem(this.title, this.content);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String content;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatusTile extends StatelessWidget {
|
||||||
|
const _StatusTile({required this.title, required this.content});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||||
|
color: _mutedColor(context, 0.68),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SelectableText(
|
||||||
|
content,
|
||||||
|
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -230,7 +230,6 @@ class _StudioShellState extends State<StudioShell> {
|
|||||||
activities: List<StudioActivity>.unmodifiable(
|
activities: List<StudioActivity>.unmodifiable(
|
||||||
_activities,
|
_activities,
|
||||||
),
|
),
|
||||||
rootNodes: visibleRoots,
|
|
||||||
refreshToken: _refreshRevision,
|
refreshToken: _refreshRevision,
|
||||||
onSelectTab: _selectTab,
|
onSelectTab: _selectTab,
|
||||||
onCloseTab: _closeTab,
|
onCloseTab: _closeTab,
|
||||||
@@ -250,7 +249,6 @@ class _StudioShellState extends State<StudioShell> {
|
|||||||
selectedTab: _selectedTab,
|
selectedTab: _selectedTab,
|
||||||
tabs: List<StudioTab>.unmodifiable(_tabs),
|
tabs: List<StudioTab>.unmodifiable(_tabs),
|
||||||
activities: List<StudioActivity>.unmodifiable(_activities),
|
activities: List<StudioActivity>.unmodifiable(_activities),
|
||||||
rootNodes: visibleRoots,
|
|
||||||
refreshToken: _refreshRevision,
|
refreshToken: _refreshRevision,
|
||||||
onSelectTab: _selectTab,
|
onSelectTab: _selectTab,
|
||||||
onCloseTab: _closeTab,
|
onCloseTab: _closeTab,
|
||||||
@@ -266,4 +264,3 @@ class _StudioShellState extends State<StudioShell> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,13 +186,24 @@ class _SidebarHero extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
FilledButton.tonalIcon(
|
Row(
|
||||||
onPressed: onLogout,
|
children: [
|
||||||
icon: const Icon(Icons.logout_rounded),
|
Expanded(
|
||||||
label: const Text('退出登录'),
|
child: FilledButton.tonalIcon(
|
||||||
|
onPressed: onLogout,
|
||||||
|
icon: const Icon(Icons.logout_rounded),
|
||||||
|
label: const Text('退出登录'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: _ThemeToggleButton(
|
||||||
|
themeMode: themeMode,
|
||||||
|
onPressed: onToggleTheme,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_ThemeToggleButton(themeMode: themeMode, onPressed: onToggleTheme),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
required this.selectedTab,
|
required this.selectedTab,
|
||||||
required this.tabs,
|
required this.tabs,
|
||||||
required this.activities,
|
required this.activities,
|
||||||
required this.rootNodes,
|
|
||||||
required this.refreshToken,
|
required this.refreshToken,
|
||||||
required this.onSelectTab,
|
required this.onSelectTab,
|
||||||
required this.onCloseTab,
|
required this.onCloseTab,
|
||||||
@@ -25,7 +24,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
final StudioTab selectedTab;
|
final StudioTab selectedTab;
|
||||||
final List<StudioTab> tabs;
|
final List<StudioTab> tabs;
|
||||||
final List<StudioActivity> activities;
|
final List<StudioActivity> activities;
|
||||||
final List<StudioNavNode> rootNodes;
|
|
||||||
final int refreshToken;
|
final int refreshToken;
|
||||||
final ValueChanged<String> onSelectTab;
|
final ValueChanged<String> onSelectTab;
|
||||||
final ValueChanged<String> onCloseTab;
|
final ValueChanged<String> onCloseTab;
|
||||||
@@ -66,20 +64,12 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
key: ValueKey('${selectedTab.id}:$refreshToken'),
|
key: ValueKey('${selectedTab.id}:$refreshToken'),
|
||||||
tab: selectedTab,
|
tab: selectedTab,
|
||||||
activities: activities,
|
activities: activities,
|
||||||
rootNodes: rootNodes,
|
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
onPageAction: onPageAction,
|
onPageAction: onPageAction,
|
||||||
onOpenNode: onOpenNode,
|
onOpenNode: onOpenNode,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_StudioStatusBar(
|
|
||||||
user: user,
|
|
||||||
selectedTab: selectedTab,
|
|
||||||
tabsCount: tabs.length,
|
|
||||||
activitiesCount: activities.length,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -89,7 +79,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
required Key key,
|
required Key key,
|
||||||
required StudioTab tab,
|
required StudioTab tab,
|
||||||
required List<StudioActivity> activities,
|
required List<StudioActivity> activities,
|
||||||
required List<StudioNavNode> rootNodes,
|
|
||||||
required StudioRuntime runtime,
|
required StudioRuntime runtime,
|
||||||
required void Function(String title, String detail) onPageAction,
|
required void Function(String title, String detail) onPageAction,
|
||||||
required ValueChanged<StudioNavNode> onOpenNode,
|
required ValueChanged<StudioNavNode> onOpenNode,
|
||||||
@@ -101,8 +90,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
tab: tab,
|
tab: tab,
|
||||||
user: user,
|
user: user,
|
||||||
activities: activities,
|
activities: activities,
|
||||||
rootNodes: rootNodes,
|
|
||||||
onOpenNode: onOpenNode,
|
|
||||||
);
|
);
|
||||||
case StudioPageType.history:
|
case StudioPageType.history:
|
||||||
return StudioHistoryPage(
|
return StudioHistoryPage(
|
||||||
@@ -133,6 +120,11 @@ class StudioWorkspaceView extends StatelessWidget {
|
|||||||
tab: tab,
|
tab: tab,
|
||||||
runtime: runtime,
|
runtime: runtime,
|
||||||
onAction: onPageAction,
|
onAction: onPageAction,
|
||||||
|
onOpenMenu: () => onOpenNode(
|
||||||
|
studioNavTree
|
||||||
|
.expand((node) => [node, ...node.children])
|
||||||
|
.firstWhere((node) => node.id == 'NavigationEditor'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return StudioFormEditorPage(
|
return StudioFormEditorPage(
|
||||||
@@ -239,42 +231,3 @@ class _TabStrip extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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: 12, vertical: 10),
|
|
||||||
child: Wrap(
|
|
||||||
spacing: 10,
|
|
||||||
runSpacing: 8,
|
|
||||||
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}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ List<Color> _shellGradientColors(BuildContext context) {
|
|||||||
|
|
||||||
ThemeData buildStudioTheme(Brightness brightness) {
|
ThemeData buildStudioTheme(Brightness brightness) {
|
||||||
final colorScheme = ColorScheme.fromSeed(
|
final colorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: const Color(0xFF1AA6A6),
|
seedColor: brightness == Brightness.light
|
||||||
|
? const Color(0xFF1275DA)
|
||||||
|
: const Color(0xFF1AA6A6),
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
);
|
);
|
||||||
final isDark = brightness == Brightness.dark;
|
final isDark = brightness == Brightness.dark;
|
||||||
@@ -45,7 +47,9 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
|||||||
: const Color(0xFFF6F8FB),
|
: const Color(0xFFF6F8FB),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
backgroundColor: isDark ? const Color(0xFF07111A) : const Color(0xFFF8FBFF),
|
backgroundColor: isDark
|
||||||
|
? const Color(0xFF07111A)
|
||||||
|
: const Color(0xFFF8FBFF),
|
||||||
foregroundColor: colorScheme.onSurface,
|
foregroundColor: colorScheme.onSurface,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
@@ -77,8 +81,14 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
|||||||
borderSide: BorderSide(color: colorScheme.primary),
|
borderSide: BorderSide(color: colorScheme.primary),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||||
prefixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
prefixIconConstraints: const BoxConstraints.tightFor(
|
||||||
suffixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
),
|
||||||
|
suffixIconConstraints: const BoxConstraints.tightFor(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
filledButtonTheme: FilledButtonThemeData(
|
filledButtonTheme: FilledButtonThemeData(
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
@@ -117,8 +127,9 @@ class _MiniBadge extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
style: Theme.of(context).textTheme.bodySmall
|
style: Theme.of(
|
||||||
?.copyWith(color: _mutedColor(context, 0.78)),
|
context,
|
||||||
|
).textTheme.bodySmall?.copyWith(color: _mutedColor(context, 0.78)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,13 @@ dependencies:
|
|||||||
crypto: ^3.0.6
|
crypto: ^3.0.6
|
||||||
dio: ^5.8.0
|
dio: ^5.8.0
|
||||||
file_selector: ^1.0.3
|
file_selector: ^1.0.3
|
||||||
|
flutter_highlight: ^0.7.0
|
||||||
pointycastle: ^4.0.0
|
pointycastle: ^4.0.0
|
||||||
shared_preferences: ^2.3.2
|
shared_preferences: ^2.3.2
|
||||||
code_text_field: ^1.1.0
|
code_text_field: ^1.1.0
|
||||||
|
highlight: ^0.7.0
|
||||||
|
json_field_editor: ^1.2.1
|
||||||
|
linked_scroll_controller: ^0.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user