SHA256
init
This commit is contained in:
@@ -535,6 +535,7 @@ class StudioBackendClient {
|
||||
responseType: ResponseType.bytes,
|
||||
followRedirects: true,
|
||||
receiveTimeout: const Duration(seconds: 60),
|
||||
extra: const <String, dynamic>{'withCredentials': true},
|
||||
),
|
||||
);
|
||||
_captureCookies(response);
|
||||
@@ -594,6 +595,8 @@ class StudioBackendClient {
|
||||
session: session,
|
||||
includeSessionCookies: includeSessionCookies,
|
||||
),
|
||||
// Web 端需要浏览器带上 cookie,避免登录态和会话丢失。
|
||||
extra: const <String, dynamic>{'withCredentials': true},
|
||||
),
|
||||
);
|
||||
_captureCookies(response);
|
||||
@@ -623,6 +626,7 @@ class StudioBackendClient {
|
||||
? (request, options) => utf8.encode(request)
|
||||
: null,
|
||||
followRedirects: followRedirects,
|
||||
extra: const <String, dynamic>{'withCredentials': true},
|
||||
),
|
||||
);
|
||||
_captureCookies(response);
|
||||
@@ -668,6 +672,7 @@ class StudioBackendClient {
|
||||
includeSessionCookies: true,
|
||||
),
|
||||
followRedirects: true,
|
||||
extra: const <String, dynamic>{'withCredentials': true},
|
||||
),
|
||||
);
|
||||
_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-light.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:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
||||
@@ -194,6 +194,7 @@ class _StudioLoginViewState extends State<StudioLoginView> {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
// 登录页的配置读取只做“增强体验”;就算后端暂时不可达,用户仍然可以手动输入地址继续登录。
|
||||
_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(
|
||||
id: 'history',
|
||||
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(
|
||||
id: 'ModelSync',
|
||||
title: '配置下载',
|
||||
|
||||
+18
-119
@@ -6,15 +6,11 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
required this.tab,
|
||||
required this.user,
|
||||
required this.activities,
|
||||
required this.rootNodes,
|
||||
required this.onOpenNode,
|
||||
});
|
||||
|
||||
final StudioTab tab;
|
||||
final StudioUser user;
|
||||
final List<StudioActivity> activities;
|
||||
final List<StudioNavNode> rootNodes;
|
||||
final ValueChanged<StudioNavNode> onOpenNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -126,58 +122,12 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LayoutBuilder(
|
||||
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 {
|
||||
@@ -216,20 +166,23 @@ class _InfoPanel extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.68)),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: _mutedColor(context, 0.68),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: _mutedColor(context, 0.72),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -256,11 +209,14 @@ class _ActivityPanel extends StatelessWidget {
|
||||
children: [
|
||||
Text(
|
||||
'最近活动',
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
...activities.take(8).map(
|
||||
...activities
|
||||
.take(8)
|
||||
.map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
@@ -281,7 +237,9 @@ class _ActivityPanel extends StatelessWidget {
|
||||
Text(
|
||||
item.detail,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.70)),
|
||||
?.copyWith(
|
||||
color: _mutedColor(context, 0.70),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -296,62 +254,3 @@ class _ActivityPanel extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+1042
-314
File diff suppressed because it is too large
Load Diff
@@ -6,11 +6,13 @@ class StudioPortalMaintainPage extends StatefulWidget {
|
||||
required this.tab,
|
||||
required this.runtime,
|
||||
required this.onAction,
|
||||
required this.onOpenMenu,
|
||||
});
|
||||
|
||||
final StudioTab tab;
|
||||
final StudioRuntime runtime;
|
||||
final void Function(String title, String detail) onAction;
|
||||
final VoidCallback onOpenMenu;
|
||||
|
||||
@override
|
||||
State<StudioPortalMaintainPage> createState() => _StudioPortalMaintainPageState();
|
||||
@@ -108,6 +110,12 @@ class _StudioPortalMaintainPageState extends State<StudioPortalMaintainPage> {
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
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),
|
||||
|
||||
+200
-87
@@ -39,8 +39,7 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sqlController = CodeController(
|
||||
text:
|
||||
'select id, code, name from item order by id desc limit 20',
|
||||
text: 'select id, code, name from item order by id desc limit 20',
|
||||
language: sql_highlight.sql,
|
||||
);
|
||||
_limitController = TextEditingController(text: '100');
|
||||
@@ -157,10 +156,20 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
setState(() {
|
||||
_templateLoaded = true;
|
||||
_sqlController.text = _readValue(template, const ['value', 'sql']);
|
||||
_countSqlController.text = _readValue(template, const ['countSql', 'count']);
|
||||
_summarySqlController.text = _readValue(template, const ['summarySql', 'summary']);
|
||||
_criteriaController.text = _readValue(template, const ['criteria'], '{}');
|
||||
_extConfigController.text = _readValue(template, const ['extConfig'], '{}');
|
||||
_countSqlController.text = _readValue(template, const [
|
||||
'countSql',
|
||||
'count',
|
||||
]);
|
||||
_summarySqlController.text = _readValue(template, const [
|
||||
'summarySql',
|
||||
'summary',
|
||||
]);
|
||||
_criteriaController.text = _readValue(template, const [
|
||||
'criteria',
|
||||
], '{}');
|
||||
_extConfigController.text = _readValue(template, const [
|
||||
'extConfig',
|
||||
], '{}');
|
||||
_status = '模板已加载';
|
||||
_detail = '$tid / $oid';
|
||||
});
|
||||
@@ -319,7 +328,11 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
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) {
|
||||
final value = map[key];
|
||||
if (value != null && value.toString().trim().isNotEmpty) {
|
||||
@@ -354,58 +367,40 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
}
|
||||
|
||||
Widget _buildStatusGrid(BuildContext context) {
|
||||
final rows = <DataRow>[
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('状态')),
|
||||
DataCell(SelectableText(_status)),
|
||||
],
|
||||
),
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('详情')),
|
||||
DataCell(
|
||||
SelectableText(
|
||||
_detail.isEmpty ? '-' : _detail,
|
||||
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('结果')),
|
||||
DataCell(
|
||||
SelectableText(
|
||||
final items = <_StatusItem>[
|
||||
_StatusItem('状态', _status),
|
||||
_StatusItem('详情', _detail.isEmpty ? '-' : _detail),
|
||||
_StatusItem(
|
||||
'结果',
|
||||
_resultRows.isEmpty
|
||||
? '无结果'
|
||||
: '共 ${_resultRows.length} 行,${_resultColumns.length} 列',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_isTemplateMode)
|
||||
_StatusItem('模板', '${_selectedTid()} / ${_selectedOid()}'),
|
||||
];
|
||||
|
||||
if (_isTemplateMode) {
|
||||
rows.add(
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('模板')),
|
||||
DataCell(SelectableText('${_selectedTid()} / ${_selectedOid()}')),
|
||||
],
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardWidth = constraints.maxWidth >= 920
|
||||
? (constraints.maxWidth - 12) / 2
|
||||
: constraints.maxWidth;
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: items
|
||||
.map(
|
||||
(item) => SizedBox(
|
||||
width: cardWidth,
|
||||
child: _StatusTile(
|
||||
title: item.title,
|
||||
content: item.content,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
columns: const [
|
||||
DataColumn(label: Text('字段')),
|
||||
DataColumn(label: Text('内容')),
|
||||
],
|
||||
rows: rows,
|
||||
),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -428,7 +423,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
value: tids.contains(selectedTid) ? selectedTid : null,
|
||||
decoration: const InputDecoration(labelText: 'tid'),
|
||||
items: tids
|
||||
.map((tid) => DropdownMenuItem(value: tid, child: Text(tid)))
|
||||
.map(
|
||||
(tid) => DropdownMenuItem(value: tid, child: Text(tid)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) {
|
||||
@@ -436,7 +433,8 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
}
|
||||
setState(() {
|
||||
_selectedTidValue = value;
|
||||
_selectedOidValue = (_templateIndex[value] ?? const <String>[]).isNotEmpty
|
||||
_selectedOidValue =
|
||||
(_templateIndex[value] ?? const <String>[]).isNotEmpty
|
||||
? _templateIndex[value]!.first
|
||||
: '';
|
||||
});
|
||||
@@ -449,7 +447,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
value: oids.contains(_selectedOid()) ? _selectedOid() : null,
|
||||
decoration: const InputDecoration(labelText: 'oid'),
|
||||
items: oids
|
||||
.map((oid) => DropdownMenuItem(value: oid, child: Text(oid)))
|
||||
.map(
|
||||
(oid) => DropdownMenuItem(value: oid, child: Text(oid)),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) {
|
||||
@@ -511,38 +511,94 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_isTemplateMode && _selectedTidValue.isEmpty && _templateIndex.isNotEmpty) {
|
||||
if (_isTemplateMode &&
|
||||
_selectedTidValue.isEmpty &&
|
||||
_templateIndex.isNotEmpty) {
|
||||
_selectedTidValue = _templateIndex.keys.first;
|
||||
_selectedOidValue = _templateIndex[_selectedTidValue]!.first;
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final wide = constraints.maxWidth >= 1120;
|
||||
final content = _loading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
: _isTemplateMode
|
||||
? _buildTemplateBody(context)
|
||||
: _buildSqlEditors(context, includeTemplateFields: false);
|
||||
|
||||
if (!wide) {
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (_loading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
else if (_isTemplateMode)
|
||||
_buildTemplateBody(context)
|
||||
else
|
||||
_buildSqlEditors(context, includeTemplateFields: false),
|
||||
_buildSqlHeader(context),
|
||||
const SizedBox(height: 12),
|
||||
Card(
|
||||
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: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SingleChildScrollView(child: content),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildExecutionStatusCard(context),
|
||||
if (_resultRows.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
_buildExecutionResultCard(context),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSqlHeader(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
'执行状态',
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
_isTemplateMode ? 'SQL 模板执行' : 'SQL 执行',
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
),
|
||||
if (!_isTemplateMode)
|
||||
@@ -567,15 +623,33 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_resultRows.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExecutionResultCard(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
@@ -583,8 +657,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
children: [
|
||||
Text(
|
||||
'执行结果',
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
SingleChildScrollView(
|
||||
@@ -611,14 +686,13 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSqlEditors(BuildContext context, {required bool includeTemplateFields}) {
|
||||
Widget _buildSqlEditors(
|
||||
BuildContext context, {
|
||||
required bool includeTemplateFields,
|
||||
}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(14),
|
||||
@@ -642,7 +716,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _summarySqlController,
|
||||
decoration: const InputDecoration(labelText: 'summary SQL'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'summary SQL',
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
@@ -671,9 +747,7 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
SizedBox(
|
||||
height: 240,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(
|
||||
styles: _sqlCodeTheme(context),
|
||||
),
|
||||
data: CodeThemeData(styles: _sqlCodeTheme(context)),
|
||||
child: CodeField(
|
||||
controller: _sqlController,
|
||||
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,
|
||||
),
|
||||
rootNodes: visibleRoots,
|
||||
refreshToken: _refreshRevision,
|
||||
onSelectTab: _selectTab,
|
||||
onCloseTab: _closeTab,
|
||||
@@ -250,7 +249,6 @@ class _StudioShellState extends State<StudioShell> {
|
||||
selectedTab: _selectedTab,
|
||||
tabs: List<StudioTab>.unmodifiable(_tabs),
|
||||
activities: List<StudioActivity>.unmodifiable(_activities),
|
||||
rootNodes: visibleRoots,
|
||||
refreshToken: _refreshRevision,
|
||||
onSelectTab: _selectTab,
|
||||
onCloseTab: _closeTab,
|
||||
@@ -266,4 +264,3 @@ class _StudioShellState extends State<StudioShell> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,13 +186,24 @@ class _SidebarHero extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
FilledButton.tonalIcon(
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FilledButton.tonalIcon(
|
||||
onPressed: onLogout,
|
||||
icon: const Icon(Icons.logout_rounded),
|
||||
label: const Text('退出登录'),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ThemeToggleButton(themeMode: themeMode, onPressed: onToggleTheme),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: _ThemeToggleButton(
|
||||
themeMode: themeMode,
|
||||
onPressed: onToggleTheme,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
required this.selectedTab,
|
||||
required this.tabs,
|
||||
required this.activities,
|
||||
required this.rootNodes,
|
||||
required this.refreshToken,
|
||||
required this.onSelectTab,
|
||||
required this.onCloseTab,
|
||||
@@ -25,7 +24,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
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;
|
||||
@@ -66,20 +64,12 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
key: ValueKey('${selectedTab.id}:$refreshToken'),
|
||||
tab: selectedTab,
|
||||
activities: activities,
|
||||
rootNodes: rootNodes,
|
||||
runtime: runtime,
|
||||
onPageAction: onPageAction,
|
||||
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 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,
|
||||
@@ -101,8 +90,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
tab: tab,
|
||||
user: user,
|
||||
activities: activities,
|
||||
rootNodes: rootNodes,
|
||||
onOpenNode: onOpenNode,
|
||||
);
|
||||
case StudioPageType.history:
|
||||
return StudioHistoryPage(
|
||||
@@ -133,6 +120,11 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
tab: tab,
|
||||
runtime: runtime,
|
||||
onAction: onPageAction,
|
||||
onOpenMenu: () => onOpenNode(
|
||||
studioNavTree
|
||||
.expand((node) => [node, ...node.children])
|
||||
.firstWhere((node) => node.id == 'NavigationEditor'),
|
||||
),
|
||||
);
|
||||
default:
|
||||
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) {
|
||||
final colorScheme = ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFF1AA6A6),
|
||||
seedColor: brightness == Brightness.light
|
||||
? const Color(0xFF1275DA)
|
||||
: const Color(0xFF1AA6A6),
|
||||
brightness: brightness,
|
||||
);
|
||||
final isDark = brightness == Brightness.dark;
|
||||
@@ -45,7 +47,9 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
||||
: const Color(0xFFF6F8FB),
|
||||
appBarTheme: AppBarTheme(
|
||||
centerTitle: false,
|
||||
backgroundColor: isDark ? const Color(0xFF07111A) : const Color(0xFFF8FBFF),
|
||||
backgroundColor: isDark
|
||||
? const Color(0xFF07111A)
|
||||
: const Color(0xFFF8FBFF),
|
||||
foregroundColor: colorScheme.onSurface,
|
||||
elevation: 0,
|
||||
),
|
||||
@@ -77,8 +81,14 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
||||
borderSide: BorderSide(color: colorScheme.primary),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
prefixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
||||
suffixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
||||
prefixIconConstraints: const BoxConstraints.tightFor(
|
||||
width: 34,
|
||||
height: 34,
|
||||
),
|
||||
suffixIconConstraints: const BoxConstraints.tightFor(
|
||||
width: 34,
|
||||
height: 34,
|
||||
),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
@@ -117,8 +127,9 @@ class _MiniBadge extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.78)),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: _mutedColor(context, 0.78)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,13 @@ dependencies:
|
||||
crypto: ^3.0.6
|
||||
dio: ^5.8.0
|
||||
file_selector: ^1.0.3
|
||||
flutter_highlight: ^0.7.0
|
||||
pointycastle: ^4.0.0
|
||||
shared_preferences: ^2.3.2
|
||||
code_text_field: ^1.1.0
|
||||
highlight: ^0.7.0
|
||||
json_field_editor: ^1.2.1
|
||||
linked_scroll_controller: ^0.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user