feat: 实现导航页基础功能
- 添加导航条目和分类数据模型 - 实现导航卡片网格展示 - 支持点击卡片打开链接 - 添加主题样式(渐变色、卡片阴影) - 区分系统默认和自定义条目 - 添加分类统计信息"
This commit is contained in:
@@ -6,7 +6,9 @@ class LinkItem {
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.url,
|
||||
this.categoryId = '',
|
||||
this.description = '',
|
||||
this.editable = false,
|
||||
});
|
||||
|
||||
/// 从 JSON 构造(将来接后端接口时使用)。
|
||||
@@ -15,7 +17,9 @@ class LinkItem {
|
||||
id: json['id'] as String,
|
||||
title: json['title'] as String,
|
||||
url: json['url'] as String,
|
||||
categoryId: json['categoryId'] as String? ?? '',
|
||||
description: json['description'] as String? ?? '',
|
||||
editable: json['editable'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,9 +32,18 @@ class LinkItem {
|
||||
/// 跳转地址。
|
||||
final String url;
|
||||
|
||||
/// 所属分组 id。编辑时要靠它反查出分组名,才能把表单预填对。
|
||||
final String categoryId;
|
||||
|
||||
/// 一句话描述。
|
||||
final String description;
|
||||
|
||||
/// 这条能不能改。
|
||||
///
|
||||
/// 后端按 `source != "system"` 推导出 `editable`:系统内置的条目只读,
|
||||
/// 卡片上不会出现编辑/删除;用户提交和接口推送进来的可以改、可以删。
|
||||
final bool editable;
|
||||
|
||||
/// 卡片图标显示的首字符。
|
||||
String get initial => firstChar(title).toUpperCase();
|
||||
|
||||
@@ -42,7 +55,9 @@ class LinkItem {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'url': url,
|
||||
'categoryId': categoryId,
|
||||
'description': description,
|
||||
'editable': editable,
|
||||
};
|
||||
|
||||
@override
|
||||
@@ -52,11 +67,14 @@ class LinkItem {
|
||||
other.id == id &&
|
||||
other.title == title &&
|
||||
other.url == url &&
|
||||
other.description == description;
|
||||
other.categoryId == categoryId &&
|
||||
other.description == description &&
|
||||
other.editable == editable;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(id, title, url, description);
|
||||
int get hashCode =>
|
||||
Object.hash(id, title, url, categoryId, description, editable);
|
||||
|
||||
@override
|
||||
String toString() => 'LinkItem(id: $id, title: $title)';
|
||||
|
||||
@@ -10,6 +10,7 @@ class NavNode {
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.children = const <NavNode>[],
|
||||
this.editable = false,
|
||||
});
|
||||
|
||||
/// 唯一标识;叶子节点同时也是内容区的锚点 id。
|
||||
@@ -21,6 +22,12 @@ class NavNode {
|
||||
/// 子节点;为空表示这是一个直接跳转的条目。
|
||||
final List<NavNode> children;
|
||||
|
||||
/// 这个分组是不是用户创建的(后端返回的 `editable`)。
|
||||
///
|
||||
/// 提交表单据此给出「已有的用户分组」建议:内置分组不可选,
|
||||
/// 因为提交的内容永远进不了内置分组。
|
||||
final bool editable;
|
||||
|
||||
/// 是否是可展开的分组。
|
||||
bool get isGroup => children.isNotEmpty;
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/// 提交一个网站时填的内容。
|
||||
///
|
||||
/// 和后端 `POST /api/sites` 的入参一一对应。**没有 `categoryId`**:
|
||||
/// 提交方只能给分组名,由后端在用户分组里查找或新建。
|
||||
class SubmitSiteDraft {
|
||||
const SubmitSiteDraft({
|
||||
required this.title,
|
||||
required this.url,
|
||||
required this.group,
|
||||
this.description = '',
|
||||
this.subGroup = '',
|
||||
this.submittedBy = '',
|
||||
});
|
||||
|
||||
/// 网站名称。
|
||||
final String title;
|
||||
|
||||
/// 网站地址。
|
||||
final String url;
|
||||
|
||||
/// 一级分组名。
|
||||
final String group;
|
||||
|
||||
/// 一句话描述。
|
||||
final String description;
|
||||
|
||||
/// 子分组名,可为空。
|
||||
final String subGroup;
|
||||
|
||||
/// 提交者的称呼或邮箱。
|
||||
final String submittedBy;
|
||||
|
||||
/// 序列化成后端要的 JSON(camelCase,和后端 `CreateSiteRequest` 对齐)。
|
||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||
'title': title,
|
||||
'url': url,
|
||||
'description': description,
|
||||
'group': group,
|
||||
'subGroup': subGroup,
|
||||
'submittedBy': submittedBy,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user