新增头部导航链接配置与多语言支持
- 迁移 v10 新增 nav_links 与 nav_link_translations 两张表,文案按语言存储 - 公开 GET /api/nav-links 返回启用链接(按 sort/id 排序);管理员可增删改查与启停 - URL 仅允许站内路径、http(s) 与 mailto,拒绝 javascript: 等危险协议;译文替换与校验 - 前端删除写死的“主页”按钮,头部按当前语言渲染动态链接,新窗口加 rel=noopener noreferrer - 后台管理页新增“头部导航链接”卡片(三语文案、URL、打开方式、排序、状态),改动即时生效 - 补充 nav 接口与迁移测试、三语文案,重新生成 Swagger 文档
This commit is contained in:
19 files changed
+2198
-13
No files matched your search
+375
@@ -538,6 +538,279 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nav-links": {
|
||||
"get": {
|
||||
"description": "Public header navigation links (status enabled), ordered by sort ASC then id ASC, with translations.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"public"
|
||||
],
|
||||
"summary": "List nav links",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/model.NavLink"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Admin only. Create a header navigation link. url accepts site-relative paths (/...), http(s) URLs, and mailto links; translations must include at least one non-empty label.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Create a nav link",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Nav link payload",
|
||||
"name": "link",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/nav.Request"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.NavLink"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "admin permission required or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nav-links/list": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Admin only. List all header navigation links including disabled ones, with translations.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "List all nav links",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/model.NavLink"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "admin permission required or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/nav-links/{id}": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Admin only. Update a header navigation link; translations are replaced by the provided list.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Update a nav link",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "Nav link ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Nav link payload",
|
||||
"name": "link",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/nav.Request"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.NavLink"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request or id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "admin permission required or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Admin only. Delete a header navigation link and its translations.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"summary": "Delete a nav link",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "Nav link ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted"
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "admin permission required or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/notes": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1869,6 +2142,52 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.NavLink": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"open_in_new_window": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sort": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
},
|
||||
"translations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/model.NavLinkTranslation"
|
||||
}
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.NavLinkTranslation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string"
|
||||
},
|
||||
"nav_link_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.Note": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1975,6 +2294,62 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav.Request": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"translations",
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"open_in_new_window": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"sort": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"example": 1
|
||||
},
|
||||
"translations": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "#/definitions/nav.TranslationRequest"
|
||||
}
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"maxLength": 512,
|
||||
"example": "/profile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav.TranslationRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"label",
|
||||
"locale"
|
||||
],
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"example": "首页"
|
||||
},
|
||||
"locale": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"example": "zh-CN"
|
||||
}
|
||||
}
|
||||
},
|
||||
"note.ListResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user