💄 Optimize publication site screen

This commit is contained in:
2025-11-22 14:39:03 +08:00
parent 4fb739b33b
commit 9b85b7573c
4 changed files with 345 additions and 336 deletions

View File

@@ -203,237 +203,207 @@ class PageForm extends HookConsumerWidget {
return SheetScaffold(
titleText: page == null ? 'Create Page' : 'Edit Page',
child: Builder(
builder:
(context) => SingleChildScrollView(
child: SingleChildScrollView(
child: Column(
children: [
Form(
key: formKey,
child: Column(
children: [
Form(
key: formKey,
child: Column(
children: [
// Page type selector
DropdownButtonFormField<int>(
value: pageType.value,
decoration: const InputDecoration(
labelText: 'Page Type',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
),
items: const [
DropdownMenuItem(
value: 0,
child: Row(
children: [
Icon(Symbols.code, size: 20),
Gap(8),
Text('HTML Page'),
],
),
),
DropdownMenuItem(
value: 1,
child: Row(
children: [
Icon(Symbols.link, size: 20),
Gap(8),
Text('Redirect Page'),
],
),
),
// Page type selector
DropdownButtonFormField<int>(
value: pageType.value,
decoration: const InputDecoration(
labelText: 'Page Type',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
items: const [
DropdownMenuItem(
value: 0,
child: Row(
children: [
Icon(Symbols.code, size: 20),
Gap(8),
Text('HTML Page'),
],
onChanged: (value) {
if (value != null) {
pageType.value = value;
}
},
validator: (value) {
if (value == null) {
return 'Please select a page type';
}
return null;
},
).padding(all: 20),
// Conditional form fields based on page type
if (pageType.value == 0) ...[
// HTML Page fields
TextFormField(
controller: pathController,
decoration: const InputDecoration(
labelText: 'Page Path',
hintText: '/about, /contact, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page path';
}
if (!RegExp(
r'^[a-zA-Z0-9\-/_]+$',
).hasMatch(value)) {
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
}
if (!value.startsWith('/')) {
return 'Page path must start with /';
}
if (value.contains('//')) {
return 'Page path cannot have consecutive slashes';
}
return null;
},
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: titleController,
decoration: const InputDecoration(
labelText: 'Page Title',
hintText: 'About Us, Contact, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page title';
}
return null;
},
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: htmlController,
decoration: const InputDecoration(
labelText: 'Page Content (HTML)',
hintText:
'<h1>Hello World</h1><p>This is my page content...</p>',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
alignLabelWithHint: true,
),
maxLines: 10,
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter HTML content for the page';
}
return null;
},
).padding(horizontal: 20),
] else ...[
// Redirect Page fields
TextFormField(
controller: pathController,
decoration: const InputDecoration(
labelText: 'Page Path',
hintText: '/old-page, /redirect, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
prefixText: '/',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page path';
}
if (!RegExp(
r'^[a-zA-Z0-9\-/_]+$',
).hasMatch(value)) {
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
}
if (!value.startsWith('/')) {
return 'Page path must start with /';
}
if (value.contains('//')) {
return 'Page path cannot have consecutive slashes';
}
return null;
},
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: targetController,
decoration: const InputDecoration(
labelText: 'Redirect Target',
hintText: '/new-page, https://example.com, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a redirect target';
}
if (!value.startsWith('/') &&
!value.startsWith('http://') &&
!value.startsWith('https://')) {
return 'Target must be a relative path (/) or absolute URL (http/https)';
}
return null;
},
onTapOutside:
(_) =>
FocusManager.instance.primaryFocus
?.unfocus(),
).padding(horizontal: 20),
],
],
).padding(vertical: 20),
),
Row(
children: [
if (page != null) ...[
TextButton.icon(
onPressed: deletePage,
icon: const Icon(Symbols.delete_forever),
label: const Text('Delete Page'),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
).alignment(Alignment.centerRight),
const Spacer(),
] else
const Spacer(),
TextButton.icon(
onPressed: savePage,
icon: const Icon(Symbols.save),
label: const Text('Save Page'),
),
),
DropdownMenuItem(
value: 1,
child: Row(
children: [
Icon(Symbols.link, size: 20),
Gap(8),
Text('Redirect Page'),
],
),
),
],
).padding(horizontal: 20, vertical: 12),
onChanged: (value) {
if (value != null) {
pageType.value = value;
}
},
validator: (value) {
if (value == null) {
return 'Please select a page type';
}
return null;
},
).padding(all: 20),
// Conditional form fields based on page type
if (pageType.value == 0) ...[
// HTML Page fields
TextFormField(
controller: pathController,
decoration: const InputDecoration(
labelText: 'Page Path',
hintText: '/about, /contact, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page path';
}
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
}
if (!value.startsWith('/')) {
return 'Page path must start with /';
}
if (value.contains('//')) {
return 'Page path cannot have consecutive slashes';
}
return null;
},
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: titleController,
decoration: const InputDecoration(
labelText: 'Page Title',
hintText: 'About Us, Contact, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page title';
}
return null;
},
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: htmlController,
decoration: const InputDecoration(
labelText: 'Page Content (HTML)',
hintText:
'<h1>Hello World</h1><p>This is my page content...</p>',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
alignLabelWithHint: true,
),
maxLines: 10,
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter HTML content for the page';
}
return null;
},
).padding(horizontal: 20),
] else ...[
// Redirect Page fields
TextFormField(
controller: pathController,
decoration: const InputDecoration(
labelText: 'Page Path',
hintText: '/old-page, /redirect, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a page path';
}
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
}
if (!value.startsWith('/')) {
return 'Page path must start with /';
}
if (value.contains('//')) {
return 'Page path cannot have consecutive slashes';
}
return null;
},
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 20),
const SizedBox(height: 16),
TextFormField(
controller: targetController,
decoration: const InputDecoration(
labelText: 'Redirect Target',
hintText: '/new-page, https://example.com, etc.',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter a redirect target';
}
if (!value.startsWith('/') &&
!value.startsWith('http://') &&
!value.startsWith('https://')) {
return 'Target must be a relative path (/) or absolute URL (http/https)';
}
return null;
},
onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
).padding(horizontal: 20),
Row(
children: [
if (page != null) ...[
TextButton.icon(
onPressed: deletePage,
icon: const Icon(Symbols.delete_forever),
label: const Text('Delete Page'),
style: TextButton.styleFrom(
foregroundColor: Colors.red,
),
).alignment(Alignment.centerRight),
const Spacer(),
] else
const Spacer(),
TextButton.icon(
onPressed: savePage,
icon: const Icon(Symbols.save),
label: const Text('Save Page'),
),
],
).padding(horizontal: 20, vertical: 16),
],
],
),
),
],
),
),
);
}