SHA256
826 lines
25 KiB
Dart
826 lines
25 KiB
Dart
part of 'studio_app.dart';
|
|
|
|
class StudioFormEditorPage extends StatefulWidget {
|
|
const StudioFormEditorPage({
|
|
super.key,
|
|
required this.tab,
|
|
required this.runtime,
|
|
required this.onAction,
|
|
});
|
|
|
|
final StudioTab tab;
|
|
final StudioRuntime runtime;
|
|
final void Function(String title, String detail) onAction;
|
|
|
|
@override
|
|
State<StudioFormEditorPage> createState() => _StudioFormEditorPageState();
|
|
}
|
|
|
|
class _StudioFormEditorPageState extends State<StudioFormEditorPage> {
|
|
late final TextEditingController _nameController;
|
|
late final TextEditingController _codeController;
|
|
late final TextEditingController _versionController;
|
|
late final TextEditingController _remarkController;
|
|
late final TextEditingController _contentController;
|
|
bool _loading = true;
|
|
bool _saving = false;
|
|
bool _loadedExisting = false;
|
|
String _status = '加载中';
|
|
String _detail = '';
|
|
List<Map<String, dynamic>> _options = <Map<String, dynamic>>[];
|
|
String _selectedKey = '';
|
|
|
|
bool get _isTableMode =>
|
|
widget.tab.id.contains('Table') || widget.tab.id.contains('table');
|
|
|
|
bool get _isBillMode =>
|
|
widget.tab.id.contains('Bill') || widget.tab.id.contains('bill');
|
|
|
|
bool get _isViewMode =>
|
|
widget.tab.id.contains('View') ||
|
|
widget.tab.id.contains('WsoData') ||
|
|
widget.tab.id.contains('view');
|
|
|
|
bool get _isNavigatorMode =>
|
|
widget.tab.id.contains('Navigator') ||
|
|
widget.tab.id.contains('Navigation') ||
|
|
widget.tab.id.contains('navigator') ||
|
|
widget.tab.id.contains('navigation');
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_nameController = TextEditingController(text: widget.tab.title);
|
|
_codeController = TextEditingController(text: widget.tab.id);
|
|
_versionController = TextEditingController(text: '1');
|
|
_remarkController = TextEditingController(text: widget.tab.summary);
|
|
_contentController = TextEditingController(text: '{}');
|
|
_loadInitialData();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_codeController.dispose();
|
|
_versionController.dispose();
|
|
_remarkController.dispose();
|
|
_contentController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadInitialData() async {
|
|
if (_isTableMode) {
|
|
await _loadTableLikeOptions();
|
|
return;
|
|
}
|
|
if (_isBillMode) {
|
|
await _loadBillLikeOptions();
|
|
return;
|
|
}
|
|
if (_isViewMode) {
|
|
await _loadViewOptions();
|
|
return;
|
|
}
|
|
if (_isNavigatorMode) {
|
|
await _loadNavigator();
|
|
return;
|
|
}
|
|
setState(() {
|
|
_contentController.text = _prettyJson(_defaultPayload());
|
|
_status = '可直接编辑';
|
|
_loading = false;
|
|
});
|
|
}
|
|
|
|
Future<void> _loadTableLikeOptions() async {
|
|
try {
|
|
final items = await widget.runtime.client.fetchTableModels(
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_options = items;
|
|
_selectedKey = items.isNotEmpty ? _itemKey(items.first) : '';
|
|
});
|
|
if (_selectedKey.isNotEmpty) {
|
|
await _loadTableLikeItem();
|
|
} else {
|
|
setState(() {
|
|
_loading = false;
|
|
_status = '没有找到数据模型';
|
|
_contentController.text = _prettyJson(_defaultPayload());
|
|
});
|
|
}
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载数据模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadBillLikeOptions() async {
|
|
try {
|
|
final items = await widget.runtime.client.fetchBillModels(
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_options = items;
|
|
_selectedKey = items.isNotEmpty ? _itemKey(items.first) : '';
|
|
});
|
|
if (_selectedKey.isNotEmpty) {
|
|
await _loadBillLikeItem();
|
|
} else {
|
|
setState(() {
|
|
_loading = false;
|
|
_status = '没有找到单据模型';
|
|
_contentController.text = _prettyJson(_defaultPayload());
|
|
});
|
|
}
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载单据模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadViewOptions() async {
|
|
try {
|
|
final items = await widget.runtime.client.fetchViewModelOptions(
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_options = items;
|
|
_selectedKey = items.isNotEmpty ? _viewItemKey(items.first) : '';
|
|
});
|
|
if (_selectedKey.isNotEmpty) {
|
|
await _loadViewItem();
|
|
} else {
|
|
setState(() {
|
|
_loading = false;
|
|
_status = '没有找到界面模型';
|
|
_contentController.text = _prettyJson(_defaultPayload());
|
|
});
|
|
}
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载界面模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadNavigator() async {
|
|
try {
|
|
final navigator = await widget.runtime.client.fetchCreationNavigatorRecord(
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
final structure = navigator['structure'] is List
|
|
? List<Map<String, dynamic>>.from(
|
|
(navigator['structure'] as List).whereType<Map>(),
|
|
)
|
|
: <Map<String, dynamic>>[];
|
|
setState(() {
|
|
_nameController.text = widget.tab.title;
|
|
_codeController.text = widget.tab.id;
|
|
_versionController.text = _readValue(navigator, const ['version'], '1');
|
|
_remarkController.text = widget.tab.summary;
|
|
_contentController.text = _prettyJson(navigator.isNotEmpty
|
|
? navigator
|
|
: <String, dynamic>{'structure': structure});
|
|
_status = '导航数据已加载';
|
|
_detail = 'root items: ${structure.length}';
|
|
_loadedExisting = true;
|
|
_loading = false;
|
|
});
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载导航失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadTableLikeItem() async {
|
|
final code = _selectedKey;
|
|
if (code.isEmpty) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_loading = true;
|
|
_status = '正在加载数据模型...';
|
|
});
|
|
try {
|
|
final item = await widget.runtime.client.fetchTableModel(
|
|
code: code,
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (item == null) {
|
|
setState(() {
|
|
_status = '模型不存在';
|
|
_detail = code;
|
|
_loading = false;
|
|
});
|
|
return;
|
|
}
|
|
_applyLoadedItem(item, code: _readValue(item, const ['code'], code));
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载数据模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadBillLikeItem() async {
|
|
final code = _selectedKey;
|
|
if (code.isEmpty) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_loading = true;
|
|
_status = '正在加载单据模型...';
|
|
});
|
|
try {
|
|
final item = await widget.runtime.client.fetchBillModel(
|
|
code: code,
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (item == null) {
|
|
setState(() {
|
|
_status = '模型不存在';
|
|
_detail = code;
|
|
_loading = false;
|
|
});
|
|
return;
|
|
}
|
|
_applyLoadedItem(item, code: _readValue(item, const ['code'], code));
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载单据模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
Future<void> _loadViewItem() async {
|
|
final parts = _selectedKey.split('::');
|
|
if (parts.length != 2) {
|
|
return;
|
|
}
|
|
final tid = parts[0];
|
|
final oid = parts[1];
|
|
setState(() {
|
|
_loading = true;
|
|
_status = '正在加载界面模型...';
|
|
});
|
|
try {
|
|
final item = await widget.runtime.client.fetchViewModel(
|
|
tid: tid,
|
|
oid: oid,
|
|
session: widget.runtime.session,
|
|
);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
if (item == null) {
|
|
setState(() {
|
|
_status = '模型不存在';
|
|
_detail = '$tid / $oid';
|
|
_loading = false;
|
|
});
|
|
return;
|
|
}
|
|
_applyLoadedItem(
|
|
item,
|
|
code: _readValue(item, const ['tid', 'code'], tid),
|
|
version: _readValue(item, const ['oid', 'version'], oid),
|
|
);
|
|
} catch (error, stackTrace) {
|
|
_reportError('加载界面模型失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
void _applyLoadedItem(
|
|
Map<String, dynamic> item, {
|
|
required String code,
|
|
String? version,
|
|
}) {
|
|
setState(() {
|
|
_nameController.text = _readValue(item, const ['name', 'title'], widget.tab.title);
|
|
_codeController.text = code;
|
|
_versionController.text = version ?? _readValue(item, const ['version', 'oid'], '1');
|
|
_remarkController.text = _readValue(item, const ['remark', 'description', 'summary'], widget.tab.summary);
|
|
_contentController.text = _prettyJson(item);
|
|
_status = '已加载';
|
|
_detail = code;
|
|
_loadedExisting = true;
|
|
_loading = false;
|
|
});
|
|
}
|
|
|
|
Map<String, dynamic> _defaultPayload() {
|
|
return <String, dynamic>{
|
|
'name': widget.tab.title,
|
|
'code': widget.tab.id,
|
|
'version': '1',
|
|
'remark': widget.tab.summary,
|
|
'enabled': true,
|
|
};
|
|
}
|
|
|
|
String _itemKey(Map<String, dynamic> item) {
|
|
return _readValue(item, const ['code', 'id']);
|
|
}
|
|
|
|
String _viewItemKey(Map<String, dynamic> item) {
|
|
final tid = _readValue(item, const ['tid']);
|
|
final oid = _readValue(item, const ['oid']);
|
|
if (tid.isEmpty || oid.isEmpty) {
|
|
return '';
|
|
}
|
|
return '$tid::$oid';
|
|
}
|
|
|
|
String _readValue(Map<String, dynamic> item, List<String> keys, [String fallback = '']) {
|
|
for (final key in keys) {
|
|
final value = item[key];
|
|
if (value != null && value.toString().trim().isNotEmpty) {
|
|
return value.toString();
|
|
}
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
String _prettyJson(dynamic value) {
|
|
try {
|
|
return const JsonEncoder.withIndent(' ').convert(value);
|
|
} catch (_) {
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
void _reportError(String title, Object error, StackTrace stackTrace) {
|
|
debugPrint('========== $title ==========');
|
|
debugPrint('error: $error');
|
|
debugPrint('stackTrace:\n$stackTrace');
|
|
debugPrint('========== $title end ==========');
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_status = '$title';
|
|
_detail = error.toString().replaceFirst('Bad state: ', '');
|
|
_loading = false;
|
|
});
|
|
}
|
|
|
|
Future<void> _save() async {
|
|
final parsed = _decodeContent();
|
|
if (parsed == null) {
|
|
setState(() {
|
|
_status = 'JSON 格式错误';
|
|
_detail = '请先修正内容后再保存';
|
|
});
|
|
return;
|
|
}
|
|
setState(() {
|
|
_saving = true;
|
|
_status = _loadedExisting ? '正在更新...' : '正在创建...';
|
|
_detail = '';
|
|
});
|
|
try {
|
|
final wasExisting = _loadedExisting;
|
|
final result = await _saveByMode(parsed);
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_loadedExisting = true;
|
|
_status = result.success ? '保存成功' : '保存失败';
|
|
_detail = _prettyJson(result.raw);
|
|
});
|
|
widget.onAction(wasExisting ? '更新' : '创建', widget.tab.title);
|
|
} catch (error, stackTrace) {
|
|
_reportError('保存失败', error, stackTrace);
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_saving = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<StudioBackendActionResult> _saveByMode(dynamic parsed) async {
|
|
if (_isNavigatorMode) {
|
|
final map = parsed is Map<String, dynamic>
|
|
? Map<String, dynamic>.from(parsed)
|
|
: <String, dynamic>{'structure': parsed};
|
|
final structure = map['structure'] is List
|
|
? List<Map<String, dynamic>>.from(
|
|
(map['structure'] as List).whereType<Map>(),
|
|
)
|
|
: <Map<String, dynamic>>[];
|
|
final version = int.tryParse(_versionController.text.trim()) ?? 1;
|
|
return widget.runtime.client.saveCreationNavigator(
|
|
data: structure,
|
|
version: version,
|
|
session: widget.runtime.session,
|
|
);
|
|
}
|
|
|
|
final payload = parsed is Map<String, dynamic>
|
|
? Map<String, dynamic>.from(parsed)
|
|
: <String, dynamic>{'data': parsed};
|
|
payload['name'] = _nameController.text.trim();
|
|
payload['code'] = _codeController.text.trim();
|
|
payload['version'] = _versionController.text.trim();
|
|
payload['remark'] = _remarkController.text.trim();
|
|
|
|
if (_isViewMode) {
|
|
payload['tid'] = _codeController.text.trim();
|
|
payload['oid'] = _versionController.text.trim();
|
|
}
|
|
|
|
if (_isTableMode) {
|
|
return _loadedExisting
|
|
? widget.runtime.client.updateTableModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
)
|
|
: widget.runtime.client.createTableModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
);
|
|
}
|
|
|
|
if (_isBillMode) {
|
|
return _loadedExisting
|
|
? widget.runtime.client.updateBillModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
)
|
|
: widget.runtime.client.createBillModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
);
|
|
}
|
|
|
|
if (_isViewMode) {
|
|
return _loadedExisting
|
|
? widget.runtime.client.updateViewModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
)
|
|
: widget.runtime.client.createViewModel(
|
|
payload: payload,
|
|
session: widget.runtime.session,
|
|
);
|
|
}
|
|
|
|
return StudioBackendActionResult(
|
|
success: true,
|
|
code: '',
|
|
message: '无需保存',
|
|
data: payload,
|
|
raw: payload,
|
|
);
|
|
}
|
|
|
|
dynamic _decodeContent() {
|
|
try {
|
|
return jsonDecode(_contentController.text);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
void _formatContent() {
|
|
final parsed = _decodeContent();
|
|
if (parsed == null) {
|
|
setState(() {
|
|
_status = 'JSON 格式错误,无法格式化';
|
|
});
|
|
return;
|
|
}
|
|
setState(() {
|
|
_contentController.text = _prettyJson(parsed);
|
|
_status = 'JSON 已格式化';
|
|
});
|
|
widget.onAction('格式化 JSON', widget.tab.title);
|
|
}
|
|
|
|
Future<void> _reload() async {
|
|
if (_isTableMode) {
|
|
await _loadTableLikeItem();
|
|
return;
|
|
}
|
|
if (_isBillMode) {
|
|
await _loadBillLikeItem();
|
|
return;
|
|
}
|
|
if (_isViewMode) {
|
|
await _loadViewItem();
|
|
return;
|
|
}
|
|
if (_isNavigatorMode) {
|
|
await _loadNavigator();
|
|
return;
|
|
}
|
|
}
|
|
|
|
Future<void> _delete() async {
|
|
if (!_loadedExisting) {
|
|
setState(() {
|
|
_status = '当前内容还没有保存过,无需删除';
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
StudioBackendActionResult result;
|
|
if (_isTableMode) {
|
|
result = await widget.runtime.client.deleteTableModel(
|
|
code: _codeController.text.trim(),
|
|
session: widget.runtime.session,
|
|
);
|
|
} else if (_isBillMode) {
|
|
result = await widget.runtime.client.deleteBillModel(
|
|
code: _codeController.text.trim(),
|
|
session: widget.runtime.session,
|
|
);
|
|
} else if (_isViewMode) {
|
|
result = await widget.runtime.client.deleteViewModel(
|
|
tid: _codeController.text.trim(),
|
|
oid: _versionController.text.trim(),
|
|
session: widget.runtime.session,
|
|
);
|
|
} else {
|
|
setState(() {
|
|
_status = '当前页面不支持删除';
|
|
});
|
|
return;
|
|
}
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_status = result.success ? '删除成功' : '删除失败';
|
|
_detail = _prettyJson(result.raw);
|
|
});
|
|
widget.onAction('删除', widget.tab.title);
|
|
} catch (error, stackTrace) {
|
|
_reportError('删除失败', error, stackTrace);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SingleChildScrollView(
|
|
child: LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
final wide = constraints.maxWidth >= 980;
|
|
final leftPanel = _buildEditorPanel(context);
|
|
final rightPanel = _buildPreviewPanel(context);
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
if (wide)
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(child: leftPanel),
|
|
const SizedBox(width: 16),
|
|
Expanded(child: rightPanel),
|
|
],
|
|
)
|
|
else ...[
|
|
leftPanel,
|
|
const SizedBox(height: 16),
|
|
rightPanel,
|
|
],
|
|
const SizedBox(height: 16),
|
|
Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(18),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: SelectableText(
|
|
_status,
|
|
style: Theme.of(context).textTheme.bodyLarge
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
),
|
|
FilledButton.tonalIcon(
|
|
onPressed: _loading || _saving ? null : _reload,
|
|
icon: const Icon(Icons.refresh_rounded),
|
|
label: const Text('重新加载'),
|
|
),
|
|
const SizedBox(width: 10),
|
|
FilledButton.tonalIcon(
|
|
onPressed: _loading || _saving ? null : _formatContent,
|
|
icon: const Icon(Icons.data_object_rounded),
|
|
label: const Text('格式化'),
|
|
),
|
|
const SizedBox(width: 10),
|
|
FilledButton.icon(
|
|
onPressed: _loading || _saving ? null : _save,
|
|
icon: _saving
|
|
? const SizedBox(
|
|
width: 16,
|
|
height: 16,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.save_rounded),
|
|
label: Text(_loadedExisting ? '更新' : '保存'),
|
|
),
|
|
const SizedBox(width: 10),
|
|
if (!_isNavigatorMode)
|
|
FilledButton.tonalIcon(
|
|
onPressed: _loading || _saving ? null : _delete,
|
|
icon: const Icon(Icons.delete_rounded),
|
|
label: const Text('删除'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEditorPanel(BuildContext context) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(18),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text(
|
|
'属性编辑',
|
|
style: Theme.of(context).textTheme.titleLarge
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
const SizedBox(height: 14),
|
|
TextField(
|
|
controller: _nameController,
|
|
decoration: const InputDecoration(
|
|
labelText: '名称',
|
|
prefixIcon: Icon(Icons.title_rounded),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _codeController,
|
|
decoration: InputDecoration(
|
|
labelText: _isViewMode ? 'tid' : '编码',
|
|
prefixIcon: const Icon(Icons.code_rounded),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _versionController,
|
|
decoration: InputDecoration(
|
|
labelText: _isViewMode ? 'oid' : '版本',
|
|
prefixIcon: const Icon(Icons.sell_rounded),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _remarkController,
|
|
maxLines: 3,
|
|
decoration: const InputDecoration(
|
|
labelText: '说明',
|
|
alignLabelWithHint: true,
|
|
prefixIcon: Icon(Icons.notes_rounded),
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
if (_options.isNotEmpty && !_isNavigatorMode) ...[
|
|
DropdownButtonFormField<String>(
|
|
value: _selectedKey.isNotEmpty ? _selectedKey : null,
|
|
decoration: InputDecoration(
|
|
labelText: _isViewMode ? '选择 tid / oid' : '选择模型',
|
|
),
|
|
items: _options.map((item) {
|
|
final value = _isViewMode ? _viewItemKey(item) : _itemKey(item);
|
|
final label = _isViewMode
|
|
? '${_readValue(item, const ['tid'])} / ${_readValue(item, const ['oid'])}'
|
|
: _readValue(item, const ['code', 'id']);
|
|
return DropdownMenuItem<String>(
|
|
value: value,
|
|
child: Text(label),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
if (value == null || value.isEmpty) {
|
|
return;
|
|
}
|
|
setState(() {
|
|
_selectedKey = value;
|
|
});
|
|
_reload();
|
|
},
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
Text(
|
|
'JSON 内容',
|
|
style: Theme.of(context).textTheme.titleMedium
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
const SizedBox(height: 10),
|
|
SizedBox(
|
|
height: 360,
|
|
child: TextField(
|
|
controller: _contentController,
|
|
expands: true,
|
|
maxLines: null,
|
|
minLines: null,
|
|
keyboardType: TextInputType.multiline,
|
|
textAlignVertical: TextAlignVertical.top,
|
|
style: const TextStyle(fontFamily: 'monospace'),
|
|
decoration: const InputDecoration(
|
|
hintText: '编辑或粘贴 JSON',
|
|
alignLabelWithHint: true,
|
|
prefixIcon: Icon(Icons.description_rounded),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPreviewPanel(BuildContext context) {
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(18),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text(
|
|
'预览与原文',
|
|
style: Theme.of(context).textTheme.titleLarge
|
|
?.copyWith(fontWeight: FontWeight.w700),
|
|
),
|
|
const SizedBox(height: 14),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: _softPanelColor(context),
|
|
borderRadius: BorderRadius.circular(18),
|
|
border: Border.all(
|
|
color: Theme.of(context).colorScheme.outlineVariant,
|
|
),
|
|
),
|
|
child: SelectableText(
|
|
_prettyJson(_decodeContent() ?? _currentPreview()),
|
|
style: const TextStyle(fontFamily: 'monospace', height: 1.5),
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
Wrap(
|
|
spacing: 10,
|
|
runSpacing: 10,
|
|
children: [
|
|
_MiniBadge(text: widget.tab.pageType.name),
|
|
_MiniBadge(text: widget.tab.id),
|
|
_MiniBadge(text: _loadedExisting ? '已加载' : '新建'),
|
|
],
|
|
),
|
|
const SizedBox(height: 14),
|
|
SelectableText(
|
|
_detail.isEmpty ? '暂无详情' : _detail,
|
|
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
dynamic _currentPreview() {
|
|
if (_isNavigatorMode) {
|
|
return <String, dynamic>{
|
|
'structure': <Map<String, dynamic>>[],
|
|
'version': _versionController.text.trim(),
|
|
};
|
|
}
|
|
return <String, dynamic>{
|
|
'name': _nameController.text.trim(),
|
|
'code': _codeController.text.trim(),
|
|
'version': _versionController.text.trim(),
|
|
'remark': _remarkController.text.trim(),
|
|
};
|
|
}
|
|
}
|
|
|