diff --git a/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json b/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json
index 160a8ad..5b8d303 100644
--- a/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json
+++ b/src/Api/TakeoutSaaS.AdminApi/Properties/launchSettings.json
@@ -3,10 +3,21 @@
"TakeoutSaaS.AdminApi": {
"commandName": "Project",
"launchBrowser": true,
+ "launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:2680"
+ },
+ "Docker": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+ "publishAllPorts": true,
+ "useSSL": false,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj b/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj
index 57391fc..554814b 100644
Binary files a/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj and b/src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj differ
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuAuthItemDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuAuthItemDto.cs
new file mode 100644
index 0000000..5699738
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuAuthItemDto.cs
@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace TakeoutSaaS.Application.Identity.Contracts;
+
+///
+/// 菜单操作权限描述。
+///
+public sealed record MenuAuthItemDto
+{
+ ///
+ /// 操作名称。
+ ///
+ [JsonPropertyName("title")]
+ public required string Title { get; init; }
+
+ ///
+ /// 权限标识。
+ ///
+ [JsonPropertyName("authMark")]
+ public required string AuthMark { get; init; }
+}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuMetaDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuMetaDto.cs
new file mode 100644
index 0000000..1a25a73
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuMetaDto.cs
@@ -0,0 +1,58 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace TakeoutSaaS.Application.Identity.Contracts;
+
+///
+/// 菜单前端元数据。
+///
+public sealed record MenuMetaDto
+{
+ ///
+ /// 菜单标题(i18n key)。
+ ///
+ [JsonPropertyName("title")]
+ public required string Title { get; init; }
+
+ ///
+ /// 图标标识。
+ ///
+ [JsonPropertyName("icon")]
+ public string? Icon { get; init; }
+
+ ///
+ /// 是否缓存页面。
+ ///
+ [JsonPropertyName("keepAlive")]
+ public bool KeepAlive { get; init; }
+
+ ///
+ /// 是否为 iframe 页面。
+ ///
+ [JsonPropertyName("isIframe")]
+ public bool IsIframe { get; init; }
+
+ ///
+ /// 外链或 iframe 地址。
+ ///
+ [JsonPropertyName("link")]
+ public string? Link { get; init; }
+
+ ///
+ /// 允许访问的角色编码集合。
+ ///
+ [JsonPropertyName("roles")]
+ public string[] Roles { get; init; } = System.Array.Empty();
+
+ ///
+ /// 按钮/操作级别权限。
+ ///
+ [JsonPropertyName("authList")]
+ public IReadOnlyList AuthList { get; init; } = System.Array.Empty();
+
+ ///
+ /// 访问该菜单所需的权限编码集合(前端用于路由级鉴权)。
+ ///
+ [JsonPropertyName("permissions")]
+ public string[] Permissions { get; init; } = System.Array.Empty();
+}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuNodeDto.cs b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuNodeDto.cs
index 43f4f4d..b86f891 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuNodeDto.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Contracts/MenuNodeDto.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Text.Json.Serialization;
namespace TakeoutSaaS.Application.Identity.Contracts;
@@ -8,32 +9,37 @@ namespace TakeoutSaaS.Application.Identity.Contracts;
public sealed record MenuNodeDto
{
///
- /// 菜单编码(唯一标识)。
+ /// 菜单名称。
///
- public string Code { get; init; } = string.Empty;
+ [JsonPropertyName("name")]
+ public required string Name { get; init; }
///
- /// 展示名称(中文)。
+ /// 路由路径(顶级以 / 开头,子级为相对路径)。
///
- public string Name { get; init; } = string.Empty;
+ [JsonPropertyName("path")]
+ public required string Path { get; init; }
///
- /// 前端路由路径。
+ /// 前端组件路径(基于 src/views,不含 .vue)。
///
- public string Path { get; init; } = string.Empty;
+ [JsonPropertyName("component")]
+ public required string Component { get; init; }
///
- /// 可选图标标识。
+ /// 前端渲染元数据。
///
- public string? Icon { get; init; }
-
- ///
- /// 访问该菜单所需的任一权限,留空表示公共可见。
- ///
- public string[] RequiredPermissions { get; init; } = System.Array.Empty();
+ [JsonPropertyName("meta")]
+ public required MenuMetaDto Meta { get; init; }
///
/// 子菜单集合。
///
+ [JsonPropertyName("children")]
public IReadOnlyList Children { get; init; } = System.Array.Empty();
+
+ ///
+ /// 访问该菜单所需的任一权限(仅用于后端过滤,不序列化)。
+ ///
+ internal string[] RequiredPermissions { get; init; } = System.Array.Empty();
}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminMenuProvider.cs b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminMenuProvider.cs
index 2738f4e..b7201e9 100644
--- a/src/Application/TakeoutSaaS.Application/Identity/Services/AdminMenuProvider.cs
+++ b/src/Application/TakeoutSaaS.Application/Identity/Services/AdminMenuProvider.cs
@@ -29,7 +29,10 @@ public static class AdminMenuProvider
var result = new List();
foreach (var node in nodes)
{
+ // 1.1 递归过滤子节点
var filteredChildren = Filter(node.Children, permissionSet);
+
+ // 1.2 判断当前节点是否可见
var visible = node.RequiredPermissions.Length == 0 || node.RequiredPermissions.Any(permissionSet.Contains);
if (visible || filteredChildren.Count > 0)
{
@@ -43,223 +46,515 @@ public static class AdminMenuProvider
private static IReadOnlyList GetMenuDefinitions()
{
- // 1. 顶部菜单定义
+ // 1. 顶部菜单定义(按需求自定义排序)
return
[
new MenuNodeDto
{
- Code = "dashboard",
- Name = "仪表盘",
+ Name = "Dashboard",
Path = "/dashboard",
- Icon = "dashboard"
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "仪表盘",
+ Icon = "ri:pie-chart-line",
+ Permissions = System.Array.Empty()
+ },
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "Console",
+ Path = "console",
+ Component = "/dashboard/console",
+ Meta = new MenuMetaDto
+ {
+ Title = "工作台",
+ Icon = "ri:dashboard-line",
+ KeepAlive = false,
+ Permissions = System.Array.Empty()
+ },
+ RequiredPermissions = System.Array.Empty()
+ }
+ ]
},
new MenuNodeDto
{
- Code = "merchant",
- Name = "商户",
+ Name = "Tenant",
+ Path = "/tenant",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "租户管理",
+ Icon = "ri:building-3-line",
+ Permissions = ["tenant:read"]
+ },
+ RequiredPermissions = ["tenant:read"],
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "TenantList",
+ Path = "list",
+ Component = "/tenant/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "租户列表",
+ Icon = "ri:community-line",
+ KeepAlive = true,
+ Permissions = ["tenant:read"],
+ AuthList =
+ [
+ new MenuAuthItemDto { Title = "新增", AuthMark = "add" },
+ new MenuAuthItemDto { Title = "编辑", AuthMark = "edit" },
+ new MenuAuthItemDto { Title = "删除", AuthMark = "delete" },
+ new MenuAuthItemDto { Title = "审核", AuthMark = "review" }
+ ]
+ },
+ RequiredPermissions = ["tenant:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "TenantReview",
+ Path = "review",
+ Component = "/tenant/review",
+ Meta = new MenuMetaDto
+ {
+ Title = "入驻审核",
+ Icon = "ri:checkbox-line",
+ KeepAlive = true,
+ Permissions = ["tenant:review"]
+ },
+ RequiredPermissions = ["tenant:review"]
+ },
+ new MenuNodeDto
+ {
+ Name = "TenantPackage",
+ Path = "package",
+ Component = "/tenant/package",
+ Meta = new MenuMetaDto
+ {
+ Title = "套餐管理",
+ Icon = "ri:vip-crown-2-line",
+ KeepAlive = true,
+ Permissions = ["tenant-package:read"]
+ },
+ RequiredPermissions = ["tenant-package:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "TenantSubscription",
+ Path = "subscription",
+ Component = "/tenant/subscription",
+ Meta = new MenuMetaDto
+ {
+ Title = "订阅管理",
+ Icon = "ri:hand-coin-line",
+ KeepAlive = true,
+ Permissions = ["tenant:subscription"]
+ },
+ RequiredPermissions = ["tenant:subscription"]
+ },
+ new MenuNodeDto
+ {
+ Name = "TenantBill",
+ Path = "bill",
+ Component = "/tenant/bill",
+ Meta = new MenuMetaDto
+ {
+ Title = "账单管理",
+ Icon = "ri:bill-line",
+ KeepAlive = true,
+ Permissions = ["tenant-bill:read"]
+ },
+ RequiredPermissions = ["tenant-bill:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "TenantAnnouncement",
+ Path = "announcement",
+ Component = "/tenant/announcement",
+ Meta = new MenuMetaDto
+ {
+ Title = "公告管理",
+ Icon = "ri:notification-3-line",
+ KeepAlive = true,
+ Permissions = ["tenant-announcement:read"]
+ },
+ RequiredPermissions = ["tenant-announcement:read"]
+ }
+ ]
+ },
+ new MenuNodeDto
+ {
+ Name = "Merchant",
Path = "/merchant",
- Icon = "store",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "商户中心",
+ Icon = "ri:store-2-line",
+ Permissions = ["merchant:read"]
+ },
RequiredPermissions = ["merchant:read"],
Children =
[
new MenuNodeDto
{
- Code = "merchant-list",
- Name = "商户管理",
- Path = "/merchant/list",
+ Name = "MerchantList",
+ Path = "list",
+ Component = "/merchant/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "商户列表",
+ Icon = "ri:community-line",
+ KeepAlive = true,
+ Permissions = ["merchant:read"]
+ },
RequiredPermissions = ["merchant:read"]
},
new MenuNodeDto
{
- Code = "store",
- Name = "门店管理",
- Path = "/merchant/stores",
+ Name = "Store",
+ Path = "store",
+ Component = "/merchant/store",
+ Meta = new MenuMetaDto
+ {
+ Title = "门店管理",
+ Icon = "ri:store-line",
+ KeepAlive = true,
+ Permissions = ["store:read"]
+ },
RequiredPermissions = ["store:read"]
},
new MenuNodeDto
{
- Code = "staff",
- Name = "员工管理",
- Path = "/merchant/staff",
+ Name = "Staff",
+ Path = "staff",
+ Component = "/merchant/staff",
+ Meta = new MenuMetaDto
+ {
+ Title = "员工管理",
+ Icon = "ri:team-line",
+ KeepAlive = true,
+ Permissions = ["store-staff:read"]
+ },
RequiredPermissions = ["store-staff:read"]
}
]
},
new MenuNodeDto
{
- Code = "product",
- Name = "商品",
- Path = "/product",
- Icon = "goods",
- RequiredPermissions = ["product:read"],
- Children =
- [
- new MenuNodeDto
- {
- Code = "product-list",
- Name = "商品管理",
- Path = "/product/list",
- RequiredPermissions = ["product:read"]
- },
- new MenuNodeDto
- {
- Code = "category",
- Name = "品类管理",
- Path = "/product/category",
- RequiredPermissions = ["merchant_category:read"]
- }
- ]
- },
- new MenuNodeDto
- {
- Code = "order",
- Name = "订单",
- Path = "/order",
- Icon = "order",
- RequiredPermissions = ["order:read"],
- Children =
- [
- new MenuNodeDto
- {
- Code = "order-list",
- Name = "订单管理",
- Path = "/order/list",
- RequiredPermissions = ["order:read"]
- },
- new MenuNodeDto
- {
- Code = "delivery",
- Name = "配送管理",
- Path = "/order/delivery",
- RequiredPermissions = ["delivery:read"]
- }
- ]
- },
- new MenuNodeDto
- {
- Code = "inventory",
- Name = "库存",
- Path = "/inventory",
- Icon = "inventory",
- RequiredPermissions = ["inventory:read"],
- Children =
- [
- new MenuNodeDto
- {
- Code = "inventory-list",
- Name = "库存查询",
- Path = "/inventory/list",
- RequiredPermissions = ["inventory:read"]
- },
- new MenuNodeDto
- {
- Code = "inventory-batch",
- Name = "批次管理",
- Path = "/inventory/batch",
- RequiredPermissions = ["inventory:batch:read"]
- }
- ]
- },
- new MenuNodeDto
- {
- Code = "payment",
- Name = "支付",
- Path = "/payment",
- Icon = "payment",
- RequiredPermissions = ["payment:read"],
- Children =
- [
- new MenuNodeDto
- {
- Code = "payment-list",
- Name = "支付记录",
- Path = "/payment/list",
- RequiredPermissions = ["payment:read"]
- }
- ]
- },
- new MenuNodeDto
- {
- Code = "dictionary",
- Name = "字典",
+ Name = "Dictionary",
Path = "/dictionary",
- Icon = "dictionary",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "字典管理",
+ Icon = "ri:book-open-line",
+ Permissions = ["dictionary:group:read", "dictionary:item:read"]
+ },
RequiredPermissions = ["dictionary:group:read", "dictionary:item:read"],
Children =
[
new MenuNodeDto
{
- Code = "dictionary-group",
- Name = "字典分组",
- Path = "/dictionary/group",
+ Name = "DictionaryGroup",
+ Path = "group",
+ Component = "/dictionary/group",
+ Meta = new MenuMetaDto
+ {
+ Title = "字典分组",
+ Icon = "ri:booklet-line",
+ KeepAlive = true,
+ Permissions = ["dictionary:group:read"]
+ },
RequiredPermissions = ["dictionary:group:read"]
},
new MenuNodeDto
{
- Code = "dictionary-item",
- Name = "字典项",
- Path = "/dictionary/item",
+ Name = "DictionaryItem",
+ Path = "item",
+ Component = "/dictionary/item",
+ Meta = new MenuMetaDto
+ {
+ Title = "字典条目",
+ Icon = "ri:links-line",
+ KeepAlive = true,
+ Permissions = ["dictionary:item:read"]
+ },
RequiredPermissions = ["dictionary:item:read"]
}
]
},
new MenuNodeDto
{
- Code = "identity",
- Name = "权限",
- Path = "/identity",
- Icon = "shield",
- RequiredPermissions = ["identity:role:read", "identity:permission:read"],
+ Name = "Product",
+ Path = "/product",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "商品中心",
+ Icon = "ri:shopping-basket-line",
+ Permissions = ["product:read"]
+ },
+ RequiredPermissions = ["product:read"],
Children =
[
new MenuNodeDto
{
- Code = "identity-user",
- Name = "用户权限",
- Path = "/identity/users",
+ Name = "ProductList",
+ Path = "list",
+ Component = "/product/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "商品列表",
+ Icon = "ri:shopping-cart-2-line",
+ KeepAlive = true,
+ Permissions = ["product:read"]
+ },
+ RequiredPermissions = ["product:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "Category",
+ Path = "category",
+ Component = "/product/category",
+ Meta = new MenuMetaDto
+ {
+ Title = "类目管理",
+ Icon = "ri:folder-settings-line",
+ KeepAlive = true,
+ Permissions = ["merchant_category:read"]
+ },
+ RequiredPermissions = ["merchant_category:read"]
+ }
+ ]
+ },
+ new MenuNodeDto
+ {
+ Name = "Order",
+ Path = "/order",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "订单中心",
+ Icon = "ri:bill-line",
+ Permissions = ["order:read"]
+ },
+ RequiredPermissions = ["order:read"],
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "OrderList",
+ Path = "list",
+ Component = "/order/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "订单列表",
+ Icon = "ri:file-list-3-line",
+ KeepAlive = true,
+ Permissions = ["order:read"]
+ },
+ RequiredPermissions = ["order:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "Delivery",
+ Path = "delivery",
+ Component = "/order/delivery",
+ Meta = new MenuMetaDto
+ {
+ Title = "配送管理",
+ Icon = "ri:route-line",
+ KeepAlive = true,
+ Permissions = ["delivery:read"]
+ },
+ RequiredPermissions = ["delivery:read"]
+ }
+ ]
+ },
+ new MenuNodeDto
+ {
+ Name = "Payment",
+ Path = "/payment",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "支付结算",
+ Icon = "ri:wallet-3-line",
+ Permissions = ["payment:read"]
+ },
+ RequiredPermissions = ["payment:read"],
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "PaymentList",
+ Path = "list",
+ Component = "/payment/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "收支明细",
+ Icon = "ri:money-cny-circle-line",
+ KeepAlive = true,
+ Permissions = ["payment:read"]
+ },
+ RequiredPermissions = ["payment:read"]
+ }
+ ]
+ },
+ new MenuNodeDto
+ {
+ Name = "Inventory",
+ Path = "/inventory",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "库存中心",
+ Icon = "ri:archive-stack-line",
+ Permissions = ["inventory:read"]
+ },
+ RequiredPermissions = ["inventory:read"],
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "InventoryList",
+ Path = "list",
+ Component = "/inventory/list",
+ Meta = new MenuMetaDto
+ {
+ Title = "库存明细",
+ Icon = "ri:stock-line",
+ KeepAlive = true,
+ Permissions = ["inventory:read"]
+ },
+ RequiredPermissions = ["inventory:read"]
+ },
+ new MenuNodeDto
+ {
+ Name = "InventoryBatch",
+ Path = "batch",
+ Component = "/inventory/batch",
+ Meta = new MenuMetaDto
+ {
+ Title = "批次管理",
+ Icon = "ri:layout-grid-line",
+ KeepAlive = true,
+ Permissions = ["inventory:batch:read"]
+ },
+ RequiredPermissions = ["inventory:batch:read"]
+ }
+ ]
+ },
+ new MenuNodeDto
+ {
+ Name = "System",
+ Path = "/system",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "系统设置",
+ Icon = "ri:settings-3-line",
+ Permissions = ["identity:permission:read", "identity:role:read"]
+ },
+ RequiredPermissions = ["identity:permission:read", "identity:role:read"],
+ Children =
+ [
+ new MenuNodeDto
+ {
+ Name = "User",
+ Path = "user",
+ Component = "/system/user",
+ Meta = new MenuMetaDto
+ {
+ Title = "用户管理",
+ Icon = "ri:user-3-line",
+ KeepAlive = true,
+ Permissions = ["identity:permission:read"]
+ },
RequiredPermissions = ["identity:permission:read"]
},
new MenuNodeDto
{
- Code = "identity-role",
- Name = "角色管理",
- Path = "/identity/roles",
+ Name = "Role",
+ Path = "role",
+ Component = "/system/role",
+ Meta = new MenuMetaDto
+ {
+ Title = "角色管理",
+ Icon = "ri:shield-user-line",
+ KeepAlive = true,
+ Permissions = ["identity:role:read"]
+ },
RequiredPermissions = ["identity:role:read"]
},
new MenuNodeDto
{
- Code = "identity-permission",
- Name = "权限管理",
- Path = "/identity/permissions",
+ Name = "Menus",
+ Path = "menu",
+ Component = "/system/menu",
+ Meta = new MenuMetaDto
+ {
+ Title = "菜单管理",
+ Icon = "ri:menu-fold-line",
+ KeepAlive = true,
+ Permissions = ["identity:permission:read"],
+ AuthList =
+ [
+ new MenuAuthItemDto { Title = "新增", AuthMark = "add" },
+ new MenuAuthItemDto { Title = "编辑", AuthMark = "edit" },
+ new MenuAuthItemDto { Title = "删除", AuthMark = "delete" }
+ ]
+ },
RequiredPermissions = ["identity:permission:read"]
}
]
},
new MenuNodeDto
{
- Code = "system",
- Name = "系统",
- Path = "/system",
- Icon = "settings",
- RequiredPermissions = ["system-parameter:read", "tenant-announcement:read"],
+ Name = "Docs",
+ Path = "/docs",
+ Component = "/index/index",
+ Meta = new MenuMetaDto
+ {
+ Title = "帮助文档",
+ Icon = "ri:book-2-line",
+ Permissions = System.Array.Empty()
+ },
Children =
[
new MenuNodeDto
{
- Code = "system-parameter",
- Name = "系统参数",
- Path = "/system/parameters",
- RequiredPermissions = ["system-parameter:read"]
+ Name = "GuideIframe",
+ Path = "guide",
+ Component = "",
+ Meta = new MenuMetaDto
+ {
+ Title = "操作指南",
+ IsIframe = true,
+ Link = "https://example.com/guide",
+ Icon = "ri:article-line",
+ Permissions = System.Array.Empty()
+ }
},
new MenuNodeDto
{
- Code = "announcement",
- Name = "公告管理",
- Path = "/system/announcements",
- RequiredPermissions = ["tenant-announcement:read"]
+ Name = "ExternalLink",
+ Path = "official",
+ Component = "",
+ Meta = new MenuMetaDto
+ {
+ Title = "官网链接",
+ Link = "https://example.com",
+ Icon = "ri:external-link-line",
+ Permissions = System.Array.Empty()
+ }
}
]
}
];
}
-}
+}
\ No newline at end of file