From 646a49a26efc6623a87ca0abe1b0b896fccbc9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=81=E5=86=9C=E9=98=BF=E6=A5=A0?= Date: Tue, 8 Sep 2026 23:40:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E9=A1=B5=E8=84=9A=E5=93=81?= =?UTF-8?q?=E7=89=8C=E4=B8=8E=E5=A4=87=E6=A1=88=E4=BF=A1=E6=81=AF=E3=80=81?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=96=87=E6=A1=88=E7=B2=BE=E7=AE=80=E5=8F=8A?= =?UTF-8?q?=E5=AF=BC=E8=88=AA/=E4=BE=A7=E6=A0=8F=E4=BA=A4=E4=BA=92?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 页脚「纯文本驱动」改为 Powered by Pure Note(链至仓库地址),新增 公安备案展示:国徽图标 + 备案号链至 beian.mps.gov.cn,备案号可在 后台设置(站点描述下一项)配置,留空不显示 - settings 白名单新增 beian_no 键(trim、≤200 字符、可清空),/api/site 一并下发;TestSettingsWhitelist 补生效/清空/超长用例 - 去除首页 hero 文案区(PURE NOTE · 极简笔记/记录,而不打扰。等), 同步清理失效样式并微调顶部留白 - 博客 header 首页/标签菜单补 House/Tags 图标,与管理按钮同风格 - 管理侧栏「返回博客/退出登录」浓缩为图标按钮置于用户行右侧,CSS 悬浮 tooltip(top-center、带箭头,原生 title 延迟高不采用),返回 博客改新窗口打开 --- internal/httpapi/admin.go | 14 +++- internal/httpapi/api_test.go | 23 +++++- internal/store/settings.go | 12 +++- web/public/national_emblem.png | Bin 0 -> 19256 bytes web/src/components/Layout.tsx | 58 +++++++++++---- web/src/index.css | 123 ++++++++++++++++++++++++-------- web/src/lib/api.ts | 1 + web/src/pages/AdminSettings.tsx | 15 +++- web/src/pages/Home.tsx | 6 -- 9 files changed, 196 insertions(+), 56 deletions(-) create mode 100644 web/public/national_emblem.png diff --git a/internal/httpapi/admin.go b/internal/httpapi/admin.go index 6f184e8..2d4fca2 100644 --- a/internal/httpapi/admin.go +++ b/internal/httpapi/admin.go @@ -396,6 +396,7 @@ type settingsDTO struct { SiteTitle *string `json:"site_title"` SiteDesc *string `json:"site_desc"` PageSize *int `json:"page_size"` + BeianNo *string `json:"beian_no"` } // handleAdminSettingsGet GET /api/admin/settings:白名单三键;永不序列化 admin_password_hash。 @@ -414,7 +415,7 @@ func (s *Server) handleAdminSettingsPut(w http.ResponseWriter, r *http.Request) dec.DisallowUnknownFields() var dto settingsDTO if err := dec.Decode(&dto); err != nil { - writeError(w, http.StatusBadRequest, "bad_request", "包含未知字段或类型不合法(白名单:site_title/site_desc/page_size)") + writeError(w, http.StatusBadRequest, "bad_request", "包含未知字段或类型不合法(白名单:site_title/site_desc/page_size/beian_no)") return } if dto.SiteTitle != nil { @@ -448,6 +449,17 @@ func (s *Server) handleAdminSettingsPut(w http.ResponseWriter, r *http.Request) return } } + if dto.BeianNo != nil { + v := strings.TrimSpace(*dto.BeianNo) + if len([]rune(v)) > 200 { + writeError(w, http.StatusBadRequest, "bad_request", "备案号 ≤200 字符") + return + } + if err := s.st.SetSetting(store.KeyBeianNo, v); err != nil { + writeError(w, http.StatusInternalServerError, "internal", "内部错误") + return + } + } // admin_password_hash 不可经此接口写入(§9.3):不在白名单结构体中,天然拒绝。 s.log.Info("admin_action", "op", "settings.update") ss, err := s.st.GetSiteSettings() diff --git a/internal/httpapi/api_test.go b/internal/httpapi/api_test.go index 2e14d1e..b584475 100644 --- a/internal/httpapi/api_test.go +++ b/internal/httpapi/api_test.go @@ -284,10 +284,31 @@ func TestSettingsWhitelist(t *testing.T) { } // PUT 合法键 resp, _ = e.do(admin, http.MethodPut, "/api/admin/settings", - []byte(`{"site_title":"新标题","site_desc":"描述","page_size":5}`), e.adminHeaders()) + []byte(`{"site_title":"新标题","site_desc":"描述","page_size":5,"beian_no":"京公网安备12345678901号"}`), e.adminHeaders()) if resp.StatusCode != 200 { t.Errorf("合法 PUT 应 200: %d", resp.StatusCode) } + // beian_no 经 /api/site 可读且生效 + _, body = e.get(e.client(), "/api/site") + if !strings.Contains(string(body), `"beian_no":"京公网安备12345678901号"`) { + t.Errorf("beian_no 设置应生效: %s", body) + } + // beian_no 可清空(空串 = 不展示) + resp, _ = e.do(admin, http.MethodPut, "/api/admin/settings", + []byte(`{"beian_no":""}`), e.adminHeaders()) + if resp.StatusCode != 200 { + t.Errorf("beian_no 清空应 200: %d", resp.StatusCode) + } + _, body = e.get(e.client(), "/api/site") + if !strings.Contains(string(body), `"beian_no":""`) { + t.Errorf("beian_no 应可清空: %s", body) + } + // beian_no 过长拒绝 + resp, _ = e.do(admin, http.MethodPut, "/api/admin/settings", + []byte(`{"beian_no":"`+strings.Repeat("备", 201)+`"}`), e.adminHeaders()) + if resp.StatusCode != http.StatusBadRequest { + t.Errorf("beian_no 超长应 400: %d", resp.StatusCode) + } // page_size 生效为公开列表默认 _, body = e.get(e.client(), "/api/notes") if !strings.Contains(string(body), `"page_size":5`) { diff --git a/internal/store/settings.go b/internal/store/settings.go index 23b1b76..eee6d1a 100644 --- a/internal/store/settings.go +++ b/internal/store/settings.go @@ -13,6 +13,7 @@ const ( KeySiteTitle = "site_title" KeySiteDesc = "site_desc" KeyPageSize = "page_size" + KeyBeianNo = "beian_no" ) // GetSetting 读取单个设置。 @@ -37,6 +38,7 @@ type SiteSettings struct { SiteTitle string `json:"site_title"` SiteDesc string `json:"site_desc"` PageSize int `json:"page_size"` + BeianNo string `json:"beian_no"` } // Defaults,未初始化时兜底。 @@ -44,11 +46,12 @@ const ( DefaultSiteTitle = "Pure Note" DefaultSiteDesc = "" DefaultPageSize = 10 + DefaultBeianNo = "" ) -// GetSiteSettings 读取站点设置(白名单三键,带默认值)。 +// GetSiteSettings 读取站点设置(白名单键,带默认值)。 func (s *Store) GetSiteSettings() (*SiteSettings, error) { - ss := &SiteSettings{SiteTitle: DefaultSiteTitle, SiteDesc: DefaultSiteDesc, PageSize: DefaultPageSize} + ss := &SiteSettings{SiteTitle: DefaultSiteTitle, SiteDesc: DefaultSiteDesc, PageSize: DefaultPageSize, BeianNo: DefaultBeianNo} if v, ok, err := s.GetSetting(KeySiteTitle); err != nil { return nil, err } else if ok && v != "" { @@ -67,5 +70,10 @@ func (s *Store) GetSiteSettings() (*SiteSettings, error) { ss.PageSize = n } } + if v, ok, err := s.GetSetting(KeyBeianNo); err != nil { + return nil, err + } else if ok { + ss.BeianNo = strings.TrimSpace(v) + } return ss, nil } diff --git a/web/public/national_emblem.png b/web/public/national_emblem.png new file mode 100644 index 0000000000000000000000000000000000000000..9f763946dd6606addeab24e40e2369124d53a048 GIT binary patch literal 19256 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 literal 0 HcmV?d00001 diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index 993a408..14e7e79 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -1,5 +1,5 @@ import { Link, NavLink, Outlet, useLocation, useNavigate } from 'react-router' -import { Moon, Sun, Monitor, ExternalLink, House, PenLine, Trash2, Settings, LogOut } from 'lucide-react' +import { Moon, Sun, Monitor, ExternalLink, House, PenLine, Tags, Trash2, Settings, LogOut } from 'lucide-react' import { useSiteTitle } from '../features/site' import { useTheme } from '../lib/theme' import { useAuth } from '../lib/auth' @@ -32,8 +32,12 @@ export function BlogLayout() {
{title}
@@ -108,22 +127,33 @@ function AdminSidebar() {
- - 返回博客 - -
管 管理员 站点所有者 + + + + + +
diff --git a/web/src/index.css b/web/src/index.css index 2234231..dadfd1e 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -517,6 +517,24 @@ button:focus-visible { .pn-site-footer-inner a:hover { color: var(--foreground); } +.pn-site-footer-line { + margin: 0; +} +.pn-site-beian { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 8px; +} +.pn-site-beian img { + width: 14px; + height: 14px; + flex: none; + opacity: 0.85; +} +.pn-site-beian:hover img { + opacity: 1; +} @media (max-width: 640px) { .pn-site-header-inner { @@ -535,35 +553,9 @@ button:focus-visible { 首页(博客首页.html) ============================================================ */ .pn-home-content { - padding-top: 64px; + padding-top: 56px; padding-bottom: 24px; } -.pn-home-hero { - margin-bottom: 48px; -} -.pn-home-eyebrow { - margin: 0 0 18px; - font-family: var(--font-mono); - font-size: 12px; - letter-spacing: 0.14em; - color: var(--muted-foreground); -} -.pn-home-title { - margin: 0 0 18px; - font-family: var(--font-serif); - font-weight: 400; - font-size: clamp(34px, 6vw, 46px); - line-height: 1.18; - letter-spacing: var(--tracking-tight); - color: var(--foreground); -} -.pn-home-desc { - margin: 0; - max-width: 560px; - font-size: 15px; - line-height: 1.8; - color: var(--muted-foreground); -} .pn-home-filter { margin-bottom: 8px; } @@ -728,10 +720,7 @@ button:focus-visible { } @media (max-width: 640px) { .pn-home-content { - padding-top: 44px; - } - .pn-home-hero { - margin-bottom: 40px; + padding-top: 40px; } .pn-home-post { padding: 26px 0; @@ -1597,6 +1586,78 @@ button:focus-visible { gap: 10px; padding: 8px 10px 2px; } +.pn-admin-user-actions { + display: inline-flex; + align-items: center; + gap: 4px; + margin-left: auto; +} +.pn-admin-iconbtn { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: 0; + border-radius: var(--radius-sm); + background: transparent; + color: var(--muted-foreground); + text-decoration: none; + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; +} +.pn-admin-iconbtn:hover { + background: var(--sidebar-accent); + color: var(--sidebar-accent-foreground); +} +.pn-admin-iconbtn svg { + width: 15px; + height: 15px; +} +/* CSS 悬浮 tooltip(原生 title 延迟高且部分环境不显示) */ +.pn-admin-iconbtn::after { + content: attr(data-tip); + position: absolute; + left: 50%; + bottom: calc(100% + 10px); + z-index: 30; + padding: 4px 8px; + border-radius: var(--radius-sm); + background: var(--primary); + color: var(--primary-foreground); + font-size: 11px; + font-weight: 500; + line-height: 1.4; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transform: translate(-50%, 2px); + transition: opacity 0.15s ease, transform 0.15s ease; +} +.pn-admin-iconbtn::before { + content: ""; + position: absolute; + left: 50%; + bottom: 100%; + z-index: 30; + margin-left: -5px; + border: 5px solid transparent; + border-top-color: var(--primary); + opacity: 0; + pointer-events: none; + transition: opacity 0.15s ease; +} +.pn-admin-iconbtn:hover::after, +.pn-admin-iconbtn:focus-visible::after { + opacity: 1; + transform: translate(-50%, 0); +} +.pn-admin-iconbtn:hover::before, +.pn-admin-iconbtn:focus-visible::before { + opacity: 1; +} .pn-admin-avatar { width: 28px; height: 28px; diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts index 8f5a5db..5a31fa6 100644 --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -87,6 +87,7 @@ export interface SiteSettings { site_title: string site_desc: string page_size: number + beian_no: string } export interface NoteItem { diff --git a/web/src/pages/AdminSettings.tsx b/web/src/pages/AdminSettings.tsx index eac5799..e4902df 100644 --- a/web/src/pages/AdminSettings.tsx +++ b/web/src/pages/AdminSettings.tsx @@ -18,6 +18,7 @@ export default function AdminSettings() { const [title, setTitle] = useState('') const [desc, setDesc] = useState('') + const [beian, setBeian] = useState('') const [pageSize, setPageSize] = useState(10) const [toast, setToast] = useState<{ msg: string; kind: 'success' | 'error' } | null>(null) @@ -25,6 +26,7 @@ export default function AdminSettings() { if (data) { setTitle(data.site_title) setDesc(data.site_desc) + setBeian(data.beian_no) setPageSize(data.page_size) } }, [data]) @@ -33,7 +35,7 @@ export default function AdminSettings() { mutationFn: () => api('/api/admin/settings', { method: 'PUT', - body: JSON.stringify({ site_title: title, site_desc: desc, page_size: pageSize }), + body: JSON.stringify({ site_title: title, site_desc: desc, beian_no: beian, page_size: pageSize }), }), onSuccess: () => { setToast({ msg: '设置已保存', kind: 'success' }) @@ -74,6 +76,17 @@ export default function AdminSettings() { onChange={(e) => setDesc(e.target.value)} /> +
+ + setBeian(e.target.value)} + /> +

显示在博客页脚并链接至公安部备案官网;留空则不显示。

+
-
-

PURE NOTE · 极简笔记

-

记录,而不打扰。

-

Pure Note 是一个纯文本驱动的极简笔记工具,这里是公开的笔记。

-
- {topTags.length > 0 && (