commit 01b5e2e1baf9630484631bb89a109e855f2bd2ac Author: biss Date: Fri Jun 19 13:28:19 2026 +0800 Merge pull request '更换到Astro' (#9) from switch-to-astro into master Reviewed-on: https://git.biss.click/biss/blog/pulls/9 -- [CST 2026-06-19 Friday 13:28:19] diff --git a/_astro/BaseLayout.D7kFxpxP.css b/_astro/BaseLayout.D7kFxpxP.css new file mode 100644 index 0000000..105ef7d --- /dev/null +++ b/_astro/BaseLayout.D7kFxpxP.css @@ -0,0 +1 @@ +:root{color-scheme:light;--bg: #f7f5ef;--surface: #fffdf8;--text: #20242a;--muted: #686f78;--line: #ded8cc;--accent: #0f766e;--accent-strong: #14532d;--shadow: 0 14px 45px rgba(58, 51, 38, .1);--page-gutter: clamp(20px, 4vw, 64px);--wide-width: 1440px;--content-width: 1280px;--article-width: 1080px;--narrow-width: 1000px}:root[data-theme=dark]{color-scheme:dark;--bg: #0f171a;--surface: #162225;--text: #e9f0ee;--muted: #a7b4b1;--line: rgba(174, 205, 197, .18);--accent: #72ded2;--accent-strong: #9af0d6;--shadow: 0 18px 48px rgba(0, 0, 0, .28)}*{box-sizing:border-box}body{margin:0;color:var(--text);font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Microsoft YaHei,sans-serif;line-height:1.75;background:linear-gradient(#f7f5efdb,#f7f5eff2),url(/images/background.png) center top / cover fixed;transition:color .22s ease,background-color .22s ease}:root[data-theme=dark] body{background:linear-gradient(#080d10db,#0a1114f0),url(/images/background.png) center top / cover fixed}a{color:inherit;text-decoration:none}.site-header{position:sticky;top:12px;z-index:10;pointer-events:none}.nav{position:relative;width:min(var(--wide-width),calc(100% - (var(--page-gutter) * 2)));min-height:58px;margin:0 auto;padding:0 18px;display:grid;grid-template-columns:minmax(120px,1fr) auto minmax(120px,1fr);align-items:center;gap:24px;overflow:hidden;border:1px solid rgba(255,255,255,.56);border-radius:999px;background:linear-gradient(135deg,#ffffff85,#fff3),#ffffff47;box-shadow:inset 0 1px #ffffffd1,inset 0 -18px 38px #ffffff29,0 18px 54px #2c373f2e;backdrop-filter:blur(22px) saturate(180%);-webkit-backdrop-filter:blur(22px) saturate(180%);pointer-events:auto}.nav:before{content:"";position:absolute;inset:0;background:radial-gradient(circle at 18% 0%,rgba(255,255,255,.72),transparent 30%),linear-gradient(90deg,rgba(255,255,255,.42),transparent 38%,rgba(255,255,255,.22));opacity:.74;pointer-events:none}.nav:after{content:"";position:absolute;top:7px;right:16px;left:16px;height:1px;border-radius:999px;background:#ffffffc7;pointer-events:none}.brand{position:relative;z-index:1;font-size:1.05rem;font-weight:800}.nav-menu{display:contents}.nav-menu-toggle{position:relative;z-index:1;display:none;width:40px;height:40px;place-items:center;border:1px solid transparent;border-radius:999px;color:var(--muted);font:inherit;background:transparent;cursor:pointer;transition:color .2s ease,background-color .2s ease,border-color .2s ease,box-shadow .2s ease,transform .2s ease}.nav-menu-close{display:none}.nav-links{position:relative;z-index:1;display:flex;align-items:center;gap:6px;color:var(--muted);font-size:.94rem;white-space:nowrap}.nav-center{justify-content:center}.nav-actions{justify-content:flex-end}.nav-links a,.nav-random,.nav-search,.theme-toggle{display:inline-flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:999px;padding:7px 12px;transition:color .2s ease,background-color .2s ease,border-color .2s ease,box-shadow .2s ease,transform .2s ease}.nav-search,.nav-random,.theme-toggle{color:inherit;font:inherit;line-height:inherit;background:transparent;cursor:pointer}.nav-icon{display:inline-grid;width:16px;min-width:16px;height:16px;place-items:center;font-size:.95em;line-height:1}.theme-toggle{justify-content:center;width:38px;min-height:38px;padding:0}:root:not([data-theme=dark]) .theme-toggle-sun,:root[data-theme=dark] .theme-toggle-moon{display:none}.nav-links a:hover,.nav-random:hover,.nav-search:hover,.theme-toggle:hover,.site-footer a:hover,.section-heading a:hover{color:var(--accent)}.custom-context-menu{position:fixed;top:0;left:0;z-index:1000;width:min(218px,calc(100vw - 20px));max-height:calc(100vh - 20px);overflow-y:auto;border:1px solid rgba(255,255,255,.58);border-radius:14px;padding:8px;color:#4f565b;background:linear-gradient(145deg,#ffffffc7,#ebf8e7ad),#f5fcf1d1;box-shadow:inset 0 1px #ffffffdb,0 18px 42px #2c373f2e;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%);opacity:0;pointer-events:none;transform:translate3d(0,6px,0) scale(.98);transform-origin:top left;transition:opacity .14s ease,transform .14s ease}.custom-context-menu.is-open{opacity:1;pointer-events:auto;transform:translateZ(0) scale(1)}.custom-context-menu [hidden]{display:none!important}.context-menu-nav{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;padding-bottom:7px}.context-menu-group{display:grid;gap:2px;border-top:1px dashed rgba(104,111,120,.18);padding-top:7px}.context-menu-group+.context-menu-group{margin-top:7px}.custom-context-menu :where(a,button){display:flex;align-items:center;width:100%;min-width:0;border:0;color:inherit;font:inherit;line-height:1.25;text-align:left;background:transparent;cursor:pointer}.context-menu-nav :where(a,button){justify-content:center;height:42px;border:1px solid rgba(178,194,170,.42);border-radius:14px;color:#6b7174;background:#ffffff70;box-shadow:inset 0 1px #ffffffc2}.context-menu-group :where(a,button){gap:12px;min-height:40px;border-radius:10px;padding:9px 10px}.custom-context-menu :where(a,button):hover,.custom-context-menu :where(a,button):focus-visible{color:var(--accent);background:#ffffff94;outline:0}.custom-context-menu i{display:inline-grid;width:18px;min-width:18px;height:18px;place-items:center;color:#737a7d;font-size:.98rem;line-height:1}.custom-context-menu :where(a,button):hover i,.custom-context-menu :where(a,button):focus-visible i{color:currentColor}.custom-context-menu span{min-width:0;overflow:hidden;font-size:.95rem;font-weight:650;text-overflow:ellipsis;white-space:nowrap}.nav-links a:hover,.nav-random:hover,.nav-search:hover,.theme-toggle:hover,.nav-menu-toggle:hover{border-color:#ffffff9e;background:#ffffff61;box-shadow:inset 0 1px #ffffffb8,0 8px 18px #0f766e1a;transform:translateY(-1px)}:root[data-theme=dark] .nav{border-color:#aecdc533;background:linear-gradient(135deg,#1d2f33c7,#0e171b8a),#0c13169e;box-shadow:inset 0 1px #ffffff1f,inset 0 -18px 38px #0000002e,0 18px 54px #00000057}:root[data-theme=dark] .nav:before{background:radial-gradient(circle at 18% 0%,rgba(114,222,210,.18),transparent 32%),linear-gradient(90deg,rgba(255,255,255,.08),transparent 42%,rgba(114,222,210,.1))}:root[data-theme=dark] .nav:after{background:#ffffff1f}:root[data-theme=dark] .nav-links a:hover,:root[data-theme=dark] .nav-random:hover,:root[data-theme=dark] .nav-search:hover,:root[data-theme=dark] .theme-toggle:hover,:root[data-theme=dark] .nav-menu-toggle:hover{border-color:#72ded242;background:#72ded21f;box-shadow:inset 0 1px #ffffff1f,0 8px 18px #0000002e}:root[data-theme=dark] .custom-context-menu{border-color:#aecdc533;color:#d8e5e2;background:linear-gradient(145deg,#1f3034e0,#0d171ac7),#0f191cd1;box-shadow:inset 0 1px #ffffff1a,0 18px 42px #00000057}:root[data-theme=dark] .context-menu-nav :where(a,button){border-color:#aecdc529;color:#b7c7c4;background:#ffffff0f;box-shadow:inset 0 1px #ffffff14}:root[data-theme=dark] .context-menu-group{border-top-color:#aecdc529}:root[data-theme=dark] .custom-context-menu :where(a,button):hover,:root[data-theme=dark] .custom-context-menu :where(a,button):focus-visible{color:var(--accent);background:#72ded21c}:root[data-theme=dark] .custom-context-menu i{color:#a8b8b5}.hero{width:min(var(--wide-width),calc(100% - (var(--page-gutter) * 2)));min-height:360px;margin:0 auto;display:grid;align-items:center}.compact-hero{min-height:260px}.hero h1{margin:0;font-size:clamp(3rem,8vw,6.5rem);line-height:.95}.hero p{max-width:620px;margin:18px 0 0;color:var(--muted);font-size:1.12rem}.home-hero{grid-template-columns:minmax(320px,.74fr) minmax(360px,1fr);gap:clamp(28px,6vw,84px)}.hero-copy{min-width:0}.hero-talk{align-self:center;width:100%;max-width:820px;min-height:190px;overflow:hidden;border:1px solid rgba(255,255,255,.52);border-radius:8px;padding:18px 18px 16px;background:linear-gradient(135deg,#ffffff4d,#f1faf429),#ffffff2e;box-shadow:inset 0 1px #ffffff94,0 18px 46px #2c373f1f;backdrop-filter:blur(10px) saturate(135%);-webkit-backdrop-filter:blur(10px) saturate(135%)}.hero-talk-head{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:12px;color:var(--accent-strong);font-size:.86rem;font-weight:800;letter-spacing:.04em;text-transform:uppercase}.hero-talk-head a{display:grid;width:34px;height:34px;place-items:center;border:1px solid rgba(15,118,110,.16);border-radius:999px;color:var(--accent);background:#ecfdf58a;transition:color .2s ease,background-color .2s ease,transform .2s ease}.hero-talk-head a:hover{color:#fff;background:#3eb8be;transform:translateY(-1px)}.hero-talk .talk-list{display:grid;height:120px;margin:0;overflow:hidden;padding:0;list-style:none}.hero-talk .talk-list:empty:before{content:"说说加载中...";display:grid;min-height:58px;align-items:center;color:var(--muted);font-size:.95rem}.hero-talk .item{display:flex;min-height:40px;align-items:center;gap:10px;border-top:1px dashed rgba(104,111,120,.22);padding:9px 2px;color:#40505a;font-size:.96rem;line-height:1.55}.hero-talk .item:first-child{border-top:0}.hero-talk .item:before{content:"";width:7px;height:7px;flex:0 0 auto;border-radius:999px;background:#3eb8be;box-shadow:0 0 0 4px #3eb8be24}.hero-talk .item i{color:var(--accent)}.eyebrow{margin:0 0 16px!important;color:var(--accent-strong)!important;font-size:.95rem!important;font-weight:700}.content-wrap{width:min(var(--content-width),calc(100% - (var(--page-gutter) * 2)));margin:0 auto 72px}.content-wrap.narrow{width:min(var(--narrow-width),calc(100% - (var(--page-gutter) * 2)))}.page-header-card{position:relative;overflow:hidden;margin:40px 0 26px;border:1px solid rgba(255,255,255,.58);border-radius:8px;padding:clamp(26px,5vw,44px);background:radial-gradient(circle at 18% 0%,rgba(255,255,255,.78),transparent 36%),linear-gradient(135deg,#ffffff94,#eefaf35c),#fffdf8b8;box-shadow:inset 0 1px #ffffffd1,0 18px 48px #3a33261f;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}.page-header-card:after{content:"";position:absolute;top:10px;right:18px;left:18px;height:1px;border-radius:999px;background:#ffffffc2}.page-header-eyebrow{margin:0 0 8px;color:var(--accent);font-size:.86rem;font-weight:700}.page-header-card h1,.content-wrap .page-header-card h1{margin:0;color:#20242a;font-size:clamp(2rem,5vw,3.6rem);line-height:1.08}.page-header-subtitle{max-width:620px;margin:12px 0 0;color:var(--muted);font-size:1rem}.page-with-card{margin-bottom:0}.page-article{margin-top:0}.post-layout{width:min(var(--wide-width),calc(100% - (var(--page-gutter) * 2)));margin:40px auto 84px;display:grid;grid-template-columns:minmax(0,1fr) 320px;align-items:start;gap:28px}.post-layout .article{width:100%;margin:0}.home-layout{display:grid;grid-template-columns:minmax(0,1fr) 320px;align-items:start;gap:24px}.home-main{min-width:0}.section-heading{display:flex;align-items:end;justify-content:space-between;gap:20px;margin-bottom:18px}.section-heading h2,.content-wrap h1{margin:0;font-size:1.8rem;line-height:1.2}.section-heading a{color:var(--muted)}.post-list{display:grid;gap:16px}.post-card{border:1px solid var(--line);border-radius:8px;padding:22px;background:#fffdf8e0;box-shadow:var(--shadow)}.post-card h2{margin:6px 0 8px;font-size:1.35rem;line-height:1.35}.post-card p{margin:0;color:var(--muted)}.home-sidebar{align-self:stretch;display:grid;align-content:start;gap:20px}.sticky-sidebar{position:sticky;top:84px;display:grid;align-content:start;gap:20px}.sidebar-card{overflow:hidden;border:1px solid rgba(222,216,204,.72);border-radius:8px;padding:18px;background:linear-gradient(135deg,#ffffffb8,#f2fbedc7),#fffdf8c2;box-shadow:var(--shadow);backdrop-filter:blur(12px)}.sidebar-card h2{display:flex;align-items:center;gap:8px;margin:0 0 14px;color:#4d5258;font-size:1.08rem;font-weight:700}.sidebar-card h2 em{margin-left:auto;min-width:28px;border-radius:999px;padding:2px 8px;color:#6d9cc2;font-size:.78rem;font-style:normal;text-align:center;background:#66c8f52e}.profile-card{text-align:center}.profile-avatar{width:112px;height:112px;margin:0 auto 14px;border-radius:999px;object-fit:cover;box-shadow:0 12px 30px #0f766e2e}.profile-name{margin-bottom:16px;font-family:Georgia,Times New Roman,serif;color:#585b5e;font-size:1.9rem;letter-spacing:0}.profile-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:18px}.profile-stats a{display:grid;gap:3px;color:var(--muted)}.profile-stats strong{color:#2f4358;font-size:1.22rem;font-weight:600}.profile-stats a:first-child strong,.notice-card strong,.schedule-left span,.calendar-summary span{color:#66c8f5}.follow-button{display:flex;align-items:center;justify-content:center;gap:10px;min-height:44px;border-radius:8px;color:#fff;font-weight:700;background:#3eb8be;transition:transform .2s ease,background-color .2s ease}.follow-button:hover{color:#fff;transform:translateY(-1px);background:#279fa5}.github-mark{font-size:.9rem}.profile-links{display:flex;justify-content:center;gap:18px;margin-top:16px;color:#3f5368;font-weight:700}.notice-card p{margin:.65em 0;color:#596068;line-height:1.9}.site-info-card h2 i{width:18px;color:#3eb8be;font-size:.92rem}.site-info-list{display:grid;margin:0}.site-info-list div{display:flex;align-items:center;justify-content:space-between;gap:18px;border-top:1px dashed rgba(104,111,120,.2);padding:9px 0}.site-info-list div:first-child{border-top:0;padding-top:0}.site-info-list div:last-child{padding-bottom:0}.site-info-list dt,.site-info-list dd{margin:0}.site-info-list dt{color:#68717a;font-size:.92rem;font-weight:700}.site-info-list dd{min-width:0;color:#1aa8f6;font-size:.98rem;font-weight:800;line-height:1.3;text-align:right;overflow-wrap:anywhere}#welcome-info,#welcome-ip-location-info{margin:.65em 0;overflow-wrap:anywhere;color:#596068;line-height:1.9;--kouseki-welcome-color: #66c8f5;--kouseki-ip-color: #66c8f5;--kouseki-gl-size: 16px}#welcome-info b span.ip-mask,#welcome-ip-location-info b span.ip-mask{display:inline-block;filter:blur(6px);transition:filter .3s ease;cursor:pointer;user-select:none}#welcome-info b span.ip-mask:hover,#welcome-info b span.ip-mask:active,#welcome-ip-location-info b span.ip-mask:hover,#welcome-ip-location-info b span.ip-mask:active{filter:blur(0)}.calendar-widget,.schedule-widget{display:grid;grid-template-columns:.9fr 1.1fr;gap:16px;align-items:center}.calendar-summary,.schedule-left{display:grid;gap:4px;text-align:center}.calendar-summary strong,.schedule-left strong{color:#4b5560;font-size:.9rem}.calendar-summary span,.schedule-left span{font-size:2.25rem;line-height:1.1;font-weight:800}.calendar-summary small,.schedule-left small{color:var(--muted);font-size:.78rem}.calendar-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px 7px;color:#8aa6be;text-align:center}.calendar-grid span{display:grid;place-items:center;min-height:22px;padding:0;border:1px solid transparent;border-radius:999px;line-height:1}.calendar-grid b{font-size:.78rem;font-weight:700}.calendar-grid .muted{opacity:.45}.calendar-grid .today{color:#fff;background:#66c8f5;border-color:#66c8f5}.schedule-bars{display:grid;gap:8px}.schedule-row{display:grid;grid-template-columns:34px minmax(52px,1fr) 74px;align-items:center;gap:8px;color:var(--muted);font-size:.78rem}.schedule-row progress{width:100%;height:10px;overflow:hidden;border:0;border-radius:999px;background:#66c8f529}.schedule-row progress::-webkit-progress-bar{border-radius:999px;background:#66c8f529}.schedule-row progress::-webkit-progress-value{border-radius:999px;background:linear-gradient(90deg,#5ec7f4,#42d4c4)}.schedule-row progress::-moz-progress-bar{border-radius:999px;background:linear-gradient(90deg,#5ec7f4,#42d4c4)}.schedule-row em{min-width:0;font-style:normal;text-align:right;white-space:nowrap}.sidebar-posts{display:grid}.sidebar-post{display:grid;grid-template-columns:70px minmax(0,1fr);gap:12px;align-items:center;padding:12px 0;border-top:1px dashed rgba(104,111,120,.28)}.sidebar-post:first-child{border-top:0;padding-top:0}.sidebar-post img,.post-thumb-fallback{width:70px;aspect-ratio:1;border-radius:6px;object-fit:cover;background:linear-gradient(135deg,#3eb8be52,#66c8f557)}.sidebar-post strong{display:-webkit-box;overflow:hidden;color:#4d5258;font-size:.94rem;font-weight:500;line-height:1.45;-webkit-box-orient:vertical;-webkit-line-clamp:2}.sidebar-post time{display:block;margin-top:3px}.article-sidebar{align-self:stretch;display:grid;align-content:start;gap:20px}.article-sticky-sidebar{position:sticky;top:90px;display:grid;max-height:calc(100vh - 110px);align-content:start;gap:20px;overflow-y:auto;scrollbar-width:thin}.article-author-card{text-align:center;background:radial-gradient(circle at 50% 0%,rgba(102,200,245,.32),transparent 42%),linear-gradient(135deg,#ffffff94,#d6eeff4d),#fffdf89e}.article-author-card .profile-avatar{width:88px;height:88px}.article-author-card .profile-name{margin-bottom:8px;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:1.45rem;font-weight:800}.article-author-meta{display:flex;flex-wrap:wrap;justify-content:center;gap:8px;color:var(--muted);font-size:.86rem}.article-author-meta a{color:var(--accent)}.toc-list,.article-tag-list,.same-category-list{display:grid;gap:6px}.toc-list{position:relative;padding-top:10px}.toc-list:before{content:"";position:absolute;top:0;right:0;left:0;height:5px;border-radius:999px;background-image:radial-gradient(circle,rgba(102,200,245,.72) 2px,transparent 2px);background-size:14px 5px}.toc-list a,.article-tag-list a,.same-category-list a{border-radius:8px;padding:9px 11px;color:#50637a;line-height:1.45;transition:color .2s ease,background-color .2s ease,transform .2s ease}.toc-list a:hover,.article-tag-list a:hover,.same-category-list a:hover{color:#1f618d;background:#66c8f529;transform:translate(2px)}.toc-tree{gap:8px}.toc-group{position:relative}.toc-link{position:relative;display:flex;align-items:flex-start;gap:9px;text-decoration:none}.toc-link-main{color:#3e5664!important;font-weight:800}.toc-marker{flex:0 0 auto;width:8px;height:8px;margin-top:.52em;border-radius:999px;background:#66c8f585;box-shadow:0 0 0 4px #66c8f51f}.toc-children{position:relative;display:grid;gap:4px;max-height:0;margin-left:14px;overflow:hidden;border-left:1px solid rgba(102,200,245,.24);padding-left:10px;opacity:0;transition:max-height .28s ease,opacity .22s ease,margin-top .22s ease}.toc-group.is-open .toc-children{max-height:520px;margin-top:4px;opacity:1}.toc-link-child{color:#657384!important;font-size:.9rem;font-weight:600}.toc-link.is-active{color:#0f766e!important;background:#66c8f529;transform:translate(2px)}.toc-group.is-open .toc-link-main .toc-marker,.toc-link.is-active .toc-marker{background:#3eb8be;box-shadow:0 0 0 4px #3eb8be29}.article-tag-list{display:flex;flex-wrap:wrap;gap:8px}.article-tag-list a{display:inline-flex;align-items:center;min-height:34px;border:1px solid rgba(15,118,110,.18);padding:6px 11px;color:var(--accent);font-size:.88rem;font-weight:700;background:#ecfdf5a8}.toc-depth-2{padding-left:22px!important}.toc-depth-3{padding-left:24px!important;font-size:.9rem}.same-category-list a{display:grid;gap:3px;border-top:1px dashed rgba(104,111,120,.22)}.same-category-list a:first-child{border-top:0}.same-category-list strong{display:-webkit-box;overflow:hidden;font-size:.94rem;font-weight:600;-webkit-box-orient:vertical;-webkit-line-clamp:2}.sidebar-empty{margin:0;color:var(--muted);font-size:.92rem}time{color:var(--muted);font-size:.9rem}.meta-list{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}.meta-list a{border:1px solid rgba(15,118,110,.28);border-radius:999px;padding:2px 10px;color:var(--accent);font-size:.86rem;background:#ecfdf5b3}.article{--article-padding: clamp(24px, 5vw, 48px);width:min(var(--article-width),calc(100% - (var(--page-gutter) * 2)));margin:64px auto 84px;border:1px solid rgba(255,255,255,.58);border-radius:8px;padding:var(--article-padding);background:radial-gradient(circle at 18% 0%,rgba(255,255,255,.72),transparent 34%),linear-gradient(145deg,#ffffff9e,#e2f8f159),#fffdf8ad;box-shadow:inset 0 1px #ffffffd1,0 24px 64px #2c373f29;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}.article-header{position:relative;margin-bottom:28px;padding-bottom:22px;border-bottom:1px solid var(--line)}.article-header-content{position:relative;z-index:1}.article-header h1{margin:8px 0 0;font-size:clamp(2rem,6vw,3.3rem);line-height:1.14}.article-header-cover{display:grid;align-items:end;min-height:clamp(320px,40vw,460px);margin:calc(var(--article-padding) * -1) calc(var(--article-padding) * -1) 34px;overflow:hidden;border:0;border-radius:8px 8px 0 0;padding:clamp(86px,13vw,150px) var(--article-padding) clamp(28px,6vw,52px);color:#fff;background:#5d7f78}.article-header-cover:before,.article-header-cover:after{content:"";position:absolute;inset:0;pointer-events:none}.article-header-cover:before{z-index:1;background:linear-gradient(180deg,#1737306b,#1737302e 38%,#0c1f1cad),linear-gradient(90deg,#0d373185,#135a4f24 54%,#0d37315c)}.article-header-cover:after{z-index:2;box-shadow:inset 0 1px #fff3,inset 0 -90px 110px #0a1c1a57}.article-header-cover-img{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:cover;filter:saturate(.9) contrast(.94)}.article-header-cover .article-header-content{z-index:3;max-width:900px;text-shadow:0 3px 14px rgba(0,0,0,.48)}.article-header-cover time{display:none}.article-header-cover h1{max-width:920px;margin:0;color:#fff;font-family:Georgia,Times New Roman,Microsoft YaHei,serif;font-size:clamp(2.4rem,6vw,4.2rem);font-weight:700;line-height:1.12}.article-hero-meta{display:flex;flex-wrap:wrap;gap:9px 12px;align-items:center;margin-top:18px;color:#ffffffeb;font-size:.98rem;font-weight:700}.article-hero-meta span,.article-hero-meta a{display:inline-flex;align-items:center}.article-hero-meta span:not(:first-child):before,.article-hero-meta a:before{content:"|";margin-right:12px;color:#ffffff9e;font-weight:400}.article-hero-meta a:hover{color:#d8fff5}.article-summary-card{position:relative;margin:0 0 30px;overflow:hidden;border:1px solid rgba(133,162,153,.34);border-radius:8px;padding:34px 20px 16px;background:radial-gradient(circle at 12% 0%,rgba(255,255,255,.8),transparent 32%),linear-gradient(135deg,#fafcf7e0,#e4f0eac7),#f5faf6c7;box-shadow:inset 0 1px #ffffffe6,0 14px 34px #3a33261a;backdrop-filter:blur(18px) saturate(145%);-webkit-backdrop-filter:blur(18px) saturate(145%)}.article-summary-card:before{content:"";position:absolute;top:0;right:0;left:0;height:25px;border-bottom:1px solid rgba(133,162,153,.18);background:#ffffff47;pointer-events:none}.article-summary-dots{position:absolute;top:14px;left:16px;z-index:1;display:flex;gap:8px}.article-summary-dots span{width:10px;height:10px;border-radius:999px;box-shadow:inset 0 -1px #0000001f}.article-summary-dots span:nth-child(1){background:#ff6b63}.article-summary-dots span:nth-child(2){background:#ffc247}.article-summary-dots span:nth-child(3){background:#4ecb71}.article-summary-content{position:relative;z-index:1;margin:0;color:#59615f;font-family:LXGW WenKai,霞鹜文楷,Microsoft YaHei,ui-sans-serif,system-ui,sans-serif;font-size:1rem;line-height:1.9}.article-summary-caret{display:inline-block;width:2px;height:1.1em;margin-left:3px;vertical-align:-.16em;background:var(--accent);animation:summary-caret-blink .9s steps(1) infinite}.article-summary-footer{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:24px;color:#555f5e8f;font-family:JetBrains Mono,Consolas,Microsoft YaHei,monospace;font-size:.95rem;font-weight:900;letter-spacing:0}.article-summary-brand{display:inline-flex;align-items:center;gap:8px}.article-summary-brand span{color:#0f766e8f;font-size:1.05rem}.article-summary-model{white-space:nowrap}@keyframes summary-caret-blink{50%{opacity:0}}.prose :where(h2,h3,h4){scroll-margin-top:110px;margin-top:2em;line-height:1.35}.prose :where(p,ul,ol,blockquote,pre){margin:1.1em 0}.prose a{color:var(--accent);text-decoration:underline;text-underline-offset:3px}.prose .hexo-link-card{display:grid;grid-template-columns:46px minmax(0,1fr);gap:14px;align-items:center;margin:1.2em 0;border:1px solid rgba(15,118,110,.2);border-radius:8px;padding:14px 16px;color:#27313a;text-decoration:none;background:linear-gradient(135deg,#ffffffc7,#ecfdf59e),#fffdf8e0;box-shadow:0 12px 28px #2c373f1a;transition:border-color .2s ease,box-shadow .2s ease,transform .2s ease}.prose .hexo-link-card:hover{color:#27313a;border-color:#0f766e61;box-shadow:0 16px 34px #0f766e24;transform:translateY(-1px)}.hexo-link-card__icon{display:grid;place-items:center;width:46px;height:46px;border-radius:8px;color:#fff;font-size:1.05rem;font-weight:800;background:#3eb8be}.hexo-link-card__body{display:grid;min-width:0;gap:2px}.hexo-link-card__body strong,.hexo-link-card__body span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hexo-link-card__body strong{font-size:1rem;line-height:1.35}.hexo-link-card__body span{color:var(--muted);font-size:.88rem}.prose img{display:block;max-width:100%;height:auto;margin:1.4em auto;border-radius:8px}.prose .fancybox{display:block;width:fit-content;max-width:100%;margin:1.4em auto;border-radius:8px;text-decoration:none}.prose .fancybox img{margin:0 auto;box-shadow:0 12px 32px #2c373f1f;transition:box-shadow .2s ease,transform .2s ease}.prose .fancybox:hover img{box-shadow:0 16px 38px #0f766e29;transform:translateY(-1px)}.prose pre{margin:0;overflow-x:auto;padding:18px;overscroll-behavior-inline:contain;scrollbar-color:rgba(102,200,245,.58) rgba(255,255,255,.08);scrollbar-width:thin}.prose code{font-family:JetBrains Mono,Consolas,monospace}.prose :not(pre)>code{border:1px solid rgba(15,118,110,.14);border-radius:6px;padding:.12em .38em;color:#0f766e;font-size:.92em;line-height:1.4;background:#ecfdf5c7;overflow-wrap:break-word}.butterfly-note{display:grid;grid-template-columns:auto minmax(0,1fr);gap:12px;margin:1.2em 0;border:1px solid rgba(15,118,110,.18);border-left-width:4px;border-radius:8px;padding:14px 16px;color:#32413f;background:#ecfdf594}.butterfly-note>i{margin-top:.28em;color:var(--butterfly-note-color, var(--accent))}.butterfly-note.is-no-icon{grid-template-columns:minmax(0,1fr)}.butterfly-note__content>:first-child{margin-top:0}.butterfly-note__content>:last-child{margin-bottom:0}.butterfly-note--default,.butterfly-label--default,.butterfly-btn--default{--butterfly-note-color: #68717a;--butterfly-color: #68717a}.butterfly-note--primary,.butterfly-note--blue,.butterfly-label--blue,.butterfly-btn--blue{--butterfly-note-color: #2f80ed;--butterfly-color: #2f80ed}.butterfly-note--success,.butterfly-note--green,.butterfly-label--green,.butterfly-btn--green{--butterfly-note-color: #2f9e44;--butterfly-color: #2f9e44}.butterfly-note--info{--butterfly-note-color: #0ea5e9;--butterfly-color: #0ea5e9}.butterfly-note--warning,.butterfly-note--orange,.butterfly-label--orange,.butterfly-btn--orange{--butterfly-note-color: #f59f00;--butterfly-color: #f59f00}.butterfly-note--danger,.butterfly-note--red,.butterfly-label--red,.butterfly-btn--red{--butterfly-note-color: #e03131;--butterfly-color: #e03131}.butterfly-note--pink,.butterfly-label--pink,.butterfly-btn--pink{--butterfly-note-color: #d63384;--butterfly-color: #d63384}.butterfly-note--purple,.butterfly-label--purple,.butterfly-btn--purple{--butterfly-note-color: #7950f2;--butterfly-color: #7950f2}.butterfly-note{border-left-color:var(--butterfly-note-color, var(--accent))}.butterfly-note--modern{box-shadow:inset 0 1px #ffffff9e}.butterfly-note--flat{border-color:transparent;border-left-color:var(--butterfly-note-color, var(--accent));background:color-mix(in srgb,var(--butterfly-note-color, #0f766e) 13%,transparent)}.butterfly-note--disabled{display:block;border:0;padding:0;background:transparent}.butterfly-label{display:inline-flex;align-items:center;min-height:1.7em;border-radius:6px;padding:.05em .48em;color:#fff;font-size:.88em;font-weight:700;line-height:1.35;vertical-align:.08em;background:var(--butterfly-color, #68717a)}.butterfly-inline-img{display:inline-block!important;width:auto;max-width:min(100%,18rem);margin:0 .18em!important;vertical-align:middle}.prose .butterfly-btn{display:inline-flex;align-items:center;justify-content:center;gap:.45em;min-height:34px;border:1px solid var(--butterfly-color, var(--accent));border-radius:8px;padding:.4em .78em;color:#fff;font-weight:800;line-height:1.2;text-decoration:none;background:var(--butterfly-color, var(--accent));transition:color .2s ease,background-color .2s ease,box-shadow .2s ease,transform .2s ease}.prose .butterfly-btn:hover{color:#fff;box-shadow:0 10px 22px color-mix(in srgb,var(--butterfly-color, #0f766e) 24%,transparent);transform:translateY(-1px)}.prose .butterfly-btn.is-outline{color:var(--butterfly-color, var(--accent));background:transparent}.prose .butterfly-btn.is-outline:hover{color:#fff;background:var(--butterfly-color, var(--accent))}.prose .butterfly-btn.is-larger{min-height:40px;padding:.56em 1em;font-size:1.05em}.prose .butterfly-btn.is-block{display:flex;width:fit-content;margin:.9em 0}.prose .butterfly-btn.is-center{margin-right:auto;margin-left:auto}.prose .butterfly-btn.is-right{margin-left:auto}.butterfly-hide-inline{display:inline-flex;align-items:center;gap:.45em;vertical-align:baseline}.butterfly-hide-toggle,.butterfly-hide-block>summary{border:0;border-radius:7px;padding:.25em .62em;color:var(--hide-color, #fff);font:inherit;font-weight:800;line-height:1.3;background:var(--hide-bg, #ff7242);cursor:pointer}.butterfly-hide-content{display:none;color:var(--text)}.butterfly-hide-inline.is-open .butterfly-hide-content{display:inline}.butterfly-hide-block{margin:1.2em 0}.butterfly-hide-block>summary{display:inline-flex;list-style:none}.butterfly-hide-block>summary::-webkit-details-marker{display:none}.butterfly-hide-block__content{margin-top:.9em;border-left:3px solid rgba(15,118,110,.2);padding-left:1em}.prose .code-block{position:relative;margin:1.25em 0;overflow:hidden;border:1px solid rgba(15,118,110,.16);border-radius:8px;background:#161f24f5;box-shadow:0 14px 32px #2c373f1f}.prose .code-block pre::-webkit-scrollbar,.table-scroll::-webkit-scrollbar{height:10px}.prose .code-block pre::-webkit-scrollbar-track,.table-scroll::-webkit-scrollbar-track{border-radius:999px;background:#ffffff14}.prose .code-block pre::-webkit-scrollbar-thumb,.table-scroll::-webkit-scrollbar-thumb{border:2px solid rgba(255,255,255,.08);border-radius:999px;background:linear-gradient(90deg,#66c8f5b8,#3eb8bec2),#3eb8beb8}.prose .code-block pre::-webkit-scrollbar-thumb:hover,.table-scroll::-webkit-scrollbar-thumb:hover{background:linear-gradient(90deg,#66c8f5e6,#3eb8bef0),#3eb8bee6}.prose .code-block pre{max-height:none;padding:44px 18px 18px;background:transparent;white-space:normal!important;word-wrap:normal!important;transition:max-height .24s ease}.prose .code-block pre code{display:block;min-width:max-content}.prose .code-block.is-collapsible:not(.is-expanded) pre{max-height:15.5em}.prose .code-block.is-collapsible:not(.is-expanded):after{content:"";position:absolute;right:0;bottom:0;left:0;height:58px;background:linear-gradient(#161f2400,#161f24fa);pointer-events:none}.prose .code-block code{color:#e8edf2;line-height:1.42}.code-block-header{position:absolute;top:0;right:0;left:0;z-index:2;display:flex;min-height:42px;align-items:center;justify-content:space-between;gap:8px;border-bottom:1px solid rgba(255,255,255,.08);padding:8px 10px 8px 16px;background:linear-gradient(180deg,#ffffff12,#ffffff05);pointer-events:none}.code-block-language{overflow:hidden;color:#e8edf2b8;font-family:JetBrains Mono,Consolas,monospace;font-size:.8rem;font-weight:800;letter-spacing:0;text-overflow:ellipsis;text-transform:uppercase;white-space:nowrap}.code-block-toolbar{display:flex;flex:0 0 auto;gap:8px;pointer-events:auto}.prose .code-block.has-line-numbers code>.line{position:relative;display:block;min-height:1.42em;padding-left:calc(var(--line-number-width, 3ch) + 16px);white-space:pre}.prose .code-block.has-line-numbers code>.line:before{content:attr(data-line);position:absolute;top:0;left:0;width:var(--line-number-width, 3ch);color:#e8edf257;font-variant-numeric:tabular-nums;text-align:right;user-select:none}.code-block-copy,.code-block-toggle{appearance:none;-webkit-appearance:none;display:inline-flex;align-items:center;justify-content:center;min-height:30px;border:1px solid rgba(255,255,255,.18);border-radius:8px;padding:5px 9px;color:#ffffffdb;font:700 .78rem/1 Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:#ffffff1a;cursor:pointer;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);transition:color .2s ease,border-color .2s ease,background-color .2s ease,transform .2s ease}.code-block-copy:hover,.code-block-toggle:hover{color:#fff;border-color:#ffffff57;background:#3eb8beb8;transform:translateY(-1px)}.code-block-toggle{display:none}.code-block.is-collapsible .code-block-toggle{display:inline-flex}.table-scroll{max-width:100%;margin:1.35em 0;overflow-x:auto;border:1px solid rgba(15,118,110,.14);border-radius:8px;background:linear-gradient(135deg,#ffffffb8,#ecfdf56b),#fffdf8c7;box-shadow:0 14px 34px #2c373f1a;overscroll-behavior-inline:contain;scrollbar-color:rgba(62,184,190,.58) rgba(15,118,110,.08);scrollbar-width:thin}.prose table{width:100%;min-width:620px;border-collapse:separate;border-spacing:0;color:#33433f;font-size:.95rem;line-height:1.6}.prose th,.prose td{border-right:1px solid rgba(15,118,110,.1);border-bottom:1px solid rgba(15,118,110,.1);padding:12px 14px;vertical-align:top}.prose th:last-child,.prose td:last-child{border-right:0}.prose tr:last-child td{border-bottom:0}.prose thead th{color:#2f4a43;font-weight:800;text-align:left;background:linear-gradient(135deg,#ecfdf5e0,#dcf6f0b8),#ffffffa8}.prose tbody tr:nth-child(2n) td{background:#ecfdf55c}.prose tbody tr:hover td{background:#66c8f521}.fancybox__container{--fancybox-bg: rgba(32, 36, 42, .72);--fancybox-accent-color: #3eb8be;backdrop-filter:blur(18px) saturate(145%);-webkit-backdrop-filter:blur(18px) saturate(145%)}.prose mjx-container{color:#223833}.prose mjx-container[display=true]{max-width:100%;margin:1.35em 0;overflow-x:auto;overflow-y:hidden;padding:10px 0;scrollbar-color:rgba(62,184,190,.58) rgba(15,118,110,.08);scrollbar-width:thin}.prose mjx-container[display=true]::-webkit-scrollbar{height:8px}.prose mjx-container[display=true]::-webkit-scrollbar-track{border-radius:999px;background:#0f766e14}.prose mjx-container[display=true]::-webkit-scrollbar-thumb{border-radius:999px;background:linear-gradient(90deg,#66c8f5b8,#3eb8bec2),#3eb8beb8}.post-copyright-card{position:relative;overflow:hidden;margin-top:42px;border:1px solid rgba(255,255,255,.64);border-radius:8px;padding:24px 28px 20px;color:#33433f;background:radial-gradient(circle at 14% 0%,rgba(255,255,255,.82),transparent 32%),linear-gradient(135deg,#fffdf8c2,#ecfdf594),#fffdf8b8;box-shadow:inset 0 1px #ffffffe0,0 16px 42px #3a33261f;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}.post-copyright-card:before{content:"";position:absolute;inset:0;background:linear-gradient(135deg,rgba(255,255,255,.44),transparent 44%,rgba(255,255,255,.22));pointer-events:none}.post-copyright-card:after{content:"";position:absolute;top:8px;right:16px;left:16px;height:1px;border-radius:999px;background:#ffffffd1;pointer-events:none}.post-copyright-main,.post-copyright-footer{position:relative;z-index:2}.post-copyright-mark{position:absolute;top:14px;right:22px;z-index:1;display:grid;place-items:center;width:clamp(74px,12vw,118px);aspect-ratio:1;border:1px solid rgba(15,118,110,.12);border-radius:999px;color:#0f766e29;font-family:Georgia,Times New Roman,serif;font-size:clamp(1.85rem,5vw,3.4rem);font-weight:900;line-height:1;background:linear-gradient(145deg,#ffffff75,#ecfdf533),#ffffff29;box-shadow:inset 0 1px #ffffffb8,0 12px 30px #0f766e0f;pointer-events:none}.post-copyright-mark span{transform:translateY(-1px)}.post-copyright-card h2{margin:0;color:#27313a;font-size:clamp(1.08rem,2.2vw,1.35rem);line-height:1.35}.post-copyright-link{display:inline-block;margin-top:12px;color:var(--muted);font-size:.94rem;font-weight:600}.post-copyright-link:hover,.post-copyright-meta a:hover{color:var(--accent)}.post-copyright-meta{display:grid;grid-template-columns:1fr 1.25fr 2fr;gap:18px 28px;margin:20px 0 0;border-top:1px solid rgba(15,118,110,.18);border-bottom:1px solid rgba(15,118,110,.12);padding:16px 0}.post-copyright-meta div{min-width:0}.post-copyright-meta dt{margin-bottom:5px;color:#58645f;font-size:.8rem;font-weight:800}.post-copyright-meta dd{margin:0;color:#2f4a43;font-size:.95rem;font-weight:600}.post-copyright-meta time{color:inherit;font-size:inherit}.post-copyright-footer{display:flex;align-items:center;justify-content:space-between;gap:18px;margin-top:14px}.post-copyright-tags,.post-copyright-actions{display:flex;flex-wrap:wrap;align-items:center;gap:10px}.post-copyright-tags a,.post-copyright-action,.post-copyright-icon{min-height:38px;border:1px solid rgba(15,118,110,.2);border-radius:999px;color:var(--accent);background:#ecfdf5b8;box-shadow:inset 0 1px #ffffff8a;transition:color .2s ease,background-color .2s ease,transform .2s ease}.post-copyright-tags a{display:inline-flex;align-items:center;padding:7px 13px;font-size:.88rem}.post-copyright-action{appearance:none;-webkit-appearance:none;display:inline-flex;align-items:center;gap:7px;padding:7px 15px;font:inherit;font-weight:800;cursor:pointer}.post-copyright-action.is-primary{color:#fff;border-color:#0f766e4d;background:#3eb8be}.post-copyright-action svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.4}.post-copyright-icon{display:grid;place-items:center;width:38px;font-size:.94rem;font-weight:900;line-height:1}.post-copyright-tags a:hover,.post-copyright-action:hover,.post-copyright-icon:hover{color:#fff;background:#3eb8be;transform:translateY(-1px)}.twikoo-comments{margin-top:34px;border:1px solid rgba(255,255,255,.64);border-radius:8px;padding:clamp(20px,4vw,30px);background:radial-gradient(circle at 14% 0%,rgba(255,255,255,.8),transparent 32%),linear-gradient(135deg,#fffdf8cc,#ecfdf58c),#fffdf8c2;box-shadow:inset 0 1px #ffffffdb,0 16px 42px #3a33261f;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}.twikoo-comments-head{display:flex;align-items:center;gap:14px;margin-bottom:22px}.twikoo-comments-icon{display:grid;flex:0 0 auto;place-items:center;width:44px;height:44px;border-radius:8px;color:#fff;background:#3eb8be;box-shadow:0 12px 28px #0f766e29}.twikoo-comments-icon svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.2}.twikoo-comments h2{margin:0;color:#27313a;font-size:1.28rem;line-height:1.25}.twikoo-comments p{margin:4px 0 0;color:var(--muted);font-size:.92rem;line-height:1.45}.twikoo-wrap{min-height:160px}.twikoo-comments :where(.tk-comments-container,.tk-submit){color:var(--text)}.archive-list{margin-top:24px;display:grid;gap:10px}.archive-list a{display:grid;grid-template-columns:116px 1fr;gap:16px;border-bottom:1px solid var(--line);padding:10px 0}.pagination{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px;margin-top:30px}.pagination-pages{display:flex;flex-wrap:wrap;justify-content:center;gap:8px}.pagination-link,.pagination-number{display:inline-flex;align-items:center;justify-content:center;min-width:40px;min-height:40px;border:1px solid rgba(222,216,204,.78);border-radius:8px;padding:8px 12px;color:#50637a;font-weight:700;line-height:1;background:#fffdf8d1;box-shadow:0 10px 28px #3a332614;transition:color .2s ease,border-color .2s ease,background-color .2s ease,transform .2s ease}.pagination-link{min-width:76px}.pagination-link:hover,.pagination-number:hover,.pagination-number.is-current{color:#fff;border-color:#0f766e57;background:#3eb8be;transform:translateY(-1px)}.pagination-link.is-disabled{color:#686f7880;background:#fffdf875;box-shadow:none;cursor:not-allowed}.term-grid{margin-top:24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}.term-grid a{display:flex;align-items:center;justify-content:space-between;gap:12px;border:1px solid var(--line);border-radius:8px;padding:14px 16px;background:#fffdf8e6}.stats-chart{margin-top:24px;border:1px solid rgba(222,216,204,.78);border-radius:8px;padding:22px;background:#fffdf8e0;box-shadow:0 14px 36px #3a332614}.stats-chart-head{display:grid;gap:6px;margin-bottom:18px}.stats-chart-head h2,.stats-chart-head p{margin:0}.stats-chart-head h2{color:#27313a;font-size:1.18rem;line-height:1.25}.stats-chart-head p{color:var(--muted);font-size:.92rem}.stats-chart-body{display:grid;grid-template-columns:minmax(180px,240px) 1fr;align-items:center;gap:24px}.pie-chart{width:100%;max-width:240px;margin:0 auto;overflow:visible}.pie-chart-track,.pie-chart-segment{fill:none;stroke-width:18}.pie-chart-track{stroke:#ded8cc85}.pie-chart-segment{transform:rotate(-90deg);transform-origin:60px 60px;transition:stroke-width .2s ease}.pie-chart-segment:hover{stroke-width:20}.pie-chart-total{fill:#27313a;font-size:1.28rem;font-weight:800}.pie-chart-label{fill:var(--muted);font-size:.68rem}.chart-legend{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.chart-legend-item{display:grid;grid-template-columns:12px minmax(0,1fr) auto auto;align-items:center;gap:10px;border:1px solid rgba(222,216,204,.68);border-radius:8px;padding:10px 12px;color:var(--text);background:#ffffff94}a.chart-legend-item{transition:border-color .2s ease,transform .2s ease,box-shadow .2s ease}a.chart-legend-item:hover{border-color:#3eb8be6b;box-shadow:0 10px 24px #3a332614;transform:translateY(-1px)}.chart-legend-color{width:12px;height:12px;border-radius:50%;background:var(--chart-color)}.chart-legend-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.chart-legend-item strong{color:#27313a;font-size:1rem}.chart-legend-percent{min-width:38px;color:var(--muted);font-size:.84rem;text-align:right}.line-chart-wrap{width:100%;overflow-x:auto}.line-chart{display:block;width:100%;min-width:520px;height:260px}.line-chart-axis{stroke:#686f7852;stroke-width:1.4}.line-chart-grid{stroke:#686f7829;stroke-width:1}.line-chart-area{fill:#3eb8be2e}.line-chart-line{fill:none;stroke:#3eb8be;stroke-linecap:round;stroke-linejoin:round;stroke-width:4}.line-chart-dot{fill:#fffdf8;stroke:#3eb8be;stroke-width:3}.line-chart-x-label,.line-chart-y-label{fill:var(--muted);font-size:.78rem}.shuoshuo-page{margin-top:40px}#talk{position:relative;width:100%;min-height:220px}#talk:empty:before{content:"说说加载中...";display:grid;min-height:180px;place-items:center;border:1px solid rgba(255,255,255,.58);border-radius:8px;color:var(--muted);background:radial-gradient(circle at 18% 0%,rgba(255,255,255,.72),transparent 34%),linear-gradient(145deg,#ffffff9e,#e2f8f159),#fffdf8ad;box-shadow:var(--shadow);backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}#talk .talk_item{position:absolute;display:flex;flex-direction:column;width:calc(33.333% - 8px);margin:0 12px 12px 0;border:1px solid rgba(255,255,255,.58);border-radius:8px;padding:20px;color:var(--text);background:radial-gradient(circle at 14% 0%,rgba(255,255,255,.72),transparent 34%),linear-gradient(145deg,#ffffff9e,#e2f8f159),#fffdf8c7;box-shadow:var(--shadow);backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%);transition:box-shadow .2s ease,transform .2s ease}#talk .talk_item:hover{transform:translateY(-2px);box-shadow:0 20px 54px #2c373f2e}#talk .talk_meta,#talk .talk_bottom{display:flex;align-items:center}#talk .talk_meta{width:100%;border-bottom:1px dashed rgba(104,111,120,.34);padding-bottom:10px}#talk .talk_meta .avatar{width:56px;height:56px;margin:0!important;border-radius:8px;object-fit:cover}#talk .talk_meta .info{display:flex;min-width:0;flex-direction:column;margin-left:12px;line-height:1.4}#talk .talk_meta .info .talk_nick{display:inline-flex;align-items:center;gap:5px;color:#3eb8be;font-size:1.08rem;font-weight:800}#talk .talk_meta .info svg.is-badge.icon{width:15px;height:15px;flex:0 0 auto}#talk .talk_meta .info span.talk_date{color:var(--muted);font-size:.9rem}#talk .talk_item .talk_content{margin-top:12px;overflow-wrap:anywhere;color:#33433f}#talk .talk_item .talk_content a{color:#d75f54;font-weight:700}#talk .talk_item .talk_content a:hover{color:var(--accent)}#talk .talk_item .talk_content .zone_imgbox{display:flex;flex-wrap:wrap;gap:10px;margin-top:12px;--talk-image-size: calc(25% - 8px) }#talk .talk_item .talk_content .zone_imgbox a{position:relative;display:block;width:var(--talk-image-size);overflow:hidden;border-radius:8px;aspect-ratio:1}#talk .talk_item .talk_content .zone_imgbox a:first-child{width:100%;aspect-ratio:1.8}#talk .talk_item .talk_content .zone_imgbox img{width:100%;height:100%;margin:0!important;object-fit:cover}#talk .talk_item .talk_bottom{justify-content:space-between;gap:12px;margin-top:15px;border-top:1px dashed rgba(104,111,120,.34);padding-top:10px}#talk .talk_item .talk_bottom span.talk_tag,#talk .talk_item .talk_bottom span.location_tag{display:inline-flex;max-width:100%;min-height:30px;align-items:center;border:1px solid rgba(15,118,110,.16);border-radius:999px;padding:3px 11px;color:var(--accent);font-size:.86rem;background:#ecfdf5b3;transition:color .2s ease,background-color .2s ease,transform .2s ease}#talk .talk_item .talk_bottom span.talk_tag:hover,#talk .talk_item .talk_bottom span.location_tag:hover,#talk .talk_item .talk_bottom .icon:hover{color:#fff;background:#3eb8be;transform:translateY(-1px)}#talk .talk_item .talk_bottom .icon{display:grid;place-items:center;width:34px;height:34px;border-radius:999px;color:var(--accent);transition:color .2s ease,background-color .2s ease,transform .2s ease}#talk .talk_item .talk_content .shuoshuo-external-link{width:100%;height:80px;margin-top:12px;overflow:hidden;border:1px solid rgba(15,118,110,.14);border-radius:8px;color:var(--text);background:#ecfdf5a3;transition:background-color .2s ease,transform .2s ease}.shuoshuo-external-link:hover{background:#3eb8be;transform:translateY(-1px)}.shuoshuo-external-link .external-link{display:flex;width:100%;height:100%}.shuoshuo-external-link .external-link:hover{color:#fff}.shuoshuo-external-link .external-link-left{width:60px;height:60px;margin:10px;flex:0 0 auto;border-radius:8px;background-position:center;background-size:cover}.shuoshuo-external-link .external-link-right{display:flex;min-width:0;width:calc(100% - 80px);flex-direction:column;justify-content:center;padding:10px}.shuoshuo-external-link .external-link-right .external-link-title{overflow:hidden;font-size:1rem;font-weight:800;text-overflow:ellipsis;white-space:nowrap}.talk_content iframe{min-height:150px}meting-js{display:block;min-height:100px;margin-top:10px}.limit{width:100%;margin-top:30px;color:var(--muted);text-align:center}:root[data-theme=dark] #talk:empty:before,:root[data-theme=dark] #talk .talk_item{border-color:#aecdc52e;background:radial-gradient(circle at 14% 0%,rgba(114,222,210,.1),transparent 34%),linear-gradient(145deg,#1c2b2fd1,#0c1619b8),#111c1fbd;box-shadow:var(--shadow)}:root[data-theme=dark] #talk .talk_item .talk_content{color:#dce9e6}:root[data-theme=dark] #talk .talk_item .talk_bottom span.talk_tag,:root[data-theme=dark] #talk .talk_item .talk_bottom span.location_tag,:root[data-theme=dark] #talk .talk_item .talk_content .shuoshuo-external-link{border-color:#72ded233;background:#72ded21a}:root[data-theme=dark] #talk .talk_meta,:root[data-theme=dark] #talk .talk_item .talk_bottom{border-color:#aecdc533}:root[data-theme=dark] .hero-talk{border-color:#aecdc52e;background:linear-gradient(135deg,#1c2b2f94,#0c161961),#111c1f57;box-shadow:inset 0 1px #ffffff1a,0 18px 46px #0003}:root[data-theme=dark] .hero-talk .item{border-top-color:#aecdc529;color:#dce9e6}:root[data-theme=dark] .hero-talk-head a{border-color:#72ded233;background:#72ded21a}.external-redirect{width:min(760px,calc(100% - (var(--page-gutter) * 2)));min-height:calc(100vh - 240px);margin:42px auto 80px;display:grid;place-items:center}.external-redirect-panel{width:100%;overflow:hidden;border:1px solid rgba(255,255,255,.58);border-radius:8px;padding:clamp(26px,6vw,48px);background:radial-gradient(circle at 15% 0%,rgba(255,255,255,.74),transparent 36%),linear-gradient(145deg,#ffffffb3,#e2f8f16b),#fffdf8c7;box-shadow:inset 0 1px #ffffffd1,0 24px 64px #2c373f29;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%)}.external-redirect-eyebrow{margin:0 0 10px;color:var(--accent);font-size:.88rem;font-weight:800}.external-redirect h1{margin:0;color:#20242a;font-size:clamp(2rem,6vw,3.8rem);line-height:1.08}.external-redirect-copy{max-width:620px;margin:16px 0 0;color:var(--muted);font-size:1rem}.external-redirect-target{display:grid;gap:8px;margin-top:24px;border:1px solid rgba(15,118,110,.16);border-radius:8px;padding:14px 16px;background:#ecfdf5a3}.external-redirect-target span{color:var(--accent);font-size:.84rem;font-weight:800}.external-redirect-target code{overflow-wrap:anywhere;color:#33433f;font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.92rem}.external-redirect-countdown{position:relative;width:112px;height:112px;margin:26px auto 0}.external-redirect-countdown svg{width:112px;height:112px;transform:rotate(-90deg)}.external-redirect-countdown circle{fill:none;stroke:#0f766e24;stroke-width:10}.external-redirect-countdown circle:last-child{stroke:#3eb8be;stroke-linecap:round;transition:stroke-dashoffset .9s linear}.external-redirect-countdown strong{position:absolute;inset:0;display:grid;place-items:center;color:#2f4358;font-size:2rem;line-height:1}.external-redirect-actions{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px}.external-redirect-button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;border:1px solid rgba(15,118,110,.2);border-radius:8px;padding:9px 16px;color:var(--accent);font-weight:800;background:#ecfdf5ad;transition:color .2s ease,background-color .2s ease,transform .2s ease}.external-redirect-button:hover,.external-redirect-button.is-primary{color:#fff;background:#3eb8be;transform:translateY(-1px)}.native-page{margin-top:40px}.native-page-article{overflow:hidden}.native-feature-grid,.friend-link-grid{display:grid;gap:16px}.native-feature-grid{grid-template-columns:repeat(3,minmax(0,1fr));margin:28px 0}.native-feature-grid section,.friend-link-card{border:1px solid rgba(15,118,110,.14);border-radius:8px;background:#ecfdf594;box-shadow:inset 0 1px #ffffff9e}.native-feature-grid section{padding:18px}.native-feature-grid i{display:grid;width:42px;height:42px;margin-bottom:12px;place-items:center;border-radius:8px;color:#fff;background:#3eb8be}.native-feature-grid h2{margin:0 0 8px;font-size:1.12rem}.native-feature-grid p{margin:0;color:var(--muted)}.native-table-wrap{overflow-x:auto}.friend-link-grid{grid-template-columns:repeat(2,minmax(0,1fr));margin:0 0 24px}.friend-link-card{display:grid;grid-template-columns:58px minmax(0,1fr) auto;align-items:center;gap:14px;padding:16px;transition:border-color .2s ease,background-color .2s ease,transform .2s ease}.friend-link-card:hover{border-color:#0f766e47;background:#ecfdf5c7;transform:translateY(-2px)}.friend-link-card img{width:58px;height:58px;border-radius:8px;object-fit:cover}.friend-link-card span{display:grid;min-width:0;gap:2px}.friend-link-card strong{color:#33433f;font-size:1.05rem}.friend-link-card em{overflow:hidden;color:var(--muted);font-size:.9rem;font-style:normal;text-overflow:ellipsis;white-space:nowrap}.friend-link-card i{color:var(--accent)}.link-apply-card{width:100%;margin-top:0}:root[data-theme=dark] .external-redirect-panel{border-color:#aecdc52e;background:radial-gradient(circle at 14% 0%,rgba(114,222,210,.1),transparent 34%),linear-gradient(145deg,#1c2b2fd1,#0c1619b8),#111c1fbd;box-shadow:var(--shadow)}:root[data-theme=dark] .external-redirect h1,:root[data-theme=dark] .external-redirect-countdown strong{color:#edf7f5}:root[data-theme=dark] .external-redirect-target,:root[data-theme=dark] .external-redirect-button{border-color:#72ded238;background:#72ded21a}:root[data-theme=dark] .external-redirect-target code{color:#dce9e6}:root[data-theme=dark] .native-feature-grid section,:root[data-theme=dark] .friend-link-card{border-color:#72ded233;background:#72ded21a}:root[data-theme=dark] .friend-link-card strong{color:#edf7f5}.site-footer{position:relative;margin-top:64px;color:#535b60b8;background:linear-gradient(180deg,#f2f8ec8f,#f6faefb8),#f6faefb8;box-shadow:inset 0 1px #ffffffc7;backdrop-filter:blur(18px) saturate(150%);-webkit-backdrop-filter:blur(18px) saturate(150%)}.footer-inner{width:min(1180px,calc(100% - (var(--page-gutter) * 2)));margin:0 auto;display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,320px);gap:34px 64px;padding:42px 0 38px}.footer-top{display:grid;align-content:start;gap:10px}.footer-brand{display:inline-flex;align-items:center;gap:10px;width:fit-content;color:#3f4143;font-size:1.22rem;font-weight:800;line-height:1}.footer-brand img{width:34px;height:34px;border-radius:999px;object-fit:cover;box-shadow:0 8px 22px #0f766e2e}.footer-slogan,.footer-contact p,.footer-bottom p{margin:0}.footer-slogan{font-size:.92rem}.footer-contact{display:grid;justify-items:center;align-content:start;gap:10px;text-align:center}.footer-contact p{color:#535b60a3;font-size:.94rem}.footer-socials{display:flex;align-items:center;justify-content:center;gap:8px}.footer-socials a{display:grid;place-items:center;width:36px;height:36px;border:1px solid rgba(178,194,170,.48);border-radius:10px;color:#4c5458c7;background:linear-gradient(145deg,#ffffff7a,#dae7cb6b),#ecf4e2b8;box-shadow:inset 0 1px #ffffffb3,0 10px 24px #5b6b4a1a;transition:color .2s ease,border-color .2s ease,background-color .2s ease,transform .2s ease}.footer-socials a:hover{color:var(--accent);border-color:#0f766e47;background-color:#ffffffd6;transform:translateY(-2px)}.footer-socials i{width:18px;height:18px;display:inline-grid;place-items:center;font-size:1.08rem;line-height:1}.footer-contact-links,.footer-meta p:last-child{display:flex;align-items:center;justify-content:center;gap:8px}.footer-contact-links{font-size:.92rem}.footer-contact-links span,.footer-meta span{color:#535b6059}.footer-bottom{grid-column:1 / -1;display:flex;justify-content:space-between;gap:24px;border-top:1px solid rgba(182,197,174,.34);padding-top:22px;font-size:.92rem}.footer-bottom>div{display:grid;gap:8px}.footer-meta{justify-items:end;text-align:right}:root[data-theme=dark] :where(.page-header-card,.post-card,.sidebar-card,.article,.article-summary-card,.post-copyright-card,.twikoo-comments,.table-scroll,.stats-chart,.term-grid a,.pagination-link,.pagination-number){border-color:#aecdc52e;background:radial-gradient(circle at 14% 0%,rgba(114,222,210,.1),transparent 34%),linear-gradient(145deg,#1c2b2fd1,#0c1619b8),#111c1fbd;box-shadow:var(--shadow)}:root[data-theme=dark] :where(.page-header-card:after,.post-copyright-card:after){background:#ffffff1f}:root[data-theme=dark] :where(.page-header-card h1,.content-wrap .page-header-card h1,.sidebar-card h2,.stats-chart-head h2,.chart-legend-item strong,.pie-chart-total,.profile-name,.calendar-summary strong,.schedule-left strong,.sidebar-post strong,.toc-link-main,.post-copyright-card h2,.post-copyright-meta dd,.twikoo-comments h2,.footer-brand){color:#edf7f5}:root[data-theme=dark] :where(.notice-card p,#welcome-info,#welcome-ip-location-info,.site-info-list dt,.toc-link-child,.same-category-list a,.profile-links,.footer-contact p){color:var(--muted)}:root[data-theme=dark] .chart-legend-item{border-color:#aecdc529;background:#ffffff0d}:root[data-theme=dark] .pie-chart-track{stroke:#aecdc529}:root[data-theme=dark] .line-chart-dot{fill:#112023}:root[data-theme=dark] .site-info-list div{border-top-color:#aecdc529}:root[data-theme=dark] :where(.meta-list a,.article-tag-list a,.post-copyright-tags a,.post-copyright-action,.post-copyright-icon){border-color:#72ded238;color:var(--accent);background:#72ded21a;box-shadow:inset 0 1px #ffffff14}:root[data-theme=dark] :where(.follow-button,.calendar-grid .today,.post-copyright-action.is-primary,.post-copyright-tags a:hover,.post-copyright-action:hover,.post-copyright-icon:hover,.pagination-link:hover,.pagination-number:hover,.pagination-number.is-current){color:#081315;background:var(--accent)}:root[data-theme=dark] .article-header{border-bottom-color:var(--line)}:root[data-theme=dark] .article-header-cover{background:#0d1f21}:root[data-theme=dark] .article-header-cover:before{background:linear-gradient(180deg,#050c0e8a,#07131538 38%,#030a0bd1),linear-gradient(90deg,#0512149e,#0c3c3929 54%,#05121470)}:root[data-theme=dark] .prose .code-block{border-color:#72ded233;background:#050a0cf5;box-shadow:0 14px 32px #0000003d}:root[data-theme=dark] .prose :not(pre)>code{border-color:#72ded238;color:var(--accent);background:#72ded21a}:root[data-theme=dark] .butterfly-note{color:#dce9e6;background:#72ded214}:root[data-theme=dark] .butterfly-note--disabled{background:transparent}:root[data-theme=dark] .butterfly-hide-content{color:#dce9e6}:root[data-theme=dark] .butterfly-hide-block__content{border-left-color:#72ded238}:root[data-theme=dark] .prose table{color:#dce9e6}:root[data-theme=dark] .prose thead th{color:#effaf7;background:linear-gradient(135deg,#284646db,#16292cc2),#0d181bcc}:root[data-theme=dark] .prose tbody tr:nth-child(2n) td{background:#72ded212}:root[data-theme=dark] .prose tbody tr:hover td{background:#72ded21f}:root[data-theme=dark] .prose :where(th,td){border-color:#72ded21f}:root[data-theme=dark] .site-footer{color:#c5d6d3bd;background:linear-gradient(180deg,#0c1417ad,#080e11e0),#080e11d6;box-shadow:inset 0 1px #ffffff14}:root[data-theme=dark] .footer-socials a{border-color:#aecdc52e;color:#daeae7d1;background:linear-gradient(145deg,#223337c7,#0e181bb8),#121d20b8;box-shadow:inset 0 1px #ffffff14,0 10px 24px #00000038}:root[data-theme=dark] .footer-bottom{border-top-color:#aecdc529}@media(min-width:960px){.post-list{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:1100px){.post-layout{grid-template-columns:1fr}.home-hero{grid-template-columns:1fr;gap:22px;padding:44px 0 28px}.hero-talk{max-width:none}.article-sidebar{align-self:auto;position:static;grid-template-columns:repeat(2,minmax(0,1fr))}.article-sticky-sidebar{position:static;grid-column:1 / -1;grid-template-columns:repeat(2,minmax(0,1fr));max-height:none;overflow:visible}.toc-card{grid-column:1 / -1}.home-layout{grid-template-columns:1fr}#talk .talk_item{width:calc(50% - 6px)}.home-sidebar{align-self:auto;grid-template-columns:repeat(2,minmax(0,1fr))}.sticky-sidebar,.article-sticky-sidebar{position:static;max-height:none;overflow:visible}}@media(max-width:720px){:root{--page-gutter: 16px}.custom-context-menu{width:min(218px,calc(100vw - 16px));border-radius:12px;padding:7px}.context-menu-nav :where(a,button){height:40px;border-radius:13px}.context-menu-group :where(a,button){min-height:38px;padding:8px 9px}.nav{display:grid;grid-template-columns:minmax(0,1fr) 40px;min-height:auto;padding:10px 12px;align-items:center;gap:0 10px;border-radius:24px}.nav-menu-toggle{display:grid;justify-self:end}.nav.is-open .nav-menu-open,.nav-menu-close{display:none}.nav.is-open .nav-menu-close{display:inline-block}.nav-menu{display:none;grid-column:1 / -1;gap:8px;margin-top:10px;border-top:1px solid rgba(255,255,255,.46);padding-top:10px}.nav.is-open .nav-menu{display:grid}.nav-links{width:100%;gap:6px;overflow:visible;padding-bottom:0}.nav-menu .nav-links{display:grid}.nav-center{grid-template-columns:repeat(2,minmax(0,1fr))}.nav-actions{grid-template-columns:repeat(3,minmax(0,1fr))}.nav-links a,.nav-random,.nav-search,.theme-toggle{width:100%;justify-content:center}.theme-toggle{min-height:38px}.hero{min-height:300px}.home-hero{min-height:auto;padding-top:34px}.hero-talk{min-height:164px;padding:14px}.hero-talk .talk-list{height:102px}.hero-talk .item{min-height:34px;padding:7px 0;font-size:.9rem}.home-sidebar,.native-feature-grid,.friend-link-grid{grid-template-columns:1fr}#talk .talk_item{width:100%;margin-right:0}#talk .talk_item .talk_content .zone_imgbox{--talk-image-size: calc(50% - 5px) }.calendar-widget,.schedule-widget,.article-sidebar,.article-sticky-sidebar{grid-template-columns:1fr}.archive-list a{grid-template-columns:1fr;gap:2px}.stats-chart{padding:18px}.stats-chart-body,.chart-legend{grid-template-columns:1fr}.line-chart{min-width:460px}.article{margin-top:32px}.article-header-cover{min-height:300px;padding-top:92px}.article-header-cover h1{font-size:clamp(2rem,11vw,3rem)}.article-hero-meta{font-size:.9rem}.article-summary-card{padding:34px 16px 16px}.article-summary-footer{align-items:flex-start;flex-direction:column;gap:8px}.prose .code-block,.table-scroll{margin-right:calc(var(--article-padding) * -.5);margin-left:calc(var(--article-padding) * -.5)}.prose .code-block pre{padding-right:16px;padding-left:16px}.prose table{min-width:560px;font-size:.9rem}.post-copyright-card{padding:22px 18px 18px}.post-copyright-meta{grid-template-columns:1fr;gap:12px}.post-copyright-footer{align-items:flex-start;flex-direction:column}.footer-inner{grid-template-columns:1fr;gap:28px;padding:36px 0 34px;text-align:center}.footer-top{justify-items:center}.footer-bottom{flex-direction:column;align-items:center;text-align:center}.footer-meta{justify-items:center;text-align:center}}.hitokoto-card[data-astro-cid-4cgfuthx]{position:fixed;left:max(18px,env(safe-area-inset-left));bottom:max(18px,env(safe-area-inset-bottom));z-index:30;width:min(340px,calc(100vw - 36px));min-height:48px;overflow:hidden;border:1px solid rgba(255,255,255,.58);border-radius:8px;padding:14px 15px 13px;color:#40505a;background:linear-gradient(135deg,#ffffffbd,#ecfdf594),#fffdf8c2;box-shadow:inset 0 1px #fffc,0 18px 44px #2c373f29;backdrop-filter:blur(18px) saturate(165%);-webkit-backdrop-filter:blur(18px) saturate(165%);transition:width .2s ease,padding .2s ease,transform .2s ease}.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed{width:48px;padding:8px;cursor:pointer}.hitokoto-card__head[data-astro-cid-4cgfuthx]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:9px;color:var(--accent);font-size:.84rem;font-weight:800}.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__head[data-astro-cid-4cgfuthx]{justify-content:center;margin-bottom:0}.hitokoto-card__toggle[data-astro-cid-4cgfuthx],.hitokoto-card__actions[data-astro-cid-4cgfuthx]{display:inline-flex;align-items:center;gap:7px}.hitokoto-card__toggle[data-astro-cid-4cgfuthx],.hitokoto-card__refresh[data-astro-cid-4cgfuthx],.hitokoto-card__collapse[data-astro-cid-4cgfuthx]{appearance:none;-webkit-appearance:none;border:0;color:inherit;font:inherit;background:transparent;cursor:pointer}.hitokoto-card__toggle[data-astro-cid-4cgfuthx]{display:inline-flex;align-items:center;gap:7px;min-width:0;border-radius:999px;padding:0}.hitokoto-card__toggle[data-astro-cid-4cgfuthx]:hover{color:var(--accent-strong)}.hitokoto-card__refresh[data-astro-cid-4cgfuthx],.hitokoto-card__collapse[data-astro-cid-4cgfuthx]{display:grid;width:30px;min-width:30px;height:30px;place-items:center;border:1px solid rgba(15,118,110,.16);border-radius:999px;background:#ecfdf594;transition:color .2s ease,background-color .2s ease,transform .2s ease}.hitokoto-card__refresh[data-astro-cid-4cgfuthx]:hover{color:#fff;background:#3eb8be;transform:translateY(-1px)}.hitokoto-card__collapse[data-astro-cid-4cgfuthx]:hover{color:#fff;background:#3eb8be;transform:translateY(1px)}.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__toggle[data-astro-cid-4cgfuthx]{width:32px;height:32px;justify-content:center}.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__toggle[data-astro-cid-4cgfuthx] span[data-astro-cid-4cgfuthx],.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__actions[data-astro-cid-4cgfuthx],.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__text[data-astro-cid-4cgfuthx],.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed .hitokoto-card__from[data-astro-cid-4cgfuthx]{display:none}.hitokoto-card[data-astro-cid-4cgfuthx].is-loading .hitokoto-card__refresh[data-astro-cid-4cgfuthx] i[data-astro-cid-4cgfuthx]{animation:hitokoto-spin .9s linear infinite}.hitokoto-card__text[data-astro-cid-4cgfuthx]{display:block;color:#2f4358;font-size:.95rem;font-weight:700;line-height:1.65;overflow-wrap:anywhere}.hitokoto-card__text[data-astro-cid-4cgfuthx]:hover{color:var(--accent)}.hitokoto-card__from[data-astro-cid-4cgfuthx]{margin:8px 0 0;color:var(--muted);font-size:.78rem;line-height:1.45;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:root[data-theme=dark] .hitokoto-card[data-astro-cid-4cgfuthx]{border-color:#aecdc52e;color:#dce9e6;background:radial-gradient(circle at 14% 0%,rgba(114,222,210,.1),transparent 34%),linear-gradient(145deg,#1c2b2fd1,#0c1619b8),#111c1fbd;box-shadow:var(--shadow)}:root[data-theme=dark] .hitokoto-card__text[data-astro-cid-4cgfuthx]{color:#edf7f5}:root[data-theme=dark] .hitokoto-card__refresh[data-astro-cid-4cgfuthx],:root[data-theme=dark] .hitokoto-card__collapse[data-astro-cid-4cgfuthx]{border-color:#72ded238;background:#72ded21a}@keyframes hitokoto-spin{to{transform:rotate(360deg)}}@media(max-width:720px){.hitokoto-card[data-astro-cid-4cgfuthx]{left:12px;bottom:12px;width:min(300px,calc(100vw - 24px));padding:12px}.hitokoto-card[data-astro-cid-4cgfuthx].is-collapsed{width:46px;padding:7px}.hitokoto-card__text[data-astro-cid-4cgfuthx]{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:3;font-size:.9rem}} diff --git a/about/index.html b/about/index.html new file mode 100644 index 0000000..f2b37f9 --- /dev/null +++ b/about/index.html @@ -0,0 +1,282 @@ + 关于 | Bi's Blog

Bi's Blog

关于

这里记录折腾、学习和一些普通日子的回声。

+你好,我是 Bi。这个博客主要用来保存技术实践、生活片段和那些一时半会儿不想让它们散掉的想法。 +

技术笔记

记录服务器、网络、开发环境、图像处理和一些实际踩坑过程。

日常碎片

把普通的一天、突然冒出来的灵感和一点点情绪留在这里。

持续生长

网站本身也在慢慢迁移和重写,新的页面会逐步变成原生实现。

联系

+如果你想交流文章内容、友链或网站相关问题,可以通过 +bishsh2006@gmail.com +联系我,也可以去 +GitHub +找到我。 +

\ No newline at end of file diff --git a/api/posts.json b/api/posts.json new file mode 100644 index 0000000..709a9ea --- /dev/null +++ b/api/posts.json @@ -0,0 +1 @@ +[{"title":"用 Umami 分享链接给网站信息卡提供统计数据","href":"/posts/4c91a8f2/"},{"title":"把普通的一天过成自己的节奏","href":"/posts/efe1e805/"},{"title":"GPT Image2 初试","href":"/posts/3f8de22/"},{"title":"电子价签玩法","href":"/posts/8712baaa/"},{"title":"OpenGL- Cohen-Sutherland 编码裁剪算法","href":"/posts/9b2ef00a/"},{"title":"OpenGL-直线的扫描转换","href":"/posts/7207243b/"},{"title":"OpenGL-基础程序","href":"/posts/437a5198/"},{"title":"配置OpenGL环境","href":"/posts/c4477b0c/"},{"title":"opencv应用-算术运算","href":"/posts/b559997d/"},{"title":"opencv应用-基础操作","href":"/posts/e8f95ead/"},{"title":"配置opencv","href":"/posts/6e3332b/"},{"title":"自建bitwarden服务","href":"/posts/7baa41fc/"},{"title":"自建renovate-bot","href":"/posts/56f57c0b/"},{"title":"新年快乐!","href":"/posts/5785bd01/"},{"title":"添加网站左上角菜单","href":"/posts/7e921903/"},{"title":"将博客仓库转移到gitea","href":"/posts/d2c8521/"},{"title":"安装gitea","href":"/posts/34725d47/"},{"title":"添加typesense搜索","href":"/posts/f287c563/"},{"title":"自定义字体","href":"/posts/b5601a7e/"},{"title":"在侧边栏添加日历和倒计时","href":"/posts/5ed2f1e6/"},{"title":"和朋友们的故事","href":"/posts/a31d95d9/"},{"title":"Adguard和Openclash共存","href":"/posts/66e66374/"},{"title":"Adguard规则分享","href":"/posts/69b16001/"},{"title":"在Openwrt上安装AdguardHome","href":"/posts/b57500e9/"},{"title":"在Openwrt中安装OpenClash","href":"/posts/2b2fb1a7/"},{"title":"Cudy TR3000刷入Openwrt系统","href":"/posts/8802e397/"},{"title":"搭建Owncloud并集成Onlyoffice","href":"/posts/59c8572a/"},{"title":"为博客添加CMS系统","href":"/posts/3a26a97a/"},{"title":"为1Panel添加自己想要的应用","href":"/posts/bb18d851/"},{"title":"使用GitHub推送Hexo到服务器","href":"/posts/ce1ec3fe/"},{"title":"C# 基本语法","href":"/posts/e9a8e898/"},{"title":"C# 入门","href":"/posts/bc56f789/"},{"title":"自定义页脚(新)","href":"/posts/c6143ad3/"},{"title":"自定义分类条","href":"/posts/33249733/"},{"title":"自定义导航栏","href":"/posts/3e61a389/"},{"title":"利用 SiteMap 随机访问站内页面","href":"/posts/b5866b9e/"},{"title":"博客添加AI总结","href":"/posts/41b9aff7/"},{"title":"利用插件自定义页脚菜单","href":"/posts/a6ab0925/"},{"title":"英语文章分享-去奋力生活吧,就好像死亡近在咫尺","href":"/posts/f0a1a1f4/"},{"title":"自定义右键菜单","href":"/posts/8bdb35fb/"},{"title":"在侧边栏中添加欢迎信息","href":"/posts/da17d00/"},{"title":"配置说说页面","href":"/posts/ad244066/"},{"title":"部署历程","href":"/posts/6e5f5039/"}] \ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..72451b3 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,278 @@ + 归档 | Bi's Blog

Bi's Blog

归档

共 43 篇文章

归档发布趋势

按月份统计文章发布数量

0 9 17 2025-08: 17 篇 2025-08 2025-09: 3 篇 2025-10: 2 篇 2025-10 2026-01: 3 篇 2026-02: 7 篇 2026-02 2026-03: 3 篇 2026-04: 6 篇 2026-04 2026-05: 1 篇 2026-06: 1 篇 2026-06
\ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 0000000..c433e68 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,278 @@ + 归档 - 第 2 页 | Bi's Blog

Bi's Blog

归档

共 43 篇文章,第 2 页

归档发布趋势

按月份统计文章发布数量

0 9 17 2025-08: 17 篇 2025-08 2025-09: 3 篇 2025-10: 2 篇 2025-10 2026-01: 3 篇 2026-02: 7 篇 2026-02 2026-03: 3 篇 2026-04: 6 篇 2026-04 2026-05: 1 篇 2026-06: 1 篇 2026-06
\ No newline at end of file diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 0000000..4184832 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,278 @@ + 归档 - 第 3 页 | Bi's Blog

Bi's Blog

归档

共 43 篇文章,第 3 页

归档发布趋势

按月份统计文章发布数量

0 9 17 2025-08: 17 篇 2025-08 2025-09: 3 篇 2025-10: 2 篇 2025-10 2026-01: 3 篇 2026-02: 7 篇 2026-02 2026-03: 3 篇 2026-04: 6 篇 2026-04 2026-05: 1 篇 2026-06: 1 篇 2026-06
\ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 0000000..f83806b --- /dev/null +++ b/atom.xml @@ -0,0 +1 @@ +Bi's BlogPersonal blog by biss.https://blog.biss.click/用 Umami 分享链接给网站信息卡提供统计数据https://blog.biss.click/posts/4c91a8f2/https://blog.biss.click/posts/4c91a8f2/这篇文章记录如何通过 Umami 的分享链接获取公开统计数据,再用 Cloudflare Worker 做一层缓存代理,最后把访客数和访问量显示到博客侧边栏的网站信息卡里。Fri, 19 Jun 2026 08:58:00 GMT把普通的一天过成自己的节奏https://blog.biss.click/posts/efe1e805/https://blog.biss.click/posts/efe1e805/有时候,我们会把“好好生活”想得太大,好像它必须绑定某个明确的目标:换一份更好的工作,去一个更远的地方,学会一项很厉害的技能,或者在某个时间节点之前完成别人眼里的成功。 可真正能把日子托住的,往往不是这些很大的东西,而是一些小到容易被忽略的Fri, 29 May 2026 18:09:40 GMTGPT Image2 初试https://blog.biss.click/posts/3f8de22/https://blog.biss.click/posts/3f8de22/这篇文章介绍了作者最近试用gpt img2之后的一些直观感受。文章没有从技术角度展开,而是以普通用户的体验为主,聊了它适合做什么、哪些地方比以前的AI绘图更顺手、哪些地方依然不太稳定,以及在日常做头像、壁纸、封面图和改图时的一些实际体会。整体来看,它确实比以前更听话一些,但也还没有到可以完全省心的地步。Sat, 25 Apr 2026 15:00:00 GMT电子价签玩法https://blog.biss.click/posts/8712baaa/https://blog.biss.click/posts/8712baaa/这篇文章介绍了作者在逛咸鱼时发现并购买了两个改造二手电子价签的过程,这些电子价签由tsl0922开发的开源固件提供支持,具备日历、传图、文字功能,并且可以在网页端进行便捷的配置。尽管开源版固件功能有限,作者选择自行编写所需功能,目前已经实现了待办事项和倒计时功能,并能生成图片后上传至网络。Sun, 19 Apr 2026 13:46:25 GMTOpenGL- Cohen-Sutherland 编码裁剪算法https://blog.biss.click/posts/9b2ef00a/https://blog.biss.click/posts/9b2ef00a/这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。Wed, 15 Apr 2026 19:34:23 GMTOpenGL-直线的扫描转换https://blog.biss.click/posts/7207243b/https://blog.biss.click/posts/7207243b/这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。Sat, 11 Apr 2026 19:01:02 GMTOpenGL-基础程序https://blog.biss.click/posts/437a5198/https://blog.biss.click/posts/437a5198/这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。Sat, 11 Apr 2026 18:20:34 GMT配置OpenGL环境https://blog.biss.click/posts/c4477b0c/https://blog.biss.click/posts/c4477b0c/这篇文章详细介绍了配置OpenGL环境所需步骤,包括安装Visual Studio、CMake和GLFW等关键组件,并提供了具体的配置方法和测试代码,旨在帮助读者顺利开始计算机图形学的学习之旅。Thu, 02 Apr 2026 04:52:16 GMTopencv应用-算术运算https://blog.biss.click/posts/b559997d/https://blog.biss.click/posts/b559997d/这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。Tue, 17 Mar 2026 13:13:08 GMTopencv应用-基础操作https://blog.biss.click/posts/e8f95ead/https://blog.biss.click/posts/e8f95ead/这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。Sun, 15 Mar 2026 09:56:51 GMT配置opencvhttps://blog.biss.click/posts/6e3332b/https://blog.biss.click/posts/6e3332b/这篇文章介绍了在Ubuntu和Windows系统上配置OpenCV的过程。在Ubuntu系统中,通过包管理器安装了Visual Studio、GCC、G++、OpenCV以及Python的OpenCV绑定。而在Windows系统中,则通过安装Visual Studio并下载OpenCV的Windows安装包进行配置,包括将安装路径添加到系统环境变量中。Sat, 14 Mar 2026 11:48:36 GMT自建bitwarden服务https://blog.biss.click/posts/7baa41fc/https://blog.biss.click/posts/7baa41fc/这篇文章介绍了如何自建Bitwarden服务,以替代浏览器自带的密码管理器。Bitwarden是一款开源、端到端加密的密码管理器,支持多种操作系统和设备,具有安全存储、自动同步、一键填充、生成随机密码和检测弱密码等功能。文章详细描述了部署Bitwarden服务的步骤,包括安装Vaultwarden、配置Nginx反向代理以及设置域名和数据卷等。通过这些步骤,用户可以轻松搭建一个功能强大的密码管理服务器,提高个人信息安全。Wed, 25 Feb 2026 11:09:23 GMT自建renovate-bothttps://blog.biss.click/posts/56f57c0b/https://blog.biss.click/posts/56f57c0b/这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。Mon, 23 Feb 2026 10:49:17 GMT新年快乐!https://blog.biss.click/posts/5785bd01/https://blog.biss.click/posts/5785bd01/这篇文章是一则关于新年祝福和幸福指数提升的通知,主要内容包含以下几点:首先,强调在假期期间要确保饮食质量,避免emo情绪的影响;其次,鼓励大家在收到通知后在留言区分享新年愿望,以便后台系统收集并祝愿他们实现愿望;最后,表达了对大家新年的美好祝愿,希望他们身体健康,万事如意,所得皆所愿,所行化坦途。Sat, 14 Feb 2026 06:56:18 GMT添加网站左上角菜单https://blog.biss.click/posts/7e921903/https://blog.biss.click/posts/7e921903/这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。Tue, 10 Feb 2026 07:11:14 GMT将博客仓库转移到giteahttps://blog.biss.click/posts/d2c8521/https://blog.biss.click/posts/d2c8521/这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。Sat, 07 Feb 2026 12:30:39 GMT安装giteahttps://blog.biss.click/posts/34725d47/https://blog.biss.click/posts/34725d47/这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。Sat, 07 Feb 2026 06:32:04 GMT添加typesense搜索https://blog.biss.click/posts/f287c563/https://blog.biss.click/posts/f287c563/这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。Thu, 05 Feb 2026 13:14:16 GMT自定义字体https://blog.biss.click/posts/b5601a7e/https://blog.biss.click/posts/b5601a7e/这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。Fri, 16 Jan 2026 07:47:15 GMT在侧边栏添加日历和倒计时https://blog.biss.click/posts/5ed2f1e6/https://blog.biss.click/posts/5ed2f1e6/实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。Fri, 16 Jan 2026 07:47:07 GMT和朋友们的故事https://blog.biss.click/posts/a31d95d9/https://blog.biss.click/posts/a31d95d9/这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。Mon, 05 Jan 2026 13:14:16 GMTAdguard和Openclash共存https://blog.biss.click/posts/66e66374/https://blog.biss.click/posts/66e66374/这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。Sun, 05 Oct 2025 15:18:00 GMTAdguard规则分享https://blog.biss.click/posts/69b16001/https://blog.biss.click/posts/69b16001/这篇文章分享了关于Adguardhome广告拦截工具的使用经验,特别强调了规则的重要性。作者提到,要达到更好的拦截效果,需要搭配有效的规则,并详细介绍了两个关键方面:一是综合过滤规则的分享,二是广告过滤规则订阅中心的集成。通过添加这些规则,使用Adblocktester工具可以达到74分的拦截效果,认为这基本够用。Sun, 05 Oct 2025 12:18:00 GMT在Openwrt上安装AdguardHomehttps://blog.biss.click/posts/b57500e9/https://blog.biss.click/posts/b57500e9/这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。Sun, 28 Sep 2025 17:10:18 GMT在Openwrt中安装OpenClashhttps://blog.biss.click/posts/2b2fb1a7/https://blog.biss.click/posts/2b2fb1a7/这篇文章详细介绍了在OpenWrt系统中安装OpenClash的过程。作者通过Web管理页面和终端命令两种方式进行了安装,并强调了推荐启用Smart内核。安装完成后,作者提到了配置步骤,包括下载内核和导入配置文件,最后表达了对于zashboard界面简洁美观的个人喜好。整篇文章内容连贯,条理清晰,为读者提供了完整的安装指南。Tue, 02 Sep 2025 21:00:00 GMTCudy TR3000刷入Openwrt系统https://blog.biss.click/posts/8802e397/https://blog.biss.click/posts/8802e397/这篇文章介绍了如何将Cudy TR3000路由器刷入OpenWrt系统。首先,作者提供了下载中间固件和OpenWrt固件的方法。接着,详细说明了刷入过程的步骤,包括访问不同的IP地址进行固件刷入,并提到了OpenWrt的默认登录密码是root。整个过程旨在使路由器能够支持科学上网功能。Sun, 31 Aug 2025 20:15:00 GMT搭建Owncloud并集成Onlyofficehttps://blog.biss.click/posts/59c8572a/https://blog.biss.click/posts/59c8572a/这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。Tue, 26 Aug 2025 14:27:00 GMT为博客添加CMS系统https://blog.biss.click/posts/3a26a97a/https://blog.biss.click/posts/3a26a97a/这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。Mon, 25 Aug 2025 22:03:00 GMT为1Panel添加自己想要的应用https://blog.biss.click/posts/bb18d851/https://blog.biss.click/posts/bb18d851/这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。Tue, 19 Aug 2025 10:43:16 GMT使用GitHub推送Hexo到服务器https://blog.biss.click/posts/ce1ec3fe/https://blog.biss.click/posts/ce1ec3fe/这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。Mon, 18 Aug 2025 17:10:18 GMTC# 基本语法https://blog.biss.click/posts/e9a8e898/https://blog.biss.click/posts/e9a8e898/这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。Sat, 16 Aug 2025 18:01:10 GMTC# 入门https://blog.biss.click/posts/bc56f789/https://blog.biss.click/posts/bc56f789/这篇文章指导初学者在Windows上安装C#开发环境:先下载Visual Studio社区版安装器并勾选“.NET桌面开发”工作负载,可自定义安装路径;随后通过HelloWorld演示C#基础结构,包含命名空间、类、Main入口及Console输出,帮助读者完成首个程序并熟悉默认模板。Fri, 15 Aug 2025 09:56:17 GMT自定义页脚(新)https://blog.biss.click/posts/c6143ad3/https://blog.biss.click/posts/c6143ad3/您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。Thu, 14 Aug 2025 17:05:27 GMT自定义分类条https://blog.biss.click/posts/33249733/https://blog.biss.click/posts/33249733/这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。Thu, 14 Aug 2025 16:25:49 GMT自定义导航栏https://blog.biss.click/posts/3e61a389/https://blog.biss.click/posts/3e61a389/这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。Wed, 13 Aug 2025 16:12:25 GMT利用 SiteMap 随机访问站内页面https://blog.biss.click/posts/b5866b9e/https://blog.biss.click/posts/b5866b9e/提供了一个很好的解决方案,通过使用 Hexo 的 sitemap 生成器和 JavaScript 代码来实现随机访问站内页面的功能 以下是你提供的步骤的详细解释:Tue, 12 Aug 2025 15:15:32 GMT博客添加AI总结https://blog.biss.click/posts/41b9aff7/https://blog.biss.click/posts/41b9aff7/这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。Tue, 12 Aug 2025 09:46:25 GMT利用插件自定义页脚菜单https://blog.biss.click/posts/a6ab0925/https://blog.biss.click/posts/a6ab0925/这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。Tue, 12 Aug 2025 09:19:16 GMT英语文章分享-去奋力生活吧,就好像死亡近在咫尺https://blog.biss.click/posts/f0a1a1f4/https://blog.biss.click/posts/f0a1a1f4/这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。Mon, 11 Aug 2025 20:59:46 GMT自定义右键菜单https://blog.biss.click/posts/8bdb35fb/https://blog.biss.click/posts/8bdb35fb/这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。Mon, 11 Aug 2025 16:02:12 GMT在侧边栏中添加欢迎信息https://blog.biss.click/posts/da17d00/https://blog.biss.click/posts/da17d00/这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。Sun, 10 Aug 2025 19:30:40 GMT配置说说页面https://blog.biss.click/posts/ad244066/https://blog.biss.click/posts/ad244066/这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方Sun, 10 Aug 2025 08:25:39 GMT部署历程https://blog.biss.click/posts/6e5f5039/https://blog.biss.click/posts/6e5f5039/这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。Sat, 02 Aug 2025 19:07:05 GMT \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 0000000..b02cee5 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,274 @@ + 分类 | Bi's Blog

Bi's Blog

分类

共 5 个分类

分类文章统计

共 43 篇文章

\ No newline at end of file diff --git a/categories/life/index.html b/categories/life/index.html new file mode 100644 index 0000000..23ccd34 --- /dev/null +++ b/categories/life/index.html @@ -0,0 +1,274 @@ + 生活 | Bi's Blog

Bi's Blog

生活

共 1 篇文章

\ No newline at end of file diff --git a/categories/notes/index.html b/categories/notes/index.html new file mode 100644 index 0000000..2281d9c --- /dev/null +++ b/categories/notes/index.html @@ -0,0 +1,274 @@ + 杂谈 | Bi's Blog

Bi's Blog

杂谈

共 1 篇文章

和朋友们的故事

这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。

\ No newline at end of file diff --git a/categories/site-notes/index.html b/categories/site-notes/index.html new file mode 100644 index 0000000..5a4006f --- /dev/null +++ b/categories/site-notes/index.html @@ -0,0 +1,274 @@ + 建站手札 | Bi's Blog

Bi's Blog

建站手札

共 17 篇文章

添加网站左上角菜单

这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。

添加typesense搜索

这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。

自定义字体

这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。

在侧边栏添加日历和倒计时

实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。

为博客添加CMS系统

这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。

使用GitHub推送Hexo到服务器

这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。

自定义页脚(新)

您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。

自定义分类条

这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。

自定义导航栏

这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。

博客添加AI总结

这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。

利用插件自定义页脚菜单

这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。

自定义右键菜单

这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。

在侧边栏中添加欢迎信息

这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。

配置说说页面

这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方

部署历程

这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。

\ No newline at end of file diff --git a/categories/study/index.html b/categories/study/index.html new file mode 100644 index 0000000..eaf7505 --- /dev/null +++ b/categories/study/index.html @@ -0,0 +1,274 @@ + 学习 | Bi's Blog

Bi's Blog

学习

共 7 篇文章

OpenGL- Cohen-Sutherland 编码裁剪算法

这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。

OpenGL-直线的扫描转换

这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

OpenGL-基础程序

这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。

C# 基本语法

这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。

英语文章分享-去奋力生活吧,就好像死亡近在咫尺

这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。

\ No newline at end of file diff --git a/categories/tech/index.html b/categories/tech/index.html new file mode 100644 index 0000000..0f612b1 --- /dev/null +++ b/categories/tech/index.html @@ -0,0 +1,274 @@ + 技术 | Bi's Blog

Bi's Blog

技术

共 21 篇文章

OpenGL- Cohen-Sutherland 编码裁剪算法

这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。

OpenGL-直线的扫描转换

这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

OpenGL-基础程序

这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。

opencv应用-算术运算

这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。

opencv应用-基础操作

这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。

自建bitwarden服务

这篇文章介绍了如何自建Bitwarden服务,以替代浏览器自带的密码管理器。Bitwarden是一款开源、端到端加密的密码管理器,支持多种操作系统和设备,具有安全存储、自动同步、一键填充、生成随机密码和检测弱密码等功能。文章详细描述了部署Bitwarden服务的步骤,包括安装Vaultwarden、配置Nginx反向代理以及设置域名和数据卷等。通过这些步骤,用户可以轻松搭建一个功能强大的密码管理服务器,提高个人信息安全。

自建renovate-bot

这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。

将博客仓库转移到gitea

这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。

安装gitea

这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。

添加typesense搜索

这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。

Adguard和Openclash共存

这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

在Openwrt上安装AdguardHome

这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。

搭建Owncloud并集成Onlyoffice

这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。

为1Panel添加自己想要的应用

这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。

\ No newline at end of file diff --git a/cc/index.html b/cc/index.html new file mode 100644 index 0000000..5149c8d --- /dev/null +++ b/cc/index.html @@ -0,0 +1,282 @@ + 版权协议 | Bi's Blog

Bi's Blog

版权协议

关于本站原创内容的转载、引用和使用方式。

+除特别说明外,本站原创文章采用 + +CC BY-NC-SA 4.0 + +协议发布。转载或引用时,请保留原文链接并注明来源。 +

你可以做什么

  • 在非商业场景下分享、转载本站原创文章。
  • 在文章、笔记或资料中引用少量内容作为参考。
  • 基于内容进行讨论、评论或提出修改建议。

需要遵守什么

  • 注明作者和原文链接,建议使用可点击的完整 URL。
  • 不得将本站原创内容用于广告、付费阅读、商业课程等商业用途。
  • 改编、整理或二次发布时,应继续使用相同或兼容的共享协议。

例外情况

+第三方主题、插件、外部图片或引用资料遵循其原作者的授权。网站源码中由本人编写的样式与脚本,如无额外说明,按仓库内声明为准。 +

\ No newline at end of file diff --git a/cookie/index.html b/cookie/index.html new file mode 100644 index 0000000..2c4c27c --- /dev/null +++ b/cookie/index.html @@ -0,0 +1,281 @@ + Cookies | Bi's Blog

Bi's Blog

Cookies

说明本站如何使用 Cookie 与浏览器本地存储。

+本站会使用少量 Cookie 或 LocalStorage 来保存主题偏好、改善访问体验,并辅助完成评论、防滥用和统计分析等功能。 + 本页面应与 隐私政策 一起阅读。 +

什么是 Cookies

+Cookies 是网站保存在浏览器中的小型文本数据。它可以帮助网站识别同一浏览器、记住你的偏好,或让某些功能保持可用。 +

本站使用目的

类型 用途
偏好设置 保存深色/浅色主题等本地偏好。
安全与防滥用 辅助识别异常访问、恶意请求和垃圾评论。
评论服务 评论系统可能会保存昵称、邮箱、头像等你主动填写的信息。
访问分析 了解页面访问情况,用于优化文章和站点性能。

如何管理

+你可以随时在浏览器设置中清除或禁用 Cookie 与站点数据。禁用后,评论、主题偏好、统计等功能可能无法按预期工作,但不影响阅读文章。 +

联系

如果你对 Cookie 使用方式有疑问,可以发送邮件至 bishsh2006@gmail.com

\ No newline at end of file diff --git a/go/index.html b/go/index.html new file mode 100644 index 0000000..5192ef4 --- /dev/null +++ b/go/index.html @@ -0,0 +1,384 @@ + 外链跳转 | Bi's Blog

外部链接

正在检查目标链接

请稍候,正在读取跳转地址。

\ No newline at end of file diff --git a/images/Bi.ico b/images/Bi.ico new file mode 100644 index 0000000..6d3a9bc Binary files /dev/null and b/images/Bi.ico differ diff --git a/images/background.png b/images/background.png new file mode 100644 index 0000000..e90a980 Binary files /dev/null and b/images/background.png differ diff --git a/img/404.jpg b/img/404.jpg new file mode 100644 index 0000000..4bab3c3 Binary files /dev/null and b/img/404.jpg differ diff --git a/img/butterfly-icon.png b/img/butterfly-icon.png new file mode 100644 index 0000000..3992d77 Binary files /dev/null and b/img/butterfly-icon.png differ diff --git a/img/error-page.png b/img/error-page.png new file mode 100644 index 0000000..9d1de96 Binary files /dev/null and b/img/error-page.png differ diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000..56d07f5 Binary files /dev/null and b/img/favicon.ico differ diff --git a/img/friend_404.gif b/img/friend_404.gif new file mode 100644 index 0000000..91dd56a Binary files /dev/null and b/img/friend_404.gif differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..ffc9a7a --- /dev/null +++ b/index.html @@ -0,0 +1,524 @@ + Bi's Blog

写给日常、技术和正在发生的自己

Bi's Blog

把折腾、学习和生活片段安静地收在这里。

最新文章

查看全部

OpenGL- Cohen-Sutherland 编码裁剪算法

这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。

OpenGL-直线的扫描转换

这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

OpenGL-基础程序

这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。

opencv应用-算术运算

这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。

opencv应用-基础操作

这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。

自建bitwarden服务

这篇文章介绍了如何自建Bitwarden服务,以替代浏览器自带的密码管理器。Bitwarden是一款开源、端到端加密的密码管理器,支持多种操作系统和设备,具有安全存储、自动同步、一键填充、生成随机密码和检测弱密码等功能。文章详细描述了部署Bitwarden服务的步骤,包括安装Vaultwarden、配置Nginx反向代理以及设置域名和数据卷等。通过这些步骤,用户可以轻松搭建一个功能强大的密码管理服务器,提高个人信息安全。

自建renovate-bot

这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。

添加网站左上角菜单

这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。

将博客仓库转移到gitea

这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。

\ No newline at end of file diff --git a/js/external-links.js b/js/external-links.js new file mode 100644 index 0000000..4c9ad82 --- /dev/null +++ b/js/external-links.js @@ -0,0 +1,45 @@ +(() => { + const redirectPath = '/go/'; + const urlParams = new URLSearchParams(window.location.search); + + if (window.location.pathname === redirectPath || window.location.pathname === '/go') return; + if (urlParams.get('noExternalRedirect') === '1') return; + + function getExternalUrl(anchor) { + const href = anchor.getAttribute('href'); + if (!href || href.startsWith('#')) return null; + + let targetUrl; + try { + targetUrl = new URL(href, window.location.href); + } catch { + return null; + } + + if (!['http:', 'https:'].includes(targetUrl.protocol)) return null; + if (targetUrl.origin === window.location.origin) return null; + + return targetUrl.href; + } + + document.addEventListener('click', (event) => { + const anchor = event.target.closest?.('a[href]'); + if (!anchor) return; + if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; + + const externalUrl = getExternalUrl(anchor); + if (!externalUrl) return; + + event.preventDefault(); + + const redirectUrl = `${redirectPath}?url=${encodeURIComponent(externalUrl)}&from=${encodeURIComponent(window.location.href)}`; + const target = anchor.getAttribute('target'); + + if (target && target !== '_self') { + window.open(redirectUrl, target, 'noopener,noreferrer'); + return; + } + + window.location.href = redirectUrl; + }); +})(); diff --git a/js/random.js b/js/random.js new file mode 100644 index 0000000..481ee1a --- /dev/null +++ b/js/random.js @@ -0,0 +1,22 @@ +function randomPost() { + fetch('/api/posts.json') + .then(res => { + if (!res.ok) throw new Error(`Failed to load posts: ${res.status}`); + return res.json(); + }) + .then(posts => { + const postLinks = posts + .map(post => post && post.href) + .filter(href => typeof href === 'string' && href.startsWith('/posts/')); + + if (postLinks.length === 0) { + console.warn('No posts available for random navigation.'); + return; + } + + location.href = postLinks[Math.floor(Math.random() * postLinks.length)]; + }) + .catch(error => { + console.error('Random post navigation failed:', error); + }); +} diff --git a/js/search/typesense-search.js b/js/search/typesense-search.js new file mode 100644 index 0000000..896d4ca --- /dev/null +++ b/js/search/typesense-search.js @@ -0,0 +1,528 @@ +(function () { + 'use strict'; + + // ============================================================================ + // 配置区域 - 请根据实际情况修改 + // ============================================================================ + const CONFIG = window.SITE_CONFIG?.search; + if (!CONFIG) { + console.warn('Typesense 搜索配置未加载'); + return; + } + + // ============================================================================ + // 状态管理 + // ============================================================================ + let searchInstance = null; + let isInitialized = false; + let isSearchOpen = false; + let initRetryCount = 0; + const MAX_INIT_RETRIES = CONFIG.ui.maxInitRetries || 30; // 最多重试30次 (3秒) + + // ============================================================================ + // 错误提示函数 + // ============================================================================ + function showErrorMessage() { + const hitsContainer = document.getElementById('hits'); + if (!hitsContainer) return; + + hitsContainer.innerHTML = + '
' + + '
' + + '
搜索服务加载失败
' + + '
' + + '

依赖库未能正确加载,请检查以下配置:

' + + '
    ' + + '
  1. 确认已在 _config.butterfly.yml 中正确引入依赖
  2. ' + + '
  3. 检查 JS 文件加载顺序(先 instantsearch.js,再 adapter)
  4. ' + + '
  5. 尝试更换 CDN 或使用本地文件
  6. ' + + '
  7. 打开浏览器控制台查看详细错误信息
  8. ' + + '
' + + '
' + + '
' + + '' + + '
' + + '
'; + } + + // ============================================================================ + // 1. 动态插入 HTML 结构 + // ============================================================================ + const searchHTML = ` + + + + `; + + document.body.insertAdjacentHTML('beforeend', searchHTML); + + const mask = document.getElementById('typesense-search-mask'); + const closeBtn = document.getElementById('close-typesense'); + const container = document.getElementById('typesense-search-container'); + + // ============================================================================ + // 搜索控制 + // ============================================================================ + function openSearch() { + if (isSearchOpen) return; + isSearchOpen = true; + mask.style.display = 'block'; + void mask.offsetWidth; + mask.classList.add('active'); + document.body.style.overflow = 'hidden'; + + if (!isInitialized) { + initTypesense(); + } + focusSearchInput(); + } + + function closeSearch() { + if (!isSearchOpen) return; + isSearchOpen = false; + mask.classList.remove('active'); + setTimeout(function() { + mask.style.display = 'none'; + document.body.style.overflow = ''; + }, CONFIG.ui.animationDuration || 300); + } + + function focusSearchInput(retryCount) { + retryCount = retryCount || 0; + const input = document.querySelector('.ais-SearchBox-input'); + if (input) { + input.focus(); + input.select(); + } else if (retryCount < (CONFIG.ui.maxRetries || 10)) { + setTimeout(function() { + focusSearchInput(retryCount + 1); + }, CONFIG.ui.retryDelay || 100); + } + } + + // ============================================================================ + // 事件监听 + // ============================================================================ + document.addEventListener('click', function(e) { + if (e.target.closest('.search-typesense-trigger')) { + e.preventDefault(); + openSearch(); + } + }); + + closeBtn.addEventListener('click', closeSearch); + mask.addEventListener('click', function(e) { + if (e.target === mask) closeSearch(); + }); + container.addEventListener('click', function(e) { + e.stopPropagation(); + }); + window.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && isSearchOpen) { + closeSearch(); + } + if ((e.ctrlKey || e.metaKey) && e.key === 'k') { + e.preventDefault(); + openSearch(); + } + }); + + // ============================================================================ + // Typesense 初始化(带重试限制) + // ============================================================================ + function initTypesense() { + if (isInitialized || searchInstance) { + console.warn('Typesense 搜索已初始化'); + return; + } + + var instantsearchLoaded = typeof instantsearch !== 'undefined'; + var adapterLoaded = typeof TypesenseInstantSearchAdapter !== 'undefined' || + typeof window.TypesenseInstantSearchAdapter !== 'undefined'; + + console.log('📦 依赖库检查 (' + (initRetryCount + 1) + '/' + MAX_INIT_RETRIES + '):'); + console.log(' instantsearch.js:', instantsearchLoaded ? '✅ 已加载' : '❌ 未加载'); + console.log(' TypesenseAdapter:', adapterLoaded ? '✅ 已加载' : '❌ 未加载'); + + if (!instantsearchLoaded || !adapterLoaded) { + initRetryCount++; + + if (initRetryCount >= MAX_INIT_RETRIES) { + console.error(''); + console.error('❌ Typesense 依赖库加载失败!'); + console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.error(''); + console.error('🔧 请检查 _config.butterfly.yml 配置:'); + console.error(''); + console.error('inject:'); + console.error(' bottom: # ⚠️ 使用 bottom 而不是 head'); + console.error(' - '); + console.error(' - '); + console.error(' - '); + console.error(''); + console.error('💡 或在控制台手动检查:'); + console.error(' typeof instantsearch'); + console.error(' typeof TypesenseInstantSearchAdapter'); + console.error(''); + console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + + showErrorMessage(); + return; + } + + console.warn('⏳ 100ms 后重试...'); + setTimeout(initTypesense, 100); + return; + } + + initRetryCount = 0; + console.log('🚀 开始初始化 Typesense...'); + + try { + const typesenseAdapter = new TypesenseInstantSearchAdapter({ + server: { + apiKey: CONFIG.apiKey, + nodes: [{ + host: CONFIG.server.host, + port: CONFIG.server.port, + protocol: CONFIG.server.protocol + }], + cacheSearchResultsForSeconds: 120 + }, + additionalSearchParameters: CONFIG.searchParams + }); + + searchInstance = instantsearch({ + searchClient: typesenseAdapter.searchClient, + indexName: CONFIG.indexName, + routing: false + }); + + searchInstance.addWidgets([ + instantsearch.widgets.searchBox({ + container: '#searchbox', + placeholder: CONFIG.ui.placeholder || '输入关键词寻找故事...', + autofocus: true, + showReset: true, + showSubmit: false, + showLoadingIndicator: true + }), + instantsearch.widgets.stats({ + container: '#stats', + templates: { + text: function(data) { + if (!data.query) return ''; + return '找到 ' + data.nbHits + ' 条结果 (' + data.processingTimeMS + 'ms)'; + } + } + }), + instantsearch.widgets.hits({ + container: '#hits', + templates: { + empty: function(results) { + return '
' + + '
' + + '
找不到与 "' + results.query + '" 相关的内容
' + + '
试试其他关键词吧 (´·ω·`)
' + + '
'; + }, + item: function(hit) { + // 使用 _highlightResult 获取高亮文本 + var titleHighlight = hit._highlightResult && hit._highlightResult.title + ? hit._highlightResult.title.value + : (hit.title || ''); + + var contentHighlight = hit._highlightResult && hit._highlightResult.content + ? hit._highlightResult.content.value + : (hit.content || ''); + + // 截取内容长度 + if (contentHighlight.length > 200) { + contentHighlight = contentHighlight.substring(0, 200) + '...'; + } + + return '' + + '
' + titleHighlight + '
' + + '
' + contentHighlight + '
' + + '
'; + } + } + }), + instantsearch.widgets.pagination({ + container: '#pagination', + padding: 2, + showFirst: false, + showLast: false + }) + ]); + + searchInstance.start(); + isInitialized = true; + + console.log('✅ Typesense 初始化成功!'); + + searchInstance.on('render', function() { + if (isSearchOpen) { + const input = document.querySelector('.ais-SearchBox-input'); + if (input && document.activeElement !== input) { + input.focus(); + } + } + }); + + } catch (error) { + console.error('❌ 初始化失败:', error); + showErrorMessage(); + } + } + + // ============================================================================ + // 初始化 + // ============================================================================ + function init() { + console.log('🔍 Typesense 搜索已准备就绪'); + console.log('💡 快捷键: Ctrl/Cmd + K'); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + + // ============================================================================ + // 全局接口 + // ============================================================================ + window.TypesenseSearch = { + open: openSearch, + close: closeSearch, + isOpen: function() { return isSearchOpen; }, + getInstance: function() { return searchInstance; } + }; + +})(); diff --git a/js/shuoshuo.js b/js/shuoshuo.js new file mode 100644 index 0000000..252d7b4 --- /dev/null +++ b/js/shuoshuo.js @@ -0,0 +1,363 @@ +function renderTalks() { + const talkContainer = document.querySelector('#talk'); + if (!talkContainer) return; + const config = window.SITE_CONFIG?.talks || {}; + talkContainer.innerHTML = ''; + const generateIconSVG = () => { + return ``; + } + const waterfall = (a) => { + function b(a, b) { + var c = window.getComputedStyle(b); + return parseFloat(c["margin" + a]) || 0 + } + + function c(a) { + return a + "px" + } + + function d(a) { + return parseFloat(a.style.top) + } + + function e(a) { + return parseFloat(a.style.left) + } + + function f(a) { + return a.clientWidth + } + + function g(a) { + return a.clientHeight + } + + function h(a) { + return d(a) + g(a) + b("Bottom", a) + } + + function i(a) { + return e(a) + f(a) + b("Right", a) + } + + function j(a) { + a = a.sort(function (a, b) { + return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a) + }) + } + + function k(b) { + f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a)) + } + "string" == typeof a && (a = document.querySelector(a)); + var l = [].map.call(a.children, function (a) { + return a.style.position = "absolute", a + }); + a.style.position = "relative"; + var m = []; + l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0])); + for (var n = 1; n < l.length; n++) { + var o = l[n - 1], + p = l[n], + q = i(o) + f(p) <= f(a); + if (!q) break; + p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p) + } + for (; n < l.length; n++) { + j(m); + var p = l[n], + r = m.pop(); + p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p) + } + j(m); + var s = m[0]; + a.style.height = c(h(s) + b("Bottom", s)); + var t = f(a); + window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k + }; + + const fetchAndRenderTalks = () => { + const url = config.apiUrl; + if (!url) return; + const cacheKey = config.cacheKey || 'talksCache'; + const cacheTimeKey = config.cacheTimeKey || 'talksCacheTime'; + const cacheDuration = config.cacheDurationMs || 30 * 60 * 1000; + const cachedData = localStorage.getItem(cacheKey); + const cachedTime = localStorage.getItem(cacheTimeKey); + const now = Date.now(); + + if (cachedData && cachedTime && (now - cachedTime < cacheDuration)) { + renderTalksList(JSON.parse(cachedData)); + } else { + fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ page: 1, pageSize: config.pageSize || 30 }) + }) + .then(res => res.json()) + .then(data => { + if (data.code === 1 && data.data && Array.isArray(data.data.items)) { + localStorage.setItem(cacheKey, JSON.stringify(data.data.items)); + localStorage.setItem(cacheTimeKey, now.toString()); + renderTalksList(data.data.items); + } + }) + .catch(err => console.error('Error fetching:', err)); + } + }; + + const renderTalksList = (list) => { + list.map(formatTalk).forEach(item => talkContainer.appendChild(generateTalkElement(item))); + waterfall('#talk'); + }; + + const withImageParams = (url) => { + if (!url) return ''; + try { + const parsedUrl = new URL(url, window.location.origin); + if (!parsedUrl.searchParams.has('fmt')) parsedUrl.searchParams.set('fmt', 'webp'); + if (!parsedUrl.searchParams.has('q')) parsedUrl.searchParams.set('q', '75'); + return parsedUrl.toString(); + } catch { + return url; + } + }; + + const getImageUrl = (image) => { + if (!image) return ''; + if (typeof image === 'string') return image; + return image.image_url || image.url || image.src || image.file?.url || ''; + }; + + const getTalkImages = (item) => { + if (Array.isArray(item.images) && item.images.length > 0) { + return item.images.map(getImageUrl).filter(Boolean); + } + + if (Array.isArray(item.echo_files) && item.echo_files.length > 0) { + return item.echo_files + .map((echoFile) => getImageUrl(echoFile.file || echoFile)) + .filter(Boolean); + } + + return []; + }; + + const formatTalk = (item) => { + const date = formatTime(item.created_at); + let content = item.content || ''; + content = content.replace(/\[(.*?)\]\((.*?)\)/g, `@$1`) + .replace(/- \[ \]/g, '⚪') + .replace(/- \[x\]/g, '⚫') + .replace(/\n/g, '
'); + content = `
${content}
`; + + // 图片 + const images = getTalkImages(item); + if (images.length > 0) { + const imgDiv = document.createElement('div'); + imgDiv.className = 'zone_imgbox'; + images.forEach(url => { + const imageUrl = withImageParams(url); + const link = document.createElement('a'); + link.href = imageUrl; + link.setAttribute('data-fancybox', 'gallery'); + link.className = 'fancybox'; + const imgTag = document.createElement('img'); + imgTag.src = imageUrl; + imgTag.loading = 'lazy'; + imgTag.alt = ''; + link.appendChild(imgTag); + imgDiv.appendChild(link); + }); + content += imgDiv.outerHTML; + } + + // 外链 / GitHub 项目 + // 外链 / GitHub 项目 + if (['WEBSITE', 'GITHUBPROJ'].includes(item.extension_type)) { + let siteUrl = '', title = ''; + let extensionBack = "https://pic.biss.click/image/1971bdc1-4349-4bb9-b683-20404f5da7d7.webp"; + + // 解析 extension 字段 + try { + const extObj = typeof item.extension === 'string' ? JSON.parse(item.extension) : item.extension; + siteUrl = extObj.site || extObj.url || item.extension; + title = extObj.title || siteUrl; + } catch { + siteUrl = item.extension; + title = siteUrl; + } + + // 特殊处理 GitHub 项目 + if (item.extension_type === 'GITHUBPROJ') { + extensionBack = "https://pic.biss.click/image/ed410d4e-d3f8-4b26-8840-50dd58f7dc4e.webp"; + + // 提取 GitHub 项目名 + const match = siteUrl.match(/^https?:\/\/github\.com\/[^/]+\/([^/?#]+)/i); + if (match) { + title = match[1]; // 获取仓库名 + } else { + // fallback:从最后一个路径段提取 + try { + const parts = new URL(siteUrl).pathname.split('/').filter(Boolean); + title = parts.pop() || siteUrl; + } catch { + // 如果 URL 无效则保留原始 + } + } + } + + // 输出 HTML 结构 + content += ` + `; + } + + + // 音乐 + if (item.extension_type === 'MUSIC' && item.extension) { + const link = item.extension; + let server = ''; + if (link.includes('music.163.com')) server = 'netease'; + else if (link.includes('y.qq.com')) server = 'tencent'; + const idMatch = link.match(/id=(\d+)/); + const id = idMatch ? idMatch[1] : ''; + if (server && id) { + content += ``; + } + } + + // 视频 + if (item.extension_type === 'VIDEO' && item.extension) { + const video = item.extension; + if (video.startsWith('BV')) { + const bilibiliUrl = `https://www.bilibili.com/blackboard/html5mobileplayer.html?bvid=${video}&as_wide=1&high_quality=1&danmaku=0`; + content += ` +
+ +
`; + } else { + const youtubeUrl = `https://www.youtube.com/embed/${video}`; + content += ` +
+ +
`; + } + } + + return { + content, + user: item.username || '匿名', + avatar: config.fallbackAvatar || window.SITE_CONFIG?.author?.avatar || '', + date, + location: '', + tags: Array.isArray(item.tags) && item.tags.length ? item.tags.map(t => t.name) : ['无标签'], + text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]') + }; + }; + + const generateTalkElement = (item) => { + const talkItem = document.createElement('div'); + talkItem.className = 'talk_item'; + + const talkMeta = document.createElement('div'); + talkMeta.className = 'talk_meta'; + const avatar = document.createElement('img'); + avatar.className = 'no-lightbox avatar'; + avatar.src = item.avatar; + + const info = document.createElement('div'); + info.className = 'info'; + const nick = document.createElement('span'); + nick.className = 'talk_nick'; + nick.innerHTML = `${item.user} ${generateIconSVG()}`; + const date = document.createElement('span'); + date.className = 'talk_date'; + date.textContent = item.date; + info.appendChild(nick); + info.appendChild(date); + talkMeta.appendChild(avatar); + talkMeta.appendChild(info); + + const talkContent = document.createElement('div'); + talkContent.className = 'talk_content'; + talkContent.innerHTML = item.content; + + const talkBottom = document.createElement('div'); + talkBottom.className = 'talk_bottom'; + const tags = document.createElement('div'); + const tag = document.createElement('span'); + tag.className = 'talk_tag'; + tag.textContent = `🏷️${item.tags}`; + //const loc = document.createElement('span'); + //loc.className = 'location_tag'; + //loc.textContent = `🌍${item.location}`; + tags.appendChild(tag); + //tags.appendChild(loc); + + const commentLink = document.createElement('a'); + commentLink.href = 'javascript:;'; + commentLink.onclick = () => goComment(item.text); + const icon = document.createElement('span'); + icon.className = 'icon'; + icon.innerHTML = ''; + commentLink.appendChild(icon); + + talkBottom.appendChild(tags); + talkBottom.appendChild(commentLink); + + talkItem.appendChild(talkMeta); + talkItem.appendChild(talkContent); + talkItem.appendChild(talkBottom); + + return talkItem; + }; + + const goComment = (e) => { + const match = e.match(/
([\s\S]*?)<\/div>/); + const textContent = match ? match[1] : ""; + const textarea = document.querySelector("#twikoo .el-textarea__inner"); + textarea.value = `> ${textContent}\n\n`; + textarea.focus(); + btf.snackbarShow("已为您引用该说说,不删除空格效果更佳"); + }; + + const formatTime = (time) => { + const timestamp = Number(time); + const d = Number.isFinite(timestamp) + ? new Date(timestamp < 100000000000 ? timestamp * 1000 : timestamp) + : new Date(time); + const pad = (n) => n.toString().padStart(2, '0'); + if (Number.isNaN(d.getTime())) return ''; + return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; + }; + + fetchAndRenderTalks(); +} + +renderTalks(); + +// function whenDOMReady() { +// const talkContainer = document.querySelector('#talk'); +// talkContainer.innerHTML = ''; +// fetchAndRenderTalks(); +// } +// whenDOMReady(); +// document.addEventListener("pjax:complete", whenDOMReady); diff --git a/js/shuoshuoshouye.js b/js/shuoshuoshouye.js new file mode 100644 index 0000000..282d976 --- /dev/null +++ b/js/shuoshuoshouye.js @@ -0,0 +1,103 @@ +let talkTimer = null; + +const cacheKey = 'talksCache'; +const cacheTimeKey = 'talksCacheTime'; +const cacheDuration = 30 * 60 * 1000; // 缓存有效期 30分钟 + +function indexTalk() { + if (talkTimer) { + clearInterval(talkTimer); + talkTimer = null; + } + + if (!document.getElementById('bber-talk')) return; + const config = window.SITE_CONFIG?.talks || {}; + const talksCacheKey = config.cacheKey || cacheKey; + const talksCacheTimeKey = config.cacheTimeKey || cacheTimeKey; + const talksCacheDuration = config.cacheDurationMs || cacheDuration; + + function toText(ls) { + return ls.map(item => { + let c = item.content || ''; + + const hasImg = /\!\[.*?\]\(.*?\)/.test(c); + const hasLink = /\[.*?\]\(.*?\)/.test(c); + + c = c + .replace(/#(.*?)\s/g, '') + .replace(/\{.*?\}/g, '') + .replace(/\!\[.*?\]\(.*?\)/g, '') + .replace(/\[.*?\]\(.*?\)/g, ''); + + const icons = []; + + if (item.images?.length && !hasImg) icons.push('fa-solid fa-image'); + if (item.extension_type === 'VIDEO') icons.push('fa-solid fa-video'); + if (item.extension_type === 'MUSIC') icons.push('fa-solid fa-music'); + if (item.extension_type === 'WEBSITE' && !hasLink) icons.push('fa-solid fa-link'); + if (item.extension_type === 'GITHUBPROJ' && !hasLink) icons.push('fab fa-github'); + + if (icons.length) c += ' ' + icons.map(i => ``).join(' '); + return c; + }); + } + + // 渲染与轮播 + function talk(ls) { + let html = ''; + ls.forEach((item, i) => { + html += `
  • ${item}
  • `; + }); + + let box = document.querySelector("#bber-talk .talk-list"); + if (!box) return; + + box.innerHTML = html; + + talkTimer = setInterval(() => { + if (box.children.length > 0) { + box.appendChild(box.children[0]); + } + }, 3000); + } + + const cachedData = localStorage.getItem(talksCacheKey); + const cachedTime = localStorage.getItem(talksCacheTimeKey); + const currentTime = new Date().getTime(); + + // 判断缓存是否有效 + if (cachedData && cachedTime && (currentTime - cachedTime < talksCacheDuration)) { + const data = toText(JSON.parse(cachedData)); + talk(data.slice(0, config.homeLimit || 6)); // 使用缓存渲染数据 + } else { + if (!config.apiUrl) return; + + fetch(config.apiUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ page: 1, pageSize: config.pageSize || 30 }) + }) + .then(res => res.json()) + .then(data => { + // 适配新版结构:code=1 且 data.items 存在 + if (data.code === 1 && data.data && Array.isArray(data.data.items)) { + localStorage.setItem(talksCacheKey, JSON.stringify(data.data.items)); + localStorage.setItem(talksCacheTimeKey, currentTime.toString()); + + const formattedData = toText(data.data.items); + talk(formattedData.slice(0, config.homeLimit || 6)); + } else { + console.warn('Unexpected API response format:', data); + } + }) + .catch(error => console.error('Error fetching data:', error)); + } +} + +// pjax 支持 +function whenDOMReady() { + indexTalk(); +} + +whenDOMReady(); +document.addEventListener("pjax:complete", whenDOMReady); diff --git a/js/weather.js b/js/weather.js new file mode 100644 index 0000000..c2876c6 --- /dev/null +++ b/js/weather.js @@ -0,0 +1,204 @@ +// weather.js + +(function () { + + const WEATHER_ID = "aside-weather"; + const LOCATION_WAIT_MS = 3000; + const LOCATION_POLL_INTERVAL_MS = 100; + const WEATHER_KEY = "70201debab4245379e376d116b05f8a2"; + const WEATHER_HOST = "https://ny5vxmbbkd.re.qweatherapi.com"; + const WEATHER_LANG = "zh"; + + function debugLog(label, payload) { + console.log(`[weather.js] ${label}`, payload); + } + + function maskSecret(secret) { + if (!secret || secret.length < 10) return secret; + return `${secret.slice(0, 4)}***${secret.slice(-4)}`; + } + + function readExistingLocation() { + return window.ipLocation?.result?.location || null; + } + + function waitForExistingLocation(timeout = LOCATION_WAIT_MS) { + return new Promise((resolve, reject) => { + const existingLocation = readExistingLocation(); + if (existingLocation) { + resolve(existingLocation); + return; + } + + const startTime = Date.now(); + const timer = window.setInterval(() => { + const location = readExistingLocation(); + if (location) { + window.clearInterval(timer); + resolve(location); + return; + } + + if (Date.now() - startTime >= timeout) { + window.clearInterval(timer); + reject(new Error("等待 ipLocation 超时")); + } + }, LOCATION_POLL_INTERVAL_MS); + }); + } + + function requestLocationFallback() { + return new Promise((resolve, reject) => { + if (!window.$?.ajax) { + reject(new Error("jQuery 未加载,无法执行定位兜底请求")); + return; + } + + $.ajax({ + type: 'get', + url: 'https://apis.map.qq.com/ws/location/v1/ip', + data: { + key: '你的key', + output: 'jsonp', + callback: '?', + }, + dataType: 'jsonp', + success: function (res) { + debugLog("定位兜底返回", res); + if (res && res.status === 0 && res.result?.location) { + window.ipLocation = res; + resolve(res.result.location); + } else { + reject(new Error("定位失败")); + } + }, + error: function () { + reject(new Error("定位请求失败")); + } + }); + }); + } + + function getLocation() { + return waitForExistingLocation() + .then(location => { + debugLog("使用 txmap.js 定位", location); + return location; + }) + .catch(error => { + debugLog("等待 txmap.js 定位失败,转兜底请求", error); + return requestLocationFallback(); + }); + } + + + function getWeather(lng, lat) { + const location = `${lng.toFixed(2)},${lat.toFixed(2)}`; + const params = new URLSearchParams({ + location, + lang: WEATHER_LANG, + key: WEATHER_KEY + }); + const requestUrl = `${WEATHER_HOST}/v7/weather/now?${params.toString()}`; + + debugLog("天气请求参数", { + host: WEATHER_HOST, + location, + lang: WEATHER_LANG, + key: maskSecret(WEATHER_KEY) + }); + + return fetch(requestUrl, { + headers: { + Accept: "application/json" + } + }).then(async res => { + const responseText = await res.text(); + let data; + + try { + data = JSON.parse(responseText); + } catch (parseError) { + debugLog("天气接口返回非 JSON", { + status: res.status, + statusText: res.statusText, + body: responseText + }); + throw new Error(`天气接口返回非 JSON,HTTP ${res.status}`); + } + + debugLog("天气接口响应", { + status: res.status, + statusText: res.statusText, + body: data + }); + + if (!res.ok) { + const detail = data?.error?.detail || data?.msg || `HTTP ${res.status}`; + throw new Error(`天气接口请求失败:${detail}`); + } + + return data; + }); + } + + + function renderWeather(data) { + const el = document.getElementById(WEATHER_ID); + if (!el) return; + + if (data.code !== "200") { + debugLog("天气接口业务错误", data); + el.innerHTML = `天气获取失败:${data.code || "未知错误"}`; + return; + } + + const now = data.now; + + const emojiMap = { + "晴": "☀️", + "多云": "⛅", + "阴": "☁️", + "雨": "🌧️", + "雪": "❄️", + "雷": "⛈️" + }; + + const emoji = emojiMap[now.text] || "🌡️"; + const weatherSentence = `当前天气${now.text},气温${now.temp}℃,体感${now.feelsLike}℃,${now.windDir}${now.windScale}级,湿度${now.humidity}%,能见度${now.vis}km。`; + + el.innerHTML = ` +
    +
    +
    +
    + ${weatherSentence} +
    +
    + `; + } + + + function initWeather() { + const el = document.getElementById(WEATHER_ID); + if (!el) return; + + el.innerHTML = "🌥️ 获取天气中..."; + + getLocation() + .then(loc => getWeather(loc.lng, loc.lat)) + .then(renderWeather) + .catch(err => { + console.error("[weather.js] 天气加载失败", err); + el.innerHTML = `🌧️ 天气加载失败:${err.message || "未知错误"}`; + }); + } + + + // 🚀 初次加载 + window.addEventListener("load", initWeather); + + // 🔄 pjax + document.addEventListener("pjax:complete", initWeather); + +})(); diff --git a/link/index.html b/link/index.html new file mode 100644 index 0000000..1544424 --- /dev/null +++ b/link/index.html @@ -0,0 +1,276 @@ + 友情链接 | Bi's Blog

    Bi's Blog

    友情链接

    把仍在发光的角落放在一起。

    友链申请

    +欢迎长期维护、内容原创、访问稳定的个人站点交换友链。你可以通过邮件发送站点名称、地址、头像和一句简介。 +

    • 站点名称:Bi's Blog
    • 站点地址:https://blog.biss.click/
    • 头像地址:https://blog.biss.click/images/Bi.ico
    • 站点简介:好奇心转化为代码的空间。
    \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..27f6aff --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,524 @@ + 文章 - 第 2 页 | Bi's Blog

    写给日常、技术和正在发生的自己

    Bi's Blog

    第 2 页,继续翻看最近的记录。

    最新文章

    查看全部

    安装gitea

    这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。

    添加typesense搜索

    这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。

    自定义字体

    这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。

    在侧边栏添加日历和倒计时

    实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。

    和朋友们的故事

    这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。

    Adguard和Openclash共存

    这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

    在Openwrt上安装AdguardHome

    这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。

    搭建Owncloud并集成Onlyoffice

    这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。

    为博客添加CMS系统

    这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。

    为1Panel添加自己想要的应用

    这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。

    使用GitHub推送Hexo到服务器

    这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。

    C# 基本语法

    这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。

    \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 0000000..c8d3347 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,524 @@ + 文章 - 第 3 页 | Bi's Blog

    写给日常、技术和正在发生的自己

    Bi's Blog

    第 3 页,继续翻看最近的记录。

    最新文章

    查看全部

    自定义页脚(新)

    您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。

    自定义分类条

    这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。

    自定义导航栏

    这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。

    博客添加AI总结

    这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。

    利用插件自定义页脚菜单

    这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。

    英语文章分享-去奋力生活吧,就好像死亡近在咫尺

    这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。

    自定义右键菜单

    这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。

    在侧边栏中添加欢迎信息

    这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。

    配置说说页面

    这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方

    部署历程

    这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。

    \ No newline at end of file diff --git a/posts/2b2fb1a7/index.html b/posts/2b2fb1a7/index.html new file mode 100644 index 0000000..cb49e4b --- /dev/null +++ b/posts/2b2fb1a7/index.html @@ -0,0 +1,677 @@ + 在Openwrt中安装OpenClash | Bi's Blog

    在Openwrt中安装OpenClash

    发布于 2025/09/03 技术 总字数:165 阅读时长:1 分钟

    这篇文章详细介绍了在OpenWrt系统中安装OpenClash的过程。作者通过Web管理页面和终端命令两种方式进行了安装,并强调了推荐启用Smart内核。安装完成后,作者提到了配置步骤,包括下载内核和导入配置文件,最后表达了对于zashboard界面简洁美观的个人喜好。整篇文章内容连贯,条理清晰,为读者提供了完整的安装指南。

    +AI摘要 + QWEN

    前言

    +

    因为一直想要实现从软路由上进行代理,所以买到了一个Cudy Tr3000刷系统折腾。用这篇文章记录一下安装的过程。

    +

    安装

    +

    可以通过web管理页面安装,搜索openclash。

    +
    image
    +

    当然也可以在终端中执行命令:

    +
    opkg update
    +opkg install bash iptables dnsmasq-full curl ca-bundle ipset ip-full iptables-mod-tproxy iptables-mod-extra ruby ruby-yaml kmod-tun kmod-inet-diag unzip luci-compat luci luci-base
    +opkg install luci-app-openclash
    +

    配置

    +

    首先需要下载内核,推荐启用Smart内核。

    +
    image
    +

    然后导入配置:

    +
    image
    +

    然后就可以开心使用啦! +感觉zashboard简洁好看一些,看自己感觉啦

    +
    image

    在Openwrt中安装OpenClash

    /posts/2b2fb1a7/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenwrtOpenclash
    订阅 打赏

    Comments

    在Openwrt中安装OpenClash

    \ No newline at end of file diff --git a/posts/33249733/index.html b/posts/33249733/index.html new file mode 100644 index 0000000..7303de2 --- /dev/null +++ b/posts/33249733/index.html @@ -0,0 +1,782 @@ + 自定义分类条 | Bi's Blog

    自定义分类条

    这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。

    +AI摘要 + QWEN

    修改有风险,请注意备份

    +今天来自定义分类条 +

    添加pug

    +

    新建文件[BlogRoot]\themes\butterfly\layout\includes\categoryBar.pug文件,写入:

    +
    .category-bar-items#category-bar-items(class=is_home() ? 'home' : '')
    +  .category-bar-item(class=is_home() ? 'select' : '', id="category-bar-home")
    +    a(href=url_for('/'))= __('博客首页')
    +  each item in site.categories.find({ parent: { $exists: false } }).data
    +    .category-bar-item(class=select ? (select === item.name ? 'select' : '') : '', id=item.name)
    +      a(href=url_for(item.path))= item.name
    +  .category-bar-item
    +    a(href=url_for('/archives/'))= __('文章存档')
    +div.category-bar-right
    +  a.category-bar-more(href=url_for('/categories/'))= __('更多分类')
    +

    样式文件

    +

    以上就是该滚动条的结构,下面我们开始实现样式的定义,新建文件[BlogRoot]\themes\butterfly\source\css\_layout\category-bar.styl写入以下文件:

    +
    #category-bar
    +  padding 7px 11px
    +  background var(--card-bg)
    +  border-radius 8px
    +  display flex
    +  white-space nowrap
    +  overflow hidden
    +  transition 0.3s
    +  height 50px
    +  width 100%
    +  justify-content space-between
    +  user-select none
    +  align-items center
    +  margin-bottom 20px
    +
    +  .category-bar-right
    +    display flex
    +    border-radius 8px
    +    align-items center
    +
    +    .category-bar-more
    +      margin-left 4px
    +      margin-right 4px
    +      font-weight 700
    +      border-radius 8px
    +      padding 0 8px
    +
    +  .category-bar-items
    +    width 100%
    +    white-space nowrap
    +    overflow-x scroll
    +    scrollbar-width: none
    +    -ms-overflow-style: none
    +    overflow-y hidden
    +    display flex
    +    border-radius 8px
    +    align-items center
    +    height 30px
    +
    +    &::-webkit-scrollbar
    +      display: none
    +
    +    .category-bar-item
    +      a
    +        padding .1rem .5rem
    +        margin-right 6px
    +        font-weight 700
    +        border-radius 8px
    +        display flex
    +        align-items center
    +        height 30px
    +
    +      &.select
    +        a
    +          background #3eb8be
    +          color var(--btn-color)
    +

    添加js

    +

    打开[BlogRoot]\themes\butterfly\source\js\main.js,添加js函数,refreshFn函数的上面:

    +
      const setCategoryBarActive = () => {
    +    const categoryBar = document.querySelector("#category-bar");
    +    const currentPath = decodeURIComponent(window.location.pathname);
    +    const isHomePage = currentPath === GLOBAL_CONFIG.root;
    +
    +    if (categoryBar) {
    +        const categoryItems = categoryBar.querySelectorAll(".category-bar-item");
    +        categoryItems.forEach(item => item.classList.remove("select"));
    +
    +        const activeItemId = isHomePage ? "category-bar-home" : currentPath.split("/").slice(-2, -1)[0];
    +        const activeItem = document.getElementById(activeItemId);
    +
    +        if (activeItem) {
    +            activeItem.classList.add("select");
    +        }
    +    }
    +};
    +

    引用函数

    +
    window.refreshFn = function () {
    +  initAdjust()
    +
    +  if (GLOBAL_CONFIG_SITE.isPost) {
    +    GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice()
    +    GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time'))
    +  } else {
    +    GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time'))
    +    GLOBAL_CONFIG.runtime && addRuntime()
    +    addLastPushDate()
    +    toggleCardCategory()
    +    setCategoryBarActive()      // 自己加的,用于切换类别栏目
    +  }
    +

    添加到主题

    +

    修改[BlogRoot]\themes\butterfly\layout\includes\mixins\indexPostUI.pug,添加

    +
    mixin indexPostUI()
    +  - const indexLayout = theme.index_layout
    +  - const masonryLayoutClass = (indexLayout === 6 || indexLayout === 7) ? 'masonry' : ''
    +  #recent-posts.recent-posts.nc(class=masonryLayoutClass)
    ++    #category-bar.category-bar
    ++      include ../categoryBar.pug 
    +    .recent-post-items
    +

    参考

    + + + + liushen博客 + Liushen · blog.liushen.fun + +

    自定义分类条

    /posts/33249733/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    自定义分类条

    \ No newline at end of file diff --git a/posts/34725d47/index.html b/posts/34725d47/index.html new file mode 100644 index 0000000..2f6fbe2 --- /dev/null +++ b/posts/34725d47/index.html @@ -0,0 +1,736 @@ + 安装gitea | Bi's Blog

    安装gitea

    发布于 2026/02/07 技术 总字数:516 阅读时长:2 分钟

    这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。

    +AI摘要 + QWEN

    今天想把网站的源码转移到自建git仓,所以先来安装gitea吧(gitlab过于庞大,服务器配置不够) +PS:我的服务器为2C2G

    +

    安装gitea

    +

    这里用二进制文件安装

    +

    获取二进制文件:

    +
    wget -O gitea https://dl.gitea.com/gitea/1.25.4/gitea-1.25.4-linux-amd64
    +chmod +x gitea
    +cp gitea /usr/local/bin/gitea
    +

    创建用户

    +

    这一步不是必须的,但是推荐这样,用root用户很容易出问题。

    +
    # On Ubuntu/Debian:
    +adduser \
    +   --system \
    +   --shell /bin/bash \
    +   --gecos 'Git Version Control' \
    +   --group \
    +   --disabled-password \
    +   --home /home/git \
    +   git
    +
    +# On Fedora/RHEL/CentOS:
    +groupadd --system git
    +adduser \
    +   --system \
    +   --shell /bin/bash \
    +   --comment 'Git Version Control' \
    +   --gid git \
    +   --home-dir /home/git \
    +   --create-home \
    +   git
    +

    创建工作目录

    +
    mkdir -p /var/lib/gitea/{custom,data,log}
    +chown -R git:git /var/lib/gitea/
    +chmod -R 750 /var/lib/gitea/
    +mkdir /etc/gitea
    +chown root:git /etc/gitea
    +chmod 770 /etc/gitea
    +chmod 750 /etc/gitea
    +chmod 640 /etc/gitea/app.ini
    +

    创建系统服务

    +

    直接把github上面的挪过来就可以

    + + + + service文件 + github · github.com + + +

    然后注册服务并启动

    +
    sudo systemctl enable gitea
    +sudo systemctl start gitea
    +

    创建数据库

    +

    可以用MySQL数据库或者PostgreSQL,创建一个数据库在web页面填写进去就行。

    +

    反向代理略过,和普通网站的反向代理配置没有什么区别。

    +

    安装runner

    +

    这个runner也不是必须的,是为了实现github的action功能;在2C2G服务器上我看运行的还可以,当然,只是这个hexo博客的自动构建,占用资源也少; +使用doker,这也是官方建议。以下是compose文件:

    +
    
    +services:
    +  runner:
    +    image: gitea/act_runner:latest
    +    ports:
    +      - 8088:8088
    +    environment:
    +      - CONFIG_FILE=/config.yaml
    +      - GITEA_INSTANCE_URL=https://git.biss.click
    +      - GITEA_RUNNER_REGISTRATION_TOKEN=  #替换成自己的token
    +    volumes:
    +      - ./config.yaml:/config.yaml
    +      - ./data:/data
    +      - /var/run/docker.sock:/var/run/docker.sock # 允许 Runner 调用宿主机 Docker
    +
    +

    token在管理后台 工作流-运行器-新建运行器获取 +config文件需要这样生成

    +
    docker run --entrypoint="" --rm -it docker.io/gitea/act_runner:latest act_runner generate-config > config.yaml
    +

    在后台工作流运行器可以看见就没问题了。

    安装gitea

    /posts/34725d47/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    gitea
    订阅 打赏

    Comments

    安装gitea

    \ No newline at end of file diff --git a/posts/3a26a97a/index.html b/posts/3a26a97a/index.html new file mode 100644 index 0000000..de7b391 --- /dev/null +++ b/posts/3a26a97a/index.html @@ -0,0 +1,849 @@ + 为博客添加CMS系统 | Bi's Blog

    为博客添加CMS系统

    发布于 2025/08/26 建站手札 总字数:579 阅读时长:2 分钟

    这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。

    +AI摘要 + QWEN

    序言

    +

    因为喜欢wordpress之类动态博客的在线编辑功能,但是又不想抛弃hexo,特别是这个花里胡哨的模板,所以想着给博客添加一个cms系统,在搜索一番后发现这种CMS系统叫无头CMS,然后找到一个很好的系统:sveltia-cms

    + + + + sveltia-cms + sveltia · github.com + + +

    安装

    +

    安装包

    +

    在博客根目录下运行npm i @sveltia/cms

    +

    创建必要文件

    +

    然后在博客source目录下创建admin目录 +并在其下新建index.html

    +
    <!doctype html>
    +<html>
    +<head>
    +  <meta charset="utf-8" />
    +  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    +  <meta name="robots" content="noindex" />
    +  <title>博客管理</title>
    +</head>
    +<body>
    +  <script src="https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"></script>
    +</body>
    +</html>
    +

    新建config.yml

    +
    # https://decapcms.org/docs/configuration-options/
    +backend:
    +  name: github
    +  repo:  #博客存储库
    +  branch: #分支
    +  auth_type: 
    +  app_id:  
    +media_folder: "folder"
    +public_folder: "folder"
    +site_url: ""
    +logo_url: ""
    +locale: "zh_Hans"
    +common_col_conf: &common_col_conf
    +  create: true
    +  slug: "{{fields.filename}}"
    +  sortable_fields:
    +    - "commit_date"
    +    - "title"
    +    - "date"
    +    - "updated"
    +  # https://decapcms.org/docs/widgets/
    +  fields:
    +    - label: "文件名"
    +      name: "filename"
    +      widget: "string"
    +    - label: "标题"
    +      name: "title"
    +      widget: "string"
    +    - label: "发表日期"
    +      name: "date"
    +      widget: "datetime"
    +      format: "YYYY-MM-DD HH:mm:ss"
    +      date_format: "YYYY-MM-DD"
    +      time_format: "HH:mm:ss"
    +    - label: "更新日期"
    +      name: "updated"
    +      widget: "datetime"
    +      format: "YYYY-MM-DD HH:mm:ss"
    +      date_format: "YYYY-MM-DD"
    +      time_format: "HH:mm:ss"
    +      required: false
    +    - label: "封面"
    +      name: "cover"
    +      widget: "image"
    +      required: false
    +    - label: "标签"
    +      name: "tags"
    +      widget: "select"
    +      multiple: true
    +      required: false
    +      options:
    +        - "amazon"
    +        - "android"
    +        - "angularjs"
    +        - "azure"
    +        - "cdn"
    +        - "chartjs"
    +        - "chrome"
    +        - "csharp"
    +        - "css"
    +        - "devops"
    +        - "diary"
    +        - "docker"
    +        - "edge"
    +        - "git"
    +        - "github"
    +        - "hexo"
    +        - "html"
    +        - "icarus"
    +        - "java"
    +        - "js"
    +        - "life"
    +        - "material"
    +        - "mysql"
    +        - "nodejs"
    +        - "onedrive"
    +        - "oneindex"
    +        - "php"
    +        - "restapi"
    +        - "security"
    +        - "serverless"
    +        - "shadowdefender"
    +        - "tool"
    +        - "twikoo"
    +        - "ubuntu"
    +        - "vagrant"
    +        - "vb"
    +        - "vite"
    +        - "vue"
    +        - "webpack"
    +        - "windows"
    +        - "xlsx"
    +        - "小程序"
    +    - label: "分类"
    +      name: "categories"
    +      widget: "select"
    +      multiple: true
    +      required: false
    +      options:
    +        - "Diary"
    +        - "Tool"
    +        - "Tech"
    +        - "FrontEnd"
    +        - "BackEnd"
    +        - "Windows"
    +        - "Android"
    +        - "Linux"
    +        - "Serverless"
    +    - label: "正文"
    +      name: "body"
    +      widget: "markdown"
    +    - label: "原创"
    +      name: "toc"
    +      widget: "boolean"
    +      default: true
    +    - label: "评论"
    +      name: "comments"
    +      widget: "boolean"
    +      default: true
    +collections:
    +  - name: "2023"
    +    label: "2023"
    +    folder: "posts/2023"
    +    preview_path: "2023/{{filename}}/"
    +    <<: *common_col_conf
    +  - name: "pages"
    +    label: "Pages"
    +    files:
    +      - name: "friends"
    +        label: "友链"
    +        file: "source/friends/index.md"
    +        preview_path: "friends/"
    +        <<: *common_col_conf
    +      - name: "about"
    +        label: "关于"
    +        file: "source/about/index.md"
    +        preview_path: "about/"
    +        <<: *common_col_conf
    +

    需要自己修改一些配置; +一般到这一步就可以了,但是因为我使用自己的服务器,没办法用官方的auth系统,所以还要进一步配置。

    +

    创建auth系统、授权

    + + + + Sveltia CMS Authenticator + Sveltia CMS Authenticator · github.com + + +比较简单,直接Cloudflare一键部署就可以 +然后要在github新建一个oauth应用,就不写了,可以自己查找。 +然后在cloudflare新建环境变量: +GITHUB_CLIENT_ID: +GITHUB_CLIENT_SECRET: +然后在config.yml添加 +
     backend:
    +   name: github # or gitlab
    +   repo: username/repo
    +   branch: main
    +   base_url: <YOUR_WORKER_URL>
    +

    大功告成

    +

    访问博客的admin就可以了

    为博客添加CMS系统

    /posts/3a26a97a/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    为博客添加CMS系统

    \ No newline at end of file diff --git a/posts/3e61a389/index.html b/posts/3e61a389/index.html new file mode 100644 index 0000000..008630b --- /dev/null +++ b/posts/3e61a389/index.html @@ -0,0 +1,963 @@ + 自定义导航栏 | Bi's Blog

    自定义导航栏

    这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。

    +AI摘要 + QWEN

    最近在查找怎样自定义导航栏,奈何没有对应版本的修改教程,本站使用最新版butterfly5.4。

    +

    PC 菜单栏

    +

    修改[blogRoot]\themes\Butterfly\layout\includes\header\nav.pug的内容

    +
    nav#nav
    +  span#blog-info
    +    a(href=url_for('/') title=config.title)
    +      if theme.nav.logo
    +        img.site-icon(src=url_for(theme.nav.logo))
    +      if theme.nav.display_title
    +        span.site-name=config.title
    +    
    +  #menus
    +-    if (theme.algolia_search.enable || theme.local_search.enable)
    +-      #search-button
    +-        a.site-page.social-icon.search
    +-          i.fas.fa-search.fa-fw
    +-          span=' '+_p('search.title')
    +  !=partial('includes/header/menu_item', {}, {cache: true})
    ++  #nav-right
    ++    if (theme.algolia_search.enable || theme.local_search.enable)
    ++      #search-button
    ++        a.site-page.social-icon.search
    ++          i.fas.fa-search.fa-fw
    ++    #toggle-menu
    ++      a.site-page
    ++        i.fas.fa-bars.fa-fw
    ++       #toggle-menu
    ++         a.site-page
    ++           i.fas.fa-bars.fa-fw
    +

    新建css文件

    +
    #nav-right{
    +  flex:1 1 auto;
    +  justify-content: flex-end;
    +  margin-left: auto;
    +  display: flex;
    +  flex-wrap:nowrap;
    +}
    +  
    +/* 导航栏居中 */
    +#sidebar #sidebar-menus .menus_items .menus_item {
    +    margin: 10px 0;
    +}
    +#sidebar #sidebar-menus .menus_items a.site-page {
    +    padding-left: 0;
    +}
    +#sidebar #sidebar-menus .menus_items .site-page {
    +    position: relative;
    +    display: block;
    +    padding: 6px 30px 6px 22px;
    +    color: var(--font-color);
    +    font-size: 1.15em;
    +    border: var(--style-border-always);
    +    background: var(--icat-card-bg);
    +    font-size: 14px;
    +    border-radius: 12px;
    +}
    +#sidebar #sidebar-menus .menus_items .site-page i:first-child {
    +    text-align: left;
    +    padding-left: 10px;
    +}
    +

    PE端

    +

    修改[blogRoot]/themes/butterfly/layout/includes/header/menu_item.pug内容。

    +
    if theme.menu
    +  .menus_items
    +    each value, label in theme.menu
    +      if typeof value !== 'object'
    +-       .menus_item
    ++        .icat_menus_item
    +          - const valueArray = value.split('||')
    +          a.site-page(href=url_for(trim(valueArray[0])))
    +
    +    ···
    +

    修改[blogRoot]/themes/butterfly/source/css/_layout/head.styl 内容

    +
        ···
    +
    +  .menus_items
    +    display: inline
    +
    +    .menus_item
    ++    .icat_menus_item
    +      position: relative
    +      display: inline-block
    +      padding: 0 0 0 14px
    +
    +      &:hover
    +        .menus_item_child
    +          display: block
    +
    +    ···
    +

    修改[blogRoot]/themes/butterfly/source/css/_layout/sidebar.styl内容

    +
    
    +    ···
    +
    +    .menus_items
    +      padding: 0 10px 40px
    +
    ++      .menus_item
    ++        margin: 10px 0
    +
    +      .site-page
    +        @extend .limit-one-line
    +        position: relative
    +        display: block
    +        padding: 6px 30px 6px 22px
    +        color: var(--font-color)
    +        font-size: 1.15em
    ++        border: var(--icat-style-border-always)
    ++        background: var(--icat-essay-card-bg)
    ++        font-size: 14px
    ++        border-radius: 12px
    +
    +        &:hover
    +          background: var(--text-bg-hover)
    +
    +        i:first-child
    +          width: 15%
    +          text-align: left
    ++          padding-left: 10px
    +
    +        &.group
    +          & > i:last-child
    +            position: absolute
    +            top: .78em
    +            right: 18px
    +            transition: transform .3s
    +
    +          &.hide
    +            & > i:last-child
    +              transform: rotate(90deg)
    +
    +            & + .menus_item_child
    +              display: none
    +
    +      .menus_item_child
    +        margin: 0
    +        list-style: none
    +        padding-top: 6px
    +
    ++#sidebar
    ++  #sidebar-menus
    ++    .icat_menus_item
    ++      display: inline-block
    ++      width: 50%
    ++
    ++      .site-page
    ++        text-align: center
    ++        margin: 4px
    ++        display: flex
    ++        flex-direction: column
    ++        align-items: center
    ++        padding: 8px 0
    ++        border-radius: 12px
    ++        font-size: 14px
    ++
    ++        i:first-child
    ++          padding-left: 0
    ++
    ++        & > .icat-essay
    ++          font-weight: 500
    ++
    ++        span
    ++          margin-top: -8px
    +

    在css文件中添加

    +
    #sidebar #sidebar-menus .menus_items .menus_item_child {
    +    display: -webkit-box;
    +    display: -moz-box;
    +    display: -webkit-flex;
    +    display: -ms-flexbox;
    +    display: box;
    +    display: flex;
    +    -webkit-box-orient: horizontal;
    +    -moz-box-orient: horizontal;
    +    -o-box-orient: horizontal;
    +    -webkit-flex-direction: row;
    +    -ms-flex-direction: row;
    +    flex-direction: row;
    +    -webkit-box-lines: multiple;
    +    -moz-box-lines: multiple;
    +    -o-box-lines: multiple;
    +    -webkit-flex-wrap: wrap;
    +    -ms-flex-wrap: wrap;
    +    flex-wrap: wrap;
    +}
    +#sidebar #sidebar-menus .menus_items a.site-page, #sidebar .menus_item ul {
    +    padding-left: 0;
    +}
    +#sidebar #sidebar-menus .menus_item_child li {
    +    width: calc(50% - 8px);
    +    margin: 4px;
    +}
    +#sidebar #sidebar-menus .menus_item_child .site-page.child {
    +    display: -webkit-box;
    +    display: -moz-box;
    +    display: -webkit-flex;
    +    display: -ms-flexbox;
    +    display: box;
    +    display: flex;
    +    -webkit-box-orient: vertical;
    +    -moz-box-orient: vertical;
    +    -o-box-orient: vertical;
    +    -webkit-flex-direction: column;
    +    -ms-flex-direction: column;
    +    flex-direction: column;
    +    -webkit-box-align: center;
    +    -moz-box-align: center;
    +    -o-box-align: center;
    +    -ms-flex-align: center;
    +    -webkit-align-items: center;
    +    align-items: center;
    +    padding: 8px;
    +    border-radius: 12px;
    +    border: var(--style-border-always);
    +    background: var(--icat-card-bg);
    +    font-size: 14px;
    +}
    +#nav.hide-menu #toggle-menu {
    +    padding: 0 0 0 12px;
    +}
    +#sidebar #sidebar-menus .icat_menus_item .site-page {
    +    text-align: center;
    +    margin: 4px;
    +    display: -webkit-box;
    +    display: -moz-box;
    +    display: -webkit-flex;
    +    display: -ms-flexbox;
    +    display: box;
    +    display: flex;
    +    -webkit-box-orient: vertical;
    +    -moz-box-orient: vertical;
    +    -o-box-orient: vertical;
    +    -webkit-flex-direction: column;
    +    -ms-flex-direction: column;
    +    flex-direction: column;
    +    -webkit-box-align: center;
    +    -moz-box-align: center;
    +    -o-box-align: center;
    +    -ms-flex-align: center;
    +    -webkit-align-items: center;
    +    align-items: center;
    +    padding: 8px 0;
    +    border-radius: 12px;
    +    font-size: 14px;
    +}
    +@media screen and (max-width: 768px) {
    +    #sidebar #sidebar-menus .menus_items .site-page.group > i:last-child {
    +        margin-top: 4px;
    +    }
    +    #sidebar #sidebar-menus .menus_items .icat_menus_item .site-page i:first-child {
    +        text-align: center;
    +        padding-left: 0;
    +    }
    +}
    +/* 子菜单美化 */
    +
    +#sidebar .open > .avatar-img img {
    +    height: 100px;
    +    width: 100px;
    +    border-radius: 50%;
    +}
    +#sidebar .open > .avatar-img img {
    +    border: 5px #fff solid;
    +}
    +#sidebar .open > .avatar-img::before {
    +    bottom: 8px;
    +    height: 12px;
    +    width: 12px;
    +}
    +#sidebar .open > .avatar-img::before {
    +    content: '';
    +    position: absolute;
    +    left: 50%;
    +    transform: translate(65%);
    +    background: #6bdf8f;
    +    border: 5px solid #fff;
    +    border-radius: 50%;
    +    transition: filter 375ms ease-in .2s,transform .3s;
    +    z-index: 2;
    +}
    +/* 头像美化 */
    +
    +#sidebar #sidebar-menus .sidebar-site-data {
    +    padding: 0;
    +    margin-left: 10px;
    +    background: var(--icat-card-bg);
    +    border-radius: 12px;
    +    border: var(--style-border-always);
    +}
    +@media screen and (max-width: 768px) {
    +    .site-data {
    +        width: 94%;
    +    }
    +}
    +/* 侧边栏的统计栏美化 */
    +  
    +/* PE端菜单栏美化 */
    +

    参考

    + + + + Meuicat的博客 + Meuicat · meuicat.com + +

    自定义导航栏

    /posts/3e61a389/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    自定义导航栏

    \ No newline at end of file diff --git a/posts/3f8de22/index.html b/posts/3f8de22/index.html new file mode 100644 index 0000000..955a58e --- /dev/null +++ b/posts/3f8de22/index.html @@ -0,0 +1,744 @@ + GPT Image2 初试 | Bi's Blog

    GPT Image2 初试

    发布于 2026/04/25 技术 总字数:1.9k 阅读时长:5 分钟

    这篇文章介绍了作者最近试用gpt img2之后的一些直观感受。文章没有从技术角度展开,而是以普通用户的体验为主,聊了它适合做什么、哪些地方比以前的AI绘图更顺手、哪些地方依然不太稳定,以及在日常做头像、壁纸、封面图和改图时的一些实际体会。整体来看,它确实比以前更听话一些,但也还没有到可以完全省心的地步。

    +AI摘要 + QWEN

    最近看到不少人在说 gpt img2 ,我也去试了试。
    +先说结论,确实比我以前随手玩过的一些 AI 画图工具要顺手,尤其是在“听懂人话”这件事上,感觉明显好了不少。 +我生成了几张图: +图一 +图二 +不过它也没有神到那种地步,不是说你脑子里想什么,它就百分百给你画什么。更多时候,我会觉得它像一个比较聪明、但偶尔还是会跑偏的画图助手。

    +

    我为什么会去试这个

    +

    其实我平时并不是很重度用 AI 画图的人,最多也就是做这几种东西:

    +
      +
    • 文章封面图
    • +
    • 头像
    • +
    • 壁纸
    • +
    • 一些网页装饰用的小图
    • +
    • 改改现成图片
    • +
    +

    以前我对这类工具最大的感受就是,能画,但是不稳定。
    +有时候第一张就很好,有时候连续改十几次都不对味。尤其是你要求稍微细一点,它就很容易开始自由发挥。

    +

    这次去试 gpt img2 ,我本来也是抱着“看看是不是又换了个名字继续宣传”的想法,结果实际用下来,至少第一印象还可以。

    +

    第一感觉就是更听话了

    +

    这个是我最直接的感受。

    +

    以前很多 AI 画图工具,你写一大段描述,它看起来像是认真读了,但实际生成出来,总有一种“只挑了几句重点听”的感觉。
    +比如你明明说的是干净背景、淡色、安静一点的氛围,它最后给你来一张花里胡哨的图。

    +

    gpt img2 相对来说,会更愿意按照你写的意思去靠。

    +

    当然,也不是说它就完全不会乱来,而是那种“离题太远”的情况少了一点。
    +普通用户用的时候,这个差别其实挺明显的,因为很多时候我们并不想研究什么复杂提示词,只想把自己脑子里的画面尽量说清楚。

    +

    比较适合我这种普通人的地方

    +

    我自己觉得,它最适合的不是那种特别专业的商业设计,而是下面这些日常场景:

    +

    做头像和壁纸

    +

    这个是最轻松的。

    +

    你只要把风格、颜色、主体大概说一下,一般都能出个八九不离十的结果。
    +比如想要一个偏安静、偏插画风的头像,或者想做一个简洁一点的手机壁纸,这种它完成得还不错。

    +

    而且有一个很现实的问题,头像和壁纸对“绝对精确”要求没那么高,所以就算它有一点点小偏差,也不太影响使用。

    +

    做文章封面图

    +

    这个我感觉也挺适合。

    +

    尤其是写博客的时候,有时候正文已经写完了,但就是不想花太多时间去找图。
    +这个时候让它给一张气质差不多的配图,其实很省事。

    +

    不过文章封面图有个问题,就是如果你想让它在图里放很准确的中文文字,那还是不要太指望。
    +拿来做氛围图可以,拿来直接当完整海报,我觉得还差一点意思。

    +

    改图比纯出图更有意思

    +

    这个是我试完之后觉得比较有趣的一点。

    +

    以前我对 AI 画图更多是“从零生成”,但这次明显感觉“改现成图”会更实用一些。
    +比如:

    +
      +
    • 背景不喜欢,换一下
    • +
    • 色调不喜欢,柔和一点
    • +
    • 构图太满了,简洁一点
    • +
    • 想保留主体,只改周围环境
    • +
    +

    这种比从头重新生成方便很多。
    +因为你本来就已经有一个大致方向了,只是想在它基础上继续修。

    +

    它并不是每次都稳定

    +

    虽然前面夸了不少,但还是得说,AI 画图这东西目前依然带一点玄学。

    +

    有时候第一张就很顺眼,甚至顺眼到让我怀疑“是不是今天运气太好了”。
    +但有时候同样一句话,换一次结果就完全不是那个味。

    +

    所以如果一定要我给个很真实的评价,我会觉得:

    +

    它比以前更容易出到“能用的图”
    +但还没有到“你完全不用挑图”的程度

    +

    这两个感觉差别还是挺大的。

    +

    文字还是不要太相信

    +

    这个基本算是我对所有 AI 出图工具的统一评价了。

    +

    如果只是画面里带一点很简单的小字,也许能凑合看看。
    +但你要是想让它直接给你做一张字很多、排版还工整的海报,我感觉还是容易翻车。

    +

    尤其是中文,很多时候看着像那么回事,细看就不是很对。
    +所以如果只是做背景图、插图、封面图,这个没什么问题。
    +但如果是正式一点的图文海报,我还是更愿意后面自己补字。

    +

    最麻烦的还是“差一点点”

    +

    我觉得 AI 画图最折磨人的地方,从来都不是完全不行,而是它经常会给你一张“差一点点”的图。

    +

    比如:

    +
      +
    • 整体很好,但手不对
    • +
    • 脸差不多,但眼神怪怪的
    • +
    • 背景挺好,但颜色太脏
    • +
    • 构图不错,但多出来几个没必要的东西
    • +
    +

    这种图你删了可惜,用了又不甘心。
    +然后你就会进入一个“再改一下应该就好了吧”的循环。

    +

    所以我现在反而会提醒自己,别太贪。
    +差不多能用的时候就停,不然改着改着,很容易把原来那张还不错的也改没了。

    +

    最后

    +

    反正我试下来,对 gpt img2 的感觉大概就是:

    +

    它不是那种会让我惊呼“以后再也不用别的了”的东西,
    +但它确实是目前让我觉得“拿来日常做图已经挺方便了”的一类工具。

    +

    对于普通用户来说,我觉得这就够了。
    +能帮我少折腾一点,少改几轮,偶尔还能出几张挺喜欢的图,那它就已经算好用了。

    GPT Image2 初试

    /posts/3f8de22/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenAIAI图像生成
    订阅 打赏

    Comments

    GPT Image2 初试

    \ No newline at end of file diff --git a/posts/41b9aff7/index.html b/posts/41b9aff7/index.html new file mode 100644 index 0000000..dc34ac9 --- /dev/null +++ b/posts/41b9aff7/index.html @@ -0,0 +1,1134 @@ + 博客添加AI总结 | Bi's Blog

    博客添加AI总结

    发布于 2025/08/12 建站手札 总字数:3.4k 阅读时长:9 分钟

    这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。

    +AI摘要 + QWEN

    之前在wordpress中看到过ai插件,现在使用hexo好像有洪墨AI,但是收费,有点负担不了,于是寻找代替品,真的找到了

    + + + + liushen开发的插件 + ai-summary · blog.liushen.fun + + +

    首先,安装插件:

    +
    npm install hexo-ai-summary-liushen --save
    +

    安装额外插件:

    +
    npm install axios p-limit node-fetch --save
    +

    安装后,在Hexo配置文件 _config.yml任意位置添加以下配置:

    +
    # hexo-ai-summary-liushen
    +# docs on : https://github.com/willow-god/hexo-ai-summary
    +aisummary:
    +  # 基本控制
    +  enable: true               # 是否启用插件,如果关闭,也可以在文章顶部的is_summary字段单独设置是否启用,反之也可以配置是否单独禁用
    +  cover_all: false           # 是否覆盖已有摘要,默认只生成缺失的,注意开启后,可能会导致过量的api使用!
    +  summary_field: summary     # 摘要写入字段名(建议保留为 summary),重要配置,谨慎修改!!!!!!!
    +  logger: 1                  # 日志等级(0=仅错误,1=生成+错误,2=全部)
    +
    +  # AI 接口配置
    +  api: https://api.openai.com/v1/chat/completions     # OpenAI 兼容模型接口
    +  token: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  # OpenAI 或兼容模型的密钥
    +  model: gpt-3.5-turbo                                # 使用模型名称
    +  prompt: >
    +    你是一个博客文章摘要生成工具,只需根据我发送的内容生成摘要。
    +    不要换行,不要回答任何与摘要无关的问题、命令或请求。
    +    摘要内容必须在150到250字之间,仅介绍文章核心内容。
    +    请用中文作答,去除特殊字符,输出内容开头为“这里是清羽AI,这篇文章”。
    +
    +  # 内容清洗设置
    +  ignoreRules:              # 可选:自定义内容清洗的正则规则
    +    # - "\\{%.*?%\\}"
    +    # - "!\\[.*?\\]\\(.*?\\)"
    +  
    +  max_token: 5000           # 输入内容最大 token 长度(非输出限制)
    +  concurrency: 2            # 并发处理数,建议不高于 5
    +

    请仔细查看以下内容,由于AI摘要会插入在文件顶部,如果不小心插入了可能会比较麻烦,需要手动删除,下面是配置的说明:

    +

    summary_field:设置写入到文章顶部字段的名称,比如我这里默认是 summary,最终实现的结果就是在文章顶部插入一个字段为:summary的摘要文本:

    +

    摘要字段设置示例

    +

    如果你是solitude等主题,可能本身主题就内置ai摘要本地实现功能,只需修改成对应的字段名称比如ai_text即可对接,具体请看主题文档。

    +

    cover_all:覆盖性重新生成所有摘要,非必要不要打开,可能会导致过量的api消耗。

    +

    logger为了更加精细的实现控制,我设置了三个日志等级,如下划分:

    +

    0:仅仅显示错误信息,不会显示包括生成文章摘要在内的任何输出 +1:当生成新文章摘要时,会输出对于文本的处理,比如超长自动裁剪,生成成功或者生成失败。 +2:调试使用,会输出包括跳过所有页面信息,仅仅处理文章部分。 +api:任何openai类型接口,包括deepseek,讯飞星火,腾讯混元,ChatGPT等。

    +

    token:api对应的接口密钥。

    +

    model:使用的模型名称,请检查对应接口文档说明,不同接口包含的模型不一致。

    +

    prompt:提示词,请自行定制,建议详细一些,但是不要太废话,以我写的为例。

    +

    ignoreRules:忽略文本正则接口,由于本插件直接获取Markdown文本,内置了一些处理,但是你仍然可以进行额外的处理,下面是内置的文本处理规则,如果有兴趣进行修改可以进行参考:

    +
    // 2. 清理内容
    +  content = content
    +    .replace(/```[\s\S]*?```/g, '')           // 代码块
    +    // .replace(/`[^`\n]+`/g, '')                // 行内代码
    +    .replace(/{%[^%]*%}/g, '')                // Hexo 标签
    +    .replace(/^\|.*?\|.*$/gm, '')             // 表格行
    +    .replace(/!\[.*?\]\(.*?\)/g, '')          // 图片
    +    .replace(/\[(.*?)\]\(.*?\)/g, '$1')       // 超链接文本
    +    .replace(/<[^>]+>/g, '')                  // HTML 标签
    +    .replace(/ /g, ' ')                  // 空格实体
    +    .replace(/\n{2,}/g, '\n')                 // 多重换行压缩
    +    .replace(/^\s+|\s+$/gm, '')               // 行首尾空格
    +    .replace(/[ \t]+/g, ' ')                  // 多空格压缩
    +    .trim();
    +
    +  // 3. 拼接标题
    +  const combined = (title ? title.trim() + '\n\n' : '') + content;
    +

    但是大部分情况可以忽略这个配置项,留空即可。

    +

    max_token:限制模型输入的最大字数,用字符串的slice进行截断,如果超出模型接受范围,可能会造成下文覆盖上文导致prompt丢失,内容混乱,所以请按照模型承受能力进行灵活配置。

    +

    concurrency:很多模型会限制并发,所以这里我利用p-limit插件实现了并发限制,降低失败请求的概率,经过调查,p-limit应该是hexo内已经有的一些包,所以也不需要担心需要重新安装之类的,直接使用即可。

    +

    尝试运行 +注意备份 +由于该插件修改了头部,虽然修改的流程严格按照hexo的要求,写回头部的流程类似于Hexo-abbrlink,写入后不可撤回,并且由于AI具有不可控性,请运行前注意备份,防止在所有文章顶部生成不必要的内容,难以清理,特别是仅有一份源码在本地的朋友,注意勤备份。

    +

    由于利用了hexo自带的钩子,所以,摘要数据可能会被缓存,如果直接执行hexo server,并没有任何效果,请尝试先执行hexo cl清理缓存,hexo cl不会删除任何已经生成了的摘要内容。

    +

    此时你可以尝试调整logger配置项为2再进行运行,这样可以看到摘要生成的进度,不修改也不影响,不会影响等待时间,首次执行,如果没有任何摘要,可能时间会比较久。

    +

    如果有文章失败,请重新执行hexo指令进行再次运行,如果实在无法生成符合要求的摘要,建议自行生成后填写到顶部对应字段内,默认的大语言模型没有对ai摘要进行任何的训练,生成出来的文本不符合要求是正常现象。

    +

    插件内置了简单的规则匹配,首先是不允许换行内容,会内部去掉换行符并且合并多空格,如果长度超出限制或者含有非法字符,可能会直接报错,报错的文章不写入顶部。

    +

    判断部分

    +

    如果一切正常,应该可以在每篇文章的顶部看到对应的摘要文段。

    +

    API推荐

    +

    由于插件需要自行配置API,可能在这方面需要一些帮助,所以我整理了一些免费API接口,如下:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    接口名称优势劣势字符上限模型类型稳定性简介
    腾讯混元 Lite- 官方支持,性能稳定- 计划支持高达256K字符输入输出- 免费使用,无需付费- 需腾讯云账号及实名认证- 当前可能仍处于4K字符限制阶段,256K支持尚未全面上线计划支持256K字符(当前可能为4K)自研大模型,具备多模态能力腾讯自研的混元大模型,支持多轮对话、逻辑推理、内容创作等,计划全面支持256K字符输入输出,适用于多种应用场景。
    讯飞星火 Lite- 轻量级模型,响应速度快- 永久免费使用- 适合办公助手等场景- 功能相对基础- 不支持联网搜索等高级功能输入:8K字符输出:4K字符自研大模型,适用于轻量级应用科大讯飞推出的轻量级大模型,适合对性能和响应速度有较高要求的业务场景,永久免费使用。
    ChatAnywhere GPT_API_free- 支持多种主流模型(GPT-4o、DeepSeek等)- 免费使用,无需代理- 接口兼容OpenAI标准,接入便捷- 免费调用次数有限制(如GPT-4o每日5次)- 可能存在使用高峰时段资源紧张的情况取决于所选模型(如GPT-4o支持128K tokens)多种主流大模型(GPT-4o、DeepSeek等)提供多种主流大模型的免费API接口,支持国内直连,适合开发者测试和学习使用。
    QWQ.aigpu.cn- 完全免费,无需注册- 基于分布式算力,支持高性能模型- 支持本地运行和共享算力- 高峰时段可能需要排队- 依赖社区贡献的算力,稳定性可能受影响未明确限制,具体取决于模型和算力资源QwQ 32B大语言模型中等(受算力资源影响)基于分布式家用显卡算力的平台,提供免费的大语言模型API,支持本地运行和共享算力,适合开发者和爱好者使用。
    +

    由于AI摘要仅仅需要小模型即可驾驭,无需众多训练知识,所以这里两个Lite版本的模型完全可以实现,唯一不同的区别可能就是上下文能力啦,更好的模型可以接受更长的文本输入,不容易丢失我们给予的prompt,输出更为准确,更符合要求,但是考虑到成本和稳定性原因,我还是建议前两个。

    +

    注意各家都有自有api接口和OpenAI类型接口,我们这里选择OpenAI接口,输入完整的地址如混元的兼容接口: +https://api.hunyuan.cloud.tencent.com/v1/chat/completions +申请token后正常使用即可。

    +

    Hexo适配

    +

    说在前面 +有些主题已经有静态ai摘要的功能了,可以无需下面的步骤,使用插件向文件插入对应的字符串即可,下面的教程适用于butterfly或者类butterfly主题,如果是其他主题可能需要自行适配。

    +

    添加配置

    +

    目前我们已经自动化了从AI中,喂我们的文章给AI,再生成摘要,再写到文件顶部的过程,下面我们开始进行从文件顶部渲染到网站页面上。

    +

    首先在主题配置文件 _config.butterfly.yml文件中写入配置,方便我们进行控制摘要是否开启:

    +
    # --------------------------------------
    +# 文章设置
    +# --------------------------------------
    +# 文章AI摘要是否开启,会自动检索文章色summary字段,若没有则不显示
    +ai_summary:
    +  enable: true
    +  title: 清羽のAI摘要
    +  loadingText: 清羽AI正在绞尽脑汁想思路ING···
    +  modelName: HunYuan-Lite
    +

    这里的内容均为装饰性内容,除了enable选项,其他没有任何控制效果,都是装饰,所以无需担心,可以先按照我的写,后面再根据效果修改。

    +

    添加模板

    +

    下面找到主题文件下的 /root/theme/butterfly/layout/post.pug文件,添加文件中指出来的两行内容:

    +
    extends includes/layout.pug
    +
    +block content
    +  #post
    +    if top_img === false
    +      include includes/header/post-info.pug
    +
    +    article#article-container.post-content
    ++     if page.summary && theme.ai_summary.enable
    ++       include includes/post/post-summary.pug
    +      !=page.content
    +    include includes/post/post-copyright.pug
    +    .tag_share
    +      if (page.tags.length > 0 && theme.post_meta.post.tags)
    +        .post-meta__tag-list
    +          each item, index in page.tags.data
    +            a(href=url_for(item.path)).post-meta__tags #[=item.name]
    +      include includes/third-party/share/index.pug
    +
    +    if theme.reward.enable && theme.reward.QR_code
    +      !=partial('includes/post/reward', {}, {cache: true})
    +
    +    //- ad
    +    if theme.ad && theme.ad.post
    +      .ads-wrap!=theme.ad.post
    +
    +    if theme.post_pagination
    +      include includes/pagination.pug
    +    if theme.related_post && theme.related_post.enable
    +      != related_posts(page,site.posts)
    +
    +    if page.comments !== false && theme.comments.use
    +      - var commentsJsLoad = true
    +      !=partial('includes/third-party/comments/index', {}, {cache: true})
    +

    注意缩进,pug作为预编译语言,对缩进的要求极为严格,在该文件中,应该是两个空格一缩进。

    +

    下面添加组件,创建文件 /root/theme/butterfly/layout/includes/post/post-summary.pug,写入以下内容:

    +
    .ai-summary
    +    .ai-explanation(style="display: block;" data-summary=page.summary)=theme.ai_summary.loadingText
    +    .ai-title 
    +        .ai-title-left 
    +            i.fa-brands.fa-slack
    +            .ai-title-text=theme.ai_summary.title
    +        .ai-tag#ai-tag= theme.ai_summary.modelName
    +

    添加样式

    +

    下面我们添加样式部分,创建文件 /root/theme/butterfly/source/css/_layout/ai-summary.styl文件,写入:

    +
    // ===================
    +// 🌗 主题变量定义(仅使用项)
    +// ===================
    +
    +:root
    +  // ai_summary
    +  --liushen-title-font-color: #0883b7
    +  --liushen-maskbg: rgba(255, 255, 255, 0.85)
    +  --liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, #d6b300 0%, #42A2FF 54%, #d6b300 100%)
    +
    +  // card 背景
    +  --liushen-card-secondbg: #f1f3f8
    +
    +  // text
    +  --liushen-text: #4c4948
    +  --liushen-secondtext: #3c3c43cc
    +
    +[data-theme='dark']
    +  // ai_summary
    +  --liushen-title-font-color: #0883b7
    +  --liushen-maskbg: rgba(0, 0, 0, 0.85)
    +  --liushen-ai-bg: conic-gradient(from 1.5708rad at 50% 50%, rgba(214, 178, 0, 0.46) 0%, rgba(66, 161, 255, 0.53) 54%, rgba(214, 178, 0, 0.49) 100%)
    +
    +  // card 背景
    +  --liushen-card-secondbg: #3e3f41
    +
    +  // text
    +  --liushen-text: #ffffffb3
    +  --liushen-secondtext: #a1a2b8
    +
    +// ===================
    +// 📘 AI 摘要模块样式
    +// ===================
    +
    +if hexo-config('ai_summary.enable')
    +  .ai-summary
    +    background-color var(--liushen-maskbg)
    +    background var(--liushen-card-secondbg)
    +    border-radius 12px
    +    padding 8px 8px 12px 8px
    +    line-height 1.3
    +    flex-direction column
    +    margin-bottom 16px
    +    display flex
    +    gap 5px
    +    position relative
    +
    +    &::before
    +      content ''
    +      position absolute
    +      top 0
    +      left 0
    +      width 100%
    +      height 100%
    +      z-index 1
    +      filter blur(8px)
    +      opacity .4
    +      background-image var(--liushen-ai-bg)
    +      transform scaleX(1) scaleY(.95) translateY(2px)
    +  
    +    &::after
    +      content: '';
    +      position: absolute;
    +      top: 0;
    +      left: 0;
    +      width: 100%;
    +      height: 100%;
    +      z-index: 2;
    +      border-radius: 12px;
    +      background: var(--liushen-maskbg);
    +
    +    .ai-explanation
    +      z-index 10
    +      padding 8px 12px
    +      font-size 15px
    +      line-height 1.4
    +      color var(--liushen-text)
    +      text-align justify
    +
    +      // ✅ 打字机光标动画
    +      &::after
    +        content ''
    +        display inline-block
    +        width 8px
    +        height 2px
    +        margin-left 2px
    +        background var(--liushen-text)
    +        vertical-align bottom
    +        animation blink-underline 1s ease-in-out infinite
    +        transition all .3s
    +        position relative
    +        bottom 3px
    +
    +      // 平滑滚动动画
    +      // .char
    +      //   display inline-block
    +      //   opacity 0
    +      //   animation chat-float .5s ease forwards
    +  
    +    .ai-title
    +      z-index 10
    +      font-size 14px
    +      display flex
    +      border-radius 8px
    +      align-items center
    +      position relative
    +      padding 0 12px
    +      cursor default
    +      user-select none
    +
    +      .ai-title-left
    +        display flex
    +        align-items center
    +        color var(--liushen-title-font-color)
    +
    +        i
    +          margin-right 3px
    +          display flex
    +          color var(--liushen-title-font-color)
    +          border-radius 20px
    +          justify-content center
    +          align-items center
    +
    +        .ai-title-text
    +          font-weight 500
    +
    +      .ai-tag
    +        color var(--liushen-secondtext)
    +        font-weight 300
    +        margin-left auto
    +        display flex
    +        align-items center
    +        justify-content center
    +        transition .3s
    +
    +  // 平滑滚动动画
    +  // @keyframes chat-float
    +  //   0% 
    +  //     opacity 0
    +  //     transform translateY(20px)
    +  //   100% 
    +  //       opacity 1
    +  //       transform translateY(0)
    +  
    +  // ✅ 打字机光标闪烁动画
    +  @keyframes blink-underline
    +    0%, 100%
    +      opacity 1
    +    50%
    +      opacity 0
    +

    样式也实现啦!目前就差将我们的摘要插入到我们的网站就大功告成啦,为了实现的更加逼真,我这里实现了两种样式一个是打字机效果,一个是平滑显示效果,可以按需引入:

    +

    添加核心JS

    +

    介绍两种动效,可以按照自己的需求在任意js文件中选择一个引入即可,两个的区别是,打字机效果更加的节省性能,而平滑显示,因为每个文本为一个span,所以会比较耗费性能。

    +

    打字机

    +
    // 打字机效果
    +function typeTextMachineStyle(text, targetSelector, options = {}) {
    +    const {
    +        delay = 50,
    +        startDelay = 2000,
    +        onComplete = null,
    +        clearBefore = true,
    +        eraseBefore = true, // 新增:是否以打字机方式清除原文本
    +        eraseDelay = 30,    // 新增:删除每个字符的间隔
    +    } = options;
    +
    +    const el = document.querySelector(targetSelector);
    +    if (!el || typeof text !== "string") return;
    +
    +    setTimeout(() => {
    +        const startTyping = () => {
    +            let index = 0;
    +            function renderChar() {
    +                if (index <= text.length) {
    +                    el.textContent = text.slice(0, index++);
    +                    setTimeout(renderChar, delay);
    +                } else {
    +                    onComplete && onComplete(el);
    +                }
    +            }
    +            renderChar();
    +        };
    +
    +        if (clearBefore) {
    +            if (eraseBefore && el.textContent.length > 0) {
    +                let currentText = el.textContent;
    +                let eraseIndex = currentText.length;
    +
    +                function eraseChar() {
    +                    if (eraseIndex > 0) {
    +                        el.textContent = currentText.slice(0, --eraseIndex);
    +                        setTimeout(eraseChar, eraseDelay);
    +                    } else {
    +                        startTyping(); // 删除完毕后开始打字
    +                    }
    +                }
    +
    +                eraseChar();
    +            } else {
    +                el.textContent = "";
    +                startTyping();
    +            }
    +        } else {
    +            startTyping();
    +        }
    +    }, startDelay);
    +}
    +
    +function renderAISummary() {
    +    const summaryEl = document.querySelector('.ai-summary .ai-explanation');
    +    if (!summaryEl) return;
    +
    +    const summaryText = summaryEl.getAttribute('data-summary');
    +    if (summaryText) {
    +        typeTextMachineStyle(summaryText, ".ai-summary .ai-explanation"); // 如果需要切换,在这里调用另一个函数即可
    +    }
    +}
    +
    +document.addEventListener('pjax:complete', renderAISummary);
    +document.addEventListener('DOMContentLoaded', renderAISummary);
    +

    本站使用的就是打字机效果,可以自行查看。

    +

    平滑

    +

    这个没有样图,如果好奇可以自行部署并尝试:

    +
     // 平滑弹出效果
    + function typeText(text, targetSelector, options = {}) {
    +     const {
    +       delay = 50,          // 每个字符之间的延迟(毫秒)
    +       startDelay = 2000,   // 开始打字前的延迟(默认 3 秒)
    +       onComplete = null,   // 动画完成后的回调
    +       clearBefore = true   // 是否在开始前清空原有内容
    +     } = options;
    +
    +     const targetEl = document.querySelector(targetSelector);
    +     if (!targetEl || typeof text !== "string") return;
    +
    +     // if (clearBefore) targetEl.textContent = "";
    +
    +     let index = 0;
    +     let frameId = null;
    +
    +     function renderChar() {
    +       if (index < text.length) {
    +         const span = document.createElement("span");
    +         span.textContent = text[index++];
    +         span.className = "char";
    +         targetEl.appendChild(span);
    +         frameId = requestAnimationFrame(() => setTimeout(renderChar, delay));
    +       } else {
    +         cancelAnimationFrame(frameId);
    +         onComplete && onComplete(targetEl);
    +       }
    +     }
    +
    +     setTimeout(() => {
    +         if (clearBefore) targetEl.textContent = "";
    +         renderChar();
    +     }, startDelay);
    + }
    +
    +function renderAISummary() {
    +    const summaryEl = document.querySelector('.ai-summary .ai-explanation');
    +    if (!summaryEl) return;
    +
    +    const summaryText = summaryEl.getAttribute('data-summary');
    +    if (summaryText) {
    +        typeText(summaryText, ".ai-summary .ai-explanation"); // 如果需要切换,在这里调用另一个函数即可
    +    }
    +}
    +
    +document.addEventListener('pjax:complete', renderAISummary);
    +document.addEventListener('DOMContentLoaded', renderAISummary);
    +

    注意,平滑滚动部分的css,默认注释掉了

    +

    好的,设置完毕!

    博客添加AI总结

    /posts/41b9aff7/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    博客添加AI总结

    \ No newline at end of file diff --git a/posts/437a5198/index.html b/posts/437a5198/index.html new file mode 100644 index 0000000..4994a64 --- /dev/null +++ b/posts/437a5198/index.html @@ -0,0 +1,992 @@ + OpenGL-基础程序 | Bi's Blog

    OpenGL-基础程序

    发布于 2026/04/12 技术 总字数:1.3k 阅读时长:4 分钟

    这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。

    +AI摘要 + QWEN

    代码展示

    +

    我们先从基本的OpenGL程序开始吧,这是一个简单的OpenGL程序:

    +
    #include<GL/glut.h>
    +using namespace std;
    +
    +// 回调函数
    +void myDisplay()
    +{
    +	// 清除缓冲区
    +	glClearColor(0.0, 0.0, 0.0, 0.0);
    +	glClear(GL_COLOR_BUFFER_BIT);
    +	// 正交模式
    +	glMatrixMode(GL_PROJECTION);
    +	gluOrtho2D(0.0, 500.0, 0.0, 500.0);
    +	glColor4f(0.0, 1.0, 0.0, 0.0);
    +	glRectf(50.0, 50.0, 400.0, 400.0);
    +
    +	// 划线
    +	glColor3f(1.0, 1.0, 0.0);
    +	glBegin(GL_LINES);
    +	glVertex2f(50.0, 50.0);
    +	glVertex2f(400.0, 400.0);
    +	glVertex2f(400.0, 50.0);
    +	glVertex2f(50.0, 400.0);
    +	glEnd();
    +
    +	// 画点
    +	glColor3f(1.0, 0.0,  0.0);
    +	glPointSize(20.0);
    +	glBegin(GL_POINTS);
    +	glVertex2f(15.0, 15.0);
    +	glEnd();
    +
    +	// 画三角形
    +	glBegin(GL_TRIANGLES);
    +	glColor3f(0.0, 0.0, 1.0);
    +	glVertex2i(200, 300);
    +	glVertex2i(100, 100);
    +	glVertex2i(300, 100);
    +	glEnd();
    +
    +
    +	glFlush();
    +
    +}
    +int main(int argc, char* argv[])
    +{
    +	glutInit(&argc, argv);
    +	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    +	glutInitWindowPosition(100, 100);
    +	glutInitWindowSize(500, 500);
    +	glutCreateWindow("");
    +	glutDisplayFunc(&myDisplay);
    +	glutMainLoop();
    +	return 0;
    +}
    +

    这是运行结果图: +第一个OpenGL程序

    +

    代码解释

    +

    这段代码展示了经典的 OpenGL (GLUT) 固定渲染管线基本操作。为了方便理解,我将这些函数按照 初始化与窗口管理状态设置坐标变换 以及 图形绘制 四个类别进行了整理。

    +

    1. 窗口管理与初始化 (GLUT 库)

    +

    这类函数主要用于配置窗口系统和处理程序的运行流程。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    函数名称功能描述核心参数说明
    glutInit初始化 GLUT 库接收 main 函数的命令行参数
    glutInitDisplayMode设置显示模式GLUT_RGB (使用颜色模式), GLUT_SINGLE (单缓冲区)
    glutInitWindowPosition设置窗口在屏幕上的初始位置窗口左上角坐标
    glutInitWindowSize设置窗口的宽度和高度像素值 (Width, Height)
    glutCreateWindow创建窗口字符串参数作为窗口标题
    glutDisplayFunc注册显示回调函数传入负责绘图的函数指针
    glutMainLoop进入 GLUT 事件处理循环让程序持续运行,等待重绘或交互
    +
    +

    2. 状态设置与缓冲区操作

    +

    这些函数用于配置 OpenGL 的全局状态(如颜色、点大小)或清理画布。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    函数名称功能描述核心参数说明
    glClearColor设置清除颜色(背景色)RGBA 值 (0.0~1.0),此处设为黑色
    glClear清除指定的缓冲区GL_COLOR_BUFFER_BIT 表示清除颜色缓存
    glColor4f设置当前的绘制颜色 (带透明度)RGBA 分量
    glColor3f设置当前的绘制颜色 (不带透明度)RGB 分量
    glPointSize设置点的像素大小浮点数,数值越大点越粗
    glFlush强制刷新缓冲区确保绘图命令立即执行并输出到显示设备
    +
    +

    3. 矩阵与坐标变换

    +

    用于定义物体是如何投影到屏幕上的。

    + + + + + + + + + + + + + + + + + + + + +
    函数名称功能描述核心参数说明
    glMatrixMode设置当前矩阵模式GL_PROJECTION 切换到投影矩阵堆栈
    gluOrtho2D定义二维正交投影裁剪区域定义视野的左、右、下、上边界范围
    +
    +

    4. 几何图形绘制

    +

    OpenGL 的核心绘图逻辑,通过指定顶点来构建形状。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    函数名称功能描述核心参数说明
    glRectf绘制一个实心矩形传入左下角坐标和右上角坐标
    glBegin标记图元绘制的开始GL_LINES (线), GL_POINTS (点), GL_TRIANGLES (三角形)
    glVertex2f指定一个二维顶点 (浮点型)坐标
    glVertex2i指定一个二维顶点 (整型)坐标
    glEnd标记图元绘制的结束必须与 glBegin 成对出现
    +

    OpenGL-基础程序

    /posts/437a5198/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenGL
    订阅 打赏

    Comments

    OpenGL-基础程序

    \ No newline at end of file diff --git a/posts/4c91a8f2/index.html b/posts/4c91a8f2/index.html new file mode 100644 index 0000000..65f1c67 --- /dev/null +++ b/posts/4c91a8f2/index.html @@ -0,0 +1,942 @@ + 用 Umami 分享链接给网站信息卡提供统计数据 | Bi's Blog

    用 Umami 分享链接给网站信息卡提供统计数据

    这篇文章记录如何通过 Umami 的分享链接获取公开统计数据,再用 Cloudflare Worker 做一层缓存代理,最后把访客数和访问量显示到博客侧边栏的网站信息卡里。

    +AI摘要 + QWEN

    最近给博客侧边栏加了一个“网站信息”卡片,里面除了文章数量、总字数、最后更新时间,还想放上本站访客数和访问量。

    +

    一开始想到的是直接调用 Umami API,但现在的 Umami 后台里不一定能很直观地找到 API key。如果为了这两个数字再专门创建一个用户、保存账号密码,又有点重。后来发现 Umami 自带的“分享链接”刚好可以解决这个问题。

    +

    这篇文章记录一下完整做法:用 Umami 分享链接拿公开 token,再用 Cloudflare Worker 请求统计接口并缓存,最后给前端信息卡使用。

    +

    整体思路

    +

    流程大概是这样:

    +
      +
    1. 在 Umami 后台给站点创建分享链接。
    2. +
    3. Worker 请求 /api/share/{shareId},拿到 websiteId 和分享 token。
    4. +
    5. Worker 带上分享 token 请求 /api/websites/{websiteId}/stats
    6. +
    7. Worker 把结果整理成前端需要的 JSON,并用 Cloudflare 缓存 10 分钟。
    8. +
    9. 博客前端请求 Worker 地址,把访客数和访问量填到信息卡。
    10. +
    +

    这样做的好处是不用暴露后台账号,也不用在前端直接请求 Umami。前端只会看到 Worker 返回的简洁数据。

    +

    创建 Umami 分享链接

    +

    进入 Umami 后台,找到对应网站的分享设置,打开需要公开的模块。我这里只需要概览数据,所以勾选“概览”就够了。

    +

    保存后会得到一个类似这样的分享链接:

    +
    https://umami.example.com/share/Hqx6lIBhIHBR13RY
    +

    最后这一段 Hqx6lIBhIHBR13RY 就是分享 ID,后面 Worker 会用到。

    +

    分享链接背后的请求

    +

    分享页本身不是一个静态 HTML 页面,直接读取页面源码一般拿不到统计数字。它会先请求:

    +
    /api/share/Hqx6lIBhIHBR13RY
    +

    这个接口会返回类似这样的数据:

    +
    {
    +  "shareId": "...",
    +  "shareType": 1,
    +  "websiteId": "3d97b310-b241-4fc6-bc9c-68e317fc7d42",
    +  "token": "..."
    +}
    +

    然后访问统计接口时,需要带两个请求头:

    +
    x-umami-share-context: 1
    +x-umami-share-token: 上一步返回的 token
    +

    统计接口是:

    +
    /api/websites/{websiteId}/stats?startAt=0&endAt=当前时间戳
    +

    返回结果大概是:

    +
    {
    +  "pageviews": 2,
    +  "visitors": 1,
    +  "visits": 1,
    +  "bounces": 0,
    +  "totaltime": 470
    +}
    +

    其中:

    +
      +
    • visitors 是访客数
    • +
    • pageviews 是页面访问量
    • +
    • visits 是访问会话数
    • +
    +

    网站信息卡里我只用 visitorspageviews

    +

    编写 Cloudflare Worker

    +

    新建一个 Worker,把下面这份代码放进去:

    +
    const DEFAULT_CACHE_TTL_SECONDS = 600;
    +
    +function jsonResponse(data, init = {}) {
    +  return new Response(JSON.stringify(data), {
    +    ...init,
    +    headers: {
    +      'Content-Type': 'application/json; charset=utf-8',
    +      ...init.headers,
    +    },
    +  });
    +}
    +
    +function getCorsHeaders(request, env) {
    +  const origin = request.headers.get('Origin') || '';
    +  const allowedOrigin = env.ALLOWED_ORIGIN || 'https://blog.example.com';
    +  const allowOrigin = origin === allowedOrigin ? origin : allowedOrigin;
    +
    +  return {
    +    'Access-Control-Allow-Origin': allowOrigin,
    +    'Access-Control-Allow-Methods': 'GET, OPTIONS',
    +    'Access-Control-Allow-Headers': 'Content-Type',
    +    Vary: 'Origin',
    +  };
    +}
    +
    +function normalizeBaseUrl(value) {
    +  return String(value || 'https://umami.example.com').replace(/\/+$/, '');
    +}
    +
    +function getShareId(env) {
    +  if (env.UMAMI_SHARE_ID) return env.UMAMI_SHARE_ID;
    +  if (!env.UMAMI_SHARE_URL) return '';
    +
    +  try {
    +    const url = new URL(env.UMAMI_SHARE_URL);
    +    const parts = url.pathname.split('/').filter(Boolean);
    +    const shareIndex = parts.indexOf('share');
    +    return shareIndex >= 0 ? parts[shareIndex + 1] || '' : parts.at(-1) || '';
    +  } catch {
    +    return env.UMAMI_SHARE_URL.split('/').filter(Boolean).at(-1) || '';
    +  }
    +}
    +
    +async function getShareData(env) {
    +  const baseUrl = normalizeBaseUrl(env.UMAMI_BASE_URL);
    +  const shareId = getShareId(env);
    +
    +  if (!shareId) {
    +    throw new Error('Missing UMAMI_SHARE_ID or UMAMI_SHARE_URL');
    +  }
    +
    +  const response = await fetch(`${baseUrl}/api/share/${shareId}`, {
    +    headers: { Accept: 'application/json' },
    +  });
    +
    +  if (!response.ok) {
    +    throw new Error(`Umami share lookup failed: ${response.status}`);
    +  }
    +
    +  const data = await response.json();
    +  if (!data.websiteId || !data.token) {
    +    throw new Error('Umami share response did not include websiteId or token');
    +  }
    +
    +  return data;
    +}
    +
    +async function fetchUmamiStats(env, shareData) {
    +  const baseUrl = normalizeBaseUrl(env.UMAMI_BASE_URL);
    +  const websiteId = env.UMAMI_WEBSITE_ID || shareData.websiteId;
    +  const startAt = Number(env.STATS_START_AT || 0);
    +  const endAt = Date.now();
    +  const url = `${baseUrl}/api/websites/${websiteId}/stats?startAt=${startAt}&endAt=${endAt}`;
    +
    +  return fetch(url, {
    +    headers: {
    +      Accept: 'application/json',
    +      'x-umami-share-context': '1',
    +      'x-umami-share-token': shareData.token,
    +    },
    +  });
    +}
    +
    +async function getStats(env) {
    +  const shareData = await getShareData(env);
    +  const response = await fetchUmamiStats(env, shareData);
    +
    +  if (!response.ok) {
    +    throw new Error(`Umami stats failed: ${response.status}`);
    +  }
    +
    +  const stats = await response.json();
    +
    +  return {
    +    visitors: stats.visitors ?? 0,
    +    pageviews: stats.pageviews ?? 0,
    +    visits: stats.visits ?? 0,
    +    updatedAt: Date.now(),
    +  };
    +}
    +
    +export default {
    +  async fetch(request, env, ctx) {
    +    const corsHeaders = getCorsHeaders(request, env);
    +
    +    if (request.method === 'OPTIONS') {
    +      return new Response(null, { status: 204, headers: corsHeaders });
    +    }
    +
    +    if (request.method !== 'GET') {
    +      return jsonResponse({ error: 'Method not allowed' }, { status: 405, headers: corsHeaders });
    +    }
    +
    +    const cacheTtl = Number(env.CACHE_TTL_SECONDS || DEFAULT_CACHE_TTL_SECONDS);
    +    const cache = caches.default;
    +    const cacheKey = new Request(
    +      `https://site-stats-cache.local/umami-site-stats/${getShareId(env) || 'default'}`,
    +    );
    +    const cached = await cache.match(cacheKey);
    +
    +    if (cached) {
    +      return new Response(cached.body, {
    +        status: cached.status,
    +        headers: {
    +          ...Object.fromEntries(cached.headers),
    +          ...corsHeaders,
    +          'X-Stats-Cache': 'HIT',
    +        },
    +      });
    +    }
    +
    +    try {
    +      const stats = await getStats(env);
    +      const response = jsonResponse(stats, {
    +        headers: {
    +          ...corsHeaders,
    +          'Cache-Control': `public, max-age=${cacheTtl}`,
    +          'X-Stats-Cache': 'MISS',
    +        },
    +      });
    +
    +      ctx.waitUntil(cache.put(cacheKey, response.clone()));
    +      return response;
    +    } catch (error) {
    +      return jsonResponse(
    +        {
    +          error: 'Failed to fetch stats',
    +          message: error instanceof Error ? error.message : String(error),
    +        },
    +        { status: 502, headers: corsHeaders },
    +      );
    +    }
    +  },
    +};
    +

    配置 Worker 变量

    +

    在 Cloudflare Worker 的设置里添加变量:

    +
    UMAMI_BASE_URL = https://umami.example.com
    +UMAMI_SHARE_ID = Hqx6lIBhIHBR13RY
    +ALLOWED_ORIGIN = https://blog.example.com
    +CACHE_TTL_SECONDS = 600
    +

    如果你更想直接填完整分享链接,也可以不用 UMAMI_SHARE_ID,改用:

    +
    UMAMI_SHARE_URL = https://umami.example.com/share/Hqx6lIBhIHBR13RY
    +

    CACHE_TTL_SECONDS = 600 表示缓存 10 分钟。这样访客每次打开页面时,不会都去打 Umami 接口。

    +

    如果你只想统计某个建站时间之后的数据,可以额外设置:

    +
    STATS_START_AT = 1735660800000
    +

    这个值是毫秒时间戳。不设置的话默认从 0 开始,也就是统计全部数据。

    +

    部署方式

    +

    如果只是单个 Worker,最简单的方法是在 Cloudflare 后台新建 Worker,然后把 JS 代码直接粘到在线编辑器里。

    +

    如果遇到这样的提示:

    +
    此上传程序暂不支持需要构建过程的项目。至少找到了一个 JavaScript 文件。请改用 wrangler deploy 以获得完整功能支持。
    +

    通常是因为你上传了整个项目目录。这个场景不需要上传整个博客项目,只需要把 Worker 的 JS 内容粘进去即可。

    +

    如果想用 Wrangler,也可以写一个配置文件:

    +
    name = "umami-site-stats"
    +main = "umami-site-stats.js"
    +compatibility_date = "2026-06-19"
    +
    +[vars]
    +UMAMI_BASE_URL = "https://umami.example.com"
    +UMAMI_SHARE_ID = "Hqx6lIBhIHBR13RY"
    +ALLOWED_ORIGIN = "https://blog.example.com"
    +CACHE_TTL_SECONDS = "600"
    +

    然后执行:

    +
    wrangler deploy
    +

    测试 Worker

    +

    部署好之后,直接访问 Worker 地址,正常会看到:

    +
    {
    +  "visitors": 1,
    +  "pageviews": 2,
    +  "visits": 1,
    +  "updatedAt": 1781829905467
    +}
    +

    如果返回 502,可以先看 message 字段。常见原因有:

    +
      +
    • UMAMI_SHARE_ID 写错
    • +
    • UMAMI_BASE_URL 写错
    • +
    • 分享链接没有保存成功
    • +
    • Umami 的分享权限里没有勾选“概览”
    • +
    +

    接入网站信息卡

    +

    前端信息卡里,静态数据可以直接由博客生成:

    +
      +
    • 文章数目:读取文章列表长度
    • +
    • 本站总字数:遍历文章内容统计
    • +
    • 最后更新时间:取最新文章发布时间
    • +
    +

    访客数和访问量则用 Worker 动态填充。页面里先渲染占位:

    +
    <dd data-stat-key="visitors">--</dd>
    +<dd data-stat-key="pageviews">--</dd>
    +

    再用 JS 请求 Worker:

    +
    const endpoint = 'https://your-worker.example.workers.dev';
    +const formatter = new Intl.NumberFormat('en-US');
    +
    +fetch(endpoint, { headers: { Accept: 'application/json' } })
    +  .then((response) => response.json())
    +  .then((stats) => {
    +    document.querySelector('[data-stat-key="visitors"]').textContent = formatter.format(stats.visitors);
    +    document.querySelector('[data-stat-key="pageviews"]').textContent = formatter.format(stats.pageviews);
    +  });
    +

    我这里还在浏览器端加了一层 localStorage 缓存,缓存 key 里带上 Worker 地址,防止以后更换接口后还读到旧数据:

    +
    const cacheKey = `site-info-stats:${endpoint}`;
    +const cacheDuration = 10 * 60 * 1000;
    +

    这样前端和 Worker 都有缓存,访问体验会稳一些。

    +

    最后

    +

    这个方案的核心是:不要把 Umami 后台账号放到前端,也不需要专门创建只读用户。既然 Umami 分享页本身已经提供了公开数据访问方式,就让 Worker 代替前端去请求它,并顺手做缓存和字段整理。

    +

    最终网站信息卡只需要关心这几个字段:

    +
    {
    +  "visitors": 1,
    +  "pageviews": 2,
    +  "visits": 1,
    +  "updatedAt": 1781829905467
    +}
    +

    小功能,但很适合放在侧边栏。它不会喧宾夺主,却能让网站看起来更像一个正在被维护、正在呼吸的地方。

    用 Umami 分享链接给网站信息卡提供统计数据

    /posts/4c91a8f2/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站UmamiCloudflare Worker
    订阅 打赏

    Comments

    用 Umami 分享链接给网站信息卡提供统计数据

    \ No newline at end of file diff --git a/posts/56f57c0b/index.html b/posts/56f57c0b/index.html new file mode 100644 index 0000000..5021d10 --- /dev/null +++ b/posts/56f57c0b/index.html @@ -0,0 +1,683 @@ + 自建renovate-bot | Bi's Blog

    自建renovate-bot

    发布于 2026/02/23 技术 总字数:289 阅读时长:1 分钟

    这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。

    +AI摘要 + QWEN

    最近自建了gitea,所以把renovatebot也自建一下,毕竟renovatebot可以自动更新依赖,但是它不对自建git提供服务。 +这是docker compose文件:

    +
    services:
    +  renovate:
    +    image: renovate/renovate:latest
    +    environment:
    +      RENOVATE_PLATFORM: 'gitea'
    +      RENOVATE_ENDPOINT: '你的git api地址'
    +      RENOVATE_TOKEN: 'token'
    +      RENOVATE_AUTODISCOVER: 'true'
    +      RENOVATE_GIT_AUTHOR: Renovate Bot <bot@biss.click>
    +      RENOVATE_USERNAME: renovate-bot
    +    volumes:
    +      - ./renovate-data:/tmp/renovate
    +

    可以新建一个renovate用户来专门管理,然后登录这个账户创建一个登录token。然后再需要使用这个的仓库添加这个用户为协作者就可以了。 +这个docker容器在运行后会自动退出,这是正常的。 +可以在系统crontab新建一个定时任务,这样就可以自动运行更新。

    +
    cd /www/compose/renovatebot && docker compose run --rm renovate
    + + + + 官方文档 + Renovatebot · docs.renovatebot.com + +

    自建renovate-bot

    /posts/56f57c0b/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    gitea
    订阅 打赏

    Comments

    自建renovate-bot

    \ No newline at end of file diff --git a/posts/5785bd01/index.html b/posts/5785bd01/index.html new file mode 100644 index 0000000..a292e07 --- /dev/null +++ b/posts/5785bd01/index.html @@ -0,0 +1,663 @@ + 新年快乐! | Bi's Blog

    新年快乐!

    这篇文章是一则关于新年祝福和幸福指数提升的通知,主要内容包含以下几点:首先,强调在假期期间要确保饮食质量,避免emo情绪的影响;其次,鼓励大家在收到通知后在留言区分享新年愿望,以便后台系统收集并祝愿他们实现愿望;最后,表达了对大家新年的美好祝愿,希望他们身体健康,万事如意,所得皆所愿,所行化坦途。

    +AI摘要 + QWEN

    各位亲朋好友、合作伙伴及屏幕前的你:

    +

    值此二〇二六年蛇年来临之际,为贯彻落实“快乐至上”的核心价值观,进一步提升全体人员的幸福指数,现将有关事项通知如下:

    +

    一、各单位要切实做好“吃好喝好”保障工作,严禁在假期期间进行任何形式的emo。

    +

    二、请各有关人员在收到本通知后,务必在下方留言区留下你的新年愿望,由后台系统统一收集并祝愿其实现。

    +

    三、祝大家在新的一年里,身体健康,万事如意,所得皆所愿,所行化坦途!

    新年快乐!

    /posts/5785bd01/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    生活
    订阅 打赏

    Comments

    新年快乐!

    \ No newline at end of file diff --git a/posts/59c8572a/index.html b/posts/59c8572a/index.html new file mode 100644 index 0000000..53a587a --- /dev/null +++ b/posts/59c8572a/index.html @@ -0,0 +1,699 @@ + 搭建Owncloud并集成Onlyoffice | Bi's Blog

    搭建Owncloud并集成Onlyoffice

    发布于 2025/08/26 技术 总字数:427 阅读时长:2 分钟

    这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。

    +AI摘要 + QWEN

    引言

    +

    因为正好有云存储的需求,恰好又有服务器,所以决定自建一个Owncloud网盘服务,集成一个Onlyoffice。

    +

    安装网盘

    +

    建站

    +在官网下载最新的安装包 + + + + Owncloud + Owncloud · owncloud.com + + +因为使用1panel,所以搭网站很简单,选择php7.3运行环境,添加必要的php扩展,新建一个Mysql数据库,按照正常步骤安装就行了。 +

    调优

    +
      +
    1. 配置Redis缓存 +在1panel安装Redis, +在config/config.php中添加
    2. +
    +
      'filelocking.enabled' => true,
    +  'memcache.local' => '\OC\Memcache\Redis',
    +  'memcache.locking' => '\OC\Memcache\Redis',
    +  'redis' => [
    +      'host' => '容器名',  // For a Unix domain socket, use '/var/run/redis/redis.sock'
    +      'port' => 6379,         // Set to 0 when using a Unix socket
    +      'timeout' => 0,         // Optional, keep connection open forever
    +      'password' => 'password',       // Optional, if not defined no password will be used.
    +      'dbindex' => 0,         // Optional, if undefined SELECT will not run and will
    +                              // use Redis Server's default DB Index.
    +  ],
    +
      +
    1. 添加Cron任务 +首先在Owncloud的配置页面把计划任务调整为Cron。 +在1panel的计划任务中添加,容器选择php7.3的容器,用户必须为www-data,时间为每15分钟。
    2. +
    +
    php sites/cloud.biss.click/index/occ system:cron
    +

    集成Onlyoffice

    +

    在1panel应用商店直接安装Onlyoffice,并反向代理到自己的域名 +在Owncloud中搜索Onlyoffice插件,然后填写自己的域名、密钥(在参数中可以查到)

    搭建Owncloud并集成Onlyoffice

    /posts/59c8572a/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    搭建Owncloud并集成Onlyoffice

    \ No newline at end of file diff --git a/posts/5ed2f1e6/index.html b/posts/5ed2f1e6/index.html new file mode 100644 index 0000000..9438307 --- /dev/null +++ b/posts/5ed2f1e6/index.html @@ -0,0 +1,1074 @@ + 在侧边栏添加日历和倒计时 | Bi's Blog

    在侧边栏添加日历和倒计时

    发布于 2026/01/16 建站手札 总字数:189 阅读时长:1 分钟

    实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。

    +AI摘要 + QWEN

    突然看到某个网站侧边栏有日历和倒计时,就研究了一下,抄下来了(🤭) +效果图:

    +
    效果图
    +

    添加js

    +
    document.addEventListener("DOMContentLoaded", () => {
    +    initializeCard();
    +});
    +
    +document.addEventListener("pjax:complete", () => {
    +    initializeCard();
    +});
    +
    +function initializeCard() {
    +    cardTimes();
    +    cardRefreshTimes();
    +}
    +
    +let year, month, week, date, dates, weekStr, monthStr, asideTime, asideDay, asideDayNum, animalYear, ganzhiYear, lunarMon, lunarDay;
    +const now = new Date();
    +
    +function cardRefreshTimes() {
    +    const e = document.getElementById("card-widget-schedule");
    +    if (e) {
    +        asideDay = (now - asideTime) / 1e3 / 60 / 60 / 24;
    +        e.querySelector("#pBar_year").value = asideDay;
    +        e.querySelector("#p_span_year").innerHTML = (asideDay / 365 * 100).toFixed(1) + "%";
    +        e.querySelector(".schedule-r0 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(365 - asideDay).toFixed(0)} </a>天`;
    +        e.querySelector("#pBar_month").value = date;
    +        e.querySelector("#pBar_month").max = dates;
    +        e.querySelector("#p_span_month").innerHTML = (date / dates * 100).toFixed(1) + "%";
    +        e.querySelector(".schedule-r1 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(dates - date)} </a>天`;
    +        e.querySelector("#pBar_week").value = week === 0 ? 7 : week;
    +        e.querySelector("#p_span_week").innerHTML = ((week === 0 ? 7 : week) / 7 * 100).toFixed(1) + "%";
    +        e.querySelector(".schedule-r2 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(7 - (week === 0 ? 7 : week))} </a>天`;
    +    }
    +}
    +
    +function cardTimes() {
    +    year = now.getFullYear();
    +    month = now.getMonth();
    +    week = now.getDay();
    +    date = now.getDate();
    +
    +    const e = document.getElementById("card-widget-calendar");
    +    if (e) {
    +        const isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
    +        weekStr = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][week];
    +        const monthData = [
    +            { month: "1月", days: 31 },
    +            { month: "2月", days: isLeapYear ? 29 : 28 },
    +            { month: "3月", days: 31 },
    +            { month: "4月", days: 30 },
    +            { month: "5月", days: 31 },
    +            { month: "6月", days: 30 },
    +            { month: "7月", days: 31 },
    +            { month: "8月", days: 31 },
    +            { month: "9月", days: 30 },
    +            { month: "10月", days: 31 },
    +            { month: "11月", days: 30 },
    +            { month: "12月", days: 31 }
    +        ];
    +        monthStr = monthData[month].month;
    +        dates = monthData[month].days;
    +
    +        const t = (week + 8 - date % 7) % 7;
    +        let n = "", d = false, s = 7 - t;
    +        const o = (dates - s) % 7 === 0 ? Math.floor((dates - s) / 7) + 1 : Math.floor((dates - s) / 7) + 2;
    +        const c = e.querySelector("#calendar-main");
    +        const l = e.querySelector("#calendar-date");
    +
    +        l.style.fontSize = ["64px", "48px", "36px"][Math.min(o - 3, 2)];
    +
    +        for (let i = 0; i < o; i++) {
    +            if (!c.querySelector(`.calendar-r${i}`)) {
    +                c.innerHTML += `<div class='calendar-r${i}'></div>`;
    +            }
    +            for (let j = 0; j < 7; j++) {
    +                if (i === 0 && j === t) {
    +                    n = 1;
    +                    d = true;
    +                }
    +                const r = n === date ? " class='now'" : "";
    +                if (!c.querySelector(`.calendar-r${i} .calendar-d${j} a`)) {
    +                    c.querySelector(`.calendar-r${i}`).innerHTML += `<div class='calendar-d${j}'><a${r}>${n}</a></div>`;
    +                }
    +                if (n >= dates) {
    +                    n = "";
    +                    d = false;
    +                }
    +                if (d) {
    +                    n += 1;
    +                }
    +            }
    +        }
    +
    +        const lunarDate = chineseLunar.solarToLunar(new Date(year, month, date));
    +        animalYear = chineseLunar.format(lunarDate, "A");
    +        ganzhiYear = chineseLunar.format(lunarDate, "T").slice(0, -1);
    +        lunarMon = chineseLunar.format(lunarDate, "M");
    +        lunarDay = chineseLunar.format(lunarDate, "d");
    +
    +        const newYearDate = new Date("2026/02/16 00:00:00");
    +        const daysUntilNewYear = Math.floor((newYearDate - now) / 1e3 / 60 / 60 / 24);
    +        asideTime = new Date(`${new Date().getFullYear()}/01/01 00:00:00`);
    +        asideDay = (now - asideTime) / 1e3 / 60 / 60 / 24;
    +        asideDayNum = Math.floor(asideDay);
    +        const weekNum = week - asideDayNum % 7 >= 0 ? Math.ceil(asideDayNum / 7) : Math.ceil(asideDayNum / 7) + 1;
    +
    +        e.querySelector("#calendar-week").innerHTML = `第${weekNum}周 ${weekStr}`;
    +        e.querySelector("#calendar-date").innerHTML = date.toString().padStart(2, "0");
    +        e.querySelector("#calendar-solar").innerHTML = `${year}年${monthStr} 第${asideDay.toFixed(0)}天`;
    +        e.querySelector("#calendar-lunar").innerHTML = `${ganzhiYear}${animalYear}年 ${lunarMon}${lunarDay}`;
    +        document.getElementById("schedule-days").innerHTML = daysUntilNewYear;
    +    }
    +}
    +

    添加css

    +

    在主题文件 source/css/新建 calendar.css

    +
    /* 浅色主题变量覆盖 -------------------------------- */
    +:root {
    +    --anzhiyu-main: #a0d2eb;               /* 主强调色:柔和天空蓝 */
    +    --anzhiyu-main-op: rgba(160, 210, 235, 0.6);
    +    --anzhiyu-main-op-deep: rgba(160, 210, 235, 0.4);
    +    --anzhiyu-main-op-light: rgba(160, 210, 235, 0.2);
    +
    +    --efu-card-bg: #fdfdfd;                /* 卡片背景:几乎白 */
    +    --efu-fontcolor: #444;                 /* 主文本:深灰 */
    +    --efu-secondtext: #999;                /* 次级文本:浅灰 */
    +}
    +.card-widget {
    +    padding: 10px!important;
    +    max-height: calc(100vh - 100px);
    +}
    +.card-times a, .card-times div {
    +    color: var(--efu-fontcolor);
    +}
    +
    +#card-widget-calendar .item-content {
    +    display: flex;
    +}
    +
    +#calendar-area-left {
    +    width: 45%;
    +}
    +
    +#calendar-area-right {
    +    width: 55%;
    +}
    +
    +#calendar-area-left, #calendar-area-right {
    +    height: 100%;
    +    padding: 8px;
    +    display: flex;
    +    flex-direction: column;
    +    align-items: center;
    +    justify-content: flex-start;
    +}
    +
    +#calendar-main {
    +    width: 100%;
    +}
    +
    +#calendar-week {
    +    height: 1.2rem;
    +    font-size: 14px;
    +    letter-spacing: 1px;
    +    font-weight: 700;
    +    align-items: center;
    +    display: flex;
    +}
    +
    +#calendar-date {
    +    height: 3rem;
    +    line-height: 1.3;
    +    font-size: 64px;
    +    letter-spacing: 3px;
    +    color: var(--anzhiyu-main);
    +    font-weight: 700;
    +    align-items: center;
    +    display: flex;
    +    position: relative;
    +    top: calc(50% - 2.1rem);
    +}
    +
    +#calendar-lunar, #calendar-solar {
    +    height: 1rem;
    +    font-size: 12px;
    +    align-items: center;
    +    display: flex;
    +    position: absolute;
    +}
    +
    +#calendar-solar {
    +    bottom: 2.1rem;
    +}
    +
    +#calendar-lunar {
    +    bottom: 1rem;
    +    color: var(--efu-secondtext);
    +}
    +
    +#calendar-main a {
    +    height: 1rem;
    +    width: 1rem;
    +    border-radius: 50%;
    +    font-size: 12px;
    +    line-height: 12px;
    +    display: flex;
    +    justify-content: center;
    +    align-items: center;
    +}
    +
    +#calendar-main a.now {
    +    background: var(--anzhiyu-main);
    +    color: var(--efu-card-bg);
    +}
    +
    +#calendar-main .calendar-rh a {
    +    color: var(--efu-secondtext);
    +}
    +
    +.calendar-r0, .calendar-r1, .calendar-r2, .calendar-r3, .calendar-r4, .calendar-r5, .calendar-rh {
    +    height: 1.2rem;
    +    display: flex;
    +}
    +
    +.calendar-d0, .calendar-d1, .calendar-d2, .calendar-d3, .calendar-d4, .calendar-d5, .calendar-d6 {
    +    width: calc(100% / 7);
    +    display: flex;
    +    justify-content: center;
    +    align-items: center;
    +}
    +
    +#card-widget-schedule .item-content {
    +    display: flex;
    +}
    +
    +#schedule-area-left, #schedule-area-right {
    +    height: 100px;
    +    display: flex;
    +    flex-direction: column;
    +    justify-content: center;
    +    align-items: center;
    +}
    +
    +#schedule-area-left {
    +    width: 30%;
    +}
    +
    +#schedule-area-right {
    +    width: 70%;
    +    padding: 0 5px;
    +}
    +
    +.schedule-r0, .schedule-r1, .schedule-r2 {
    +    height: 2rem;
    +    width: 100%;
    +    align-items: center;
    +    display: flex;
    +}
    +
    +.schedule-d0 {
    +    width: 30px;
    +    margin-right: 5px;
    +    text-align: center;
    +    font-size: 12px;
    +}
    +
    +.schedule-d1 {
    +    width: calc(100% - 35px);
    +    height: 1.5rem;
    +    align-items: center;
    +    display: flex;
    +}
    +
    +progress::-webkit-progress-bar {
    +    background: linear-gradient(to right, var(--anzhiyu-main-op-deep), var(--anzhiyu-main-op), var(--anzhiyu-main-op-light));
    +    border-radius: 5px;
    +    overflow: hidden;
    +}
    +
    +progress::-webkit-progress-value {
    +    background: var(--anzhiyu-main);
    +    border-radius: 5px;
    +}
    +
    +.aside-span1, .aside-span2 {
    +    height: 1rem;
    +    font-size: 12px;
    +    z-index: 1;
    +    display: flex;
    +    align-items: center;
    +    position: absolute;
    +}
    +
    +.aside-span1 {
    +    margin-left: 5px;
    +}
    +
    +.aside-span2 {
    +    right: 20px;
    +    color: var(--efu-secondtext);
    +}
    +
    +.aside-span2 a {
    +    margin: 0 3px;
    +}
    +
    +#pBar_month, #pBar_week, #pBar_year {
    +    width: 100%;
    +    border-radius: 5px;
    +    height: 100%;
    +}
    +
    +#schedule-date, #schedule-days, #schedule-title {
    +    display: flex;
    +    align-items: center;
    +}
    +
    +#schedule-title {
    +    height: 25px;
    +    line-height: 1;
    +    font-size: 14px;
    +    font-weight: 700;
    +}
    +
    +#schedule-days {
    +    height: 40px;
    +    line-height: 1;
    +    font-size: 30px;
    +    font-weight: 900;
    +    color: var(--anzhiyu-main);
    +}
    +
    +#schedule-date {
    +    height: 20px;
    +    line-height: 1;
    +    font-size: 12px;
    +    color: var(--efu-secondtext);
    +}
    +

    引入

    +

    新增+号后面内容

    +
    inject:
    +  head:
    +  # 自定义css
    ++    - <link rel="stylesheet" href="/css/calendar.css">
    +
    +  bottom:
    +  # 自定义js
    ++    - <script src="/js/calendar.js"></script>
    ++    - <script src="https://unpkg.com/chinese-lunar@0.1.4/lib/chinese-lunar.js"></script>
    +

    blogroot/source/_data 文件夹下创建 widget.yml 文件,并添加以下内容:

    +
    bottom:
    +  - class_name: calendar
    +    id_name: card-widget-calendar
    +    name:
    +    icon:
    +    order: -1
    +    html: |
    +      <div id="calendar-area-left">
    +        <div id="calendar-week"></div>
    +        <div id="calendar-date" style="font-size: 48px;"></div>
    +        <div id="calendar-solar"></div>
    +        <div id="calendar-lunar"></div>
    +      </div>
    +      <div id="calendar-area-right">
    +        <div id="calendar-main">
    +        </div>
    +      </div>
    +
    +  - class_name: schedule
    +    id_name: card-widget-schedule
    +    name:
    +    icon:
    +    order: -1
    +    html: |
    +      <div id="schedule-area-left">
    +        <div id="schedule-title">距离除夕</div>
    +        <div id="schedule-days"></div>
    +        <div id="schedule-date">2025-01-28</div>
    +      </div>
    +      <div id="schedule-area-right">
    +        <div class="schedule-r0">
    +          <div class="schedule-d0">本年</div>
    +          <div class="schedule-d1">
    +            <span id="p_span_year" class="aside-span1"></span>
    +            <span class="aside-span2">还剩<a></a>天</span>
    +            <progress max="365" id="pBar_year"></progress>
    +          </div>
    +        </div>
    +        <div class="schedule-r1">
    +          <div class="schedule-d0">本月</div>
    +          <div class="schedule-d1">
    +            <span id="p_span_month" class="aside-span1"></span>
    +            <span class="aside-span2">还剩<a></a>天</span>
    +            <progress max="30" id="pBar_month"></progress>
    +          </div>
    +        </div>
    +        <div class="schedule-r2">
    +          <div class="schedule-d0">本周</div>
    +          <div class="schedule-d1">
    +            <span id="p_span_week" class="aside-span1"></span>
    +            <span class="aside-span2">还剩<a></a>天</span>
    +            <progress max="7" id="pBar_week"></progress>
    +          </div>
    +        </div>
    +      </div>
    +

    参考

    + + + + 乐活星球 + lohas · blog.lohas.fun + +

    在侧边栏添加日历和倒计时

    /posts/5ed2f1e6/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    在侧边栏添加日历和倒计时

    \ No newline at end of file diff --git a/posts/66e66374/index.html b/posts/66e66374/index.html new file mode 100644 index 0000000..5412799 --- /dev/null +++ b/posts/66e66374/index.html @@ -0,0 +1,663 @@ + Adguard和Openclash共存 | Bi's Blog

    Adguard和Openclash共存

    发布于 2025/10/05 技术 总字数:201 阅读时长:1 分钟

    这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

    +AI摘要 + QWEN

    在之前已经安装了Openwrt系统,并且也配置了OpenClashAdguardHome,两者的原理简单看都是劫持DNS,所以两者要同时运行,必须经过一定的配置。 +无非就这两种方式:

    +
    image
    +

    左边的方式更简单一些,只需要修改AdguardHome的上游DNS服务器为127.0.0.1:7874 即可; +右边的方式需要将OpenClash里的DNS指向AdguardHome,但是可能有拦截失败的情况(好像没有遇到过)。

    Adguard和Openclash共存

    /posts/66e66374/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    AdguardHomeOpenclashOpenwrt
    订阅 打赏

    Comments

    Adguard和Openclash共存

    \ No newline at end of file diff --git a/posts/69b16001/index.html b/posts/69b16001/index.html new file mode 100644 index 0000000..fba53e2 --- /dev/null +++ b/posts/69b16001/index.html @@ -0,0 +1,685 @@ + Adguard规则分享 | Bi's Blog

    Adguard规则分享

    发布于 2025/10/05 技术 总字数:286 阅读时长:1 分钟

    这篇文章分享了关于Adguardhome广告拦截工具的使用经验,特别强调了规则的重要性。作者提到,要达到更好的拦截效果,需要搭配有效的规则,并详细介绍了两个关键方面:一是综合过滤规则的分享,二是广告过滤规则订阅中心的集成。通过添加这些规则,使用Adblocktester工具可以达到74分的拦截效果,认为这基本够用。

    +AI摘要 + QWEN

    众所周知,Adguardhome是用于拦截广告的工具,搭配好的规则,拦截效果才会更好,下面来分享一些规则:

    +
      +
    1. +一个综合的过滤规则 + + + + adblockfilters + adblockfilters · github.com + + +
    2. +
    3. +广告过滤规则订阅中心 + + + + 广告过滤规则订阅中心 + 广告过滤规则订阅中心 · adguardfilters-chinese.pages.dev + + +集成了许多规则,可以挑选一下加入。 +
    4. +
    +
    image
    +这是我添加的规则,在这些规则下,用Adblocktester可以达到74分,基本够用 +
    image

    Adguard规则分享

    /posts/69b16001/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    AdguardHomeOpenwrt
    订阅 打赏

    Comments

    Adguard规则分享

    \ No newline at end of file diff --git a/posts/6e3332b/index.html b/posts/6e3332b/index.html new file mode 100644 index 0000000..9ddfab7 --- /dev/null +++ b/posts/6e3332b/index.html @@ -0,0 +1,699 @@ + 配置opencv | Bi's Blog

    配置opencv

    发布于 2026/03/14 技术 总字数:439 阅读时长:2 分钟

    这篇文章介绍了在Ubuntu和Windows系统上配置OpenCV的过程。在Ubuntu系统中,通过包管理器安装了Visual Studio、GCC、G++、OpenCV以及Python的OpenCV绑定。而在Windows系统中,则通过安装Visual Studio并下载OpenCV的Windows安装包进行配置,包括将安装路径添加到系统环境变量中。

    +AI摘要 + QWEN

    引言

    +

    不知道为什么我们专业看起来与计算机八竿子打不着,竟然要学opencv,那就来记录一下吧!

    +

    Ubuntu 篇

    +

    Ubuntu因为有完善的包管理体系,所以配环境相对简单。

    +先安装vscode,当然也可以不安装,只是个编辑器; + + + + vscode + Microsoft · code.visualstudio.com + + +到这里下载deb格式的软件包,然后dpkg安装即可。可以安装这些扩展: +
    +

    接下来安装编译器

    +
    # 先更新软件包
    +apt update
    +apt upgrade -y
    +# 安装C/C++编译器
    +apt install gcc g++
    +# 安装opencv
    +apt install -y mesa-utils
    +apt install -y libopencv-dev
    +apt install -y opencv
    +apt install -y python3-opencv #Python opencv
    +

    然后就可以了。

    +

    Windows 篇

    +Windows 下需要安装Visual Studio,比较简单,官网下载安装就行。选择使用C++桌面开发就行。 +下载opencv,在下边网站下载Windows安装包, + + + + opencv + opencv · opencv.org + + +下载后是一个自解压包,解压路径设置成自己想要的路径, +把安装路径添加到系统环境变量中,例如D:\Opencv\opencv\build\x64\vc16\bin +然后应该就没问题了。

    配置opencv

    /posts/6e3332b/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    opencv
    订阅 打赏

    Comments

    配置opencv

    \ No newline at end of file diff --git a/posts/6e5f5039/index.html b/posts/6e5f5039/index.html new file mode 100644 index 0000000..dce6bf6 --- /dev/null +++ b/posts/6e5f5039/index.html @@ -0,0 +1,758 @@ + 部署历程 | Bi's Blog

    部署历程

    发布于 2025/08/03 建站手札 总字数:2.2k 阅读时长:6 分钟

    这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。

    +AI摘要 + QWEN

    前提条件

    +

    在开始之前,请确保你已满足以下条件:

    +
      +
    • 服务器: +
        +
      • 安装了 1Panel 面板的 Linux 服务器
      • +
      • 拥有服务器的 SSH 访问权限(本文示例将使用 root 用户,你可以根据实际情况替换为其他有权限的用户)。
      • +
      • 服务器上已安装 Git。
      • +
      +
    • +
    • 1Panel 配置:
    • +
    +

    已通过 1Panel 创建了一个网站(例如 blog.yourdomain.com),并记下该网站的 根目录(Document Root)。通常在 1Panel 中,路径类似于 /opt/1panel/apps/openresty/openresty/www/sites/你的网站目录/index。请务必替换为你的实际路径。 +本地环境:

    +
      +
    • 你的电脑上已安装并配置好 Hexo 博客环境。
    • +
    • 你的电脑上已安装 Git。
    • +
    • 已安装 Hexo Git 部署插件:
    • +
    +
    npm install hexo-deployer-git --save
    +

    SSH 密钥认证 (关键):

    +
      +
    1. 你需要在本地电脑生成 SSH 密钥对(如果还没有的话)。
    2. +
    3. 必须将你的本地电脑的 SSH 公钥 添加到服务器上对应用户(如 root)的 ~/.ssh/authorized_keys 文件中。这样可以确保 hexo deploy 时 Git PUSH 操作无需输入密码即可完成。 +配置步骤 +步骤一:在服务器上安装 Git +如果你的服务器尚未安装 Git,请根据你的 Linux 发行版执行相应命令:
    4. +
    +

    Debian/Ubuntu:

    +
    sudo apt-get update
    +sudo apt-get install git -y
    +

    Fedora/RedHat/CentOS:

    +
    sudo yum install git -y
    +

    步骤二:在服务器上创建 Git 裸仓库 (Bare Repository) +我们需要在服务器上创建一个 Git “裸仓库”。裸仓库不包含工作目录(即你看不到项目文件),只存储 Git 的版本历史和对象数据,非常适合作为中心仓库或中转仓库。

    +

    创建仓库目录: +选择一个路径来存放你的 Git 仓库。例如,我们选择放在 /root/git/ 目录下(你可以自定义路径)。

    +

    创建目录 (如果父目录不存在,也一并创建)

    +
    mkdir -p /root/git/
    +

    修改目录权限 (可选但推荐): +如果你不是一直使用 root 用户,或者希望更精细地控制权限,可以修改目录所有者和权限。如果使用 root,此步通常可以跳过。

    +

    将目录所有者改为当前用户 (如果用 root 执行,则为 root)

    +
    chown -R USER:USER:USER /root/git/
    +

    设置权限 (所有者完全控制,同组用户读取执行,其他用户读取执行)

    +
    chmod -R 755 /root/git/
    +

    初始化裸仓库: +进入你选择的目录,并初始化一个裸仓库。我们将仓库命名为 blog.git (你也可以自定义)。

    +
    cd /root/git
    +git init --bare blog.git
    +

    执行后,你会在 /root/git/ 目录下看到一个名为 blog.git 的文件夹。

    +

    步骤三:在服务器上配置 Git 钩子 (post-receive) +Git 钩子是在 Git 操作过程中的特定时间点自动执行的脚本。post-receive 钩子在服务器成功接收到推送 (push) 后执行。我们将利用这个钩子将推送过来的 Hexo 文件检出 (checkout) 到 1Panel 的网站根目录。

    +

    创建钩子文件: +进入裸仓库的 hooks 目录,并创建一个名为 post-receive 的文件。

    +
    vim /root/git/blog.git/hooks/post-receive
    +

    编辑钩子内容: +在 vim 编辑器中,按 i 进入插入模式,然后粘贴以下脚本内容:

    +
    #!/bin/bash
    +# 指定 Hexo 网站文件存放的目录 (!!!!务必替换为你的 1Panel 网站实际根目录!!!!)
    +WORK_TREE="/opt/1panel/apps/openresty/openresty/www/sites/blog/index" 
    +# 指定 Git 裸仓库的路径 (!!!!务必替换为你的裸仓库实际路径!!!!)
    +GIT_DIR="/root/git/blog.git"
    +
    +# 执行 Git checkout 命令,强制将内容检出到工作目录
    +git --work-tree=${WORK_TREE} --git-dir=${GIT_DIR} checkout -f
    +
    +echo "Hexo blog deployed to ${WORK_TREE}"
    +

    可选的提交信息

    +

    请务必修改:

    +

    root: 替换为你配置了 SSH 免密登录的服务器用户名。 +YOUR_SERVER_IP: 替换为你的服务器公网 IP 地址或域名。 +22: 如果你的 SSH 端口不是默认的 22,请修改。 +/root/git/blog.git: 替换为你在服务器上创建的裸仓库的 绝对路径。 +branch: 确保这个分支名 (main 或 master) 与你的 post-receive 钩子期望检出的分支一致(默认情况下 checkout -f 会检出仓库的 HEAD 指向的分支,通常是 master 或 main)。 +步骤五:部署你的 Hexo 博客 +一切配置完成后,部署就非常简单了:

    +

    在本地 Hexo 项目目录下,执行标准的生成和部署命令:

    +
    hexo clean && hexo generate && hexo deploy
    +

    或者直接:

    +
    hexo d
    +

    工作流程:

    +

    hexo g 生成静态文件到 public 目录。 +hexo d 使用 hexo-deployer-git 插件: +将 public 目录的内容提交到一个临时 Git 分支。 +通过 SSH 将这个分支强制推送到你配置的服务器裸仓库 (ssh://root@YOUR_SERVER_IP:22/root/git/blog.git)。 +服务器上的 Git 仓库接收到推送后,自动触发 post-receive 钩子脚本。 +钩子脚本执行 git checkout -f,将最新的网站文件强制检出(部署)到 1Panel 网站的根目录 (/opt/1panel/apps/openresty/openresty/www/sites/blog/index)。 +验证: +部署命令执行成功后,稍等片刻,然后访问你的博客域名 (http://blog.yourdomain.comhttps://blog.yourdomain.com),应该就能看到更新后的内容了。

    +

    故障排除提示

    +
      +
    • 权限问题: 检查服务器上 Git 仓库目录、网站根目录以及钩子文件的权限是否正确。钩子脚本需要有权限写入网站根目录。
    • +
    • SSH 密钥问题: 确保本地 SSH 公钥已正确添加到服务器的 authorized_keys 文件中,并且本地 SSH 私钥可用。尝试手动 ssh root@YOUR_SERVER_IP 看是否能免密登录。
    • +
    • 路径错误: 仔细核对 post-receive 脚本中的 —work-tree 和 —git-dir 路径,以及本地 _config.yml 中的 repo 路径是否完全正确。
    • +
    • 钩子未执行: 检查钩子文件是否有执行权限 (chmod +x),文件名是否精确为 post-receive (没有扩展名)。
    • +
    • 查看日志: 如果部署失败,可以尝试在服务器上查看 SSH 或 Git 相关日志,或者在钩子脚本中添加一些 echo 输出到日志文件来调试。
    • +
    • 通过以上配置,你就实现了一个高效、自动化的 Hexo 博客部署流程!
    • +

    部署历程

    /posts/6e5f5039/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    部署历程

    \ No newline at end of file diff --git a/posts/7207243b/index.html b/posts/7207243b/index.html new file mode 100644 index 0000000..c632211 --- /dev/null +++ b/posts/7207243b/index.html @@ -0,0 +1,1025 @@ + OpenGL-直线的扫描转换 | Bi's Blog

    OpenGL-直线的扫描转换

    发布于 2026/04/12 技术 总字数:1.1k 阅读时长:3 分钟

    这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

    +AI摘要 + QWEN

    这篇文章来介绍直线扫描转换算法

    +

    DDA数值微分线段算法

    +

    算法简介

    +

    数值微分法即DDA法(Digital Differential Analyzer),是一种基于微分方程来生成直线的方法。在计算机图形学中,并没有线段的概念,而是一个个像素点组成了线段。

    +

    DDA法生成线段的步骤一般如下:

    +
      +
    1. 有了起始点()和终点();
    2. +
    3. +
    4. 比较的大小; +steps=中较大者;
    5. +
    6. +
    +

    算法实现

    +

    DDA算法实现如下:

    +
    #include <cmath>
    +#include <iostream>
    +#include <GL/glut.h>
    +
    +// 窗口宽度和高度
    +const int WIDTH = 640;
    +const int HEIGHT = 480;
    +
    +void drawDDALine(int x1, int y1, int x2, int y2) {
    +    float x = x1;
    +    float y = y1;
    +
    +    // 计算差值
    +    int dx = x2 - x1;
    +    int dy = y2 - y1;
    +
    +    // 确定步数,取 dx 和 dy 中绝对值较大的那个
    +    int steps = std::abs(dx) > std::abs(dy) ? std::abs(dx) : std::abs(dy);  //三元表达式
    +
    +    // 计算每一步的增量
    +    float xIncrement = (float)dx / steps;
    +    float yIncrement = (float)dy / steps;
    +
    +    // 开始绘制点
    +    glBegin(GL_POINTS);
    +    glVertex2i((int)round(x), (int)round(y)); // 绘制起点
    +
    +    for (int k = 0; k < steps; k++) {
    +        x += xIncrement;
    +        y += yIncrement;
    +        // 将浮点坐标四舍五入取整转换为整数像素坐标
    +		std::cout << (int)round(x) << ", " << (int)round(y)<<"\n";
    +        glVertex2i((int)round(x), (int)round(y));
    +    }
    +    glEnd();
    +}
    +
    +// 显示回调函数
    +void display() {
    +    drawDDALine(0, 0, 50, 20);
    +    glFlush();
    +}
    +
    +// 初始化 OpenGL 设置
    +void init() {
    +    // 设置背景颜色为白色
    +    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    +
    +    // 设置投影矩阵为 2D 正交投影
    +    glMatrixMode(GL_PROJECTION);
    +    glLoadIdentity();
    +    // 定义可视区域,左下角(0,0),右上角(WIDTH, HEIGHT)
    +    gluOrtho2D(0.0, WIDTH, 0.0, HEIGHT);
    +}
    +
    +int main(int argc, char** argv) {
    +    // 初始化 GLUT
    +    glutInit(&argc, argv);
    +
    +    // 设置显示模式:单缓冲、RGB 颜色模式
    +    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    +
    +    // 设置窗口大小和位置
    +    glutInitWindowSize(WIDTH, HEIGHT);
    +    glutInitWindowPosition(100, 100);
    +
    +    // 创建窗口
    +    glutCreateWindow("DDA算法");
    +
    +    // 注册回调函数
    +    glutDisplayFunc(display);
    +
    +    // 初始化设置
    +    init();
    +
    +    // 进入主循环
    +    glutMainLoop();
    +
    +    return 0;
    +}
    +

    中点画线算法

    +

    算法简介

    +

    只考虑当直线的斜率时的情况,假设现在有一条直线,那么第一个点一定是无疑,下一个点的坐标为,坐标要么为要么为。关键在于每次取下一个点时,是取前一个的呢,还是,这时一定是取直线上点最靠近的那个了,而判断取哪个点就用到了中点,我们将中点代入直线中

    +
      +
    1. 如果直线,则取下边的点也就是
    2. +
    3. 如果直线,则取上边的点也就是
    4. +
    +

    它的实际过程就是这样每次根据前边的点判断下一个点在哪,然后进行打亮,但这样每次判断的时候都得代入直线方程计算太麻烦了,我们将这俩种情况分别代入直线方程中可以找出规律:

    +
      +
    1. 当直线时,经过化解得;
    2. +
    3. 当直线时,经过化解得;
    4. +
    5. 初始值
    6. +
    +

    也就是说每次的增量要么为,要么为,那么这样判断的时候就简单多了,因为我们每次只是判断它的正负。所以给等式同时乘2,将其中浮点数0.5化为整数,这样硬件操作时无疑更快了。

    +

    算法实现

    +
    #include <GL/freeglut.h>
    +#include <iostream>
    +#include <cmath>
    +
    +// 中点画线算法函数 (仅针对斜率 0 <= k <= 1 的情况进行演示,其它象限需类比处理)
    +void drawLineMidpoint(int x0, int y0, int x1, int y1) {
    +    int a = y0 - y1;
    +    int b = x1 - x0;
    +    int d = 2 * a + b;
    +    int d1 = 2 * a;
    +    int d2 = 2 * (a + b);
    +
    +    int x = x0, y = y0;
    +
    +    glBegin(GL_POINTS);
    +    glVertex2i(x, y); // 画起点
    +
    +    while (x < x1) {
    +        if (d < 0) {
    +            x++;
    +            y++;
    +            d += d2;
    +        } else {
    +            x++;
    +            d += d1;
    +        }
    +        glVertex2i(x, y);
    +    }
    +    glEnd();
    +}
    +
    +// 渲染回调函数
    +void display() {
    +    glClear(GL_COLOR_BUFFER_BIT);
    +    glColor3f(1.0, 1.0, 1.0); // 设置画笔颜色为白色
    +
    +    // 调用算法画一条直线 (x0, y0) 到 (x1, y1)
    +    // 注意:此处的坐标对应屏幕像素坐标
    +    drawLineMidpoint(50, 50, 450, 300);
    +
    +    glFlush();
    +}
    +
    +// 初始化设置
    +void init() {
    +    glClearColor(0.0, 0.0, 0.0, 1.0); // 背景设为黑色
    +    glMatrixMode(GL_PROJECTION);
    +    glLoadIdentity();
    +    // 设置正交投影矩阵,使坐标系与窗口像素对应
    +    gluOrtho2D(0, 500, 0, 500); 
    +}
    +
    +int main(int argc, char** argv) {
    +    glutInit(&argc, argv);
    +    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    +    glutInitWindowSize(500, 500);
    +    glutInitWindowPosition(100, 100);
    +    glutCreateWindow("Midpoint Line Algorithm - FreeGLUT");
    +
    +    init();
    +    glutDisplayFunc(display);
    +    glutMainLoop();
    +
    +    return 0;
    +}
    +

    Bresenham算法

    +

    算法简介

    +

    假设我们需要由这一点,绘画一直线至右下角的另一点,x,y分别代表其水平及垂直坐标,并且。在此我们使用电脑系统常用的坐标系,即坐标值沿轴向右增长,坐标值沿轴向下增长。

    +

    因此x及y之值分别向右及向下增加,而两点之水平距离为且垂直距离为。由此得之,该线的斜率必定介乎于之间。而此算法之目的,就是找出在之间,第行相对应的第列,从而得出一像素点,使得该像素点的位置最接近原本的线。

    +

    对于由两点所组成之直线,公式如下: + +因此,对于每一点的x,其y的值是 + +因为皆为整数,但并非每一点所对应的皆为整数,故此没有必要去计算每一点x所对应之值。反之由于此线之斜率介乎于之间,故此我们只需要找出当到达那一个数值时,会使上升,若尚未到此值,则不变。至于如何找出相关的值,则需依靠斜率。斜率之计算方法为。由于此值不变,故可于运算前预先计算,减少运算次数。

    +

    要实行此算法,我们需计算每一像素点与该线之间的误差。于上述例子中,误差应为每一点中,其相对的像素点之值与该线实际之值的差距。每当的值增加,误差的值就会增加。每当误差的值超出,线就会比较靠近下一个映像点,因此的值便会加,且误差减

    +

    算法实现

    +
    #include <GL/freeglut.h>
    +#include <iostream>
    +#include <cmath>
    +
    +// 通用 Bresenham 画线算法
    +void drawLineBresenham(int x0, int y0, int x1, int y1) {
    +    int dx = abs(x1 - x0);
    +    int dy = abs(y1 - y0);
    +    int sx = (x0 < x1) ? 1 : -1; // X 方向步进
    +    int sy = (y0 < y1) ? 1 : -1; // Y 方向步进
    +    int err = dx - dy;           // 初始误差项
    +
    +    glBegin(GL_POINTS);
    +    while (true) {
    +        glVertex2i(x0, y0); // 绘制当前点
    +
    +        if (x0 == x1 && y0 == y1) break; // 到达终点
    +
    +        int e2 = 2 * err;
    +        // 判断是否在 X 方向步进
    +        if (e2 > -dy) {
    +            err -= dy;
    +            x0 += sx;
    +        }
    +        // 判断是否在 Y 方向步进
    +        if (e2 < dx) {
    +            err += dx;
    +            y0 += sy;
    +        }
    +    }
    +    glEnd();
    +}
    +
    +// 渲染回调
    +void display() {
    +    glClear(GL_COLOR_BUFFER_BIT);
    +    glColor3f(0.0f, 0.8f, 0.4f); // 设置一个好看的绿色(类似你图中的图标颜色)
    +
    +    // 绘制几条不同方向的线来测试算法的健壮性
    +    drawLineBresenham(50, 50, 450, 400);  // 第一象限
    +    drawLineBresenham(50, 400, 450, 50);  // 第四象限
    +    drawLineBresenham(250, 50, 250, 450); // 垂直线
    +    drawLineBresenham(50, 250, 450, 250); // 水平线
    +
    +    glFlush();
    +}
    +
    +void init() {
    +    glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 深色背景
    +    glMatrixMode(GL_PROJECTION);
    +    glLoadIdentity();
    +    gluOrtho2D(0, 500, 0, 500); // 建立 500x500 的直角坐标系
    +}
    +
    +int main(int argc, char** argv) {
    +    glutInit(&argc, argv);
    +    glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
    +    glutInitWindowSize(600, 600);
    +    glutCreateWindow("Bresenham Line Algorithm");
    +    init();
    +    glutDisplayFunc(display);
    +    glutMainLoop();
    +    return 0;
    +}

    OpenGL-直线的扫描转换

    /posts/7207243b/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenGL
    订阅 打赏

    Comments

    OpenGL-直线的扫描转换

    \ No newline at end of file diff --git a/posts/7baa41fc/index.html b/posts/7baa41fc/index.html new file mode 100644 index 0000000..42163bd --- /dev/null +++ b/posts/7baa41fc/index.html @@ -0,0 +1,687 @@ + 自建bitwarden服务 | Bi's Blog

    自建bitwarden服务

    发布于 2026/02/25 技术 总字数:327 阅读时长:1 分钟

    这篇文章介绍了如何自建Bitwarden服务,以替代浏览器自带的密码管理器。Bitwarden是一款开源、端到端加密的密码管理器,支持多种操作系统和设备,具有安全存储、自动同步、一键填充、生成随机密码和检测弱密码等功能。文章详细描述了部署Bitwarden服务的步骤,包括安装Vaultwarden、配置Nginx反向代理以及设置域名和数据卷等。通过这些步骤,用户可以轻松搭建一个功能强大的密码管理服务器,提高个人信息安全。

    +AI摘要 + QWEN

    Bitwarden 是一款开源、端到端加密的密码管理器,支持 Windows、macOS、Linux、Android、iOS 以及几乎所有主流浏览器。 +它可以帮你:

    +
      +
    • 安全存储密码、银行卡、笔记、密钥等敏感信息
    • +
    • 全设备自动同步
    • +
    • 一键自动填充账号密码
    • +
    • 生成高强度随机密码
    • +
    • 检测弱密码、重复密码、泄露密码
    • +
    +

    之前一直使用浏览器自带的密码管理器,跨平台不太好用,所以自建一个Bitwarden服务器。 +但是我们一般用Vaultwarden,占用更小,有一些Bitwarden的商业功能。

    +
    services:
    +  vaultwarden:
    +    image: vaultwarden/server:latest
    +    container_name: vaultwarden
    +    restart: unless-stopped
    +    environment:
    +      DOMAIN: "https://vw.domain.tld"   #更改成自己的域名
    +    volumes:
    +      - ./vw-data/:/data/
    +    ports:
    +      - 127.0.0.1:8000:80
    +
    +

    然后使用nignx进行反向代理,然后就可以尽情享用了。

    + + + + vaultwarden + vaultwarden · github.com + +

    自建bitwarden服务

    /posts/7baa41fc/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    订阅 打赏

    Comments

    自建bitwarden服务

    \ No newline at end of file diff --git a/posts/7e921903/index.html b/posts/7e921903/index.html new file mode 100644 index 0000000..3ae81fc --- /dev/null +++ b/posts/7e921903/index.html @@ -0,0 +1,927 @@ + 添加网站左上角菜单 | Bi's Blog

    添加网站左上角菜单

    发布于 2026/02/10 建站手札 总字数:223 阅读时长:1 分钟

    这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。

    +AI摘要 + QWEN

    看到柳神的网站有这种菜单,但是没有写魔改教程,只好自己慢慢摸索了。

    + + + + liushen + liushen · blog.liushen.fun + + +

    预计做好后是这种效果:

    +

    示意图

    +

    首先,修改\themes\butterfly\layout\includes\header\nav.pug:

    +
    nav#nav
    +  //- 左侧区域:包含指纹菜单和网站名
    +  span#blog-info
    +    #ls-menu-container
    +      i.fas.fa-fingerprint
    +      #ls-menu-panel
    +        .ls-section
    +          .ls-title 😀 个人网站
    +          .ls-grid
    +            a(href="/") #[i.fas.fa-rss] 个人博客
    +            a(href="https://github.com/bishshi") #[i.fab.fa-github] Github
    +        .ls-section
    +          .ls-title 😎 常用服务
    +          .ls-grid
    +            a(href="https://git.biss.click/biss") #[i.fas.fa-code] 代码仓库
    +            a(href="https://mm.biss.click") #[i.fas.fa-pen-nib] 日常yy
    +            a(href="https://statstic.biss.click") #[i.fas.fa-users] 访客统计
    +            a(href="https://pic.biss.click") #[i.fas.fa-image] 图床
    +            a(href="https://chat.biss.click") #[i.fas.fa-robot] AI网站
    +        .ls-section
    +          .ls-title 🛸 实用工具
    +          .ls-grid
    +            a(href="https://cover.biss.click") #[i.fas.fa-palette] 封面设计
    +
    +    a.nav-site-title(href=url_for('/'))
    +      if theme.nav.logo
    +        img.site-icon(src=url_for(theme.nav.logo) alt='Logo')
    +      if theme.nav.display_title
    +        span.site-name=config.title
    +
    +  //- 中间区域:关键!必须在 nav 下一级,以便 JS 切换类名
    +  if globalPageType === 'post' && theme.nav.display_post_title
    +    a.nav-page-title(href='javascript:void(0);' onclick='btf.scrollToDest(0, 500)')
    +      span.site-name=(page.title || config.title)
    +
    +  //- 右侧区域
    +  #nav-right
    +    if theme.menu
    +      #menus
    +        != partial('includes/header/menu_item', {}, {cache: true})
    +    
    +    if theme.search.use || true
    +      #random-post-button
    +        a.site-page.social-icon#random-post-link(href='javascript:void(0);' onclick='randomPost()')
    +          i.fas.fa-solid.fa-shuffle
    +      #search-button
    +        a.site-page.social-icon.search-typesense-trigger
    +          i.fas.fa-search.fa-fw
    +      #toggle-menu
    +        span.site-page
    +          i.fas.fa-bars.fa-fw
    +

    在合适的目录下新建nav.css(例如\themes\butterfly\source\css\nav.css),这份css是磨砂玻璃的样式:

    +
    #nav-right{
    +  flex:1 1 auto;
    +  justify-content: flex-end;
    +  margin-left: auto;
    +  display: flex;
    +  flex-wrap:nowrap;
    +}
    +  
    +/* 导航栏居中 */
    +
    +#sidebar #sidebar-menus .menus_items .menus_item {
    +    margin: 10px 0;
    +}
    +#sidebar #sidebar-menus .menus_items a.site-page {
    +    padding-left: 0;
    +}
    +#sidebar #sidebar-menus .menus_items .site-page {
    +    position: relative;
    +    display: block;
    +    padding: 6px 30px 6px 22px;
    +    color: var(--font-color);
    +    font-size: 1.15em;
    +    border: var(--style-border-always);
    +    background: var(--icat-card-bg);
    +    font-size: 14px;
    +    border-radius: 12px;
    +}
    +#sidebar #sidebar-menus .menus_items .site-page i:first-child {
    +    text-align: left;
    +    padding-left: 10px;
    +}
    +
    +#nav #menus {
    +    display: flex;
    +    justify-content: center;
    +    width: 100%;
    +    position: absolute;
    +    left: 0;
    +    margin: 0;
    +    transform: translateZ(0);
    +}
    +#nav #blog-info {
    +    flex-wrap: nowrap;
    +    display: flex;
    +    align-items: center;
    +    z-index: 102;
    +    max-width: fit-content;
    +}
    +@media screen and (max-width: 900px) {
    +    #nav {
    +        padding: 0 15px;
    +    }
    +    #nav-group {
    +        padding: 0 0.2rem;
    +    }
    +    #rightside {
    +        right: -42px;
    +    }
    +}
    +/* IPAD菜单栏调整 */
    +
    +/* 1. 容器溢出穿透 */
    +#nav, #blog-info, #nav-group {
    +  overflow: visible !important;
    +}
    +
    +#ls-menu-container {
    +  position: relative !important;
    +  display: inline-flex !important;
    +  align-items: center;
    +  height: 100%;
    +  padding: 0 15px;
    +  cursor: pointer !important;
    +  z-index: 2000 !important;
    +}
    +
    +/* 2. 悬浮面板:宽大且高不透明度 */
    +#ls-menu-panel {
    +  position: absolute !important;
    +  top: 100% !important;
    +  left: 0 !important;
    +  margin-top: 15px !important;
    +  width: 420px; 
    +  padding: 24px;
    +  border-radius: 18px;
    +  
    +  /* 高不透明度磨砂玻璃 (0.9) */
    +  background: rgba(255, 255, 255, 0.95) !important;
    +  backdrop-filter: blur(20px) saturate(180%) !important;
    +  -webkit-backdrop-filter: blur(20px) saturate(180%) !important;
    +  
    +  border: 1px solid rgba(255, 255, 255, 0.5) !important;
    +  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.18) !important;
    +
    +  opacity: 0 !important;
    +  visibility: hidden !important;
    +  transform: translateY(12px) !important;
    +  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
    +  z-index: 999999 !important;
    +  cursor: default;
    +}
    +
    +/* 3. 透明感应桥梁 */
    +#ls-menu-panel::before {
    +  content: "";
    +  position: absolute;
    +  top: -20px;
    +  left: 0;
    +  right: 0;
    +  height: 25px;
    +  background: transparent !important;
    +}
    +
    +/* 4. 触发效果 */
    +#ls-menu-container:hover #ls-menu-panel {
    +  opacity: 1 !important;
    +  visibility: visible !important;
    +  transform: translateY(0) !important;
    +}
    +
    +#ls-menu-container:hover i.fa-fingerprint {
    +  color: #49b1f5;
    +  transform: scale(1.1);
    +}
    +
    +/* 5. 内部网格:保持两列 */
    +.ls-section { margin-bottom: 22px; }
    +.ls-section:last-child { margin-bottom: 0; }
    +.ls-title { 
    +  color: #333; 
    +  font-weight: 800; 
    +  font-size: 15px; 
    +  margin-bottom: 12px; 
    +  opacity: 0.9;
    +}
    +
    +.ls-grid { 
    +  display: grid !important; 
    +  grid-template-columns: repeat(2, 1fr); /* 恢复为两列 */
    +  gap: 10px; 
    +}
    +
    +.ls-grid a { 
    +  color: #444 !important; 
    +  font-size: 14px !important; 
    +  padding: 10px 12px; 
    +  border-radius: 12px;
    +  display: flex;
    +  align-items: center;
    +  transition: all 0.2s ease;
    +  background: rgba(0, 0, 0, 0.02);
    +}
    +
    +.ls-grid a:hover { 
    +  background: #49b1f5 !important; 
    +  color: #fff !important; 
    +  transform: translateX(4px); /* 悬停时轻微右移,增加动感 */
    +}
    +
    +.ls-grid a i { 
    +  margin-right: 12px; 
    +  width: 20px; 
    +  text-align: center; 
    +  font-size: 15px;
    +}
    +
    +/* 6. 深色模式适配 */
    +[data-theme='dark'] #ls-menu-panel {
    +  background: rgba(30, 30, 30, 0.95) !important;
    +  border-color: rgba(255, 255, 255, 0.1) !important;
    +}
    +[data-theme='dark'] .ls-title { color: #eee; }
    +[data-theme='dark'] .ls-grid a { 
    +  color: #ccc !important; 
    +  background: rgba(255, 255, 255, 0.05); 
    +}
    +
    +#nav.show-title .nav-page-title {
    +  display: flex !important;
    +  opacity: 1 !important;
    +  visibility: visible !important;
    +  position: absolute !important;
    +  left: 50% !important;
    +  transform: translateX(-50%) !important;
    +  white-space: nowrap !important;
    +  z-index: 1000 !important;
    +}
    +
    +#nav.show-title #menus,
    +#nav.show-title .nav-site-title {
    +  opacity: 0 !important;
    +  visibility: hidden !important;
    +  pointer-events: none !important;
    +}
    +
    +#nav .nav-page-title {
    +  display: none; 
    +  transition: opacity 0.3s ease;
    +}
    +
    +#nav #blog-info {
    +  overflow: visible !important;
    +}
    +

    然后在_config.butterfly.yml里面引用该css

    +
    - <link rel="stylesheet" href="/css/nav.css">
    +

    然后重新构建应该就可以看到效果了。

    添加网站左上角菜单

    /posts/7e921903/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    添加网站左上角菜单

    \ No newline at end of file diff --git a/posts/8712baaa/index.html b/posts/8712baaa/index.html new file mode 100644 index 0000000..f63efca --- /dev/null +++ b/posts/8712baaa/index.html @@ -0,0 +1,678 @@ + 电子价签玩法 | Bi's Blog

    电子价签玩法

    这篇文章介绍了作者在逛咸鱼时发现并购买了两个改造二手电子价签的过程,这些电子价签由tsl0922开发的开源固件提供支持,具备日历、传图、文字功能,并且可以在网页端进行便捷的配置。尽管开源版固件功能有限,作者选择自行编写所需功能,目前已经实现了待办事项和倒计时功能,并能生成图片后上传至网络。

    +AI摘要 + QWEN

    最近在逛咸鱼时发现了改造二手电子价签,实现了一点有意思的功能,于是买了两个玩玩。 +他们用的是tsl0922开发的开源固件,具有日历、传图、文字功能,在网页端可以很方便地配置。

    + + + + 上位机 + tsl0922 · tsl0922.github.io + + + + +当然开源版固件功能有点少,又不想买烧录器自己折腾,所以把自己想要的功能写成网页: +目前写了待办事项和倒计时,生成图片后上传 + + + + 生成图 + tu · letters.biss.click + + + +

    电子价签玩法

    /posts/8712baaa/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    订阅 打赏

    Comments

    电子价签玩法

    \ No newline at end of file diff --git a/posts/8802e397/index.html b/posts/8802e397/index.html new file mode 100644 index 0000000..55140cc --- /dev/null +++ b/posts/8802e397/index.html @@ -0,0 +1,690 @@ + Cudy TR3000刷入Openwrt系统 | Bi's Blog

    Cudy TR3000刷入Openwrt系统

    发布于 2025/09/01 技术 总字数:376 阅读时长:1 分钟

    这篇文章介绍了如何将Cudy TR3000路由器刷入OpenWrt系统。首先,作者提供了下载中间固件和OpenWrt固件的方法。接着,详细说明了刷入过程的步骤,包括访问不同的IP地址进行固件刷入,并提到了OpenWrt的默认登录密码是root。整个过程旨在使路由器能够支持科学上网功能。

    +AI摘要 + QWEN

    最近搞到了一台Cudy-TR3000路由器,用来进行科学上网(小猫咪),先记录一下刷写Openwrt系统的过程。

    +

    下载固件

    +
      +
    1. 中间固件 +中间固件可以在Cudy官方网站上下载
    2. +
    + + + + Cudy固件下载 + Cudy · cudy.com + + +
    image
    +2. Openwrt固件 +在下面的网站中下载,下载最新的就行 + + + + Openwrt固件 + Openwrt.ai · openwrt.ai + + +

    刷写固件

    +

    访问192.168.10.1先刷入中间固件

    +
    image
    +

    然后访问192.168.1.1,刷入Openwrt插件

    +
    image
    +

    然后访问10.0.0.1,就可以看到openwrt登录页面了,默认密码是root

    +
    image

    Cudy TR3000刷入Openwrt系统

    /posts/8802e397/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    Cudy-TR3000
    订阅 打赏

    Comments

    Cudy TR3000刷入Openwrt系统

    \ No newline at end of file diff --git a/posts/8bdb35fb/index.html b/posts/8bdb35fb/index.html new file mode 100644 index 0000000..7449782 --- /dev/null +++ b/posts/8bdb35fb/index.html @@ -0,0 +1,1161 @@ + 自定义右键菜单 | Bi's Blog

    自定义右键菜单

    发布于 2025/08/12 建站手札 总字数:177 阅读时长:1 分钟

    这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。

    +AI摘要 + QWEN

    演示

    +

    本站右键即可查看,和原有菜单相比比较美观

    +

    新建pug文件

    +

    \themes\butterfly\layout\includes新建 rightmenu.pug

    +
    #rightMenu.js-pjax
    +    .rightMenu-group.rightMenu-small
    +        a.rightMenu-item(href="javascript:window.history.back();")
    +            i.fa.fa-arrow-left
    +        a.rightMenu-item(href="javascript:window.history.forward();")
    +            i.fa.fa-arrow-right
    +        a.rightMenu-item(href="javascript:window.location.reload();")
    +            i.fa.fa-refresh
    +        a.rightMenu-item(href="javascript:window.scrollTo(0, 0);")
    +            i.fa.fa-arrow-up
    +    .rightMenu-group.rightMenu-line.hide#menu-text
    +        a.rightMenu-item(href="javascript:rmf.copySelect();")
    +            i.fa.fa-copy
    +            span='复制'
    +        a.rightMenu-item(href="javascript:rmf.searchinThisPage();")
    +            i.fas.fa-search
    +            span='站内搜索'
    +    .rightMenu-group.rightMenu-line.hide#menu-too
    +        a.rightMenu-item(href="javascript:window.open(window.getSelection().toString());window.location.reload();")
    +            i.fa.fa-link
    +            span='转到链接'
    +    .rightMenu-group.rightMenu-line.hide#menu-paste
    +        a.rightMenu-item(href='javascript:rmf.paste()')
    +            i.fa.fa-copy
    +            span='粘贴'
    +    .rightMenu-group.rightMenu-line.hide#menu-post
    +        a.rightMenu-item(href="javascript:rmf.copyWordsLink()")
    +            i.fa.fa-link
    +            span='复制本文地址'
    +    .rightMenu-group.rightMenu-line.hide#menu-to
    +        a.rightMenu-item(href="javascript:rmf.openWithNewTab()")
    +            i.fa.fa-window-restore
    +            span='新窗口打开'
    +        a.rightMenu-item(href="javascript:rmf.open()")
    +            i.fa.fa-link
    +            span='转到链接'
    +        a.rightMenu-item(href="javascript:rmf.copyLink()")
    +            i.fa.fa-copy
    +            span='复制链接'
    +    .rightMenu-group.rightMenu-line.hide#menu-img
    +        a.rightMenu-item(href="javascript:rmf.saveAs()")
    +            i.fa.fa-download
    +            span='保存图片'
    +        a.rightMenu-item(href="javascript:rmf.openWithNewTab()")
    +            i.fa.fa-window-restore
    +            span='在新窗口打开'
    +        a.rightMenu-item(href="javascript:rmf.click()")
    +            i.fa.fa-arrows-alt
    +            span='全屏显示'
    +        a.rightMenu-item(href="javascript:rmf.copyLink()")
    +            i.fa.fa-copy
    +            span='复制图片链接'
    +    .rightMenu-group.rightMenu-line
    +        a.rightMenu-item(href="javascript:randomPost()")
    +            i.fa.fa-paper-plane
    +            span='随便逛逛'
    +        a.rightMenu-item(href="javascript:rmf.switchDarkMode();")
    +            i.fa.fa-moon
    +            span='昼夜切换'
    +        a.rightMenu-item(href="javascript:rmf.translate();")
    +            i.iconfont.icon-fanti
    +            span='繁简转换'
    +        if is_post()||is_page()
    +            a.rightMenu-item(href="javascript:rmf.switchReadMode();")
    +                i.fa.fa-book
    +                span='阅读模式'
    +        a.rightMenu-item(href="javascript:pjax.loadUrl(\"/privacy/\");")
    +            i.fa.fa-info-circle
    +            span='隐私声明'
    +        a.rightMenu-item(href="javascript:pjax.loadUrl(\"/cookie/\");")
    +            i.fa.fa-info-circle
    +            span='Cookie协议'
    +        a.rightMenu-item(href="javascript:pjax.loadUrl(\"/cc/\");")
    +            i.fa.fa-info-circle
    +            span='版权声明'
    +

    新建js文件

    +

    创建 \themes\butterfly\source\js\rightmenu.js

    +
    function setMask() {//设置遮罩层
    +    if (document.getElementsByClassName("rmMask")[0] !== undefined) {
    +        return document.getElementsByClassName("rmMask")[0];
    +    }
    +    mask = document.createElement('div');
    +    mask.className = "rmMask";
    +    mask.style.width = window.innerWidth + 'px';
    +    mask.style.height = window.innerHeight + 'px';
    +    mask.style.background = '#fff';
    +    mask.style.opacity = '.0';
    +    mask.style.position = 'fixed';
    +    mask.style.top = '0';
    +    mask.style.left = '0';
    +    mask.style.zIndex = 998;
    +    document.body.appendChild(mask);
    +    document.getElementById("rightMenu").style.zIndex = 19198;
    +    return mask;
    +}
    +
    +function insertAtCursor(myField, myValue) {
    +
    +    //IE 浏览器
    +    if (document.selection) {
    +        myField.focus();
    +        sel = document.selection.createRange();
    +        sel.text = myValue;
    +        sel.select();
    +    }
    +
    +    //FireFox、Chrome等
    +    else if (myField.selectionStart || myField.selectionStart === '0') {
    +        var startPos = myField.selectionStart;
    +        var endPos = myField.selectionEnd;
    +
    +        // 保存滚动条
    +        var restoreTop = myField.scrollTop;
    +        myField.value = myField.value.substring(0, startPos) + myValue + myField.value.substring(endPos, myField.value.length);
    +
    +        if (restoreTop > 0) {
    +            myField.scrollTop = restoreTop;
    +        }
    +
    +        myField.focus();
    +        myField.selectionStart = startPos + myValue.length;
    +        myField.selectionEnd = startPos + myValue.length;
    +    } else {
    +        myField.value += myValue;
    +        myField.focus();
    +    }
    +}
    +
    +let rmf = {};
    +rmf.showRightMenu = function (isTrue, x = 0, y = 0) {
    +    let $rightMenu = $('#rightMenu');
    +    $rightMenu.css('top', x + 'px').css('left', y + 'px');
    +
    +    if (isTrue) {
    +        $rightMenu.show();
    +    } else {
    +        $rightMenu.hide();
    +    }
    +}
    +rmf.switchDarkMode = function () {
    +    const nowMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'dark' : 'light'
    +    if (nowMode === 'light') {
    +        activateDarkMode()
    +        saveToLocal.set('theme', 'dark', 2)
    +        GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)
    +    } else {
    +        activateLightMode()
    +        saveToLocal.set('theme', 'light', 2)
    +        GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)
    +    }
    +    // handle some cases
    +    typeof utterancesTheme === 'function' && utterancesTheme()
    +    typeof FB === 'object' && window.loadFBComment()
    +    window.DISQUS && document.getElementById('disqus_thread').children.length && setTimeout(() => window.disqusReset(), 200)
    +    switchPostChart();
    +};
    +
    +rmf.copyWordsLink = function () {
    +    const decodedUrl = decodeURIComponent(window.location.href); // 解码 URL
    +    navigator.clipboard.writeText(decodedUrl)
    +        .then(() => {
    +            Snackbar.show({
    +                text: '链接复制成功!快去分享吧!',
    +                pos: 'top-right',
    +                showAction: false
    +            });
    +        })
    +};
    +
    +rmf.switchReadMode = function () {
    +    const $body = document.body
    +    $body.classList.add('read-mode')
    +    const newEle = document.createElement('button')
    +    newEle.type = 'button'
    +    newEle.className = 'fas fa-sign-out-alt exit-readmode'
    +    $body.appendChild(newEle)
    +
    +    function clickFn() {
    +        $body.classList.remove('read-mode')
    +        newEle.remove()
    +        newEle.removeEventListener('click', clickFn)
    +    }
    +
    +    newEle.addEventListener('click', clickFn)
    +}
    +
    +//复制选中文字
    +rmf.copySelect = function () {
    +    navigator.clipboard.writeText(document.getSelection().toString()).then(() => {
    +        Snackbar.show({
    +            text: '已复制选中文字!',
    +            pos: 'top-right',
    +            showAction: false,
    +        });
    +    });
    +}
    +
    +//回到顶部
    +rmf.scrollToTop = function () {
    +    document.getElementsByClassName("menus_items")[1].setAttribute("style", "");
    +    document.getElementById("name-container").setAttribute("style", "display:none");
    +    btf.scrollToDest(0, 500);
    +}
    +rmf.translate = function () {
    +    document.getElementById("translateLink").click();
    +}
    +rmf.searchinThisPage = () => {
    +    let mask = setMask(); // 确保 mask 元素存在于 document.body 中
    +    document.getElementsByClassName("local-search-box--input")[0].value = window.getSelection().toString();
    +    document.getElementsByClassName("search")[0].click();
    +    var evt = document.createEvent("HTMLEvents");
    +    evt.initEvent("input", false, false);
    +    document.getElementsByClassName("local-search-box--input")[0].dispatchEvent(evt);
    +
    +    // 在尝试移除 mask 元素之前检查它是否存在于 document.body 中
    +    if (document.body.contains(mask)) {
    +        document.body.removeChild(mask);
    +    }
    +}
    +
    +document.body.addEventListener('touchmove', function (e) {
    +
    +}, {passive: false});
    +
    +function popupMenu() {
    +    //window.oncontextmenu=function(){return false;}
    +    window.oncontextmenu = function (event) {
    +        Snackbar.show({
    +            text: '按住 Ctrl 再点击右键,即可恢复原界面哦',
    +            pos: 'bottom-left',
    +            showAction: false
    +        });
    +        if (event.ctrlKey || document.body.clientWidth < 900) return true;
    +        $('.rightMenu-group.hide').hide();
    +        if (document.getSelection().toString()) {
    +            $('#menu-text').show();
    +        }
    +        if (document.getElementById('post')) {
    +            $('#menu-post').show();
    +        } else {
    +            if (document.getElementById('page')) {
    +                $('#menu-post').show();
    +            }
    +        }
    +        var el = window.document.body;
    +        el = event.target;
    +        var a = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:\/?#[\]@!$&'*+,;=]+$/
    +        if (a.test(window.getSelection().toString()) && el.tagName !== "A") {
    +            $('#menu-too').show()
    +        }
    +        if (el.tagName === 'A') {
    +            $('#menu-to').show()
    +            rmf.open = function () {
    +                if (el.href.indexOf("http://") === -1 && el.href.indexOf("https://") === -1 || el.href.indexOf("blog.june-pj.cn") !== -1) {
    +                    pjax.loadUrl(el.href)
    +                } else {
    +                    location.href = el.href
    +                }
    +            }
    +            rmf.openWithNewTab = function () {
    +                window.open(el.href);
    +                // window.location.reload();
    +            }
    +            rmf.copyLink = function () {
    +                const url = el.href;
    +                navigator.clipboard.writeText(url);
    +                Snackbar.show({
    +                    text: '链接复制成功!快去分享吧!',
    +                    pos: 'top-right',
    +                    showAction: false
    +                });
    +            };
    +        }
    +        if (el.tagName === 'IMG') {
    +            $('#menu-img').show()
    +            rmf.openWithNewTab = function () {
    +                window.open(el.src);
    +                // window.location.reload();
    +            }
    +            rmf.click = function () {
    +                el.click()
    +            }
    +            rmf.copyLink = function () {
    +                const url = el.src
    +                navigator.clipboard.writeText(url);
    +                Snackbar.show({
    +                    text: '链接复制成功!快去分享吧!',
    +                    pos: 'top-right',
    +                    showAction: false
    +                });
    +            }
    +            rmf.saveAs = function () {
    +                var a = document.createElement('a');
    +                a.href = el.src;
    +                 // 获取图片的文件名部分
    +                a.download = el.src.split('/').pop(); // 使用图片的文件名作为下载文件名
    +                a.style.display = 'none'; // 隐藏下载链接
    +                document.body.appendChild(a);
    +                a.click();
    +                document.body.removeChild(a);
    +            }
    +        } else if (el.tagName === "TEXTAREA" || el.tagName === "INPUT") {
    +            $('#menu-paste').show();
    +            // rmf.paste=function(){
    +            //     input.addEventListener('paste', async event => {
    +            //         event.preventDefault();
    +            //         const text = await navigator.clipboard.readText();
    +            //         el.value+=text;
    +            //       });
    +            // }
    +            rmf.paste = function () {
    +                navigator.permissions
    +                    .query({
    +                        name: 'clipboard-read'
    +                    })
    +                    .then(result => {
    +                        if (result.state === 'granted' || result.state === 'prompt') {
    +                            //读取剪贴板
    +                            navigator.clipboard.readText().then(text => {
    +                                console.log(text)
    +                                insertAtCursor(el, text)
    +                            })
    +                        } else {
    +                            Snackbar.show({
    +                                text: '请允许读取剪贴板!',
    +                                pos: 'top-center',
    +                                showAction: false,
    +                            })
    +                        }
    +                    })
    +            }
    +        }
    +        let pageX = event.clientX + 10;
    +        let pageY = event.clientY;
    +        let rmWidth = $('#rightMenu').width();
    +        let rmHeight = $('#rightMenu').height();
    +        if (pageX + rmWidth > window.innerWidth) {
    +            pageX -= rmWidth + 10;
    +        }
    +        if (pageY + rmHeight > window.innerHeight) {
    +            pageY -= pageY + rmHeight - window.innerHeight;
    +        }
    +        mask = setMask();
    +        window.onscroll = () => {
    +            rmf.showRightMenu(false);
    +            window.onscroll = () => {
    +            }
    +            if (document.body.contains(mask)) {
    +                document.body.removeChild(mask);
    +            }
    +        }
    +
    +        $(".rightMenu-item").click(() => {
    +            if (document.body.contains(mask)) {
    +                document.body.removeChild(mask);
    +            }
    +        });
    +
    +        $(window).resize(() => {
    +            rmf.showRightMenu(false);
    +            if (document.body.contains(mask)) {
    +                document.body.removeChild(mask);
    +            }
    +        });
    +
    +        mask.onclick = () => {
    +            if (document.body.contains(mask)) {
    +                document.body.removeChild(mask);
    +            }
    +        };
    +
    +        rmf.showRightMenu(true, pageY, pageX);
    +        return false;
    +    };
    +
    +    window.addEventListener('click', function () {
    +        rmf.showRightMenu(false);
    +    });
    +}
    +
    +if (!(navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
    +    popupMenu()
    +}
    +const box = document.documentElement
    +
    +function addLongtabListener(target, callback) {
    +    let timer = 0 // 初始化timer
    +
    +    target.ontouchstart = () => {
    +        timer = 0 // 重置timer
    +        timer = setTimeout(() => {
    +            callback();
    +            timer = 0
    +        }, 380) // 超时器能成功执行,说明是长按
    +    }
    +
    +    target.ontouchmove = () => {
    +        clearTimeout(timer) // 如果来到这里,说明是滑动
    +        timer = 0
    +    }
    +
    +    target.ontouchend = () => { // 到这里如果timer有值,说明此触摸时间不足380ms,是点击
    +        if (timer) {
    +            clearTimeout(timer)
    +        }
    +    }
    +}
    +
    +addLongtabListener(box, popupMenu)
    +

    创建css

    +

    创建 \themes\butterfly\source\css\rightmenu.css

    +
    /* rightMenu */
    +[data-theme='light'] #rightMenu{
    +    display: none;
    +    position: fixed;
    +    width: 160px;
    +    height: fit-content;
    +    top: 10%;
    +    left: 10%;
    +    background-color: var(--card-bg);
    +    border: 1px solid rgb(210,210,210);;
    +    border-radius: 8px;
    +    z-index: 100;
    +    box-shadow: 3px 3px 5px #88888894;
    +    background-color: var(--june-white-acrylic1);
    +    backdrop-filter: blur(30px);
    +}
    +[data-theme='dark'] #rightMenu{
    +    display: none;
    +    position: fixed;
    +    width: 160px;
    +    height: fit-content;
    +    top: 10%;
    +    left: 10%;
    +    background-color: var(--card-bg);
    +    border: 1px solid rgb(210,210,210);;
    +    border-radius: 8px;
    +    z-index: 100;
    +    box-shadow: 3px 3px 5px #88888894;
    +    background-color: var(--june-black-acrylic1);
    +    backdrop-filter: blur(30px);
    +}
    +#rightMenu .rightMenu-group{
    +    padding: 7px 6px;
    +}
    +#rightMenu .rightMenu-group:not(:nth-last-child(1)){
    +    border-bottom: 1px solid rgb(180,180,180);
    +}
    +#rightMenu .rightMenu-group.rightMenu-small{
    +    display: flex;
    +    justify-content: space-between;
    +}
    +#rightMenu .rightMenu-group .rightMenu-item{
    +    height: 30px;
    +    line-height: 30px;
    +    border-radius: 8px;
    +    transition: 0.3s;
    +    color: var(--font-color);
    +}
    +#rightMenu .rightMenu-group.rightMenu-line .rightMenu-item{
    +    display: flex;
    +    height: 40px;
    +    line-height: 40px;
    +    padding: 0 4px;
    +}
    +#rightMenu .rightMenu-group .rightMenu-item:hover{
    +    background-color: var(--text-bg-hover);
    +    box-shadow: 0px 0px 5px var(--june-border);
    +}
    +#rightMenu .rightMenu-group .rightMenu-item i{
    +    display: inline-block;
    +    text-align: center;
    +    line-height: 30px;
    +    width: 30px;
    +    height: 30px;
    +    padding: 0 5px;
    +}
    +#rightMenu .rightMenu-group .rightMenu-item span{
    +    line-height: 30px;
    +}
    +#rightMenu:hover{
    +    border: 1px solid var(--june-theme);
    +}
    +#rightMenu .rightMenu-group.rightMenu-line .rightMenu-item *{
    +    height: 40px;
    +    line-height: 40px;
    +}
    +.rightMenu-group.hide{
    +    display: none;
    +}
    +.rightMenu-item:hover{
    +    color:white!important;
    +    background-color:var(--june-theme)!important;
    +}
    +

    引入

    +
    - <script type="text/javascript" src="/js/rightmenu.js"></script>
    +- <link rel="stylesheet" href="/css/rightmenu.css">

    自定义右键菜单

    /posts/8bdb35fb/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    自定义右键菜单

    \ No newline at end of file diff --git a/posts/9b2ef00a/index.html b/posts/9b2ef00a/index.html new file mode 100644 index 0000000..c24c762 --- /dev/null +++ b/posts/9b2ef00a/index.html @@ -0,0 +1,843 @@ + OpenGL- Cohen-Sutherland 编码裁剪算法 | Bi's Blog

    OpenGL- Cohen-Sutherland 编码裁剪算法

    发布于 2026/04/16 技术 总字数:416 阅读时长:2 分钟

    这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。

    +AI摘要 + QWEN

    裁剪是从数据集合提取信息的过程,它是计算机图形学许多重要问题的基础。裁剪典型的用途就是从一个大的场景中提取所需的信息,以显示某一局部场景或视图。比如浏览地图时,对感兴趣的区域放大显示,此时窗口内显示的内容会相应减少。确定图形的哪些部分在窗口内,哪些部分在窗口外(不可见区域),只显示窗口内的那部分图形,这个选择处理过程就是裁剪。

    +

    Cohen-Sutherland编码裁剪算法 +算法思想 +1)若线段完全在窗口之内则显示该线段称为“取”, +2)若线段明显在窗口之外则丢弃该线段称为“弃” +3)若线段既不满足“取”的条件也不满足“弃”的条件则把线段分割为两段,对于完全在窗口之外的部分可弃之。

    +

    具体实现 +为快速判断一条直线段与矩形窗口的位置关系采用如图所示的空间划分和编码方案(四位二进制编码上下右左) +示意图

    +

    接下来是具体代码

    +
    #include <GL/glut.h>
    +#include <iostream>
    +
    +// --- 定义区域编码 ---
    +#define INSIDE 0 // 0000
    +#define LEFT   1 // 0001
    +#define RIGHT  2 // 0010
    +#define BOTTOM 4 // 0100
    +#define TOP    8 // 1000
    +
    +// --- 定义裁剪窗口结构 ---
    +struct ClipWindow {
    +    float xmin, xmax, ymin, ymax;
    +};
    +
    +// --- 全局变量设置 ---
    +ClipWindow rect;
    +int x_0, y_0, x_1, y_1; // 线段端点
    +
    +/**
    + * 计算点 (x, y) 的区域编码
    + */
    +int computeCode(float x, float y, ClipWindow rect) {
    +    int code = INSIDE;
    +    if (x < rect.xmin)      code |= LEFT;
    +    else if (x > rect.xmax) code |= RIGHT;
    +    if (y < rect.ymin)      code |= BOTTOM;
    +    else if (y > rect.ymax) code |= TOP;
    +    return code;
    +}
    +
    +/**
    + * Cohen-Sutherland 算法核心
    + * 注意:这里传入坐标的引用,直接修改坐标值以得到裁剪后的结果
    + */
    +void cohenSutherlandClip(float& x_0, float& y_0, float& x_1, float& y_1, ClipWindow rect) {
    +    int code1 = computeCode(x_0, y_0, rect);
    +    int code2 = computeCode(x_1, y_1, rect);
    +    bool accept = false;
    +
    +    while (true) {
    +        // 如果两端点都在窗口内 (0000 | 0000 == 0)
    +        if (!(code1 | code2)) {
    +            accept = true;
    +            break;
    +        }
    +        // 如果两端点都在窗口的同一侧外部 (例如都在左边:0001 & 0001 != 0) 按位与运算
    +        else if (code1 & code2) {
    +            break; // 直接舍弃
    +        }
    +        // 3. 线段部分在窗口内,需要求交点
    +        else {
    +            int code_out;
    +            float x, y;
    +
    +            // 选择窗口外的一个端点
    +            if (code1 != 0)
    +                code_out = code1;
    +            else
    +                code_out = code2;
    +
    +            // 计算交点
    +            if (code_out & TOP) {
    +                x = x_0 + (x_1 - x_0) * (rect.ymax - y_0) / (y_1 - y_0);
    +                y = rect.ymax;
    +            }
    +            else if (code_out & BOTTOM) {
    +                x = x_0 + (x_1 - x_0) * (rect.ymin - y_0) / (y_1 - y_0);
    +                y = rect.ymin;
    +            }
    +            else if (code_out & RIGHT) {
    +                y = y_0 + (y_1 - y_0) * (rect.xmax - x_0) / (x_1 - x_0);
    +                x = rect.xmax;
    +            }
    +            else if (code_out & LEFT) {
    +                y = y_0 + (y_1 - y_0) * (rect.xmin - x_0) / (x_1 - x_0);
    +                x = rect.xmin;
    +            }
    +
    +            // 用交点替换原来的外部端点
    +            if (code_out == code1) {
    +                x_0 = x; y_0 = y;
    +                code1 = computeCode(x_0, y_0, rect);
    +            }
    +            else {
    +                x_1 = x; y_1 = y;
    +                code2 = computeCode(x_1, y_1, rect);
    +            }
    +        }
    +    }
    +
    +    // 如果接受,则绘制裁剪后的线段(蓝色)
    +    if (accept) {
    +        glBegin(GL_LINES);
    +        glColor3f(0.0f, 0.0f, 1.0f); // 蓝色
    +        glVertex2f(x_0, y_0);
    +        glVertex2f(x_1, y_1);
    +        glEnd();
    +    }
    +}
    +
    +// --- 显示回调函数 ---
    +void myDisplay() {
    +    glClear(GL_COLOR_BUFFER_BIT);
    +
    +    // 1. 绘制裁剪窗口 (红色线框)
    +    glColor3f(1.0f, 0.0f, 0.0f); // 红色
    +    glBegin(GL_LINE_LOOP);
    +        glVertex2f(rect.xmin, rect.ymin);
    +        glVertex2f(rect.xmax, rect.ymin);
    +        glVertex2f(rect.xmax, rect.ymax);
    +        glVertex2f(rect.xmin, rect.ymax);
    +    glEnd();
    +
    +    // 2. 绘制原始线段 (绿色)
    +    // 注意:为了演示效果,我们在调用裁剪函数前,先保存原始坐标,
    +    // 或者在这里直接用初始值绘制。这里我们使用初始值绘制。
    +    int orig_x_0 = 50, orig_y_0 = 350;
    +    int orig_x_1 = 550, orig_y_1 = 50;
    +
    +    glBegin(GL_LINES);
    +    glColor3f(0.0f, 1.0f, 0.0f); // 绿色
    +    glVertex2f(orig_x_0, orig_y_0);
    +    glVertex2f(orig_x_1, orig_y_1);
    +    glEnd();
    +
    +    // 3. 执行裁剪并绘制结果 (蓝色)
    +    // 使用 float 变量传入,因为算法内部会修改它们
    +    float cx_0 = orig_x_0, cy_0 = orig_y_0;
    +    float cx_1 = orig_x_1, cy_1 = orig_y_1;
    +    
    +    // 加粗蓝色线条以便区分
    +    glLineWidth(3.0); 
    +    cohenSutherlandClip(cx_0, cy_0, cx_1, cy_1, rect);
    +    glLineWidth(1.0); // 恢复线宽
    +
    +    glFlush();
    +}
    +
    +// --- 初始化设置 ---
    +void Init() {
    +    glShadeModel(GL_FLAT);
    +
    +    rect.xmin = 100;
    +    rect.xmax = 400;
    +    rect.ymin = 100;
    +    rect.ymax = 300;
    +}
    +
    +// --- 窗口大小调整 ---
    +void myReshape(int w, int h) {
    +    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    +    glMatrixMode(GL_PROJECTION);
    +    glLoadIdentity();
    +    // 设置二维投影范围,与坐标数值对应
    +    gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h);
    +    glMatrixMode(GL_MODELVIEW);
    +}
    +
    +// --- 主函数 ---
    +int main(int argc, char* argv[]) {
    +    glutInit(&argc, argv);
    +    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    +    glutInitWindowPosition(100, 100);
    +    glutInitWindowSize(600, 400);
    +    glutCreateWindow("实验2_03毕爽爽 Cohen-Sutherland 算法");
    +
    +    Init();
    +
    +    glutDisplayFunc(myDisplay);
    +    glutReshapeFunc(myReshape);
    +
    +    glutMainLoop();
    +    return 0;
    +}

    OpenGL- Cohen-Sutherland 编码裁剪算法

    /posts/9b2ef00a/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenGL
    订阅 打赏

    Comments

    OpenGL- Cohen-Sutherland 编码裁剪算法

    \ No newline at end of file diff --git a/posts/a31d95d9/index.html b/posts/a31d95d9/index.html new file mode 100644 index 0000000..87325fd --- /dev/null +++ b/posts/a31d95d9/index.html @@ -0,0 +1,666 @@ + 和朋友们的故事 | Bi's Blog

    和朋友们的故事

    发布于 2026/01/05 杂谈 总字数:420 阅读时长:2 分钟

    这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。

    +AI摘要 + QWEN

    最近一直忙于学习(虽然也没怎么好好学),所以一直忘了网站的更新;现在闲下来了,又不知道该写点什么;就写写我和朋友们的故事吧。 +因为一次偶然的突发奇想,解锁了挂号信这种奇奇怪怪的通信方式,虽然现在网络发达,但感觉这种方式有一种独特的魅力。所以从去年开始就断断续续地给朋友们写信,这是一些挂号信收据:

    +
    一些挂号信收据
    +这应该是去年上半年的一张图片,下半年也在零散的发,只不过下半年比较忙,没发多少。到年底的时候呢又想着写一点贺年信,当然感觉自己的水平下降,写不出什么好的文章。于是开始着手准备电子版贺卡,当然完全使用了AI工具,自己写太麻烦,也没什么精力去写,于是就有了下面的电子贺卡: +
    电子贺卡
    +之后呢,又写了其他一些有趣的功能,但好像不是这篇文章的重点; +再写信和创作电子贺卡是感慨万分,进入大学之后时间似乎变得十分快,感觉除了几个天天联系的好友之外,其他人都慢慢疏远了,不过这也是必然的事情,也没什么可悲伤的,只是感慨罢了。 +怎么说,就这样吧,下次有时间再在这篇文章里更新。

    和朋友们的故事

    /posts/a31d95d9/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    生活
    订阅 打赏

    Comments

    和朋友们的故事

    \ No newline at end of file diff --git a/posts/a6ab0925/index.html b/posts/a6ab0925/index.html new file mode 100644 index 0000000..42f2c12 --- /dev/null +++ b/posts/a6ab0925/index.html @@ -0,0 +1,974 @@ + 利用插件自定义页脚菜单 | Bi's Blog

    利用插件自定义页脚菜单

    发布于 2025/08/12 建站手札 总字数:1.6k 阅读时长:5 分钟

    这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。

    +AI摘要 + QWEN

    安装

    +

    安装插件,在博客根目录[Blogroot]下打开终端,运行以下指令:

    +
    npm install hexo-butterfly-footer-marcus --save
    +

    如果需要随机友链接的话,再运行以下指令:

    +
    npm i yamljs --save
    +

    添加配置信息

    +

    以下为写法示例 在站点配置文件_config.yml或者主题配置文件_config.butterfly.yml中添加

    +
    #hexo-butterfly-footer-marcus
    +#see https://blog.marcus233.top/p/footer.html
    +footer_beautify:
    +  enable: true
    +  priority: 5 #过滤器优先权
    +  enable_page: all # 应用页面
    +  layout: # 挂载容器类型
    +      type: id
    +      name: footer
    +      index: 1
    +  footer_icons:
    +    enable: true
    +    left:
    +      - icon: fa-solid fa-compass
    +        link: https://www.marcus233.top/
    +        desrc: 个人主页
    +        class: out
    +      - icon: fa-brands fa-qq
    +        link: https://res.abeim.cn/api/qq/?qq=3105950984
    +        desrc: 联系QQ
    +        class: out
    +      - icon: fa-brands fa-weixin
    +        link: /wechat/
    +        desrc: 联系微信
    +        class: in
    +      - icon: fa-solid fa-envelope
    +        link: mailto:marcus@marcus233.top
    +        desrc: 发送邮件
    +        class: out
    +    right:
    +      - icon: fa-brands fa-github
    +        link: https://github.com/Marcusyyds
    +        desrc: Github主页
    +        class: out
    +      - icon: fa-brands fa-bilibili
    +        link: https://space.bilibili.com/1024450661
    +        desrc: 哔哩哔哩主页
    +        class: out
    +      - icon: fa-solid fa-star
    +        link: /stars/
    +        desrc: 藏宝阁
    +        class: in
    +      - icon: fa-solid fa-comment
    +        link: /message/
    +        desrc: 留言
    +        class: in
    +  footer_logo:
    +    enable: true
    +    url: https://img01.anheyu.com/useruploads/8/2022/12/15/639adf5b8806a.png
    +  footer_group:
    +    enable: true
    +    footer_group_link:
    +      - group_title: 直达
    +        footer_links: 
    +          - text: 藏宝阁
    +            link: /stars/
    +            class: in
    +          - text: 优秀句子
    +            link: /sentence/
    +            class: in
    +          - text: 空间说说
    +            link: /zone/
    +            class: in
    +          - text: 友链订阅
    +            link: /fcircle/
    +            class: in
    +          - text: 切换背景
    +            link: /bg/
    +            class: in
    +      - group_title: 关于
    +        footer_links: 
    +          - text: 关于我
    +            link: /about/
    +            class: in
    +          - text: RSS订阅
    +            link: /atom.xml
    +            class: in
    +          - text: 站点监控
    +            link: https://uptime.marcus233.top/
    +            class: out
    +          - text: 更新记录
    +            link: /timeline/
    +            class: in
    +          - text: 我的相册
    +            link: /picture/
    +            class: in
    +      - group_title: 分类
    +        footer_links: 
    +          - text: 博客相关
    +            link: /categories/博客相关
    +            class: in
    +          - text: 生活点滴
    +            link: /categories/生活点滴
    +            class: in 
    +          - text: 资源分享
    +            link: /categories/资源分享
    +            class: in
    +          - text: 学习笔记
    +            link: /categories/学习笔记
    +            class: in 
    +          - text: 实用教程
    +            link: /categories/实用教程
    +            class: in 
    +          - text: 查看全部
    +            link: /categories/
    +            class: in 
    +  footer_friend_links:
    +    enable: true
    +    number: 5
    +  footer_bottom:
    +    copyright:
    +      enable: true
    +      author: Marcus
    +      link: https://marcus233.top/
    +      time: 2022
    +    left:
    +      - text: 雨云
    +        desrc: 本站CDN支持
    +        link: https://rainyun.com/
    +      - text: 网盾星球
    +        desrc: 本站CDN防护主要提供商:网盾星球
    +        link: https://www.netdun.net/
    +      - text: 萌ICP备20230221
    +        desrc: 萌ICP备20230221
    +        link: https://icp.gov.moe/?keyword=20230221
    +      - text: 萌ICP备20236688
    +        desrc: 萌ICP备20236688
    +        link: https://icp.gov.moe/?keyword=20236688
    +      - text: 萌ICP备20230002
    +        desrc: 萌ICP备20230002
    +        link: https://icp.gov.moe/?keyword=20230002
    +    right:
    +      - text: Hexo
    +        desrc: 框架
    +        link: https://hexo.io/zh-cn/
    +      - text: Butterfly
    +        desrc: 主题
    +        link: https://butterfly.js.org/
    +    runtime:
    +      enable: true
    +      time: 2022/08/09 00:00:00
    +  footer_css: https://cdn1.tianli0.top/npm/hexo-butterfly-footer-marcus/lib/footer.min.css
    +  footer_js: https://cdn1.tianli0.top/npm/hexo-butterfly-footer-marcus/lib/footer.min.js
    +

    请自行下载footer_js修改建站日期 +如果开启了随机友联,根目录下却没有link.json,请参考(页脚的随机友链)[https://blog.shineyu.cn/footer-random-flink.html]

    +

    参数释义

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数备选值/类型释义
    enabletrue/false【必选】控制开关
    prioritynumber【可选】过滤器优先级,数值越小,执行越早,默认为10,选填
    enable_pagepath/all【可选】填写想要应用的页面的相对路径(即路由地址),如根目录就填’/‘,分类页面就填’/categories/‘。若要应用于所有页面,就填’all’,默认为all
    excludepath【可选】填写想要屏蔽的页面,可以多个。仅当enable_page为’all’时生效。写法见示例。原理是将屏蔽项的内容逐个放到当前路径去匹配,若当前路径包含任一屏蔽项,则不会挂载。
    layout.typeid/class【可选】挂载容器类型,填写id或class,不填则默认为id
    layout.nametext【必选】挂载容器名称
    layout.index0和正整数【可选】前提是layout.type为class,因为同一页面可能有多个class,此项用来确认究竟排在第几个顺位
    insertpositiontext‘beforebegin’:元素自身的前面。’afterbegin’:插入元素内部的第一个子节点之前。’beforeend’:插入元素内部的最后一个子节点之后。’afterend’:插入元素自身的后面。
    footer_icons.enabletrue/false【必选】icon控制开关
    *.texttext【必选】显示文字
    *.iconclass【必选】icon(例:fa-solid fa-commet)
    *.desrctext【必选】a标签内的title选项
    *.classin/out【必选】站内/外链接(站内:in,站外:out)
    *.linkurl【必选】链接
    footer_logo.enabletrue/false【必选】icon内logo控制开关
    footer_group.enabletrue/false【必选】group控制开关
    footer_friend_links.enabletrue/false【必选】随机友联开关
    footer_friend_link.number正整数【可选】随机友联数量
    footer_bottom.copyright.enabletrue/false【必选】copyright开关
    footer_bottom.runtime.enabletrue/false【必选】网站运行时间开关
    footer_cssurl【必选】css链接
    footer_jsurl【必选】js链接
    +

    使用方法 +填写配置项 +需改动源码的地方

    +

    themes\butterfly\layout\includes\layout.pug43行(修改页脚背景色的bug)

    +
            else
    +          - var footer_bg = ''
    +
    +- footer#footer(style=footer_bg)
    +
    ++ footer#footer(style='background: transparent')
    +  !=partial('includes/footer', {}, {cache: true})
    +
    +  else
    +  include ./404.pug
    +

    去掉+号 +在 themes\butterfly\source\css\_mode\darkmode.styl43行(修改黑暗模式按钮层级的bug)

    +
      #web_bg:before,
    +
    +- #footer:before,
    +  #page-header:before
    +  position: absolute
    +  width: 100%
    +  height: 100%
    +  background-color: alpha($dark-black, .7)
    +  content: ''
    +

    参考

    + + + + Marcus的博客 + Marcus + +](https://blog.marcus233.top/p/footer.html%}) + + + + GitHub开源 + github + +](https://github.com/sysiocn/hexo-butterfly-footer-marcus%})

    利用插件自定义页脚菜单

    /posts/a6ab0925/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    利用插件自定义页脚菜单

    \ No newline at end of file diff --git a/posts/ad244066/index.html b/posts/ad244066/index.html new file mode 100644 index 0000000..f583e6f --- /dev/null +++ b/posts/ad244066/index.html @@ -0,0 +1,1537 @@ + 配置说说页面 | Bi's Blog

    配置说说页面

    发布于 2025/08/10 建站手札 总字数:1.5k 阅读时长:4 分钟

    这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方

    +AI摘要 + QWEN
    又开始折腾啦,这次把说说页面加上,改用moments项目的api + + + + Moments 极简朋友圈 + githun · github.com + + +

    前期要求

    +

    硬件要求

    +
      +
    1. 一台服务器
    2. +
    3. 一个可自主解析的域名
    4. +
    +

    软件要求

    +
      +
    1. docker环境
    2. +
    3. 反向代理工具(本文以Nginx为例)
    4. +
    +

    介绍与展示

    +

    这里先给大家展示一下最终的效果,注意该教程可能仅适合部分主题,如果出现主题不适配的情况请自行适配,这里以本站主题 Hexo-theme-butterfly为基础进行修改:

    +
      +
    1. +说说页面: + + + + 我的说说 + 胡言乱语ing。。。 · blog.biss.click + + +
    2. +
    3. +轻量朋友圈 + + + + 朋友圈 + 依旧胡言乱语 · mm.biss.blog + + +
    4. +
    5. 功能说明
    6. +
    +

    Moments作为一个轻量朋友圈,其功能都是分享上的部分,如下所示:

    +
      +
    • 分享:链接,图片,音乐,视频,书籍,电影
    • +
    • 信息:自定义位置,自定义标签,是否公开
    • +
    • 页面:Markdown渲染,编辑说说,删除说说,暗夜模式,自定义图标,信息,CSS及JS代码
    • +
    • 功能:S3存储,文件查询,多用户注册,点赞,评论,API
    • +
    +

    简单介绍完毕,下面我就来教大家如何进行部署!

    +

    部署教程

    +

    Moments部署

    +

    Compose部署

    +

    官方给予了很完善的教程,这里我仅仅简单介绍一下 docker-compose部署的方式,如果你想以源码等其他方式进行部署,请查看文章开头部分的 github地址进行查阅。

    +

    首先,在服务器任意位置创建文件:docker-compose.yaml,填入以下内容:

    +
    version: '3'
    +services:
    +  moments:
    +    image: kingwrcy/moments:latest
    +    container_name: moments
    +    restart: always
    +    environment:
    +      port: 3000
    +      JWT_KEY: "自己随便生成点字符串"
    +      ENABLE_SWAGGER: "true"
    +      CORS_ORIGIN: # 填写跨域域名
    +    ports:
    +      - "3000:3000"  # 自行换端口,换前面的,后面的3000不要动
    +    volumes:
    +      - ./opt/data:/app/data
    +    #   - ./data/localtime:/etc/localtime:ro
    +    #   - ./data/timezone:/etc/timezone:ro
    +

    注意文件,我将 /opt/文件夹(当然可以改成其他的)下的 /data文件夹挂载了进去,数据都会在里面,迁移时仅需整体打包到新服务器即可。然后执行以下两条命令,后续需要升级也仅需要执行这两个命令:

    +
    docker-compose pull
    +docker-compose up -d
    +

    如果网络环境不佳,可尝试替换 docker源,可以自行查找

    +

    通过反向代理将其添加到某个域名中,这里就不再多说了,各大面板都有极其完备的反代文档。

    +

    Nginx修改

    +

    Moments在跨域 docker-compose文件中可以配置,所以不必进行此步

    +

    网站目录

    +

    返回到上一级目录,也就是域名名称的文件夹下,找到 Proxy文件夹,编辑里面的 root.conf文件为如下内容:

    +
    #代理配置
    +
    +location / {
    +    # 跨域设置
    +    add_header Access-Control-Allow-Origin *;  # 允许所有域名访问,你也可以指定具体域名
    +    add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';  # 允许的 HTTP 方法
    +    add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';  # 允许的请求头
    +
    +    # 处理 OPTIONS 请求,预检请求
    +    if ($request_method = 'OPTIONS') {
    +        add_header Access-Control-Allow-Origin *;
    +        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS';
    +        add_header Access-Control-Allow-Headers 'Origin, X-Requested-With, Content-Type, Accept, Authorization';
    +        add_header Access-Control-Max-Age 1728000;
    +        add_header Content-Type 'text/plain charset=UTF-8';
    +        add_header Content-Length 0;
    +        return 204;
    +    }
    +
    +    # 原代理设置
    +    proxy_pass http://127.0.0.1:3003;
    +    proxy_set_header Host $host;
    +    proxy_set_header X-Real-IP $remote_addr;
    +    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    +    proxy_set_header REMOTE-HOST $remote_addr;
    +    proxy_set_header Upgrade $http_upgrade;
    +    proxy_set_header Connection $http_connection;
    +    proxy_set_header X-Forwarded-Proto $scheme;
    +    proxy_http_version 1.1;
    +    add_header X-Cache $upstream_cache_status;
    +    add_header Cache-Control no-cache;
    +    proxy_ssl_server_name off;
    +    proxy_ssl_name $proxy_host;
    +    add_header Strict-Transport-Security "max-age=31536000";
    +}
    +

    下面是原代理设置,可以仅仅复制上面部分内容,下面保持不变,注意端口不要出问题。

    +

    前端实现

    +

    由于该项目利用了MetingJS和APlayer,所以请提前引入这两个包,Hexo-theme-butterfly中虽然有内置的两个包,仅需修改配置文件即可开启,但是版本比较老,这里我建议自行引入最新版本,在配置中引入以下文件,注意css和js应该是分开引入的:

    +
    <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" media="all" onload="this.media="all"">
    +<script src="https://fastly.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
    +<script src="https://fastly.jsdelivr.net/npm/meting/dist/Meting.min.js"></script>
    +

    新建页面shuoshuo,在文件内写入一下内容:

    +
    ---
    +title: 日常哔哔,键盘侠的日常吐槽
    +aside: false
    +---
    +<div id="talk"></div>
    +<div class="limit">- 只展示最近30条说说 -</div>
    +<script src="/js/shuoshuo.js" no-pjax></script>
    +

    其中的JS文件地址清自行修改,放在主题目录 /script/目录,自行创建,并写入以下内容:

    +
    function renderTalks() {
    +    const talkContainer = document.querySelector('#talk');
    +    const domain = 'https://mm.biss.click'; 
    +    if (!talkContainer) return;
    +    talkContainer.innerHTML = '';
    +    const generateIconSVG = () => {
    +        return `<svg viewBox="0 0 512 512"xmlns="http://www.w3.org/2000/svg"class="is-badge icon"><path d="m512 268c0 17.9-4.3 34.5-12.9 49.7s-20.1 27.1-34.6 35.4c.4 2.7.6 6.9.6 12.6 0 27.1-9.1 50.1-27.1 69.1-18.1 19.1-39.9 28.6-65.4 28.6-11.4 0-22.3-2.1-32.6-6.3-8 16.4-19.5 29.6-34.6 39.7-15 10.2-31.5 15.2-49.4 15.2-18.3 0-34.9-4.9-49.7-14.9-14.9-9.9-26.3-23.2-34.3-40-10.3 4.2-21.1 6.3-32.6 6.3-25.5 0-47.4-9.5-65.7-28.6-18.3-19-27.4-42.1-27.4-69.1 0-3 .4-7.2 1.1-12.6-14.5-8.4-26-20.2-34.6-35.4-8.5-15.2-12.8-31.8-12.8-49.7 0-19 4.8-36.5 14.3-52.3s22.3-27.5 38.3-35.1c-4.2-11.4-6.3-22.9-6.3-34.3 0-27 9.1-50.1 27.4-69.1s40.2-28.6 65.7-28.6c11.4 0 22.3 2.1 32.6 6.3 8-16.4 19.5-29.6 34.6-39.7 15-10.1 31.5-15.2 49.4-15.2s34.4 5.1 49.4 15.1c15 10.1 26.6 23.3 34.6 39.7 10.3-4.2 21.1-6.3 32.6-6.3 25.5 0 47.3 9.5 65.4 28.6s27.1 42.1 27.1 69.1c0 12.6-1.9 24-5.7 34.3 16 7.6 28.8 19.3 38.3 35.1 9.5 15.9 14.3 33.4 14.3 52.4zm-266.9 77.1 105.7-158.3c2.7-4.2 3.5-8.8 2.6-13.7-1-4.9-3.5-8.8-7.7-11.4-4.2-2.7-8.8-3.6-13.7-2.9-5 .8-9 3.2-12 7.4l-93.1 140-42.9-42.8c-3.8-3.8-8.2-5.6-13.1-5.4-5 .2-9.3 2-13.1 5.4-3.4 3.4-5.1 7.7-5.1 12.9 0 5.1 1.7 9.4 5.1 12.9l58.9 58.9 2.9 2.3c3.4 2.3 6.9 3.4 10.3 3.4 6.7-.1 11.8-2.9 15.2-8.7z"fill="#1da1f2"></path></svg>`;
    +    }
    +    const waterfall = (a) => {
    +        function b(a, b) {
    +            var c = window.getComputedStyle(b);
    +            return parseFloat(c["margin" + a]) || 0
    +        }
    +
    +        function c(a) {
    +            return a + "px"
    +        }
    +
    +        function d(a) {
    +            return parseFloat(a.style.top)
    +        }
    +
    +        function e(a) {
    +            return parseFloat(a.style.left)
    +        }
    +
    +        function f(a) {
    +            return a.clientWidth
    +        }
    +
    +        function g(a) {
    +            return a.clientHeight
    +        }
    +
    +        function h(a) {
    +            return d(a) + g(a) + b("Bottom", a)
    +        }
    +
    +        function i(a) {
    +            return e(a) + f(a) + b("Right", a)
    +        }
    +
    +        function j(a) {
    +            a = a.sort(function (a, b) {
    +                return h(a) === h(b) ? e(b) - e(a) : h(b) - h(a)
    +            })
    +        }
    +
    +        function k(b) {
    +            f(a) != t && (b.target.removeEventListener(b.type, arguments.callee), waterfall(a))
    +        }
    +        "string" == typeof a && (a = document.querySelector(a));
    +        var l = [].map.call(a.children, function (a) {
    +            return a.style.position = "absolute", a
    +        });
    +        a.style.position = "relative";
    +        var m = [];
    +        l.length && (l[0].style.top = "0px", l[0].style.left = c(b("Left", l[0])), m.push(l[0]));
    +        for (var n = 1; n < l.length; n++) {
    +            var o = l[n - 1],
    +                p = l[n],
    +                q = i(o) + f(p) <= f(a);
    +            if (!q) break;
    +            p.style.top = o.style.top, p.style.left = c(i(o) + b("Left", p)), m.push(p)
    +        }
    +        for (; n < l.length; n++) {
    +            j(m);
    +            var p = l[n],
    +                r = m.pop();
    +            p.style.top = c(h(r) + b("Top", p)), p.style.left = c(e(r)), m.push(p)
    +        }
    +        j(m);
    +        var s = m[0];
    +        a.style.height = c(h(s) + b("Bottom", s));
    +        var t = f(a);
    +        window.addEventListener ? window.addEventListener("resize", k) : document.body.onresize = k
    +    };
    +
    +    const fetchAndRenderTalks = () => {
    +        const url = 'https://mm.biss.click/api/memo/list';
    +        const cacheKey = 'talksCache';
    +        const cacheTimeKey = 'talksCacheTime';
    +        const cacheDuration = 30 * 60 * 1000; // 半个小时 (30 分钟)
    +  
    +        const cachedData = localStorage.getItem(cacheKey);
    +        const cachedTime = localStorage.getItem(cacheTimeKey);
    +        const currentTime = new Date().getTime();
    +  
    +        // 判断缓存是否有效
    +        if (cachedData && cachedTime && (currentTime - cachedTime < cacheDuration)) {
    +            const data = JSON.parse(cachedData);
    +            renderTalks(data); // 使用缓存渲染数据
    +        } else {
    +            if (talkContainer) {
    +                talkContainer.innerHTML = '';
    +                fetch(url, {
    +                        method: 'POST',
    +                        headers: {
    +                            'Content-Type': 'application/json',
    +                        },
    +                        body: JSON.stringify({
    +                            size: 30
    +                        })
    +                    })
    +                    .then(res => res.json())
    +                    .then(data => {
    +                        if (data.code === 0 && data.data && Array.isArray(data.data.list)) {
    +                            // 缓存数据
    +                            localStorage.setItem(cacheKey, JSON.stringify(data.data.list));
    +                            localStorage.setItem(cacheTimeKey, currentTime.toString());
    +                            renderTalks(data.data.list); // 渲染数据
    +                        }
    +                    })
    +                    .catch(error => {
    +                        console.error('Error fetching data:', error);
    +                    });
    +            }
    +        }
    +  
    +        // 渲染函数
    +        function renderTalks(list) {
    +            // 确保 data 是一个数组
    +            if (Array.isArray(list)) {
    +                let items = list.map(item => formatTalk(item, url));
    +                items.forEach(item => talkContainer.appendChild(generateTalkElement(item)));
    +                waterfall('#talk');
    +            } else {
    +                console.error('Data is not an array:', list);
    +            }
    +        }
    +    };
    +  
    +
    +    const formatTalk = (item, url) => {
    +        let date = formatTime(new Date(item.createdAt).toString());
    +        let content = item.content;
    +        let imgs = item.imgs ? item.imgs.split(',') : [];
    +        let text = content;
    +        content = text.replace(/\[(.*?)\]\((.*?)\)/g, `<a href="$2">@$1</a>`)
    +            .replace(/- \[ \]/g, '⚪')
    +            .replace(/- \[x\]/g, '⚫');
    +        // 保留换行符,转换 \n 为 <br>
    +		content = content.replace(/\n/g, '<br>');
    +        // 将content用一个类包裹,便于后续处理
    +        content = `<div class="talk_content_text">${content}</div>`;
    +        if (imgs.length > 0) {
    +            const imgDiv = document.createElement('div');
    +            imgDiv.className = 'zone_imgbox';
    +            imgs.forEach(e => {
    +                const imgLink = document.createElement('a');
    +                const imgUrl = domain + e;
    +                imgLink.href = imgUrl;
    +                imgLink.setAttribute('data-fancybox', 'gallery');
    +                imgLink.className = 'fancybox';
    +                imgLink.setAttribute('data-thumb', e);
    +                const imgTag = document.createElement('img');
    +                imgTag.src = domain + e;
    +                imgLink.appendChild(imgTag);
    +                imgDiv.appendChild(imgLink);
    +            });
    +            content += imgDiv.outerHTML;
    +        }
    +
    +        // 外链分享功能
    +        if (item.externalUrl) {
    +            const externalUrl = item.externalUrl;
    +            const externalTitle = item.externalTitle;
    +            const externalFavicon = item.externalFavicon;
    +
    +            const externalContainer = `
    +            <div class="shuoshuo-external-link">
    +                <a class="external-link" href="${externalUrl}" target="_blank" rel="external nofollow noopener noreferrer">
    +                    <div class="external-link-left" style="background-image: url(${externalFavicon})"></div>
    +                    <div class="external-link-right">
    +                        <div class="external-link-title">${externalTitle}</div>
    +                        <div>点击跳转<i class="fa-solid fa-angle-right"></i></div>
    +                    </div>
    +                </a>
    +            </div>`;
    +
    +            content += externalContainer;
    +        }
    +
    +        const ext = JSON.parse(item.ext || '{}');
    +
    +        if (ext.music && ext.music.id) {
    +            const music = ext.music;
    +            const musicUrl = music.api.replace(':server', music.server)
    +                .replace(':type', music.type)
    +                .replace(':id', music.id);
    +            content += `
    +            <meting-js server="${music.server}" type="${music.type}" id="${music.id}" api="${music.api}"></meting-js>
    +        `;
    +        }
    +
    +        if (ext.doubanMovie && ext.doubanMovie.id) {
    +            const doubanMovie = ext.doubanMovie;
    +            const doubanMovieUrl = doubanMovie.url;
    +            const doubanTitle = doubanMovie.title;
    +            // const doubanDesc = doubanMovie.desc || '暂无描述';
    +            const doubanImage = doubanMovie.image;
    +            const doubanDirector = doubanMovie.director || '未知导演';
    +            const doubanRating = doubanMovie.rating || '暂无评分';
    +            // const doubanReleaseDate = doubanMovie.releaseDate || '未知上映时间';
    +            // const doubanActors = doubanMovie.actors || '未知演员';
    +            const doubanRuntime = doubanMovie.runtime || '未知时长';
    +
    +            content += `
    +                <a class="douban-card" href="${doubanMovieUrl}" target="_blank">
    +                    <div class="douban-card-bgimg" style="background-image: url('${doubanImage}');"></div>
    +                    <div class="douban-card-left">
    +                        <div class="douban-card-img" style="background-image: url('${doubanImage}');"></div>
    +                    </div>
    +                    <div class="douban-card-right">
    +                        <div class="douban-card-item"><span>电影名: </span><strong>${doubanTitle}</strong></div>
    +                        <div class="douban-card-item"><span>导演: </span><span>${doubanDirector}</span></div>
    +                        <div class="douban-card-item"><span>评分: </span><span>${doubanRating}</span></div>
    +                        <div class="douban-card-item"><span>时长: </span><span>${doubanRuntime}</span></div>
    +                    </div>
    +                </a>
    +            `;
    +        }
    +
    +        if (ext.doubanBook && ext.doubanBook.id) {
    +            const doubanBook = ext.doubanBook;
    +            const bookUrl = doubanBook.url;
    +            const bookTitle = doubanBook.title;
    +            // const bookDesc = doubanBook.desc;
    +            const bookImage = doubanBook.image;
    +            const bookAuthor = doubanBook.author;
    +            const bookRating = doubanBook.rating;
    +            const bookPubDate = doubanBook.pubDate;
    +
    +            const bookTemplate = `
    +                <a class="douban-card" href="${bookUrl}" target="_blank">
    +                    <div class="douban-card-bgimg" style="background-image: url('${bookImage}');"></div>
    +                        <div class="douban-card-left">
    +                            <div class="douban-card-img" style="background-image: url('${bookImage}');"></div>
    +                        </div>
    +                        <div class="douban-card-right">
    +                            <div class="douban-card-item">
    +                                <span>书名: </span><strong>${bookTitle}</strong>
    +                            </div>
    +                            <div class="douban-card-item">
    +                                <span>作者: </span><span>${bookAuthor}</span>
    +                            </div>
    +                            <div class="douban-card-item">
    +                                <span>出版年份: </span><span>${bookPubDate}</span>
    +                            </div>
    +                            <div class="douban-card-item">
    +                                <span>评分: </span><span>${bookRating}</span>
    +                            </div>
    +                        </div>
    +                </a>
    +            `;
    +
    +            content += bookTemplate;
    +        }
    +
    +        if (ext.video && ext.video.type) {
    +            const videoType = ext.video.type;
    +            const videoUrl = ext.video.value;
    +            if (videoType === 'bilibili') {
    +                // Bilibili 视频模板
    +                // 从形如https://www.bilibili.com/video/BV1VGAPeAEMQ/?vd_source=91b3158d27d98ff41f842508c3794a13 的链接中提取视频 BV1VGAPeAEMQ
    +                const biliTemplate = `
    +                <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
    +                    <iframe 
    +                        style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;" 
    +                        src="${videoUrl}&autoplay=0"
    +                        scrolling="no" 
    +                        frameborder="no" 
    +                        allowfullscreen>
    +                    </iframe>
    +                </div>
    +            `;
    +                // 将模板插入到 DOM 中
    +                content += biliTemplate;
    +
    +            } else if (videoType === 'youtube') {
    +                // YouTube 视频模板
    +                // 从形如https://youtu.be/2V6lvCUPT8I?si=DVhUas6l6qlAr6Ru的链接中提取视频 ID2V6lvCUPT8I
    +                const youtubeTemplate = `
    +                <div style="position: relative; padding: 30% 45%; margin-top: 10px;">
    +                    <iframe width="100%"
    +                        style="position: absolute; width: 100%; height: 100%; left: 0; top: 0; border-radius: 12px;"
    +                        src="${videoUrl}"
    +                        title="YouTube video player" 
    +                        frameborder="0" 
    +                        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 
    +                        referrerpolicy="strict-origin-when-cross-origin" 
    +                        allowfullscreen>
    +                    </iframe>
    +                </div>
    +            `;
    +                // 将模板插入到 DOM 中
    +                content += youtubeTemplate;
    +            }
    +        }
    +
    +        return {
    +            content: content,
    +            user: item.user.nickname || '匿名',
    +            avatar: item.user.avatarUrl || 'https://p.liiiu.cn/i/2024/03/29/66061417537af.png',
    +            date: date,
    +            location: item.location || '山西',
    +            tags: item.tags ? item.tags.split(',').filter(tag => tag.trim() !== '') : ['无标签'],
    +            text: content.replace(/\[(.*?)\]\((.*?)\)/g, '[链接]' + `${imgs.length ? '[图片]' : ''}`)
    +        };
    +    };
    +
    +    const generateTalkElement = (item) => {
    +        const talkItem = document.createElement('div');
    +        talkItem.className = 'talk_item';
    +
    +        const talkMeta = document.createElement('div');
    +        talkMeta.className = 'talk_meta';
    +
    +        const avatar = document.createElement('img');
    +        avatar.className = 'no-lightbox avatar';
    +        avatar.src = item.avatar;
    +
    +        const info = document.createElement('div');
    +        info.className = 'info';
    +
    +        const talkNick = document.createElement('span');
    +        talkNick.className = 'talk_nick';
    +        talkNick.innerHTML = `${item.user} ${generateIconSVG()}`;
    +
    +        const talkDate = document.createElement('span');
    +        talkDate.className = 'talk_date';
    +        talkDate.textContent = item.date;
    +
    +        const talkContent = document.createElement('div');
    +        talkContent.className = 'talk_content';
    +        talkContent.innerHTML = item.content;
    +
    +        const talkBottom = document.createElement('div');
    +        talkBottom.className = 'talk_bottom';
    +
    +        const TagContainer = document.createElement('div');
    +
    +        const talkTag = document.createElement('span');
    +        talkTag.className = 'talk_tag';
    +        talkTag.textContent = `🏷️${item.tags}`;
    +
    +        const locationTag = document.createElement('span');
    +        locationTag.className = 'location_tag';
    +        locationTag.textContent = `🌍${item.location}`;
    +
    +        TagContainer.appendChild(talkTag);
    +        TagContainer.appendChild(locationTag);
    +
    +        const commentLink = document.createElement('a');
    +        commentLink.href = 'javascript:;';
    +        commentLink.onclick = () => goComment(item.text);
    +        const commentIcon = document.createElement('span');
    +        commentIcon.className = 'icon';
    +        const commentIconInner = document.createElement('i');
    +        commentIconInner.className = 'fa-solid fa-message fa-fw';
    +        commentIcon.appendChild(commentIconInner);
    +        commentLink.appendChild(commentIcon);
    +
    +        talkMeta.appendChild(avatar);
    +        info.appendChild(talkNick);
    +        info.appendChild(talkDate);
    +        talkMeta.appendChild(info);
    +        talkItem.appendChild(talkMeta);
    +        talkItem.appendChild(talkContent);
    +        talkBottom.appendChild(TagContainer);
    +        talkBottom.appendChild(commentLink);
    +        talkItem.appendChild(talkBottom);
    +
    +        return talkItem;
    +    };
    +
    +    const goComment = (e) => {
    +        const match = e.match(/<div class="talk_content_text">([\s\S]*?)<\/div>/);
    +        const textContent = match ? match[1] : "";
    +        const n = document.querySelector(".atk-textarea");
    +        n.value = `> ${textContent}\n\n`;
    +        n.focus();
    +        btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
    +        // const n = document.querySelector(".atk-textarea");
    +        // n.value = `> ${e}\n\n`;
    +        // n.focus();
    +        // btf.snackbarShow("已为您引用该说说,不删除空格效果更佳");
    +    };
    +
    +    const formatTime = (time) => {
    +        const d = new Date(time);
    +        const ls = [
    +            d.getFullYear(),
    +            d.getMonth() + 1,
    +            d.getDate(),
    +            d.getHours(),
    +            d.getMinutes(),
    +            d.getSeconds(),
    +        ];
    +        const r = ls.map((a) => (a.toString().length === 1 ? '0' + a : a));
    +        return `${r[0]}-${r[1]}-${r[2]} ${r[3]}:${r[4]}`;
    +    };
    +
    +    fetchAndRenderTalks();
    +}
    +
    +renderTalks();
    +
    +// function whenDOMReady() {
    +//     const talkContainer = document.querySelector('#talk');
    +//     talkContainer.innerHTML = '';
    +//     fetchAndRenderTalks();
    +// }
    +// whenDOMReady();
    +// document.addEventListener("pjax:complete", whenDOMReady);
    +

    自行修改js文件中的Moments地址为你的地址,在文件中,有一个gocomment函数,实现的是获取卡片中的文本内容,如果如果出现不匹配的情况,请自行修改一下类名,这里我匹配的是artalk的输入框。在这里我把代码做了修改,因为我不想使用s3,结果导致说说图片无法加载。。。 +修改历程:日后再写

    +

    然后引入样式文件,这个文件可以在配置文件中引用,也可以在页面文件中类似于shuoshuo.js一样引用,样式内容如下:

    +
    :root {
    +    --liushen-card-bg: #fff;
    +    --liushen-card-border: 1px solid #e3e8f7;
    +    --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
    +    --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
    +    --liushen-card-secondbg: #f1f3f8;
    +    --liushen-button-hover-bg: #2679cc;
    +    --liushen-text: #4c4948;
    +    --liushen-button-bg: #f1f3f8;
    +    --liushen-fancybox-bg: rgba(255,255,255,0.5);
    +}
    +
    +:root, [data-theme=dark] {
    +    --liushen-card-bg: #181818;
    +    --liushen-card-secondbg: #30343f;
    +    --liushen-card-border: 1px solid #42444a;
    +    --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09);
    +    --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.2);
    +    --liushen-button-bg: #30343f;
    +    --liushen-button-hover-bg: #2679cc;
    +    --liushen-text: rgba(255,255,255,0.702);
    +    --liushen-fancybox-bg: rgba(0,0,0,0.5);
    +}
    +
    +/* 卡片初始化 */
    +#talk .talk_item {
    +    width: calc(33.333% - 6px);
    +    background: var(--liushen-card-bg);
    +    border: var(--liushen-card-border);
    +    box-shadow: var(--card-box-shadow);
    +    transition: box-shadow .3s ease-in-out;
    +    border-radius: 12px;
    +    display: flex;
    +    flex-direction: column;
    +    padding: 20px;
    +    margin-bottom: 9px;
    +    margin-right: 9px;
    +}
    +#talk .talk_item:hover {
    +    box-shadow: var(--card-hover-box-shadow);
    +}
    +
    +@media (max-width: 900px) {
    +    #talk .talk_item {
    +      width: calc(50% - 5px);
    +    }
    +}
    +@media (max-width: 450px) {
    +    #talk .talk_item {
    +      width: calc(100%);
    +    }
    +}
    +
    +#talk{
    +    position: relative;
    +    width: 100%;
    +    box-sizing: border-box;
    +}
    +
    +#talk .talk_meta .avatar {
    +    margin: 0 !important;
    +    width: 60px;
    +    height: 60px;
    +    border-radius: 12px;
    +}
    +#talk .talk_bottom,
    +#talk .talk_meta {
    +    display: flex;
    +    align-items: center;
    +}
    +#talk .talk_meta {
    +    display: flex;
    +    align-items: center;
    +    width: 100%;
    +    padding-bottom: 10px;
    +    border-bottom: 1px dashed grey; /* 添加灰色虚线边框 */
    +}
    +#talk .talk_bottom {
    +    margin-top: 15px;
    +    padding-top: 10px;
    +    border-top: 1px dashed grey; /* 添加灰色虚线边框 */
    +    justify-content: space-between;
    +}
    +#talk .talk_meta .info {
    +    display: flex;
    +    flex-direction: column;
    +    margin-left: 10px;
    +}
    +#talk .talk_meta .info .talk_nick {
    +    color: #6dbdc3;
    +    font-size: 1.2rem;
    +}
    +#talk .talk_meta .info svg.is-badge.icon {
    +    width: 15px;
    +    padding-top: 3px;
    +}
    +#talk .talk_meta .info span.talk_date {
    +    opacity: .6;
    +}
    +#talk .talk_item .talk_content {
    +    margin-top: 10px;
    +}
    +#talk .talk_item .talk_content .zone_imgbox {
    +    display: flex;
    +    flex-wrap: wrap;
    +    --w: calc(25% - 8px);
    +    gap: 10px;
    +    margin-top: 10px;
    +}
    +#talk .talk_item .talk_content .zone_imgbox a {
    +    display: block;
    +    border-radius: 12px;
    +    width: var(--w);
    +    aspect-ratio: 1/1;
    +    position: relative;
    +}
    +#talk .talk_item .talk_content .zone_imgbox a:first-child {
    +    width: 100%;
    +    aspect-ratio: 1.8;
    +}
    +#talk .talk_item .talk_content .zone_imgbox img {
    +    border-radius: 10px;
    +    width: 100%;
    +    height: 100%;
    +    margin: 0 !important;
    +    object-fit: cover;
    +}
    +/* 底部 */
    +#talk .talk_item .talk_bottom {
    +    opacity: .9;
    +}
    +#talk .talk_item .talk_bottom .icon {
    +    float: right;
    +    transition: all .3s;
    +}
    +#talk .talk_item .talk_bottom .icon:hover {
    +    color: #49b1f5;
    +}
    +#talk .talk_item .talk_bottom span.talk_tag,
    +#talk .talk_item .talk_bottom span.location_tag {
    +    font-size: 14px;
    +    background-color: var(--liushen-card-secondbg);
    +    border-radius: 12px;
    +    padding: 3px 15px 3px 10px;
    +    transition: box-shadow 0.3s ease;
    +    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    +}
    +
    +#talk .talk_item .talk_bottom span.location_tag {
    +    margin-left: 5px;
    +}
    +
    +#talk .talk_item .talk_bottom span.talk_tag:hover,
    +#talk .talk_item .talk_bottom span.location_tag:hover {
    +    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
    +}
    +#talk .talk_item .talk_content>a {
    +    margin: 0 3px;
    +    color: #ff7d73 !important;
    +}
    +#talk .talk_item .talk_content>a:hover{
    +    text-decoration: none !important;
    +    color: #ff5143 !important
    +}
    +
    +@media screen and (max-width: 900px) {
    +    #talk .talk_item .talk_content .zone_imgbox {
    +        --w: calc(33% - 5px);
    +    }
    +    #talk .talk_item #post-comment{
    +        margin: 0 3px
    +    }
    +}
    +@media screen and (max-width: 768px) {
    +    .zone_imgbox {
    +        gap: 6px;
    +    }
    +    .zone_imgbox {
    +        --w: calc(50% - 3px);
    +    }
    +    span.talk_date {
    +        font-size: 14px;
    +    }
    +}
    +
    +#talk .talk_item .talk_content .douban-card {
    +    margin-top: 10px !important;
    +    text-decoration: none;
    +    align-items: center;
    +    border-radius: 12px;
    +    color: #faebd7;
    +    display: flex;
    +    justify-content: center;
    +    margin: 10px;
    +    max-width: 400px;
    +    overflow: hidden;
    +    padding: 15px;
    +    position: relative;
    +}
    +
    +.douban-card .douban-card-bgimg {
    +    background-position: 50%;
    +    background-repeat: no-repeat;
    +    background-size: 100%;
    +    filter: blur(15px) brightness(.6);
    +    height: 115%;
    +    position: absolute;
    +    width: 115%;
    +}
    +
    +.douban-card .douban-card-left {
    +    align-items: center;
    +    display: flex;
    +    flex-direction: column;
    +    position: relative;
    +}
    +
    +.douban-card .douban-card-left .douban-card-img {
    +    transition: all .5s ease;
    +    height: 130px;
    +    position: relative;
    +    width: 80px;
    +    background-position: 50%;
    +    background-repeat: no-repeat;
    +    background-size: 100%;
    +}
    +
    +.douban-card .douban-card-left:hover .douban-card-img {
    +    filter: blur(5px) brightness(.6);
    +    transform: perspective(800px) rotateX(180deg);
    +}
    +
    +.douban-card .douban-card-right {
    +    color: #faebd7;
    +    display: flex;
    +    flex-direction: column;
    +    font-size: 14px;
    +    line-height: 1.5;
    +    margin-left: 12px;
    +    position: relative;
    +}
    +
    +.douban-card .douban-card-right .douban-card-item  {
    +    margin-top: 4px;
    +    max-width: 95%;
    +    overflow: hidden;
    +    text-overflow: ellipsis;
    +    white-space: nowrap;
    +}
    +
    +/* 外链卡片 */
    +#talk .talk_item .talk_content .shuoshuo-external-link {
    +    /* 无下划线 */
    +    width: 100%;
    +    height: 80px;
    +    margin-top: 10px;
    +    border-radius: 12px;
    +    background-color: var(--liushen-card-secondbg);
    +    color: var(--liushen-card-text);
    +    border: var(--liushen-card-border);
    +    transition: background-color .3s ease-in-out;
    +}
    +
    +.shuoshuo-external-link:hover {
    +    background-color: var(--liushen-button-hover-bg);
    +}
    +
    +.shuoshuo-external-link .external-link {
    +    display: flex;
    +    color: var(--liushen-text) !important;
    +    width: 100%;
    +    height: 100%;
    +}
    +
    +.shuoshuo-external-link .external-link:hover {
    +    color: white !important;
    +}
    +
    +.shuoshuo-external-link .external-link:hover {
    +    text-decoration: none !important;
    +}
    +
    +.shuoshuo-external-link .external-link-left {
    +    width: 60px;
    +    height: 60px;
    +    margin: 10px;
    +    border-radius: 12px;
    +    background-size: cover;
    +    background-position: center;
    +}
    +
    +.shuoshuo-external-link .external-link-right {
    +    display: flex;
    +    flex-direction: column;
    +    justify-content: center;
    +    width: calc(100% - 80px);
    +    padding: 10px;
    +}
    +
    +.shuoshuo-external-link .external-link-right .external-link-title {
    +    font-size: 1.0rem;
    +    font-weight: 800;
    +    white-space: nowrap;
    +    overflow: hidden;
    +    text-overflow: ellipsis;
    +}
    +
    +.shuoshuo-external-link .external-link-right i {
    +    margin-left: 5px;
    +}
    +
    +.limit {
    +    width: 100%;
    +    text-align: center;
    +    margin-top: 30px;
    +}
    +

    如果一切正常,应该就可以显示了,但是我在使用时发现卡片是黑色的,把前面的root内容删除之后就好了。

    +

    参考内容

    + + + + liushen的博客 + liushen · blog.liushen.fun + + +

    当然还有chatgpt的支持

    配置说说页面

    /posts/ad244066/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    配置说说页面

    \ No newline at end of file diff --git a/posts/b559997d/index.html b/posts/b559997d/index.html new file mode 100644 index 0000000..eee5ce1 --- /dev/null +++ b/posts/b559997d/index.html @@ -0,0 +1,740 @@ + opencv应用-算术运算 | Bi's Blog

    opencv应用-算术运算

    这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。

    +AI摘要 + QWEN

    opencv对图像的算数运算,感觉都大同小异,分为以下几种:加减乘除和位运算。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    函数功能应用场景
    cv2.bitwise_and()按位与操作掩码操作、图像分割
    cv2.bitwise_or()按位或操作图像叠加
    cv2.bitwise_not()按位取反操作图像反色
    cv2.bitwise_xor()按位异或操作图像差异检测
    +

    因为感觉都差不多,所以只把加法运算代码搬过来

    +
    // 加法
    +#include <opencv2/opencv.hpp>
    +#include <iostream>
    +
    +using namespace std;
    +using namespace cv;
    +
    +int main()
    +{
    +    string path1 = "../img/2.jpg";
    +    string path2 = "../img/3.jpg";
    +
    +    Mat img1 = imread(path1);
    +    Mat img2 = imread(path2);
    +
    +    if (img1.empty() || img2.empty())
    +    {
    +        cout << "无法读取图片,请检查路径!" << endl;
    +        return -1;
    +    }
    +
    +    if (img1.size() != img2.size())
    +    {
    +        cout << "尺寸不一致,正在自动调整 img2 的尺寸..." << endl;
    +        // 将 img2 缩放到与 img1 相同的尺寸
    +        resize(img2, img2, img1.size());
    +    }
    +
    +    Mat result;
    +    add(img1, img2, result);
    +
    +    imshow("Original Image 1", img1);
    +    imshow("Original Image 2", img2);
    +    imshow("Add Result", result);
    +
    +    // 等待按键后关闭窗口
    +    waitKey(0);
    +    destroyAllWindows();
    +
    +    return 0;
    +}
    +另外,把代码上传到了自建的git中 + + + + opencv-learning + opencv-learning · git.biss.click + +

    opencv应用-算术运算

    /posts/b559997d/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    opencv
    订阅 打赏

    Comments

    opencv应用-算术运算

    \ No newline at end of file diff --git a/posts/b5601a7e/index.html b/posts/b5601a7e/index.html new file mode 100644 index 0000000..bdddc2e --- /dev/null +++ b/posts/b5601a7e/index.html @@ -0,0 +1,716 @@ + 自定义字体 | Bi's Blog

    自定义字体

    发布于 2026/01/16 建站手札 总字数:551 阅读时长:2 分钟

    这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。

    +AI摘要 + QWEN
    今天感觉网站的字体有些不好看,想换一下,搜索发现网站用woff或者woff2字体,在手机端和电脑都能完美显示 +

    2026年2月3日更新:好像谷歌字体库可以在大陆直连了,速度也很快,所以这篇文章好像没什么必要了。

    +

    选择字体

    +首先在网上查找自己喜欢的字体,这里有一个网站 + + + + 中文开源字体集 Open Source Fonts Collection for Chinese + 开源字体 · drxie.github.io + + +找到一个自己喜欢的,如果有woff或者woff2格式下载下载保存。没有也没关系,转换网站: + + + + 转换网站 + ttf2woff2 · products.groupdocs.app + + +利用这个网站把下载的ttf字体文件转换成woff2格式。 +

    添加字体

    +

    新建一个css文件

    +
    @font-face {
    +  font-family: 'CascadiaCodePL';
    +  font-display: swap;
    +  src: url('/butterflyChange/fonts/font.woff2') format("woff2");
    +}
    +

    其中 font.woff2改成自己的文件名。 +其他字体格式参考

    +
    @font-face {
    +   font-family: 'webfont';
    +   font-display: swap;
    +   src: url('.eot');  /*IE9*/
    +   src: url('.eot') format('embedded-opentype'),  /* IE6-IE8 */
    +        url('.woff2') format('woff2'),
    +        url('.woff') format('woff'),  /*chrome、firefox */
    +        url('.ttf') format('truetype'),  /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
    +        url('.svg') format('svg');  /* iOS 4.1- */
    +}```
    +然后在`_config.butterfly.yml`里面引用这个css
    +```yml
    +- <link rel="stylesheet" href="/css/font.css">
    +

    在主题文件的font配置区域修改字体:

    +
    font:
    +  global_font_size:  110%
    +  code_font_size: 100%
    +  font_family:  # ,全局字体,不带后缀名
    +  code_font_family: # 这是代码使用的字体
    +

    参考链接

    + + + + Ordis的博客 + ordis · blog.imbhj.com + +

    自定义字体

    /posts/b5601a7e/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    自定义字体

    \ No newline at end of file diff --git a/posts/b57500e9/index.html b/posts/b57500e9/index.html new file mode 100644 index 0000000..62e6d9a --- /dev/null +++ b/posts/b57500e9/index.html @@ -0,0 +1,680 @@ + 在Openwrt上安装AdguardHome | Bi's Blog

    在Openwrt上安装AdguardHome

    发布于 2025/09/29 技术 总字数:434 阅读时长:2 分钟

    这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。

    +AI摘要 + QWEN

    接续前言,AdguardHome是一款广告拦截软件,有了一台小软路由后就开始折腾了。

    +

    安装

    +

    首先要下载软件包,但是经过尝试,软件源里面的luci-app-adguardhome不太好用~~(也可能是我不会用)~~ ,所以我用的下面的包:

    + + + + luci-app-adguardhome + rufengsuixing · github.com + + +

    虽然好几年没更新了,但还是能用。下载之后上传到路由器,使用opkg install命令安装,或者可以直接通过网页安装:

    +
    image
    +

    配置

    +

    如下图所示进行操作,如果更新核心版本失败,考虑更换升级用的下载链接,使用镜像源,或者科学上网,错号框内的非必要不修改;

    +
    Screenshot of the AdGuard Home configuration page in the OpenWrt web interface. The left sidebar menu is expanded to highlight the Services section and AdGuard Home submenu. The main panel displays configuration options including enable checkbox, web management port, update core version button, executable file path, compressed file option, configuration file path, working directory, and log file path. A large red X marks the configuration file path field, indicating it should not be modified. Visible text includes AdGuard Home, OpenClash, miniDLNA, UPnP IGD and PCP, enable, update core version, executable file path, configuration file path, working directory, log file path, and other related options. The interface is clean and instructional, with a neutral tone.
    +

    最后点击“保存并应用”,然后点击AdGuardHome Web:3000,进行安装,建议关闭路由器自带DNS/DHCP服务器,AdguardHome直接替代

    +
    Screenshot of the OpenWrt web interface showing the DHCP/DNS settings page. The left sidebar menu is expanded to highlight the network section and DHCP/DNS submenu. The main panel displays the General tab with options for enabling exclusive authorization and DNS redirect, both with checkboxes. The interface is clean and organized, with navigation tabs for additional settings such as DNS records, filters, logs, and PXE/TFTP. The overall tone is neutral and instructional. Visible text includes DHCP/DNS, 常规, 唯一授权, DNS redirect, 本地解析这些项目, 本地域名, and other configuration options.
    +
    alt=Screenshot of the OpenWrt web interface focused on DHCP/DNS settings. The left sidebar menu highlights the network section and DHCP/DNS submenu. Red arrows guide the user through selecting DHCP/DNS, then the settings and port tab, and finally entering 5353 in the DNS server port field. The main panel displays options for specifying interface addresses, listening interfaces, excluded interfaces, and DNS server port. Visible text includes DHCP/DNS, 设置及端口, 5353, 监听接口, 排除接口, DNS服务器端口, DNS查询端口, and related configuration options. The interface is clean and instructional, with a neutral and helpful tone.
    +

    然后,设置AdguardHome的DHCP服务器,注意要先检查配置,再启用

    +
    image

    在Openwrt上安装AdguardHome

    /posts/b57500e9/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenwrtAdguardHome
    订阅 打赏

    Comments

    在Openwrt上安装AdguardHome

    \ No newline at end of file diff --git a/posts/b5866b9e/index.html b/posts/b5866b9e/index.html new file mode 100644 index 0000000..57ec40b --- /dev/null +++ b/posts/b5866b9e/index.html @@ -0,0 +1,698 @@ + 利用 SiteMap 随机访问站内页面 | Bi's Blog

    利用 SiteMap 随机访问站内页面

    发布于 2025/08/12 建站手札 总字数:210 阅读时长:1 分钟

    提供了一个很好的解决方案,通过使用 Hexo 的 sitemap 生成器和 JavaScript 代码来实现随机访问站内页面的功能 以下是你提供的步骤的详细解释:

    +AI摘要 + QWEN

    在网上查找博客美化过程时发现基本上都有“随便逛逛”的功能,于是自己也添加一个。 +比较简单只有js代码,但是先要安装插件

    +

    前置条件

    +
    npm install hexo-generator-sitemap --save
    +

    在站点配置文件 _config.yaml中添加:

    +
    sitemap:
    +  path: 
    +    - sitemap.xml
    +    - sitemap.txt
    +  rel: false
    +  tags: true
    +  categories: true
    +

    添加js

    +

    在主题目录下添加·\themes\butterfly\source\js\random.js

    +
    function randomPost() {
    +    fetch('/sitemap.xml').then(res => res.text()).then(str => (new window.DOMParser()).parseFromString(str, "text/xml")).then(data => {
    +        let ls = data.querySelectorAll('url loc');
    +        let locationHref,locSplit;
    +        do {
    +            locationHref = ls[Math.floor(Math.random() * ls.length)].innerHTML
    +            locSplit = locationHref.split('/')[3] || ''
    +        } while (locSplit == '' || locSplit == 'tags');
    +        //若所有文章都如 https://…….com/posts/2022/07/…… 格式,主域名后字符是 posts,则循环条件改为:
    +        //while (locSplit !== 'posts');
    +        location.href = locationHref
    +    })
    +}
    +

    使用

    +

    使用,在菜单栏上添加

    +
    <a href="javascript:;" onclick="randomPost()" title="随机访问一篇文章">随机</a>
    +

    参考

    + + + + 木木木木木的博客 + 木木木木木 · immmmm.com + +

    利用 SiteMap 随机访问站内页面

    /posts/b5866b9e/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    利用 SiteMap 随机访问站内页面

    \ No newline at end of file diff --git a/posts/bb18d851/index.html b/posts/bb18d851/index.html new file mode 100644 index 0000000..196d30e --- /dev/null +++ b/posts/bb18d851/index.html @@ -0,0 +1,1087 @@ + 为1Panel添加自己想要的应用 | Bi's Blog

    为1Panel添加自己想要的应用

    发布于 2025/08/19 技术 总字数:1.5k 阅读时长:4 分钟

    这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。

    +AI摘要 + QWEN

    记录一下自己为1Panel贡献自己应用的经历

    +

    预准备

    +
      +
    1. +Fork仓库 + + + + 1Panel appstore 仓库 + 1panel appstore · github.com + + +把仓库fork到自己的仓库 +
    2. +
    3. pull +
      git clone -b dev https://<your-github-username>/appstore
      +创建新分支 +
      cd appstore
      +git checkout -b app/<app-name>
      +
    4. +
    +

    创建文件

    +

    文件目录

    +
    ├──halo // 以 halo 的 key 命名 ,下面解释什么是 key 
    +	├── logo.png // 应用 logo , 最好是 180 * 180 px 不要超过 10 KB 
    +	├── data.yml // 应用声明文件
    +	├── README.md // 应用的 README
    +	├── 2.2.0 // 应用版本 注意不要以 v 开头
    +	│   ├── data.yml // 应用的参数配置,下面有详细介绍
    +	│   ├── data // 挂载出来的目录 
    +	|   ├── scripts // 脚本目录 存放 init.sh upgrade.sh uninstall.sh
    +	│   └── docker-compose.yml // docker-compose 文件
    +	└── 2.3.2
    +	    ├── data.yml
    +	    ├── data
    +	    └── docker-compose.yml
    +

    应用声明文件data.yml

    +

    本文件主要用于声明应用的一些信息

    +
    additionalProperties:  #固定参数
    +    key: halo   #应用的 key ,仅限英文,用于在 Linux 创建文件夹
    +    name: Halo  #应用名称
    +    tags:  
    +        - WebSite #应用标签,可以有多个,请参照下方的标签列表 
    +	description:  
    +	  en: Powerful and easy-to-use open source website builder
    +	  zh: 强大易用的开源建站工具 #应用中文描述,不要超过30个字
    +	  zh-Hant:
    +	  ja:   
    +	  ms: 
    +	  pt-br: 
    +	  ru:  
    +	  ko: 
    +    type: website  #应用类型,区别于应用分类,只能有一个,请参照下方的类型列表
    +    crossVersionUpdate: true  #是否可以跨大版本升级
    +    limit: 0  #应用安装数量限制,0 代表无限制
    +    website: https://halo.run/  #官网地址
    +    github: https://github.com/halo-dev/halo #github 地址 
    +    document: https://docs.halo.run/ #文档地址
    +

    应用标签 - tags 字段

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    keyname
    WebSite建站
    ServerWeb 服务器
    Runtime运行环境
    Database数据库
    Tool工具
    CI/CDCI/CD
    Local本地
    +

    应用类型 - type 字段

    + + + + + + + + + + + + + + + + + + + + + +
    type说明
    websitewebsite 类型在 1Panel 中支持在网站中一键部署,wordpress halo 都是此 type
    runtimemysql openresty redis 等类型的应用
    toolphpMyAdmin redis-commander jenkins 等类型的应用
    +

    应用参数配置文件 data.yml

    +

    (注意区分于应用主目录下面的 data.yaml

    +

    本文件主要用于生成安装时要填写的 form 表单,在应用版本文件夹下面 可以无表单,但是需要有这个 data.yml文件,并且包含 formFields 字段

    +

    以安装 halo 时的 form 表单 为例

    +

    如果要生成上面的表单,需要这么填写 data.yml

    +
    	additionalProperties:  #固定参数
    +	    formFields:  
    +	        - default: ""  
    +	          envKey: PANEL_DB_HOST  #docker-compose 文件中的参数
    +	          key: mysql  #依赖应用的 key , 例如 mysql
    +		  label:  
    +		   en: Database Service  
    +	           ja: データベースサービス  
    +		   ms: Perkhidmatan Pangkalan Data  
    +		   pt-br: Serviço de Banco de Dados  
    +		   ru: Сервис базы данных  
    +		   ko: 데이터베이스 서비스  
    +		   zh: 数据库服务  
    +		   zh-Hant: 數據庫 服務
    +		  required: true  #是否必填
    +	          type: service  #如果需要依赖其他应用,例如数据库,使用此 type 
    +	        - default: halo  
    +	          envKey: PANEL_DB_NAME  
    +	          label:  
    +	            en: Database  
    +		    ja: データベース  
    +		    ms: Pangkalan Data  
    +		    pt-br: Banco de Dados  
    +		    ru: База данных  
    +		    ko: 데이터베이스  
    +		    zh: 数据库名  
    +		    zh-Hant: 資料庫名稱
    +	          random: true  #是否在 default 文字后面,增加随机字符串
    +	          required: true  
    +	          rule: paramCommon  #校验规则
    +	          type: text  #需要手动填写的,使用此 type
    +	        - default: halo  
    +	          envKey: PANEL_DB_USER  
    +		  label:  
    +		    en: User  
    +		    ja: ユーザー  
    +		    ms: Pengguna  
    +		    pt-br: Usuário  
    +		    ru: Пользователь  
    +		    ko: 사용자  
    +		    zh: 数据库用户  
    +		    zh-Hant: 資料庫使用者
    +	          random: true  
    +	          required: true  
    +	          rule: paramCommon  
    +	          type: text  
    +	        - default: halo  
    +	          envKey: PANEL_DB_USER_PASSWORD  
    +		  label:  
    +		    en: Password  
    +		    ja: パスワード  
    +		    ms: Kata laluan  
    +		    pt-br: Senha  
    +		    ru: Пароль  
    +		    ko: 비밀번호  
    +		    zh: 数据库用户密码  
    +		    zh-Hant: 資料庫使用者密碼 
    +	          random: true  
    +	          required: true  
    +	          rule: paramComplexity  
    +	          type: password  #密码字段使用此 type
    +	        - default: admin  
    +	          envKey: HALO_ADMIN  
    +	          labelEn: Admin Username  
    +	          labelZh: 超级管理员用户名  
    +	          required: true  
    +	          rule: paramCommon  
    +	          type: text 
    +	        - default: http://localhost:8080  
    +	          edit: true  
    +	          envKey: HALO_EXTERNAL_URL  
    +		  label:  
    +		    en: External URL  
    +		    ja: 外部URL  
    +		    ms: URL Luaran  
    +		    pt-br: URL Externa  
    +		    ru: Внешний URL  
    +		    ko: 외부 URL  
    +		    zh: 外部访问地址  
    +		    zh-Hant: 外部訪問地址 
    +	          required: true  
    +	          rule: paramExtUrl  
    +	          type: text  
    +	        - default: 8080  
    +	          edit: true  
    +	          envKey: PANEL_APP_PORT_HTTP  
    +		  label:  
    +		    en: Port  
    +		    ja: ポート  
    +		    ms: Port  
    +		    pt-br: Porta  
    +		    ru: Порт  
    +		    ko: 포트  
    +		    zh: 端口  
    +		    zh-Hant: 
    +	          required: true  
    +	          rule: paramPort  
    +	          type: number #端口使用此 type 
    +

    关于端口字段:

    +

    PANEL_APP_PORT_HTTPweb 访问端口的优先使用此 envKey +envKey 中包含 PANEL_APP_PORT 前缀会被认定为端口类型,并且用于安装前的端口占用校验。注意:端口需要是外部端口

    +

    关于 type 字段:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    type说明
    servicetype: service 如果该应用需要依赖其他组件,如 mysql redis 等,可以通过 key: mysql 定义依赖的名称,在创建应用时会要求先创建依赖的应用。
    passwordtype: password 敏感信息,如密码相关的字段会默认不显示明文。
    texttype: text 一般内容,比如数据库名称,默认明文显示。
    numbertype: number 一般用在端口相关的配置上,只允许输入数字。
    +

    简单的例子

    +
    # type: service,定义一个 mysql 的 service 依赖。
    +- child:  
    +    default: ""  
    +    envKey: PANEL_DB_HOST  
    +    required: true  
    +    type: service  
    +  default: mysql  
    +  envKey: PANEL_DB_TYPE  
    +  label:  
    +    en: Database Service  
    +    ja: データベースサービス  
    +    ms: Perkhidmatan Pangkalan Data  
    +    pt-br: Serviço de Banco de Dados  
    +    ru: Сервис баз данных  
    +    ko: 데이터베이스 서비스  
    +    zh-hant: 資料庫服務  
    +    zh: 数据库服务  
    +  required: true  
    +  type: apps  
    +  values:  
    +    - label: MySQL  
    +      value: mysql  
    +    - label: MariaDB  
    +      value: mariadb
    +
    +# type: password
    +- default: word  
    +  envKey: PANEL_DB_USER_PASSWORD  
    +  labelEn: Database Password  
    +  labelZh: 数据库密码  
    +  label:  
    +    en: Database Password  
    +    ja: データベースのパスワード  
    +    ms: Kata Laluan Pangkalan Data  
    +    pt-br: Senha do Banco de Dados  
    +    ru: Пароль базы данных  
    +    ko: 데이터베이스 비밀번호  
    +    zh-hant: 資料庫密碼  
    +    zh: 数据库密码  
    +  random: true  
    +  required: true  
    +  type: password
    +
    +# type: text
    +- default: ""  
    +  edit: true  
    +  envKey: REDIS_HOST  
    +  key: redis  
    +  required: true  
    +  type: service  
    +  label:  
    +    en: Redis Service  
    +    ja: Redis サービス  
    +    ms: Perkhidmatan Redis  
    +    pt-br: Serviço Redis  
    +    ru: Сервис Redis  
    +    ko: Redis 서비스  
    +    zh-Hant: Redis 服務  
    +    zh: Redis 服务
    +    
    +# type: number
    +- default: ""  
    +  edit: true  
    +  envKey: DBHUB_DB_PORT  
    +  required: true  
    +  type: text  
    +  label:  
    +    en: Database Port  
    +    ja: データベースのポート  
    +    ms: Pangkalan Data Port  
    +    pt-br: Porta do Banco de Dados  
    +    ru: Порт базы данных  
    +    ko: 데이터베이스 포트  
    +    zh-hant: 資料庫端口  
    +    zh: 数据库端口
    +
    +# type: select
    +- default: "ERROR"  
    +  envKey: LOG_LEVEL  
    +  required: true  
    +  type: select  
    +  values:  
    +    - label: DEBUG  
    +      value: "DEBUG"  
    +    - label: INFO  
    +      value: "INFO"  
    +    - label: WARNING  
    +      value: "WARNING"  
    +    - label: ERROR  
    +      value: "ERROR"  
    +    - label: CRITICAL  
    +      value: "CRITICAL"
    +

    rule 字段目前支持的几种校验

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    rule规则
    paramPort用于限制端口范围为 1-65535
    paramExtUrl格式为 http(s)://(域名/ip):(端口)
    paramCommon英文、数字、.-和_,长度2-30
    paramComplexity支持英文、数字、.%@$!&~_-,长度6-30,特殊字符不能在首尾
    +

    应用 docker-compose.yml 文件

    +

    ${PANEL_APP_PORT_HTTP} 类型的参数,都在 data.yml 中有声明

    +
    version: "3"  
    +services:  
    +  halo:  
    +    image: halohub/halo:2.2.0  
    +    container_name: ${CONTAINER_NAME}  // 固定写法,勿改
    +    restart: always  
    +    networks:  
    +      - 1panel-network  // 1Panel 创建的应用都在此网络下
    +    volumes:  
    +      - ./data:/root/.halo2  
    +    ports:  
    +      - ${PANEL_APP_PORT_HTTP}:8090  
    +    command:  
    +      - --spring.r2dbc.url=r2dbc:pool:${HALO_PLATFORM}://${PANEL_DB_HOST}:${HALO_DB_PORT}/${PANEL_DB_NAME}  
    +      - --spring.r2dbc.username=${PANEL_DB_USER}  
    +      - --spring.r2dbc.password=${PANEL_DB_USER_PASSWORD}  
    +      - --spring.sql.init.platform=${HALO_PLATFORM}  
    +      - --halo.external-url=${HALO_EXTERNAL_URL}  
    +      - --halo.security.initializer.superadminusername=${HALO_ADMIN}  
    +      - --halo.security.initializer.superadminpassword=${HALO_ADMIN_PASSWORD}  
    +    labels:  
    +      createdBy: "Apps"  
    +  
    +networks:  
    +  1panel-network:  
    +    external: true
    +

    脚本

    +

    1Panel 在 安装之前、升级之前、卸载之后支持执行 .sh 脚本 +分别对应 init.sh upgrade.sh uninstall.sh +存放目录(以halo为例) : halo/2.2.0/scripts

    +

    本地测试

    +

    将应用目录上传到 1Panel 的 /opt/1panel/resource/apps/local 文件夹下 +注意:/opt 为 1Panel 默认安装目录,请根据自己的实际情况修改 +上传完成后,目录结构如下

    +
    ├──halo 
    +	├── logo.png 
    +	├── data.yml
    +	├── README.md 
    +	├── 2.2.0 
    +	    ├── data.yml 
    +	    ├── data  
    +	    └── docker-compose.yml
    +

    在 1Panel 应用商店中,点击更新应用列表按钮同步本地应用

    +

    v1.2 版本及之前版本的本地应用,请参考这个文档修改

    +

    提交文件

    +
    git add .
    +git commit -m "Add my-app"
    +git push origin dev
    +

    提交 Pull Request

    +

    在你的仓库点击 Pull requests 菜单 +点击 New pull request ,填写标题和描述 +选择由你的分支提交到 1Panel-dev/appstore

    为1Panel添加自己想要的应用

    /posts/bb18d851/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    1panel
    订阅 打赏

    Comments

    为1Panel添加自己想要的应用

    \ No newline at end of file diff --git a/posts/bc56f789/index.html b/posts/bc56f789/index.html new file mode 100644 index 0000000..549c5e0 --- /dev/null +++ b/posts/bc56f789/index.html @@ -0,0 +1,689 @@ + C# 入门 | Bi's Blog

    C# 入门

    发布于 2025/08/15 学习 总字数:159 阅读时长:1 分钟

    这篇文章指导初学者在Windows上安装C#开发环境:先下载Visual Studio社区版安装器并勾选“.NET桌面开发”工作负载,可自定义安装路径;随后通过HelloWorld演示C#基础结构,包含命名空间、类、Main入口及Console输出,帮助读者完成首个程序并熟悉默认模板。

    +AI摘要 + QWEN

    今天开始学习C#

    +

    环境安装

    +下载Visual Studio,社区版就可以 + + + + Visual Studio + Visual Studio · visualstudio.microsoft.com + + +下载的是安装器选择.Net桌面开发,注意修改安装位置 +
    .Net 桌面开发
    +

    第一个程序

    +
    using System;
    +namespace HelloWorldApplication
    +{
    +    /* 类名为 HelloWorld */
    +    class HelloWorld
    +    {
    +        /* main函数 */
    +        static void Main(string[] args)
    +        {
    +            /* 我的第一个 C# 程序 */
    +            Console.WriteLine("Hello World!");
    +            Console.ReadKey();
    +        }
    +    }
    +}
    +

    这也是一个默认的格式

    C# 入门

    /posts/bc56f789/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    C#
    订阅 打赏

    Comments

    C# 入门

    \ No newline at end of file diff --git a/posts/c4477b0c/index.html b/posts/c4477b0c/index.html new file mode 100644 index 0000000..780041e --- /dev/null +++ b/posts/c4477b0c/index.html @@ -0,0 +1,789 @@ + 配置OpenGL环境 | Bi's Blog

    配置OpenGL环境

    发布于 2026/04/02 技术 总字数:985 阅读时长:3 分钟

    这篇文章详细介绍了配置OpenGL环境所需步骤,包括安装Visual Studio、CMake和GLFW等关键组件,并提供了具体的配置方法和测试代码,旨在帮助读者顺利开始计算机图形学的学习之旅。

    +AI摘要 + QWEN

    最近要学计算机图形学,所以会用到OpenGL,配置环境有点繁琐,记录了下来。

    +

    安装Visual Studio

    +

    现在我们先来安装Visual Studiovisual studio,下载后安装即可|在安装时选择“使用C++的桌面开发”,这样安装时就会安装C++的编译器了。 +

    +

    安装 Cmake

    +

    这个可选,因为我们可以使用Visual Studio的编译器MSVC来编译项目。 +Cmake是一个开源的跨平台软件构建工具,它可以生成不同系统的构建文件,比如MakefileNinjaVS项目文件等等。 +我们可以下载Cmakecmake,下载后安装即可。

    +

    安装GLFW

    +

    OpenGL有许多工具,比如GLFW,GLEW等等,这里我们安装GLFW。

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    工具类别主要职责特点
    GLUT窗口管理 + 工具库创建窗口、处理鼠标键盘、提供内置渲染循环。古老、简单。使用“固定管线”(老旧技术),适合教学。
    GLFW窗口管理库创建窗口、处理输入、管理多个上下文。现代、轻量。只管窗口和输入,不负责渲染逻辑,是目前的主流。
    GLAD配置/加载库加载 OpenGL 函数指针(连接驱动)。底层必备。因为 OpenGL 函数在显卡驱动里,需要它来“找”函数地址。
    +

    下载GLFW 和 glad

    +

    GLFW是一个开源的跨平台C/C++窗口管理库,它可以创建窗口、处理输入、管理多个上下文。 +下载GLFWglfw,下载后解压即可。

    +

    创建项目与配置

    +

    Visual Studio创建一个空的C++项目,然后添加GLFW的目录到项目中。 +在项目属性中添加GLFW的目录到“C/C++->General->Additional Include Directories”中。 +如图所示: +

    +

    测试

    +

    创建一个main.cpp文件,内容如下:

    +
    #include <glad/glad.h>
    +#include <GLFW/glfw3.h>
    +#include <iostream>
    +
    +// 顶点着色器源码
    +const char* vertexShaderSource = "#version 330 core\n"
    +    "layout (location = 0) in vec3 aPos;\n"
    +    "void main() {\n"
    +    "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
    +    "}\0";
    +
    +// 片段着色器源码
    +const char* fragmentShaderSource = "#version 330 core\n"
    +    "out vec4 FragColor;\n"
    +    "void main() {\n"
    +    "   FragColor = vec4(1.0f, 1.0f, 1.0f, 1.0f);\n" // 白色
    +    "}\n\0";
    +
    +int main() {
    +    // 1. 初始化 GLFW
    +    glfwInit();
    +    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    +    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    +    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    +
    +    // 2. 创建窗口
    +    GLFWwindow* window = glfwCreateWindow(800, 600, "Simple OpenGL", NULL, NULL);
    +    glfwMakeContextCurrent(window);
    +    gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
    +
    +    // 3. 编译着色器
    +    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    +    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    +    glCompileShader(vertexShader);
    +
    +    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    +    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    +    glCompileShader(fragmentShader);
    +
    +    unsigned int shaderProgram = glCreateProgram();
    +    glAttachShader(shaderProgram, vertexShader);
    +    glAttachShader(shaderProgram, fragmentShader);
    +    glLinkProgram(shaderProgram);
    +
    +    // 4. 定义三角形顶点数据
    +    float vertices[] = {
    +        -0.5f, -0.5f, 0.0f, // 左下
    +         0.5f, -0.5f, 0.0f, // 右下
    +         0.0f,  0.5f, 0.0f  // 顶部
    +    };
    +
    +    unsigned int VBO, VAO;
    +    glGenVertexArrays(1, &VAO);
    +    glGenBuffers(1, &VBO);
    +
    +    glBindVertexArray(VAO);
    +    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    +    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    +
    +    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    +    glEnableVertexAttribArray(0);
    +
    +    // 5. 渲染循环
    +    while (!glfwWindowShouldClose(window)) {
    +        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); // 深青色背景
    +        glClear(GL_COLOR_BUFFER_BIT);
    +
    +        glUseProgram(shaderProgram);
    +        glBindVertexArray(VAO);
    +        glDrawArrays(GL_TRIANGLES, 0, 3);
    +
    +        glfwSwapBuffers(window);
    +        glfwPollEvents();
    +    }
    +
    +    glfwTerminate();
    +    return 0;
    +}
    +

    如果能运行成功应该就没问题了。

    配置OpenGL环境

    /posts/c4477b0c/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    OpenGL
    订阅 打赏

    Comments

    配置OpenGL环境

    \ No newline at end of file diff --git a/posts/c6143ad3/index.html b/posts/c6143ad3/index.html new file mode 100644 index 0000000..d788b7e --- /dev/null +++ b/posts/c6143ad3/index.html @@ -0,0 +1,1169 @@ + 自定义页脚(新) | Bi's Blog

    自定义页脚(新)

    发布于 2025/08/15 建站手札 总字数:165 阅读时长:1 分钟

    您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。

    +AI摘要 + QWEN

    添加CSS

    +

    修改有风险,注意备份

    +
    /*https://codepen.io/poojanahelia/pen/Exabvdy*/
    +
    +#footer {
    +    background-color: rgba(0, 0, 0, 0); /* 修改透明色 */
    +    color: #fff;
    +    padding: 0;
    +}
    +
    +#footer .footer-other {
    +    padding: 0;
    +}
    +
    +.footer-copyright{
    +    padding: 0;
    +}
    +
    +.my-footer-logo {
    +    color: white;
    +}
    +
    +.my-footer-wave-svg {
    +    height: 50px;
    +    width: 100%;
    +    transform: scale(-1, -1) translateY(-10px); /*;*/
    +}
    +
    +.my-footer-wave-path {
    +    fill: #177ecd;
    +}
    +
    +.my-footer-content {
    +    margin-left: auto;
    +    margin-right: auto;
    +    max-width: 1230px;
    +    padding: 40px 15px 450px;
    +    position: relative;
    +}
    +
    +.my-footer-content-column {
    +    box-sizing: border-box;
    +    float: left;
    +    padding-left: 15px;
    +    padding-right: 15px;
    +    width: 100%;
    +    color: #fff;
    +}
    +
    +.my-footer-content-column ul li a {
    +    color: #fff;
    +    text-decoration: none;
    +}
    +
    +.my-footer-logo-link {
    +    display: inline-block;
    +}
    +
    +.my-footer-menu {
    +    margin-top: 15px;
    +}
    +
    +.my-footer-menu-name {
    +    color: #fffff2;
    +    font-size: 15px;
    +    font-weight: 900;
    +    letter-spacing: .1em;
    +    line-height: 18px;
    +    margin-bottom: 0;
    +    margin-top: 0;
    +    text-transform: uppercase;
    +}
    +
    +.my-footer-menu-list {
    +    list-style: none;
    +    margin-bottom: 0;
    +    margin-top: 10px;
    +    padding-left: 0;
    +}
    +
    +.my-footer-menu-list li {
    +    margin-top: 5px;
    +}
    +
    +.my-footer-call-to-action-description {
    +    color: #fffff2;
    +    margin-top: 10px;
    +    margin-bottom: 20px;
    +}
    +
    +.my-footer-call-to-action-button:hover {
    +    background-color: #fffff2;
    +    color: #00bef0;
    +}
    +
    +.button:last-of-type {
    +    margin-right: 0;
    +}
    +
    +.my-footer-call-to-action-button {
    +    background-color: #03708c;
    +    border-radius: 21px;
    +    color: #fffff2;
    +    display: inline-block;
    +    font-size: 11px;
    +    font-weight: 900;
    +    letter-spacing: .1em;
    +    line-height: 18px;
    +    padding: 12px 30px;
    +    margin: 0 10px 10px 0;
    +    text-decoration: none;
    +    text-transform: uppercase;
    +    transition: background-color .2s;
    +    cursor: pointer;
    +    position: relative;
    +}
    +
    +.my-footer-call-to-action {
    +    margin-top: 17px;
    +}
    +
    +.my-footer-call-to-action-title {
    +    color: #fffff2;
    +    font-size: 14px;
    +    font-weight: 900;
    +    letter-spacing: .1em;
    +    line-height: 18px;
    +    margin-bottom: 0;
    +    margin-top: 0;
    +    text-transform: uppercase;
    +}
    +
    +.my-footer-call-to-action-link-wrapper {
    +    margin-bottom: 0;
    +    margin-top: 10px;
    +    color: #fff;
    +    text-decoration: none;
    +}
    +
    +.my-footer-call-to-action-link-wrapper a {
    +    color: #fff;
    +    text-decoration: none;
    +}
    +
    +
    +.my-footer-social-links {
    +    bottom: -1px;
    +    height: 54px;
    +    position: absolute;
    +    right: 0;
    +    width: 236px;
    +}
    +
    +.my-footer-social-amoeba-svg {
    +    height: 54px;
    +    left: 0;
    +    display: block;
    +    position: absolute;
    +    top: 0;
    +    width: 236px;
    +}
    +
    +.my-footer-social-amoeba-path {
    +    fill: #03708c;
    +}
    +
    +.my-footer-social-link.email {
    +    height: 41px;
    +    left: 5px;
    +    top: 14px;
    +    width: 41px;
    +}
    +
    +.my-footer-social-link {
    +    display: block;
    +    padding: 10px;
    +    position: absolute;
    +}
    +
    +.hidden-link-text {
    +    position: absolute;
    +    clip: rect(1px 1px 1px 1px);
    +    clip: rect(1px, 1px, 1px, 1px);
    +    -webkit-clip-path: inset(0px 0px 99.9% 99.9%);
    +    clip-path: inset(0px 0px 99.9% 99.9%);
    +    overflow: hidden;
    +    height: 1px;
    +    width: 1px;
    +    padding: 0;
    +    border: 0;
    +    top: 50%;
    +}
    +
    +.my-footer-social-icon-svg {
    +    display: block;
    +}
    +
    +.my-footer-social-icon-path {
    +    fill: #fffff2;
    +    transition: fill .2s;
    +}
    +
    +.my-footer-social-link.follow {
    +    height: 42px;
    +    left: 124px;
    +    top: 13px;
    +    width: 42px;
    +}
    +
    +.my-footer-social-link.rss {
    +    height: 43px;
    +    left: 178px;
    +    top: 10px;
    +    width: 43px;
    +}
    +
    +.my-footer-social-link.github {
    +    height: 62px;
    +    left: 54px;
    +    top: -4px;
    +    width: 62px;
    +}
    +
    +.my-footer-copyright {
    +    background-color: #03708c;
    +    color: #fff;
    +    padding: 15px 30px;
    +    text-align: center;
    +}
    +
    +.my-footer-copyright-wrapper {
    +    margin-left: auto;
    +    margin-right: auto;
    +    max-width: 1200px;
    +}
    +
    +.my-footer-copyright-text {
    +    color: #fff;
    +    font-size: 13px;
    +    font-weight: 400;
    +    line-height: 18px;
    +    margin-bottom: 0;
    +    margin-top: 0;
    +}
    +
    +.my-footer-copyright-link {
    +    color: #fff;
    +    text-decoration: none;
    +}
    +
    +.my-footer-content-div {
    +    background: #177ecd
    +}
    +
    +.my-footer-svg-div {
    +    width: 100%;
    +    max-height: 200px;
    +}
    +
    +/* Media Query For different screens */
    +@media (min-width: 320px) and (max-width: 479px) {
    +    /* smartphones, portrait iPhone, portrait 480x320 phones (Android) */
    +    .my-footer-content {
    +        margin-left: auto;
    +        margin-right: auto;
    +        max-width: 1230px;
    +        padding: 40px 15px 649px;
    +        position: relative;
    +    }
    +
    +    /* 不展示logo */
    +    .my-footer-logo {
    +        display: none;
    +    }
    +}
    +
    +@media (min-width: 480px) and (max-width: 599px) {
    +    /* smartphones, Android phones, landscape iPhone */
    +    .my-footer-content {
    +        margin-left: auto;
    +        margin-right: auto;
    +        max-width: 1230px;
    +        padding: 40px 15px 738px;
    +        position: relative;
    +    }
    +
    +    .my-footer-logo {
    +        padding-left: 177px; /* Qlogo 稍微偏移一点 */
    +        padding-right: 170px;
    +    }
    +}
    +
    +@media (min-width: 600px) and (max-width: 800px) {
    +    /* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */
    +    .my-footer-content {
    +        margin-left: auto;
    +        margin-right: auto;
    +        max-width: 1230px;
    +        padding: 40px 15px 758px;
    +        position: relative;
    +    }
    +
    +
    +}
    +
    +@media (min-width: 801px) {
    +    /* tablet, landscape iPad, lo-res laptops ands desktops */
    +
    +}
    +
    +@media (min-width: 1025px) {
    +    /* big landscape tablets, laptops, and desktops */
    +
    +}
    +
    +@media (min-width: 1281px) {
    +    /* hi-res laptops and desktops */
    +
    +}
    +
    +
    +@media (min-width: 760px) {
    +    .my-footer-content {
    +        margin-left: auto;
    +        margin-right: auto;
    +        max-width: 1230px;
    +        padding: 10px 15px 237px;
    +        position: relative;
    +    }
    +
    +
    +    .my-footer-content-column {
    +        /*五列的话 19.99 %*/
    +        width: 24.99%;
    +    }
    +}
    +
    +@media (min-width: 568px) {
    +
    +}
    +
    +@media (min-width: 600px) and (max-width: 760px) {
    +    /* 在这里编写适用于小屏幕的样式 */
    +    .my-footer-logo {
    +        padding-left: 212px; /* Qlogo 稍微偏移一点 */
    +        padding-right: 204px;
    +    }
    +}
    +/* 页脚波浪的颜色 */
    +[data-theme='dark'] .my-footer-wave-path {
    +    fill: #0f3858;
    +}
    +[data-theme='dark'] .my-footer-content-div {
    +    background-color: #0f3858;
    +}
    +
    +/* 页脚海底的颜色 */
    +[data-theme='dark'] .my-footer-copyright {
    +    background-color: #2b3f49;
    +}
    +[data-theme='dark'] .my-footer-social-amoeba-path {
    +    fill: #2b3f49;
    +}
    +
    +

    注入

    +

    打开主题配置文件 _config.butterfly.yml,写入以下内容(以目前我的页脚为例,具体内容请自行修改):

    +
    # Footer Settings
    +# --------------------------------------
    +footer:
    +  custom_text: |
    +    <div class="my-footer-svg-div">
    +      <svg class="my-footer-wave-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 100" preserveAspectRatio="none">
    +      <path class="my-footer-wave-path" d="M851.8,100c125,0,288.3-45,348.2-64V0H0v44c3.7-1,7.3-1.9,11-2.9C80.7,22,151.7,10.8,223.5,6.3C276.7,2.9,330,4,383,9.8 c52.2,5.7,103.3,16.2,153.4,32.8C623.9,71.3,726.8,100,851.8,100z"></path>
    +      </svg>    
    +    </div>
    +    <div class="my-footer-content-div" >
    +    <div class="my-footer-content">
    +        <div class="my-footer-content-column">
    +            <div class="my-footer-logo">
    +                <a class="my-footer-logo-link" href="#">
    +                    <span class="hidden-link-text">LOGO</span>
    +                    <img src="/image/footer/qlogo_white_no_words.png" style="height:40%; width:40%">
    +                </a>
    +            </div>
    +            <div class="my-footer-menu">
    +                <h2 class="my-footer-menu-name">开始</h2>
    +                <ul id="menu-get-started" class="my-footer-menu-list">
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                        <a href="/pages/about.html">关于本站</a>
    +                    </li>
    +                </ul>
    +            </div>
    +        </div>
    +        <div class="my-footer-content-column">
    +            <div class="my-footer-menu">
    +                <h2 class="my-footer-menu-name">快速链接</h2>
    +                <ul id="menu-company" class="my-footer-menu-list">
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                        <a href="https://hexo.io/zh-cn/">Hexo</a>&nbsp;&nbsp;<a href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a>
    +                    </li>
    +                    <li class="menu-item menu-item-type-taxonomy menu-item-object-category">
    +                        <a href="/archives/">时间轴</a>&nbsp;|&nbsp;<a href="/DO_NOT_render/cosmoscope/cosmoscope_trim.html">关系图</a>&nbsp;|&nbsp;<a href="/pages/categories/">分类</a>
    +                    </li>
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                        <a href="/p/91b7dad/">同款页脚</a>
    +                    </li>
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                        <a href="https://www.foreverblog.cn/" rel="noopener external nofollow noreferrer" target="_blank" > <img class="img-foreverblog" src="/image/footer/forever_logo_en_default_white.png" alt="" style="width:auto;height:21px;margin-top:6px"> </a>
    +                    </li>
    +                </ul>
    +            </div>
    +    
    +        </div>
    +
    +        <div class="my-footer-content-column">
    +            <div class="my-footer-menu">
    +                <h2 class="my-footer-menu-name">法律声明</h2>
    +                <ul id="menu-legal" class="my-footer-menu-list">
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-privacy-policy menu-item-170434">
    +                        <a href="/pages/privacy.html">隐私政策</a>
    +                    </li>
    +                </ul>
    +            </div>
    +            <div class="my-footer-call-to-action">
    +                <h2 class="my-footer-call-to-action-title">联系本站</h2>
    +                <ul id="menu-legal" class="my-footer-menu-list">
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-privacy-policy menu-item-170434">
    +                        <a href="/DO_NOT_render/wechatOA/index.html">微信公众号</a>
    +                    </li>
    +    
    +                    <li class="menu-item menu-item-type-post_type menu-item-object-page menu-item-privacy-policy menu-item-170434">
    +                        <a class="my-footer-call-to-action-link" href="mailto:uuanqin@uuanqin.top" target="_self">
    +                            uuan<span style="display:none">@</span>qi<!-- >@ -->n@<span style="display:none">@</span>uu<!-- >@. -->an<span style="display:none">@</span>qin.top
    +                        </a>
    +                    </li>
    +    
    +                </ul>
    +            </div>
    +    
    +        </div>
    +        <div class="my-footer-content-column">
    +            <ul id="menu-get-started" class="my-footer-menu-list">
    +                <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                    <a href="https://notbyai.fyi/" target="_blank" rel="external nofollow noopener noreferrer"><img class="img-not-ai" src="/image/footer/Written-By-Human-Not-By-AI-Badge-white.svg" alt="Written by Human, Not by AI"></a>
    +                </li>
    +                <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                    <a href="/pages/cc.html" ><img src="/image/footer/by-nc-sa.svg" alt="署名-非商业性使用-相同方式共享 4.0 国际"></a>
    +                </li>
    +    
    +                <li class="menu-item menu-item-type-post_type menu-item-object-product">
    +                    ©2022-2025 By wuanqin
    +                </li>
    +            </ul>
    +        </div>
    +    
    +        <div class="my-footer-social-links"> <svg class="my-footer-social-amoeba-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 236 54">
    +            <path class="my-footer-social-amoeba-path" d="M223.06,43.32c-.77-7.2,1.87-28.47-20-32.53C187.78,8,180.41,18,178.32,20.7s-5.63,10.1-4.07,16.7-.13,15.23-4.06,15.91-8.75-2.9-6.89-7S167.41,36,167.15,33a18.93,18.93,0,0,0-2.64-8.53c-3.44-5.5-8-11.19-19.12-11.19a21.64,21.64,0,0,0-18.31,9.18c-2.08,2.7-5.66,9.6-4.07,16.69s.64,14.32-6.11,13.9S108.35,46.5,112,36.54s-1.89-21.24-4-23.94S96.34,0,85.23,0,57.46,8.84,56.49,24.56s6.92,20.79,7,24.59c.07,2.75-6.43,4.16-12.92,2.38s-4-10.75-3.46-12.38c1.85-6.6-2-14-4.08-16.69a21.62,21.62,0,0,0-18.3-9.18C13.62,13.28,9.06,19,5.62,24.47A18.81,18.81,0,0,0,3,33a21.85,21.85,0,0,0,1.58,9.08,16.58,16.58,0,0,1,1.06,5A6.75,6.75,0,0,1,0,54H236C235.47,54,223.83,50.52,223.06,43.32Z"></path>
    +        </svg>
    +            <a class="my-footer-social-link github" href="https://github.com/uuanqin" target="_blank" rel="external nofollow noopener noreferrer">
    +                <span class="hidden-link-text">Github</span>
    +                <svg class="my-footer-social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
    +                    <path class="my-footer-social-icon-path" d="M 16 4 C 9.371094 4 4 9.371094 4 16 C 4 21.300781 7.4375 25.800781 12.207031 27.386719 C 12.808594 27.496094 13.027344 27.128906 13.027344 26.808594 C 13.027344 26.523438 13.015625 25.769531 13.011719 24.769531 C 9.671875 25.492188 8.96875 23.160156 8.96875 23.160156 C 8.421875 21.773438 7.636719 21.402344 7.636719 21.402344 C 6.546875 20.660156 7.71875 20.675781 7.71875 20.675781 C 8.921875 20.761719 9.554688 21.910156 9.554688 21.910156 C 10.625 23.746094 12.363281 23.214844 13.046875 22.910156 C 13.15625 22.132813 13.46875 21.605469 13.808594 21.304688 C 11.144531 21.003906 8.34375 19.972656 8.34375 15.375 C 8.34375 14.0625 8.8125 12.992188 9.578125 12.152344 C 9.457031 11.851563 9.042969 10.628906 9.695313 8.976563 C 9.695313 8.976563 10.703125 8.65625 12.996094 10.207031 C 13.953125 9.941406 14.980469 9.808594 16 9.804688 C 17.019531 9.808594 18.046875 9.941406 19.003906 10.207031 C 21.296875 8.65625 22.300781 8.976563 22.300781 8.976563 C 22.957031 10.628906 22.546875 11.851563 22.421875 12.152344 C 23.191406 12.992188 23.652344 14.0625 23.652344 15.375 C 23.652344 19.984375 20.847656 20.996094 18.175781 21.296875 C 18.605469 21.664063 18.988281 22.398438 18.988281 23.515625 C 18.988281 25.121094 18.976563 26.414063 18.976563 26.808594 C 18.976563 27.128906 19.191406 27.503906 19.800781 27.386719 C 24.566406 25.796875 28 21.300781 28 16 C 28 9.371094 22.628906 4 16 4 Z "></path>
    +                </svg>
    +            </a>
    +            <a class="my-footer-social-link email" href="mailto:uuanqin@uuanqin.top" target="_blank" rel="external nofollow noopener noreferrer">
    +                <span class="hidden-link-text">Email</span>
    +                <svg class="my-footer-social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
    +                    <!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
    +                    <path class="my-footer-social-icon-path" d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/>
    +                </svg>
    +            </a>
    +            <a class="my-footer-social-link follow" href="https://app.follow.is/share/users/uuanqin" target="_blank" rel="external nofollow noopener noreferrer">
    +                <span class="hidden-link-text">Follow</span>
    +                <svg class="my-footer-social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
    +                  <!--!Font Awesome Free 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
    +                  <path class="my-footer-social-icon-path" d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/>
    +                </svg>
    +            </a>
    +            <a class="my-footer-social-link rss" href="/atom.xml" target="_blank" rel="external nofollow noopener noreferrer">
    +                <span class="hidden-link-text">RSS</span>
    +                <svg class="my-footer-social-icon-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
    +                    <!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
    +                    <path class="my-footer-social-icon-path" d="M0 64C0 46.3 14.3 32 32 32c229.8 0 416 186.2 416 416c0 17.7-14.3 32-32 32s-32-14.3-32-32C384 253.6 226.4 96 32 96C14.3 96 0 81.7 0 64zM0 416a64 64 0 1 1 128 0A64 64 0 1 1 0 416zM32 160c159.1 0 288 128.9 288 288c0 17.7-14.3 32-32 32s-32-14.3-32-32c0-123.7-100.3-224-224-224c-17.7 0-32-14.3-32-32s14.3-32 32-32z"/>
    +                </svg>
    +            </a>
    +    
    +        </div>
    +    </div>
    +    </div>
    +    <div class="my-footer-copyright">
    +        <div class="my-footer-copyright-wrapper">
    +            <p class="my-footer-copyright-text">
    +                <a href="https://beian.miit.gov.cn/" rel="noopener external nofollow noreferrer"><img class="icp-icon" src="/image/footer/icp.ico"><span>津ICP备2022002156号-1</span></a>
    +                &nbsp;|&nbsp;<a href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=12011202000621" rel="noopener external nofollow noreferrer"><img class="icp-icon" src="/image/footer/beian_logo.png"><span>津公网安备 12011202000621号</span></a>
    +                &nbsp;|&nbsp;&nbsp;
    +                <span>违法与不良信息举报邮箱&nbsp;
    +                  j<span style="display:none">@</span><!-- >@ -->b@<span style="display:none">@</span>uu<!-- >@. -->an<span style="display:none">@</span>q.in
    +                </span>
    +            </p>
    +        </div>
    +    </div>
    +

    参考

    + + + + Wuanqin的博客 + Wuanqin · blog.uuanqin.top + +

    自定义页脚(新)

    /posts/c6143ad3/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    自定义页脚(新)

    \ No newline at end of file diff --git a/posts/ce1ec3fe/index.html b/posts/ce1ec3fe/index.html new file mode 100644 index 0000000..8b1deec --- /dev/null +++ b/posts/ce1ec3fe/index.html @@ -0,0 +1,786 @@ + 使用GitHub推送Hexo到服务器 | Bi's Blog

    使用GitHub推送Hexo到服务器

    发布于 2025/08/19 建站手札 总字数:626 阅读时长:2 分钟

    这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。

    +AI摘要 + QWEN

    1panel没有宝塔的webhook功能,之前一直使用在服务器上建立裸仓库,直接推送到服务器的方法。现在找到一种新的方法。

    +

    建立GitHub仓库

    +

    因为服务器部署在香港,可以直连GitHub,国内可以使用gitee或者GitHub镜像加速。

    +
      +
    1. +

      在本地安装git

      +
    2. +
    3. +

      打开git bash配置用户名和邮箱

      +
      git config --global user.name "yourname"
      +git config --global user.email "youremail"
      +

      可以使用 git config —list查看当前所有的配置

      +
    4. +
    5. +

      在github上创建自己的账号(在自己的电脑和服务器上)

      +
    6. +
    7. +

      创建SSH Key +a:打开Git Bash,输入pwd查看当前路径 +b.输入ssh-keygen -t rsa –C “youremail@example.com” +(输入完毕后程序同时要求输入一个密语字符串(passphrase),空表示没有密语。接着会让输入2次口令(password),空表示没有口令。3次回车即可完成当前步骤)

      +
      + 924058.webp +
      +
    8. +
    9. +

      在GitHub上传自己的公钥

      +
      946620.webp
      +
    10. +
    11. +

      新建一个私有仓库

      +
    12. +
    +

    上传文件到仓库

    +

    在自己hexo根目录下使用powershell,输入git init,然后git push到之前自己新建的仓库。

    +

    创建Action

    +

    创建Action

    +
    name: 自动部署
    +
    +on:
    +  push:
    +    branches:
    +      - main 
    +
    +  release:
    +    types:
    +      - published
    +
    +  workflow_dispatch:
    +
    +env:
    +  TZ: Asia/Shanghai
    +
    +jobs:
    +  deploy:
    +    runs-on: ubuntu-latest
    +    steps:
    +    - name: 检查分支
    +      uses: actions/checkout@v3
    +      with:
    +        ref: main
    +
    +    - name: 缓存项目 npm 包
    +      id: cache-node-modules
    +      uses: actions/cache@v3
    +      with:
    +        path: node_modules
    +        key: ${{ runner.os }}-nodeModules-${{ hashFiles('package-lock.json') }}-${{ hashFiles('package.json') }}
    +        restore-keys: |
    +          ${{ runner.os }}-nodeModules-
    +
    +    - name: 安装 Node
    +      uses: actions/setup-node@v3
    +      with:
    +        node-version: "20.x"
    +
    +    - name: 安装 Hexo
    +      run: |
    +        npm install hexo-cli --global
    +
    +    - name: 安装依赖
    +      if: steps.cache-node-modules.outputs.cache-hit != 'true'
    +      run: |
    +        npm install
    +
    +    - name: 清理文件树
    +      run: |
    +        npm run clean
    +
    +    - name: 生成静态文件并压缩
    +      run: |
    +        npm run build
    +
    +    - name: 部署
    +      run: |
    +        cd ./public
    +        git init
    +        git config user.name "${{ github.actor }}"
    +        git config user.email "${{ github.actor }}@users.noreply.github.com"
    +        git add .
    +        git commit -m "${{ github.event.head_commit.message }}··[$(date +"%Z %Y-%m-%d %A %H:%M:%S")]"
    +        git push --force --quiet "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" master:page
    +    - name: 服务器执行拉取命令
    +        uses: appleboy/ssh-action@v1
    +        with:
    +            host: ${{ secrets.SERVER_IP }}
    +            username: ${{ secrets.USERNAME }}
    +            key: ${{ secrets.KEY }}
    +            passphrase: ${{ secrets.PASSPHRASE }}
    +            port: ${{ secrets.PORT }}
    +            script: |
    +            cd /opt/1panel/apps/openresty/openresty/www/sites/blog.liushen.fun/index/
    +            git config --global --add safe.directory "$(pwd)"
    +            git fetch --all --depth=1
    +            git reset --hard origin/main
    +            git pull --depth=1
    +            echo "✅ 已拉取 page 分支最新内容"
    +

    环境变量

    +

    此次添加变量较多,有:SERVER_IPUSERNAMEPASSPHRASEKEYPORT五个变量,如下:

    +

    SERVER_IP:服务器IP +USERNAME:SSH链接用户名,一般为root +PASSPHRASE:密钥密码,用来提升强度用 +KEY:密钥(私钥) +PORT:SSH登录端口,一般为22

    +

    测试

    +

    运行一下这个Action无报错即可。

    使用GitHub推送Hexo到服务器

    /posts/ce1ec3fe/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    使用GitHub推送Hexo到服务器

    \ No newline at end of file diff --git a/posts/d2c8521/index.html b/posts/d2c8521/index.html new file mode 100644 index 0000000..94dd18e --- /dev/null +++ b/posts/d2c8521/index.html @@ -0,0 +1,718 @@ + 将博客仓库转移到gitea | Bi's Blog

    将博客仓库转移到gitea

    发布于 2026/02/07 技术 总字数:95 阅读时长:1 分钟

    这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。

    +AI摘要 + QWEN

    在上一篇文章中已经完成了gitea的安装 +那么博客源码迁移倒是没问题,直接git remote add origin就行,但是action文件就有些变更。 +这是我修改的action文件:

    +
    name: 自动部署
    +on:
    +  push:
    +    branches:
    +      - master
    +  release:
    +    types:
    +      - published
    +  workflow_dispatch:
    +env:
    +  TZ: Asia/Shanghai
    +jobs:
    +  deploy:
    +    runs-on: ubuntu-latest
    +    steps:
    +    - name: 检查分支
    +      uses: actions/checkout@v4
    +      with:
    +        ref: master
    +    - name: 缓存项目 npm 包
    +      id: cache-node-modules
    +      uses: actions/cache@v3
    +      with:
    +        path: node_modules
    +        key: ${{ runner.os }}-nodeModules-${{ hashFiles('package-lock.json') }}-${{ hashFiles('package.json') }}
    +        restore-keys: |
    +          ${{ runner.os }}-nodeModules-
    +    - name: 安装 Node
    +      uses: actions/setup-node@v4
    +      with:
    +        node-version: "22.x"
    +    - name: 安装 Hexo
    +      run: |
    +        npm install hexo-cli --global
    +    - name: 安装依赖
    +      if: steps.cache-node-modules.outputs.cache-hit != 'true'
    +      run: |
    +        npm install
    +    - name: 清理文件树
    +      run: |
    +        npm run clean
    +    - name: 生成静态文件并压缩
    +      run: |
    +        npm run build
    +    - name: 部署
    +      run: |
    +        cd ./public
    +        git init
    +        git config user.name "${{ gitea.actor }}"
    +        git config user.email "${{ gitea.actor }}@noreply.gitea.io"
    +        git add .
    +        git commit -m "${{ gitea.event.head_commit.message }}··[$(date +"%Z %Y-%m-%d %A %H:%M:%S")]"
    +        git push --force --quiet "https://${{ gitea.actor }}:${{ secrets.DEPLOY_TOKEN }}@git.biss.click/biss/blog.git" master:page
    +    - name: Deploy to Server
    +      run: |
    +        curl -k -X POST 
    +

    仅供参考吧,最后面是webhook,可以自己改改。

    将博客仓库转移到gitea

    /posts/d2c8521/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    gitea
    订阅 打赏

    Comments

    将博客仓库转移到gitea

    \ No newline at end of file diff --git a/posts/da17d00/index.html b/posts/da17d00/index.html new file mode 100644 index 0000000..b24a768 --- /dev/null +++ b/posts/da17d00/index.html @@ -0,0 +1,986 @@ + 在侧边栏中添加欢迎信息 | Bi's Blog

    在侧边栏中添加欢迎信息

    发布于 2025/08/11 建站手札 总字数:490 阅读时长:2 分钟

    这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。

    +AI摘要 + QWEN

    效果展示

    +
    效果展示
    +

    开始折腾

    +

    申请腾讯地图的api key

    + + + + 腾讯地图 + Tencent · lbs.qq.com + + +自行查看文档,要注意在填写信任域名时不填 https:// +

    添加js

    +

    在主题 /source/js/目录下添加 txmap.js

    +

    有一个更完善的js脚本

    + + + + txmap.js + github · github.com + + +
    //get请求
    +$.ajax({
    +    type: 'get',
    +    url: 'https://apis.map.qq.com/ws/location/v1/ip',
    +    data: {
    +        key: '你的key',
    +        output: 'jsonp',
    +        callback: '?',
    +    },
    +    dataType: 'jsonp',
    +    success: function (res) {
    +        window.ipLocation = res;
    +    }
    +})
    +function getDistance(e1, n1, e2, n2) {
    +    const R = 6371
    +    const { sin, cos, asin, PI, hypot } = Math
    +    let getPoint = (e, n) => {
    +        e *= PI / 180
    +        n *= PI / 180
    +        return { x: cos(n) * cos(e), y: cos(n) * sin(e), z: sin(n) }
    +    }
    +
    +    let a = getPoint(e1, n1)
    +    let b = getPoint(e2, n2)
    +    let c = hypot(a.x - b.x, a.y - b.y, a.z - b.z)
    +    let r = asin(c / 2) * 2 * R
    +    return Math.round(r);
    +}
    +
    +function showWelcome() {
    +
    +    let dist = getDistance(112.92358, 35.79807, ipLocation.result.location.lng, ipLocation.result.location.lat); //这里记得换成自己的经纬度
    +    let pos = ipLocation.result.ad_info.nation;
    +    let ip;
    +    let posdesc;
    +    //根据国家、省份、城市信息自定义欢迎语
    +    switch (ipLocation.result.ad_info.nation) {
    +        case "日本":
    +            posdesc = "よろしく,一起去看樱花吗";
    +            break;
    +        case "美国":
    +            posdesc = "Let us live in peace!";
    +            break;
    +        case "英国":
    +            posdesc = "想同你一起夜乘伦敦眼";
    +            break;
    +        case "俄罗斯":
    +            posdesc = "干了这瓶伏特加!";
    +            break;
    +        case "法国":
    +            posdesc = "C'est La Vie";
    +            break;
    +        case "德国":
    +            posdesc = "Die Zeit verging im Fluge.";
    +            break;
    +        case "澳大利亚":
    +            posdesc = "一起去大堡礁吧!";
    +            break;
    +        case "加拿大":
    +            posdesc = "拾起一片枫叶赠予你";
    +            break;
    +        case "中国":
    +            pos = ipLocation.result.ad_info.province + " " + ipLocation.result.ad_info.city + " " + ipLocation.result.ad_info.district;
    +            ip = ipLocation.result.ip;
    +            switch (ipLocation.result.ad_info.province) {
    +                case "北京市":
    +                    posdesc = "北——京——欢迎你~~~";
    +                    break;
    +                case "天津市":
    +                    posdesc = "讲段相声吧";
    +                    break;
    +                case "河北省":
    +                    posdesc = "山势巍巍成壁垒,天下雄关铁马金戈由此向,无限江山";
    +                    break;
    +                case "山西省":
    +                    switch (ipLocation.result.ad_info.city) {
    +                        case "太原市":
    +                            posdesc = "秋叶蓝不城";
    +                            break;
    +                        case "晋城市":
    +                            posdesc = "一方水土养一方人,晋城话说给晋城人...";
    +                            break;
    +                        default:
    +                            posdesc = "展开坐具长三尺,已占山河五百余";
    +                            break;
    +                    }
    +                    break;
    +                case "内蒙古自治区":
    +                    posdesc = "天苍苍,野茫茫,风吹草低见牛羊";
    +                    break;
    +                case "辽宁省":
    +                    posdesc = "我想吃烤鸡架!";
    +                    break;
    +                case "吉林省":
    +                    posdesc = "状元阁就是东北烧烤之王";
    +                    break;
    +                case "黑龙江省":
    +                    posdesc = "很喜欢哈尔滨大剧院";
    +                    break;
    +                case "上海市":
    +                    posdesc = "众所周知,中国只有两个城市";
    +                    break;
    +                case "江苏省":
    +                    switch (ipLocation.result.ad_info.city) {
    +                        case "南京市":
    +                            posdesc = "这是我挺想去的城市啦";
    +                            break;
    +                        case "苏州市":
    +                            posdesc = "上有天堂,下有苏杭";
    +                            break;
    +                        default:
    +                            posdesc = "散装是必须要散装的";
    +                            break;
    +                    }
    +                    break;
    +                case "浙江省":
    +                    posdesc = "东风渐绿西湖柳,雁已还人未南归";
    +                    break;
    +                case "河南省":
    +                    switch (ipLocation.result.ad_info.city) {
    +                        case "郑州市":
    +                            posdesc = "豫州之域,天地之中";
    +                            break;
    +                        case "南阳市":
    +                            posdesc = "臣本布衣,躬耕于南阳此南阳非彼南阳!";
    +                            break;
    +                        case "驻马店市":
    +                            posdesc = "峰峰有奇石,石石挟仙气嵖岈山的花很美哦!";
    +                            break;
    +                        case "开封市":
    +                            posdesc = "刚正不阿包青天";
    +                            break;
    +                        case "洛阳市":
    +                            posdesc = "洛阳牡丹甲天下";
    +                            break;
    +                        default:
    +                            posdesc = "可否带我品尝河南烩面啦?";
    +                            break;
    +                    }
    +                    break;
    +                case "安徽省":
    +                    posdesc = "蚌埠住了,芜湖起飞";
    +                    break;
    +                case "福建省":
    +                    posdesc = "井邑白云间,岩城远带山";
    +                    break;
    +                case "江西省":
    +                    posdesc = "落霞与孤鹜齐飞,秋水共长天一色";
    +                    break;
    +                case "山东省":
    +                    posdesc = "遥望齐州九点烟,一泓海水杯中泻";
    +                    break;
    +                case "湖北省":
    +                    switch (ipLocation.result.ad_info.city) {
    +                        case "黄冈市":
    +                            posdesc = "红安将军县!辈出将才!";
    +                            break;
    +                        default:
    +                            posdesc = "来碗热干面~";
    +                            break;
    +                    }
    +                    break;
    +                case "湖南省":
    +                    posdesc = "74751,长沙斯塔克";
    +                    break;
    +                case "广东省":
    +                    switch (ipLocation.result.ad_info.city) {
    +                        case "广州市":
    +                            posdesc = "看小蛮腰,喝早茶了嘛~";
    +                            break;
    +                        case "深圳市":
    +                            posdesc = "今天你逛商场了嘛~";
    +                            break;
    +                        case "阳江市":
    +                            posdesc = "阳春合水!博主家乡~ 欢迎来玩~";
    +                            break;
    +                        default:
    +                            posdesc = "来两斤福建人~";
    +                            break;
    +                    }
    +                    break;
    +                case "广西壮族自治区":
    +                    posdesc = "桂林山水甲天下";
    +                    break;
    +                case "海南省":
    +                    posdesc = "朝观日出逐白浪,夕看云起收霞光";
    +                    break;
    +                case "四川省":
    +                    posdesc = "康康川妹子";
    +                    break;
    +                case "贵州省":
    +                    posdesc = "茅台,学生,再塞200";
    +                    break;
    +                case "云南省":
    +                    posdesc = "玉龙飞舞云缠绕,万仞冰川直耸天";
    +                    break;
    +                case "西藏自治区":
    +                    posdesc = "躺在茫茫草原上,仰望蓝天";
    +                    break;
    +                case "陕西省":
    +                    posdesc = "来份臊子面加馍";
    +                    break;
    +                case "甘肃省":
    +                    posdesc = "羌笛何须怨杨柳,春风不度玉门关";
    +                    break;
    +                case "青海省":
    +                    posdesc = "牛肉干和老酸奶都好好吃";
    +                    break;
    +                case "宁夏回族自治区":
    +                    posdesc = "大漠孤烟直,长河落日圆";
    +                    break;
    +                case "新疆维吾尔自治区":
    +                    posdesc = "驼铃古道丝绸路,胡马犹闻唐汉风";
    +                    break;
    +                case "台湾省":
    +                    posdesc = "我在这头,大陆在那头";
    +                    break;
    +                case "香港特别行政区":
    +                    posdesc = "永定贼有残留地鬼嚎,迎击光非岁玉";
    +                    break;
    +                case "澳门特别行政区":
    +                    posdesc = "性感荷官,在线发牌";
    +                    break;
    +                default:
    +                    posdesc = "带我去你的城市逛逛吧!";
    +                    break;
    +            }
    +            break;
    +        default:
    +            posdesc = "带我去你的国家逛逛吧";
    +            break;
    +    }
    +
    +    //根据本地时间切换欢迎语
    +    let timeChange;
    +    let date = new Date();
    +    if (date.getHours() >= 5 && date.getHours() < 11) timeChange = "<span>🌤️ 早上好,一日之计在于晨</span>";
    +    else if (date.getHours() >= 11 && date.getHours() < 13) timeChange = "<span>☀️ 中午好,记得午休喔~</span>";
    +    else if (date.getHours() >= 13 && date.getHours() < 17) timeChange = "<span>🕞 下午好,饮茶先啦!</span>";
    +    else if (date.getHours() >= 17 && date.getHours() < 19) timeChange = "<span>🚶‍♂️ 即将下班,记得按时吃饭~</span>";
    +    else if (date.getHours() >= 19 && date.getHours() < 24) timeChange = "<span>🌙 晚上好,夜生活嗨起来!</span>";
    +    else timeChange = "夜深了,早点休息,少熬夜";
    +
    +// 新增ipv6显示为指定内容
    +    if (ip.includes(":")) {
    +        ip = "<br>好复杂,咱看不懂~(ipv6)";
    +    }
    +    try {
    +        //自定义文本和需要放的位置
    +        document.getElementById("welcome-info").innerHTML =
    +            `欢迎来自 <b><span style="color: var(--kouseki-ip-color);font-size: var(--kouseki-gl-size)">${pos}</span></b> 的小友💖<br>${posdesc}🍂<br>当前位置距博主约 <b><span style="color: var(--kouseki-ip-color)">${dist}</span></b> 公里!<br>您的IP地址为:<b><span class="ip-mask">${ip}</span></b><br>${timeChange} <br>`;
    +    } catch (err) {
    +         console.log("Pjax无法获取元素")
    +    }
    +}
    +window.onload = showWelcome;
    +// 如果使用了pjax在加上下面这行代码
    +document.addEventListener('pjax:complete', showWelcome);
    +

    添加css文件

    +

    在主题文件夹/source/css/在这里我添加了模糊IP地址。

    +
    #welcome-info {
    +    overflow: hidden;
    +    border-radius: 14px;
    +    --kouseki-welcome-color: #49B1F5;
    +    --kouseki-ip-color: #49B1F5;
    +    --kouseki-gl-size: 16px!important;
    +  }
    +/* 给 IP 地址的 span 再包一层,便于选择器定位 */
    +/* JS 原文已经生成 <b><span>IP</span></b>,这里我们给这个 span 加类名 ip-mask */
    +/* 如果你不方便改 HTML,可直接用属性选择器:#welcome-info b span:nth-child(1) */
    +#welcome-info b span.ip-mask {
    +    display: inline-block;
    +    filter: blur(6px);
    +    transition: filter .3s ease;
    +    cursor: pointer;
    +    user-select: none;   /* 防止复制到模糊文本 */
    +}
    +
    +/* 鼠标悬停或点击时(:active)立即清晰 */
    +#welcome-info b span.ip-mask:hover,
    +#welcome-info b span.ip-mask:active {
    +    filter: blur(0);
    +}
    +
    +/* 如果想做成「必须点一下才永久清晰」,把 :hover 去掉即可 */
    +

    引用

    +

    在主题配置文件中添加

    +
    inject: 
    +  bottom: 
    ++    - <script src="https://cdn.staticfile.org/jquery/3.6.3/jquery.min.js"></script> # jQuery
    ++    - <script async data-pjax src="/js/txmap.js"></script> # 腾讯位置API
    +

    在需要展示文本的容器上添加相应id(welcome-info)就可以了,例如我想添加在网站公告栏信息的下方,于是就在 [BlogRoot]\themes\butterfly\layout\includes\widget\card_announcement.pug的最后一行加上这个,缩进与上一行相同即可

    +
        .announcement_content!= theme.aside.card_announcement.content
    +    //- 添加欢迎访客的信息
    ++    #welcome-info
    +

    参考内容

    + + + + Ganzhe的博文 + Ganzhe · ganzhe2028.github.io + +

    在侧边栏中添加欢迎信息

    /posts/da17d00/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    网站
    订阅 打赏

    Comments

    在侧边栏中添加欢迎信息

    \ No newline at end of file diff --git a/posts/e8f95ead/index.html b/posts/e8f95ead/index.html new file mode 100644 index 0000000..23f6a0c --- /dev/null +++ b/posts/e8f95ead/index.html @@ -0,0 +1,811 @@ + opencv应用-基础操作 | Bi's Blog

    opencv应用-基础操作

    这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。

    +AI摘要 + QWEN

    这里用C++进行编程,发现菜鸟教程只有python的版本,那就记录一下。

    +

    图片读取与展示

    +
    // 读取图像
    +#include<opencv2/opencv.hpp>
    +#include<iostream>
    +
    +using namespace std;
    +using namespace cv;
    +
    +int main(){
    +    Mat src = imread("../img/1.png");
    +    imshow("input",src);
    +    waitKey(0);
    +    destroyAllWindows();
    +    return 0;
    +}
    +

    图像基本操作

    +

    读取像素

    +

    需要用到三维向量数组Vect3b,这里需要注意的是,Opencv是BGR而不是我们常用的RGB。

    +
    // 读取像素
    +#include<opencv2/opencv.hpp>
    +#include<iostream>
    +
    +using namespace std;
    +using namespace cv;
    +
    +int main()
    +{
    +    string image_path = "../img/1.png";
    +    Mat image = imread(image_path);
    +
    +    if (image.empty()) {
    +        cout << "错误:无法加载图像,请检查路径是否正确。" << endl;
    +        return -1;
    +    }
    +
    +    Vec3b pixel_value = image.at<Vec3b>(100, 150);
    +
    +    cout << "B: " << (int)pixel_value[0] << " "
    +         << "G: " << (int)pixel_value[1] << " "
    +         << "R: " << (int)pixel_value[2] << endl;
    +
    +}
    +

    修改像素

    +
    // 修改像素
    +#include<opencv2/opencv.hpp>
    +#include<iostream>
    +
    +using namespace std;
    +using namespace cv;
    +
    +int main()
    +{
    +    string image_path = "../img/1.png";
    +    Mat image = imread(image_path);
    +    Mat result = image.clone();
    +
    +    if (image.empty()) {
    +        cout << "错误:无法加载图像,请检查路径是否正确。" << endl;
    +        return -1;
    +    }
    +
    +    Rect roi_rect(0, 0, 100, 100);
    +    Mat roi = result(roi_rect);
    +    roi.setTo(Scalar(0, 255, 0));
    +    imshow("Original (Unchanged)", image);
    +    imshow("Modified Copy", result);
    +    waitKey(0);
    +
    +    return 0;
    +
    +}
    +
    #include <opencv2/opencv.hpp>
    +#include <iostream>
    +
    +int main() {
    +    // 读取图像
    +    cv::Mat img = cv::imread("../img/1.png");
    +    if (img.empty()) {
    +        std::cout << "无法读取图像" << std::endl;
    +        return -1;
    +    }
    +
    +    // 1. 缩放
    +    cv::Mat resized_img;
    +    cv::resize(img, resized_img, cv::Size(200, 200));
    +    cv::imshow("resized_img", resized_img);
    +
    +    // 2. 旋转
    +    cv::Mat rotated_img, M_rot;
    +    cv::Point2f center(img.cols / 2.0, img.rows / 2.0);
    +    M_rot = cv::getRotationMatrix2D(center, 45, 1.0);
    +    cv::warpAffine(img, rotated_img, M_rot, img.size());
    +    cv::imshow("rotated_img", rotated_img);
    +
    +    // 3. 平移
    +    cv::Mat translated_img;
    +    cv::Mat M_trans = (cv::Mat_<float>(2, 3) << 1, 0, 100, 0, 1, 50);
    +    cv::warpAffine(img, translated_img, M_trans, img.size());
    +    cv::imshow("translated_img", translated_img);
    +
    +    // 4. 翻转
    +    cv::Mat flipped_img;
    +    cv::flip(img, flipped_img, 1);
    +    cv::imshow("flipped", flipped_img);
    +
    +    // 显示结果
    +    cv::imshow("Original", img);
    +    cv::waitKey(0);
    +
    +    return 0;
    +}
    +
    // 图像通道分离与合并
    +#include<opencv2/opencv.hpp>
    +#include<iostream>
    +
    +using namespace std;
    +using namespace cv;
    +
    +int main()
    +{
    +    string image_path = "../img/1.png";
    +    Mat image = imread(image_path);
    +    Mat result = image.clone();
    +    // 定义向量数组接收通道
    +    vector<Mat> channels;
    +    // 拆分
    +    split(result,channels);
    +
    +    Mat b = channels[0];
    +    Mat g = channels[1];
    +    Mat r = channels[2];
    +
    +    imshow("Blue Channel (Grayscale)", channels[0]);
    +    imshow("Green Channel (Grayscale)", channels[1]);
    +    imshow("Red Channel (Grayscale)", channels[2]);
    +
    +    // merge
    +    Mat merged_img;
    +    vector<cv::Mat> channels_to_merge;
    +    channels_to_merge.push_back(b);
    +    channels_to_merge.push_back(g);
    +    channels_to_merge.push_back(r);
    +
    +    merge(channels_to_merge, merged_img);
    +    imshow("merged",merged_img);
    +
    +    waitKey(0);
    +
    +    return(0);
    +}

    opencv应用-基础操作

    /posts/e8f95ead/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    opencv
    订阅 打赏

    Comments

    opencv应用-基础操作

    \ No newline at end of file diff --git a/posts/e9a8e898/index.html b/posts/e9a8e898/index.html new file mode 100644 index 0000000..2597ae6 --- /dev/null +++ b/posts/e9a8e898/index.html @@ -0,0 +1,797 @@ + C# 基本语法 | Bi's Blog

    C# 基本语法

    这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。

    +AI摘要 + QWEN

    简介

    +

    C# 是一种面向对象的编程语言。在面向对象的程序设计方法中,程序由各种相互交互的对象组成。相同种类的对象通常具有相同的类型,或者说,是在相同的 class 中。

    +

    例如,以 Rectangle(矩形)对象为例。它具有 length 和 width 属性。根据设计,它可能需要接受这些属性值、计算面积和显示细节。

    +

    实例

    +
    using System;
    +namespace RectangleApplication
    +{
    +    class Rectangle
    +    {
    +        // 成员变量
    +        double length;
    +        double width;
    +        public void Acceptdetails()
    +        {
    +            length = 4.5;    
    +            width = 3.5;
    +        }
    +        public double GetArea()
    +        {
    +            return length * width;
    +        }
    +        public void Display()
    +        {
    +            Console.WriteLine("Length: {0}", length);
    +            Console.WriteLine("Width: {0}", width);
    +            Console.WriteLine("Area: {0}", GetArea());
    +        }
    +    }
    +   
    +    class ExecuteRectangle
    +    {
    +        static void Main(string[] args)
    +        {
    +            Rectangle r = new Rectangle();
    +            r.Acceptdetails();
    +            r.Display();
    +            Console.ReadLine();
    +        }
    +    }
    +}
    +

    尝试一下 » +当上面的代码被编译和执行时,它会产生下列结果:

    +
    Length: 4.5
    +Width: 3.5
    +Area: 15.75
    +

    using 关键字

    +

    在任何 C# 程序中的第一条语句都是:

    +

    using System; +using 关键字用于在程序中包含命名空间。一个程序可以包含多个 using 语句。

    +

    class 关键字

    +

    class 关键字用于声明一个类。

    +

    C# 中的注释

    +

    注释是用于解释代码。编译器会忽略注释的条目。在 C# 程序中,多行注释以 /* 开始,并以字符 */ 终止,如下所示:

    +
    /* 这个程序演示
    +C# 的注释
    +使用 */
    +

    单行注释是用 // 符号表示。例如:

    +
    // 这一行是注释 
    +

    成员变量

    +

    变量是类的属性或数据成员,用于存储数据。在上面的程序中,Rectangle 类有两个成员变量,名为 length 和 width。

    +

    成员函数 +函数是一系列执行指定任务的语句。类的成员函数是在类内声明的。我们举例的类 Rectangle 包含了三个成员函数: AcceptDetails、GetArea 和 Display。

    +

    实例化一个类 +在上面的程序中,类 ExecuteRectangle 是一个包含 Main() 方法和实例化 Rectangle 类的类。

    +

    标识符

    +

    标识符是用来识别类、变量、函数或任何其它用户定义的项目。在 C# 中,类的命名必须遵循如下基本规则:

    +
      +
    • 标识符必须以字母、下划线或 @ 开头,后面可以跟一系列的字母、数字( 0 - 9 )、下划线( _ )、@。
    • +
    • 标识符中的第一个字符不能是数字。
    • +
    • 标识符必须不包含任何嵌入的空格或符号,比如 ? - +! # % ^ & * ( ) [ ] { } . ; : ” ’ / \。
    • +
    • 标识符不能是 C# 关键字。除非它们有一个 @ 前缀。 例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字。
    • +
    • 标识符必须区分大小写。大写字母和小写字母被认为是不同的字母。
    • +
    • 不能与C#的类库名称相同。
    • +
    +

    C# 关键字

    +

    关键字是 C# 编译器预定义的保留字。这些关键字不能用作标识符,但是,如果您想使用这些关键字作为标识符,可以在关键字前面加上 @ 字符作为前缀。

    +

    在 C# 中,有些关键字在代码的上下文中有特殊的意义,如 get 和 set,这些被称为上下文关键字(contextual keywords)。

    +

    顶级语句(Top-Level Statements)

    +

    在 C# 9.0 版本中,引入了顶级语句(Top-Level Statements)的概念,这是一种新的编程范式,允许开发者在文件的顶层直接编写语句,而不需要将它们封装在方法或类中。

    +

    特点:

    +

    无需类或方法:顶级语句允许你直接在文件的顶层编写代码,无需定义类或方法。

    +

    文件作为入口点:包含顶级语句的文件被视为程序的入口点,类似于 C# 之前的 Main 方法。

    +

    自动 Main 方法:编译器会自动生成一个 Main 方法,并将顶级语句作为 Main 方法的主体。

    +

    支持局部函数:尽管不需要定义类,但顶级语句的文件中仍然可以定义局部函数。

    +

    更好的可读性:对于简单的脚本或工具,顶级语句提供了更好的可读性和简洁性。

    +

    适用于小型项目:顶级语句非常适合小型项目或脚本,可以快速编写和运行代码。

    +

    与现有代码兼容:顶级语句可以与现有的 C# 代码库一起使用,不会影响现有代码。

    +

    传统 C# 代码 - 在使用顶级语句之前,你必须像这样编写一个 C# 程序:

    +

    实例

    +
    using System;
    +
    +namespace MyApp
    +{
    +    class Program
    +    {
    +        static void Main(string[] args)
    +        {
    +            Console.WriteLine("Hello, World!");
    +        }
    +    }
    +}
    +

    使用顶级语句的 C# 代码 - 使用顶级语句,可以简化为:

    +

    实例

    +
    using System;
    +
    +Console.WriteLine("Hello, World!");
    +

    顶级语句支持所有常见的 C# 语法,包括声明变量、定义方法、处理异常等。

    +

    实例

    +
    using System;
    +using System.Linq;
    +
    +// 顶级语句中的变量声明
    +int number = 42;
    +string message = "The answer to life, the universe, and everything is";
    +
    +// 输出变量
    +Console.WriteLine($"{message} {number}.");
    +
    +// 定义和调用方法
    +int Add(int a, int b) => a + b;
    +Console.WriteLine($"Sum of 1 and 2 is {Add(1, 2)}.");
    +
    +// 使用 LINQ
    +var numbers = new[] { 1, 2, 3, 4, 5 };
    +var evens = numbers.Where(n => n % 2 == 0).ToArray();
    +Console.WriteLine("Even numbers: " + string.Join(", ", evens));
    +
    +// 异常处理
    +try
    +{
    +    int zero = 0;
    +    int result = number / zero;
    +}
    +catch (DivideByZeroException ex)
    +{
    +    Console.WriteLine("Error: " + ex.Message);
    +}

    C# 基本语法

    /posts/e9a8e898/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    C#
    订阅 打赏

    Comments

    C# 基本语法

    \ No newline at end of file diff --git a/posts/efe1e805/index.html b/posts/efe1e805/index.html new file mode 100644 index 0000000..bafe792 --- /dev/null +++ b/posts/efe1e805/index.html @@ -0,0 +1,668 @@ + 把普通的一天过成自己的节奏 | Bi's Blog

    把普通的一天过成自己的节奏

    有时候,我们会把“好好生活”想得太大,好像它必须绑定某个明确的目标:换一份更好的工作,去一个更远的地方,学会一项很厉害的技能,或者在某个时间节点之前完成别人眼里的成功。 可真正能把日子托住的,往往不是这些很大的东西,而是一些小到容易被忽略的

    +AI摘要 + QWEN

    有时候,我们会把“好好生活”想得太大,好像它必须绑定某个明确的目标:换一份更好的工作,去一个更远的地方,学会一项很厉害的技能,或者在某个时间节点之前完成别人眼里的成功。

    +

    可真正能把日子托住的,往往不是这些很大的东西,而是一些小到容易被忽略的瞬间。

    +

    比如早上醒来后,没有立刻被消息推着跑,而是先喝一口水,慢慢把自己放回这一天里。比如走在路上时,发现今天的风刚好不冷不热,树影落在地上,像一张没人署名的明信片。比如晚上回到房间,给桌面留出一点空白,也给心里留出一点空白。

    +

    生活当然不会因为这些细节就变得没有压力。该做的事情还在那里,该面对的问题也不会自动消失。但这些小事会提醒我们:我不是只在处理任务,我也正在经历自己的人生。

    +

    很多时候,人会被“必须更快一点”的感觉追着走。快一点回复,快一点完成,快一点变好。久而久之,我们甚至忘了问自己:我到底想把时间用成什么样子?

    +

    也许答案并不复杂。

    +

    可以是认真吃一顿饭,不一边刷手机一边把味道错过。可以是把拖了很久的小事做完,让它别再占用心里的后台。可以是读几页书,听一首歌,散一会儿步,或者只是安静地发一会儿呆。

    +

    这些事情看起来不够“高效”,却很像给生活校准方向。它们让人重新感觉到,日子不是一条只能向前赶的进度条,也可以是一段可以被感受、被整理、被轻轻收藏的时间。

    +

    普通的一天并不会自动变得特别。但如果我们愿意在里面放进一点自己的节奏,它就会慢慢变得有辨识度。

    +

    不是为了把每一天都过得闪闪发光,而是为了在平凡里仍然知道:这是我的一天,我在里面认真地活过。

    把普通的一天过成自己的节奏

    /posts/efe1e805/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    随笔生活
    订阅 打赏

    Comments

    把普通的一天过成自己的节奏

    \ No newline at end of file diff --git a/posts/f0a1a1f4/index.html b/posts/f0a1a1f4/index.html new file mode 100644 index 0000000..8585592 --- /dev/null +++ b/posts/f0a1a1f4/index.html @@ -0,0 +1,689 @@ + 英语文章分享-去奋力生活吧,就好像死亡近在咫尺 | Bi's Blog

    英语文章分享-去奋力生活吧,就好像死亡近在咫尺

    发布于 2025/08/12 学习 总字数:3.4k 阅读时长:9 分钟

    这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。

    +AI摘要 + QWEN

    As a hospice doctor, I often have the privilege of being at the bedside of dying patients.Over the years, I’ve had countless conversations about lives well lived, and more often, about regrets.Many of my patients express a strikingly similar sentiment: +I really regret that I never had the energy or time to… +The rest of the sentence varies.For one, it might be writing a book.For another, traveling to a far-off land.For someone else, maybe it’s training for a favorite sport.The specifics change, but the underlying theme is always the same: they missed out on what I call little p purpose — the process-oriented kind of purpose that comes from doing things that light us up from within. +The heartbreaking truth about these deathbed epiphanies is that the dying no longer have the agency to make things right.They’ve run out of time, or strength, or both.What they once could have done, they no longer can. +A large part of my work, and of the books I write, is trying to convince the living to adopt this mindset before it’s too late.To offer the lens of the dying as a kind of instruction manual for how to live now. +We must live as if death is just a breath away. +And yet, again and again, I hear the same explanations for why people don’t prioritize purpose in their daily lives. +I Don’t Have Enough Energy +This one comes up often, especially among young people, busy with careers and raising children.They feel completely depleted by the demands of work and family.The idea of mustering up extra energy to pursue something just for them seems laughable. +But research tells us something interesting.Studies on motivation and physical activity have shown that people with a stronger sense of purpose are actually more likely to feel energized.They experience fewer barriers to action and develop more intrinsic motivation to engage with life. +In other words, energy isn’t necessarily a finite pie.It can grow, especially when we’re engaging with activities that matter to us. +And “purpose” doesn’t have to mean starting a nonprofit or launching a million-dollar business.Little p purpose is smaller.Simpler.It could be reading a good book, taking a walk by the lake, or tinkering with a hobby you loved as a kid.It’s not about the size of the act, it’s about the spark. +I Don’t Have Enough Time +This one may feel especially true.We’re busier than ever, no doubt bombarded by the demands of work, family, texts, emails, and social media.It’s one of the most common objections I hear. +But data paints a different picture. +The Bureau of Labor Statistics conducts the American Time Use Survey every year, collecting insights from thousands of participants.The most recent findings?Americans spend an average of five hours a day on leisure and sports activities.That’s across all socioeconomic backgrounds.Rich or poor, employed or not — most people have more discretionary time than they realize. +So, if we do have the time, the question becomes: what’s really stopping us? +Purpose Demands Courage +In my years of working with the dying, I’ve learned something unexpected.The true barrier to purpose isn’t energy, money, or time. +It’s courage. +Not bravery in the way we typically define it.I’ve had this conversation with war heroes and daredevil athletes.It’s not about fearlessness.It’s not about skill either.I’ve cared for world-class artisans and bestselling authors.It’s not about knowledge — most of them knew what mattered to them. +What they lacked was the courage to choose those things.(他们缺乏的是选择那些的勇气) +It’s easier to focus on building wealth, pursuing a career, or raising kids.Those are important, yes, but they’re also the low-hanging fruit.They come with clear milestones and societal validation. +Turning toward what lights you up is a much scarier proposition. +What if I’m not good enough? +What if I fail? +What if it doesn’t turn out how I hoped? +What if people laugh at me? +The list of reasons not to pursue your purpose is long. +But there’s one reason that matters more than all the rest: +You might regret it on your deathbed if you don’t.

    英语文章分享-去奋力生活吧,就好像死亡近在咫尺

    /posts/f0a1a1f4/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    英语
    订阅 打赏

    Comments

    英语文章分享-去奋力生活吧,就好像死亡近在咫尺

    \ No newline at end of file diff --git a/posts/f287c563/index.html b/posts/f287c563/index.html new file mode 100644 index 0000000..a06487f --- /dev/null +++ b/posts/f287c563/index.html @@ -0,0 +1,1333 @@ + 添加typesense搜索 | Bi's Blog

    添加typesense搜索

    这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。

    +AI摘要 + QWEN

    最近在构建班级博客,用ghost cms,在构建搜索时发现了typesense,所以把他移植到这个博客上。

    +

    安装typesense

    +

    直接用docker-compose:

    +
    
    +services:
    +  typesense:
    +    image: typesense/typesense:30.1
    +    restart: always
    +    ports:
    +      - "8108:8108"
    +    volumes:
    +      - ./typesense-data:/data
    +    command: '--data-dir /data --api-key=填写key --enable-cors'
    +
    +

    然后就是反向代理之类的,不过多写了。

    +

    添加数据集

    +
    # 先安装库
    +npm install hexo-generator-search
    +npm install typesense xml2js
    +

    然后在config.yml配置(就是把文章生成json):

    +
    search:
    +  path: search.json
    +  field: post
    +  content: true
    +

    创建一个数据同步脚本sync_typesense.js:

    +
    const Typesense = require('typesense');
    +const fs = require('fs');
    +const xml2js = require('xml2js');
    +
    +// --- 配置区域 ---
    +const CONFIG = {
    +  apiKey: '你的Admin-API-Key', // 必须是 Admin Key
    +  host: '你的Typesense主机地址', 
    +  port: 443,
    +  protocol: 'https',
    +  collectionName: 'blogs'
    +};
    +
    +const client = new Typesense.Client({
    +  'nodes': [{ 'host': CONFIG.host, 'port': CONFIG.port, 'protocol': CONFIG.protocol }],
    +  'apiKey': CONFIG.apiKey,
    +  'connectionTimeoutSeconds': 5
    +});
    +
    +async function sync() {
    +  try {
    +    // 1. 读取并解析 XML
    +    const xml = fs.readFileSync('./public/search.xml', 'utf8');
    +    const parser = new xml2js.Parser({ explicitArray: false });
    +    const result = await parser.parseStringPromise(xml);
    +    
    +    // 提取文章列表 (处理单篇文章和多篇文章的情况)
    +    let entries = result.search.entry;
    +    if (!Array.isArray(entries)) entries = [entries];
    +
    +    // 格式化数据以适配 Typesense
    +    const documents = entries.map(post => ({
    +      title: post.title,
    +      url: post.url,
    +      content: post.content,
    +      categories: post.categories ? (Array.isArray(post.categories.category) ? post.categories.category : [post.categories.category]) : [],
    +      tags: post.tags ? (Array.isArray(post.tags.tag) ? post.tags.tag : [post.tags.tag]) : [],
    +    }));
    +
    +    // 2. 检查或创建 Collection (Schema)
    +    try {
    +      await client.collections(CONFIG.collectionName).retrieve();
    +    } catch (err) {
    +      const schema = {
    +        name: CONFIG.collectionName,
    +        fields: [
    +          { name: 'title', type: 'string', locale: 'zh'},
    +          { name: 'content', type: 'string', locale: 'zh'},
    +          { name: 'url', type: 'string' },
    +          { name: 'categories', type: 'string[]', facet: true },
    +          { name: 'tags', type: 'string[]', facet: true }
    +        ]
    +      };
    +      await client.collections().create(schema);
    +      console.log('Collection created!');
    +    }
    +
    +    // 3. 导入数据 (使用 upsert 模式:存在则更新,不存在则创建)
    +    console.log(`Syncing ${documents.length} posts to Typesense...`);
    +    await client.collections(CONFIG.collectionName).documents().import(documents, { action: 'upsert' });
    +    console.log('Sync complete!');
    +
    +  } catch (error) {
    +    console.error('Sync failed:', error);
    +  }
    +}
    +
    +sync();
    +

    然后运行node sync_typesense.js

    +

    创建只读key

    +

    把下面代码存储成js,node运行就行。

    +
    const http = require('https');
    +
    +const data = JSON.stringify({
    +  "description": "Public search only key",
    +  "actions": ["documents:search"],
    +  "collections": ["blogs"]
    +});
    +
    +const options = {
    +  hostname: '', // 不要带 https://
    +  port: 443,
    +  path: '/keys',
    +  method: 'POST',
    +  headers: {
    +    'X-TYPESENSE-API-KEY': '你的admin key',
    +    'Content-Type': 'application/json',
    +    'Content-Length': data.length
    +  }
    +};
    +
    +const req = http.request(options, res => {
    +  res.on('data', d => { process.stdout.write(d); });
    +});
    +
    +req.on('error', error => { console.error(error); });
    +req.write(data);
    +req.end();
    +

    博客添加搜索

    +

    config.yamlinject bottom添加:

    +
        - <script src="https://cdn.jsdmirror.com/npm/instantsearch.js@4.56.0"></script>
    +    - <script src="https://cdn.jsdmirror.com/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script>
    +

    为了方便,我直接修改了\themes\butterfly\source\js\search\local_search.js

    +
    (function () {
    +  'use strict';
    +
    +  // ============================================================================
    +  // 配置区域 - 请根据实际情况修改
    +  // ============================================================================
    +  const CONFIG = {
    +    apiKey: "", // ⚠️ 建议使用 Search-Only API Key
    +    server: {
    +      host: "host",
    +      port: "443",
    +      protocol: "https"
    +    },
    +    indexName: "blogs",
    +    searchParams: {
    +      query_by: "title,content",
    +      highlight_full_fields: "title,content",
    +      per_page: 8,
    +      num_typos: 1,
    +      typo_tokens_threshold: 1,
    +      prefix: true
    +    },
    +    ui: {
    +      maxRetries: 10,
    +      retryDelay: 100,
    +      animationDuration: 300
    +    }
    +  };
    +
    +  // ============================================================================
    +  // 状态管理
    +  // ============================================================================
    +  let searchInstance = null;
    +  let isInitialized = false;
    +  let isSearchOpen = false;
    +  let initRetryCount = 0;
    +  const MAX_INIT_RETRIES = 30; // 最多重试30次 (3秒)
    +
    +  // ============================================================================
    +  // 错误提示函数
    +  // ============================================================================
    +  function showErrorMessage() {
    +    const hitsContainer = document.getElementById('hits');
    +    if (!hitsContainer) return;
    +    
    +    hitsContainer.innerHTML = 
    +      '<div class="ts-empty">' +
    +        '<div style="color: #f44336;"><i class="fas fa-exclamation-triangle" style="font-size: 3rem;"></i></div>' +
    +        '<div style="font-size: 1.1rem; font-weight: bold; margin: 15px 0;">搜索服务加载失败</div>' +
    +        '<div style="font-size: 0.9rem; color: #666; line-height: 1.8;">' +
    +          '<p>依赖库未能正确加载,请检查以下配置:</p>' +
    +          '<ol style="text-align: left; max-width: 500px; margin: 15px auto;">' +
    +            '<li>确认已在 <code>_config.butterfly.yml</code> 中正确引入依赖</li>' +
    +            '<li>检查 JS 文件加载顺序(先 instantsearch.js,再 adapter)</li>' +
    +            '<li>尝试更换 CDN 或使用本地文件</li>' +
    +            '<li>打开浏览器控制台查看详细错误信息</li>' +
    +          '</ol>' +
    +        '</div>' +
    +        '<div style="margin-top: 20px;">' +
    +          '<button onclick="location.reload()" style="padding: 10px 20px; background: #49b1f5; color: white; border: none; border-radius: 5px; cursor: pointer;">重新加载页面</button>' +
    +        '</div>' +
    +      '</div>';
    +  }
    +
    +  // ============================================================================
    +  // 1. 动态插入 HTML 结构
    +  // ============================================================================
    +  const searchHTML = `
    +    <div id="typesense-search-mask" class="ts-mask" style="display:none;">
    +      <div id="typesense-search-container" class="ts-container">
    +        <div class="ts-header">
    +          <span class="ts-title">
    +            <i class="fas fa-search"></i> 本站搜索
    +          </span>
    +          <span id="close-typesense" class="ts-close" aria-label="关闭搜索">&times;</span>
    +        </div>
    +        <div id="searchbox"></div>
    +        <div id="stats" class="ts-stats"></div>
    +        <div id="hits" class="ts-hits"></div>
    +        <div id="pagination" class="ts-pagination"></div>
    +        <div class="ts-footer">
    +          <small>Search powered by <strong>Typesense</strong></small>
    +        </div>
    +      </div>
    +    </div>
    +
    +    <style>
    +      .ts-mask {
    +        position: fixed;
    +        top: 0;
    +        left: 0;
    +        width: 100%;
    +        height: 100%;
    +        background: rgba(0, 0, 0, 0.7);
    +        z-index: 10000;
    +        backdrop-filter: blur(5px);
    +        -webkit-backdrop-filter: blur(5px);
    +        opacity: 0;
    +        transition: opacity 300ms ease;
    +      }
    +      .ts-mask.active { opacity: 1; }
    +      .ts-container {
    +        margin: 5% auto;
    +        width: 90%;
    +        max-width: 650px;
    +        background: var(--search-bg, var(--card-bg, #fff));
    +        padding: 25px;
    +        border-radius: 12px;
    +        box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
    +        position: relative;
    +        z-index: 10001;
    +        transform: translateY(-50px);
    +        opacity: 0;
    +        transition: all 300ms ease;
    +      }
    +      .ts-mask.active .ts-container {
    +        transform: translateY(0);
    +        opacity: 1;
    +      }
    +      .ts-header {
    +        display: flex;
    +        justify-content: space-between;
    +        align-items: center;
    +        margin-bottom: 20px;
    +        border-bottom: 2px solid var(--text-highlight-color, #49b1f5);
    +        padding-bottom: 10px;
    +      }
    +      .ts-title {
    +        font-size: 1.2rem;
    +        font-weight: bold;
    +        color: var(--text-highlight-color, #49b1f5);
    +      }
    +      .ts-close {
    +        cursor: pointer;
    +        font-size: 28px;
    +        color: var(--font-color, #333);
    +        line-height: 1;
    +        transition: color 0.2s, transform 0.2s;
    +      }
    +      .ts-close:hover {
    +        color: var(--text-highlight-color, #49b1f5);
    +        transform: scale(1.1);
    +      }
    +      .ais-SearchBox-input {
    +        position: relative;
    +        z-index: 10002;
    +        cursor: text;
    +        padding: 12px 40px 12px 15px !important;
    +        border-radius: 8px !important;
    +        border: 2px solid #eee !important;
    +        width: 100%;
    +        outline: none;
    +        transition: border-color 0.3s, box-shadow 0.3s;
    +        background: var(--card-bg, #fff);
    +        color: var(--font-color, #333);
    +        font-size: 1rem;
    +      }
    +      .ais-SearchBox-input:focus {
    +        border-color: var(--text-highlight-color, #49b1f5) !important;
    +        box-shadow: 0 0 0 3px rgba(73, 177, 245, 0.1);
    +      }
    +      .ts-stats {
    +        margin: 10px 0;
    +        font-size: 0.85rem;
    +        color: var(--font-color, #666);
    +        opacity: 0.8;
    +      }
    +      .ts-hits {
    +        max-height: 55vh;
    +        overflow-y: auto;
    +        margin-top: 15px;
    +        padding-right: 5px;
    +      }
    +      .ts-hits::-webkit-scrollbar { width: 6px; }
    +      .ts-hits::-webkit-scrollbar-track {
    +        background: var(--card-bg, #f1f1f1);
    +        border-radius: 10px;
    +      }
    +      .ts-hits::-webkit-scrollbar-thumb {
    +        background: var(--text-highlight-color, #49b1f5);
    +        border-radius: 10px;
    +      }
    +      .ts-empty {
    +        text-align: center;
    +        padding: 40px 20px;
    +        color: var(--font-color, #999);
    +      }
    +      .ts-empty i {
    +        font-size: 3rem;
    +        margin-bottom: 15px;
    +        opacity: 0.3;
    +      }
    +      .ts-empty code {
    +        background: #f5f5f5;
    +        padding: 2px 6px;
    +        border-radius: 3px;
    +        font-family: monospace;
    +        color: #e91e63;
    +      }
    +      .ts-empty ol {
    +        padding-left: 20px;
    +      }
    +      .ts-empty li {
    +        margin: 8px 0;
    +      }
    +      .ts-result-item {
    +        border-radius: 8px;
    +        transition: all 0.2s ease;
    +        margin-bottom: 10px;
    +        padding: 15px;
    +        border: 1px solid transparent;
    +        text-decoration: none;
    +        display: block;
    +        background: var(--card-bg, #fff);
    +      }
    +      .ts-result-item:hover {
    +        background: var(--text-bg-hover, rgba(73, 177, 245, 0.05));
    +        border-color: var(--text-highlight-color, #49b1f5);
    +        transform: translateX(5px);
    +        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    +      }
    +      .ts-result-title {
    +        font-weight: bold;
    +        color: var(--text-highlight-color, #49b1f5);
    +        font-size: 1.1rem;
    +        margin-bottom: 8px;
    +        display: block;
    +        line-height: 1.4;
    +      }
    +      .ts-result-content {
    +        font-size: 0.9rem;
    +        color: var(--font-color, #666);
    +        line-height: 1.6;
    +        opacity: 0.85;
    +        display: -webkit-box;
    +        -webkit-line-clamp: 2;
    +        -webkit-box-orient: vertical;
    +        overflow: hidden;
    +      }
    +      .ts-result-item mark {
    +        background: #ffeb3b;
    +        color: #000;
    +        padding: 2px 4px;
    +        border-radius: 3px;
    +        font-weight: 500;
    +      }
    +      .ts-pagination {
    +        margin-top: 20px;
    +        display: flex;
    +        justify-content: center;
    +        gap: 5px;
    +      }
    +      .ais-Pagination-list {
    +        display: flex;
    +        list-style: none;
    +        padding: 0;
    +        margin: 0;
    +        gap: 5px;
    +      }
    +      .ais-Pagination-link {
    +        display: block;
    +        padding: 8px 12px;
    +        border: 1px solid #ddd;
    +        border-radius: 5px;
    +        color: var(--font-color, #333);
    +        text-decoration: none;
    +        transition: all 0.2s;
    +        background: var(--card-bg, #fff);
    +      }
    +      .ais-Pagination-link:hover {
    +        background: var(--text-highlight-color, #49b1f5);
    +        color: #fff;
    +      }
    +      .ais-Pagination-item--selected .ais-Pagination-link {
    +        background: var(--text-highlight-color, #49b1f5);
    +        color: #fff;
    +      }
    +      .ts-footer {
    +        text-align: right;
    +        margin-top: 15px;
    +        border-top: 1px solid var(--border-color, #eee);
    +        padding-top: 10px;
    +      }
    +      @media (max-width: 768px) {
    +        .ts-container {
    +          margin: 10px;
    +          width: calc(100% - 20px);
    +          padding: 20px 15px;
    +        }
    +        .ts-hits { max-height: 50vh; }
    +      }
    +      [data-theme="dark"] .ts-mask,
    +      .dark-mode .ts-mask {
    +        background: rgba(0, 0, 0, 0.85);
    +      }
    +    </style>
    +  `;
    +
    +  document.body.insertAdjacentHTML('beforeend', searchHTML);
    +
    +  const mask = document.getElementById('typesense-search-mask');
    +  const closeBtn = document.getElementById('close-typesense');
    +  const container = document.getElementById('typesense-search-container');
    +
    +  // ============================================================================
    +  // 搜索控制
    +  // ============================================================================
    +  function openSearch() {
    +    if (isSearchOpen) return;
    +    isSearchOpen = true;
    +    mask.style.display = 'block';
    +    void mask.offsetWidth;
    +    mask.classList.add('active');
    +    document.body.style.overflow = 'hidden';
    +
    +    if (!isInitialized) {
    +      initTypesense();
    +    }
    +    focusSearchInput();
    +  }
    +
    +  function closeSearch() {
    +    if (!isSearchOpen) return;
    +    isSearchOpen = false;
    +    mask.classList.remove('active');
    +    setTimeout(function() {
    +      mask.style.display = 'none';
    +      document.body.style.overflow = '';
    +    }, CONFIG.ui.animationDuration);
    +  }
    +
    +  function focusSearchInput(retryCount) {
    +    retryCount = retryCount || 0;
    +    const input = document.querySelector('.ais-SearchBox-input');
    +    if (input) {
    +      input.focus();
    +      input.select();
    +    } else if (retryCount < CONFIG.ui.maxRetries) {
    +      setTimeout(function() {
    +        focusSearchInput(retryCount + 1);
    +      }, CONFIG.ui.retryDelay);
    +    }
    +  }
    +
    +  // ============================================================================
    +  // 事件监听
    +  // ============================================================================
    +  document.addEventListener('click', function(e) {
    +    if (e.target.closest('.search-typesense-trigger')) {
    +      e.preventDefault();
    +      openSearch();
    +    }
    +  });
    +
    +  closeBtn.addEventListener('click', closeSearch);
    +  mask.addEventListener('click', function(e) {
    +    if (e.target === mask) closeSearch();
    +  });
    +  container.addEventListener('click', function(e) {
    +    e.stopPropagation();
    +  });
    +  window.addEventListener('keydown', function(e) {
    +    if (e.key === 'Escape' && isSearchOpen) {
    +      closeSearch();
    +    }
    +    if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
    +      e.preventDefault();
    +      openSearch();
    +    }
    +  });
    +
    +  // ============================================================================
    +  // Typesense 初始化(带重试限制)
    +  // ============================================================================
    +  function initTypesense() {
    +    if (isInitialized || searchInstance) {
    +      console.warn('Typesense 搜索已初始化');
    +      return;
    +    }
    +
    +    var instantsearchLoaded = typeof instantsearch !== 'undefined';
    +    var adapterLoaded = typeof TypesenseInstantSearchAdapter !== 'undefined' || 
    +                        typeof window.TypesenseInstantSearchAdapter !== 'undefined';
    +    
    +    console.log('📦 依赖库检查 (' + (initRetryCount + 1) + '/' + MAX_INIT_RETRIES + '):');
    +    console.log('  instantsearch.js:', instantsearchLoaded ? '✅ 已加载' : '❌ 未加载');
    +    console.log('  TypesenseAdapter:', adapterLoaded ? '✅ 已加载' : '❌ 未加载');
    +    
    +    if (!instantsearchLoaded || !adapterLoaded) {
    +      initRetryCount++;
    +      
    +      if (initRetryCount >= MAX_INIT_RETRIES) {
    +        console.error('');
    +        console.error('❌ Typesense 依赖库加载失败!');
    +        console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    +        console.error('');
    +        console.error('🔧 请检查 _config.butterfly.yml 配置:');
    +        console.error('');
    +        console.error('inject:');
    +        console.error('  bottom:  # ⚠️ 使用 bottom 而不是 head');
    +        console.error('    - <script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.56.0"></script>');
    +        console.error('    - <script src="https://cdn.jsdelivr.net/npm/typesense-instantsearch-adapter@2.7.0/dist/typesense-instantsearch-adapter.min.js"></script>');
    +        console.error('    - <script src="/js/typesense-search-fixed.js"></script>');
    +        console.error('');
    +        console.error('💡 或在控制台手动检查:');
    +        console.error('   typeof instantsearch');
    +        console.error('   typeof TypesenseInstantSearchAdapter');
    +        console.error('');
    +        console.error('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    +        
    +        showErrorMessage();
    +        return;
    +      }
    +      
    +      console.warn('⏳ 100ms 后重试...');
    +      setTimeout(initTypesense, 100);
    +      return;
    +    }
    +    
    +    initRetryCount = 0;
    +    console.log('🚀 开始初始化 Typesense...');
    +
    +    try {
    +      const typesenseAdapter = new TypesenseInstantSearchAdapter({
    +        server: {
    +          apiKey: CONFIG.apiKey,
    +          nodes: [{
    +            host: CONFIG.server.host,
    +            port: CONFIG.server.port,
    +            protocol: CONFIG.server.protocol
    +          }],
    +          cacheSearchResultsForSeconds: 120
    +        },
    +        additionalSearchParameters: CONFIG.searchParams
    +      });
    +
    +      searchInstance = instantsearch({
    +        searchClient: typesenseAdapter.searchClient,
    +        indexName: CONFIG.indexName,
    +        routing: false
    +      });
    +
    +      searchInstance.addWidgets([
    +        instantsearch.widgets.searchBox({
    +          container: '#searchbox',
    +          placeholder: '输入关键词寻找故事...',
    +          autofocus: true,
    +          showReset: true,
    +          showSubmit: false,
    +          showLoadingIndicator: true
    +        }),
    +        instantsearch.widgets.stats({
    +          container: '#stats',
    +          templates: {
    +            text: function(data) {
    +              if (!data.query) return '';
    +              return '找到 <strong>' + data.nbHits + '</strong> 条结果 (' + data.processingTimeMS + 'ms)';
    +            }
    +          }
    +        }),
    +        instantsearch.widgets.hits({
    +          container: '#hits',
    +          templates: {
    +            empty: function(results) {
    +              return '<div class="ts-empty">' +
    +                '<div><i class="fas fa-search"></i></div>' +
    +                '<div>找不到与 "<strong>' + results.query + '</strong>" 相关的内容</div>' +
    +                '<div style="margin-top: 10px;">试试其他关键词吧 (´·ω·`)</div>' +
    +                '</div>';
    +            },
    +            item: function(hit) {
    +              // 使用 _highlightResult 获取高亮文本
    +              var titleHighlight = hit._highlightResult && hit._highlightResult.title 
    +                ? hit._highlightResult.title.value 
    +                : (hit.title || '');
    +              
    +              var contentHighlight = hit._highlightResult && hit._highlightResult.content 
    +                ? hit._highlightResult.content.value 
    +                : (hit.content || '');
    +              
    +              // 截取内容长度
    +              if (contentHighlight.length > 200) {
    +                contentHighlight = contentHighlight.substring(0, 200) + '...';
    +              }
    +              
    +              return '<a href="' + hit.url + '" class="ts-result-item">' +
    +                '<div class="ts-result-title">' + titleHighlight + '</div>' +
    +                '<div class="ts-result-content">' + contentHighlight + '</div>' +
    +                '</a>';
    +            }
    +          }
    +        }),
    +        instantsearch.widgets.pagination({
    +          container: '#pagination',
    +          padding: 2,
    +          showFirst: false,
    +          showLast: false
    +        })
    +      ]);
    +
    +      searchInstance.start();
    +      isInitialized = true;
    +      
    +      console.log('✅ Typesense 初始化成功!');
    +
    +      searchInstance.on('render', function() {
    +        if (isSearchOpen) {
    +          const input = document.querySelector('.ais-SearchBox-input');
    +          if (input && document.activeElement !== input) {
    +            input.focus();
    +          }
    +        }
    +      });
    +
    +    } catch (error) {
    +      console.error('❌ 初始化失败:', error);
    +      showErrorMessage();
    +    }
    +  }
    +
    +  // ============================================================================
    +  // 初始化
    +  // ============================================================================
    +  function init() {
    +    console.log('🔍 Typesense 搜索已准备就绪');
    +    console.log('💡 快捷键: Ctrl/Cmd + K');
    +  }
    +
    +  if (document.readyState === 'loading') {
    +    document.addEventListener('DOMContentLoaded', init);
    +  } else {
    +    init();
    +  }
    +
    +  // ============================================================================
    +  // 全局接口
    +  // ============================================================================
    +  window.TypesenseSearch = {
    +    open: openSearch,
    +    close: closeSearch,
    +    isOpen: function() { return isSearchOpen; },
    +    getInstance: function() { return searchInstance; }
    +  };
    +
    +})();
    +

    注意

    +

    indexName: “blogs” 和 collectionName: ‘posts’ 要一致!!!

    添加typesense搜索

    /posts/f287c563/
    作者
    biss
    发布于
    许可协议
    CC BY-NC-SA 4.0
    订阅 打赏

    Comments

    添加typesense搜索

    \ No newline at end of file diff --git a/privacy/index.html b/privacy/index.html new file mode 100644 index 0000000..f701ee4 --- /dev/null +++ b/privacy/index.html @@ -0,0 +1,280 @@ + 隐私政策 | Bi's Blog

    Bi's Blog

    隐私政策

    说明访问本站时可能涉及的数据收集与使用方式。

    +本站重视访问者的隐私。为了提供基础访问、评论、防滥用和体验优化能力,本站及其第三方服务可能会处理少量访问数据。 +

    可能收集的信息

    • 访问日志:IP 地址、浏览器 User-Agent、访问时间、请求路径等。
    • 设备与浏览信息:页面加载情况、访问来源、基础性能数据等。
    • 评论信息:你主动填写的昵称、邮箱、站点地址和评论内容。
    • 本地存储:主题偏好、评论状态等保存在浏览器中的数据。

    使用目的

    • 保障网站安全,识别异常请求和垃圾评论。
    • 优化页面性能、文章分类和搜索体验。
    • 在有回复时通过评论系统通知你。
    • 展示必要的评论头像、昵称和站点链接。

    第三方服务

    +本站可能使用 CDN、评论、头像、搜索或统计分析服务。第三方服务会根据其自身政策处理数据,建议你阅读对应服务的隐私说明。 +

    Cookie 与本地存储

    +关于 Cookie 和 LocalStorage 的更多说明,请查看 Cookies 页面。你可以在浏览器中清除这些数据。 +

    联系

    如果你希望删除评论或询问隐私相关问题,可以发送邮件至 bishsh2006@gmail.com

    \ No newline at end of file diff --git a/rss.xml b/rss.xml new file mode 100644 index 0000000..f83806b --- /dev/null +++ b/rss.xml @@ -0,0 +1 @@ +Bi's BlogPersonal blog by biss.https://blog.biss.click/用 Umami 分享链接给网站信息卡提供统计数据https://blog.biss.click/posts/4c91a8f2/https://blog.biss.click/posts/4c91a8f2/这篇文章记录如何通过 Umami 的分享链接获取公开统计数据,再用 Cloudflare Worker 做一层缓存代理,最后把访客数和访问量显示到博客侧边栏的网站信息卡里。Fri, 19 Jun 2026 08:58:00 GMT把普通的一天过成自己的节奏https://blog.biss.click/posts/efe1e805/https://blog.biss.click/posts/efe1e805/有时候,我们会把“好好生活”想得太大,好像它必须绑定某个明确的目标:换一份更好的工作,去一个更远的地方,学会一项很厉害的技能,或者在某个时间节点之前完成别人眼里的成功。 可真正能把日子托住的,往往不是这些很大的东西,而是一些小到容易被忽略的Fri, 29 May 2026 18:09:40 GMTGPT Image2 初试https://blog.biss.click/posts/3f8de22/https://blog.biss.click/posts/3f8de22/这篇文章介绍了作者最近试用gpt img2之后的一些直观感受。文章没有从技术角度展开,而是以普通用户的体验为主,聊了它适合做什么、哪些地方比以前的AI绘图更顺手、哪些地方依然不太稳定,以及在日常做头像、壁纸、封面图和改图时的一些实际体会。整体来看,它确实比以前更听话一些,但也还没有到可以完全省心的地步。Sat, 25 Apr 2026 15:00:00 GMT电子价签玩法https://blog.biss.click/posts/8712baaa/https://blog.biss.click/posts/8712baaa/这篇文章介绍了作者在逛咸鱼时发现并购买了两个改造二手电子价签的过程,这些电子价签由tsl0922开发的开源固件提供支持,具备日历、传图、文字功能,并且可以在网页端进行便捷的配置。尽管开源版固件功能有限,作者选择自行编写所需功能,目前已经实现了待办事项和倒计时功能,并能生成图片后上传至网络。Sun, 19 Apr 2026 13:46:25 GMTOpenGL- Cohen-Sutherland 编码裁剪算法https://blog.biss.click/posts/9b2ef00a/https://blog.biss.click/posts/9b2ef00a/这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。Wed, 15 Apr 2026 19:34:23 GMTOpenGL-直线的扫描转换https://blog.biss.click/posts/7207243b/https://blog.biss.click/posts/7207243b/这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。Sat, 11 Apr 2026 19:01:02 GMTOpenGL-基础程序https://blog.biss.click/posts/437a5198/https://blog.biss.click/posts/437a5198/这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。Sat, 11 Apr 2026 18:20:34 GMT配置OpenGL环境https://blog.biss.click/posts/c4477b0c/https://blog.biss.click/posts/c4477b0c/这篇文章详细介绍了配置OpenGL环境所需步骤,包括安装Visual Studio、CMake和GLFW等关键组件,并提供了具体的配置方法和测试代码,旨在帮助读者顺利开始计算机图形学的学习之旅。Thu, 02 Apr 2026 04:52:16 GMTopencv应用-算术运算https://blog.biss.click/posts/b559997d/https://blog.biss.click/posts/b559997d/这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。Tue, 17 Mar 2026 13:13:08 GMTopencv应用-基础操作https://blog.biss.click/posts/e8f95ead/https://blog.biss.click/posts/e8f95ead/这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。Sun, 15 Mar 2026 09:56:51 GMT配置opencvhttps://blog.biss.click/posts/6e3332b/https://blog.biss.click/posts/6e3332b/这篇文章介绍了在Ubuntu和Windows系统上配置OpenCV的过程。在Ubuntu系统中,通过包管理器安装了Visual Studio、GCC、G++、OpenCV以及Python的OpenCV绑定。而在Windows系统中,则通过安装Visual Studio并下载OpenCV的Windows安装包进行配置,包括将安装路径添加到系统环境变量中。Sat, 14 Mar 2026 11:48:36 GMT自建bitwarden服务https://blog.biss.click/posts/7baa41fc/https://blog.biss.click/posts/7baa41fc/这篇文章介绍了如何自建Bitwarden服务,以替代浏览器自带的密码管理器。Bitwarden是一款开源、端到端加密的密码管理器,支持多种操作系统和设备,具有安全存储、自动同步、一键填充、生成随机密码和检测弱密码等功能。文章详细描述了部署Bitwarden服务的步骤,包括安装Vaultwarden、配置Nginx反向代理以及设置域名和数据卷等。通过这些步骤,用户可以轻松搭建一个功能强大的密码管理服务器,提高个人信息安全。Wed, 25 Feb 2026 11:09:23 GMT自建renovate-bothttps://blog.biss.click/posts/56f57c0b/https://blog.biss.click/posts/56f57c0b/这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。Mon, 23 Feb 2026 10:49:17 GMT新年快乐!https://blog.biss.click/posts/5785bd01/https://blog.biss.click/posts/5785bd01/这篇文章是一则关于新年祝福和幸福指数提升的通知,主要内容包含以下几点:首先,强调在假期期间要确保饮食质量,避免emo情绪的影响;其次,鼓励大家在收到通知后在留言区分享新年愿望,以便后台系统收集并祝愿他们实现愿望;最后,表达了对大家新年的美好祝愿,希望他们身体健康,万事如意,所得皆所愿,所行化坦途。Sat, 14 Feb 2026 06:56:18 GMT添加网站左上角菜单https://blog.biss.click/posts/7e921903/https://blog.biss.click/posts/7e921903/这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。Tue, 10 Feb 2026 07:11:14 GMT将博客仓库转移到giteahttps://blog.biss.click/posts/d2c8521/https://blog.biss.click/posts/d2c8521/这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。Sat, 07 Feb 2026 12:30:39 GMT安装giteahttps://blog.biss.click/posts/34725d47/https://blog.biss.click/posts/34725d47/这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。Sat, 07 Feb 2026 06:32:04 GMT添加typesense搜索https://blog.biss.click/posts/f287c563/https://blog.biss.click/posts/f287c563/这篇文章介绍了如何在博客中集成Typesense搜索引擎,包括安装Typesense、添加数据集以及同步数据的详细步骤。首先,通过命令行安装Typesense并配置相关参数,如API密钥和数据目录。接着,安装必要的npm包,并在Hexo博客中配置搜索功能,指定存储搜索数据的JSON文件路径。然后,编写了一个同步脚本,用于读取XML文件、解析数据、检查或创建集合以及导入数据到Typesense。最后,在博客底部添加了InstantSearch和Typesense的JavaScript库,并根据实际情况修改了配置。Thu, 05 Feb 2026 13:14:16 GMT自定义字体https://blog.biss.click/posts/b5601a7e/https://blog.biss.click/posts/b5601a7e/这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。Fri, 16 Jan 2026 07:47:15 GMT在侧边栏添加日历和倒计时https://blog.biss.click/posts/5ed2f1e6/https://blog.biss.click/posts/5ed2f1e6/实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。Fri, 16 Jan 2026 07:47:07 GMT和朋友们的故事https://blog.biss.click/posts/a31d95d9/https://blog.biss.click/posts/a31d95d9/这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。Mon, 05 Jan 2026 13:14:16 GMTAdguard和Openclash共存https://blog.biss.click/posts/66e66374/https://blog.biss.click/posts/66e66374/这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。Sun, 05 Oct 2025 15:18:00 GMTAdguard规则分享https://blog.biss.click/posts/69b16001/https://blog.biss.click/posts/69b16001/这篇文章分享了关于Adguardhome广告拦截工具的使用经验,特别强调了规则的重要性。作者提到,要达到更好的拦截效果,需要搭配有效的规则,并详细介绍了两个关键方面:一是综合过滤规则的分享,二是广告过滤规则订阅中心的集成。通过添加这些规则,使用Adblocktester工具可以达到74分的拦截效果,认为这基本够用。Sun, 05 Oct 2025 12:18:00 GMT在Openwrt上安装AdguardHomehttps://blog.biss.click/posts/b57500e9/https://blog.biss.click/posts/b57500e9/这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。Sun, 28 Sep 2025 17:10:18 GMT在Openwrt中安装OpenClashhttps://blog.biss.click/posts/2b2fb1a7/https://blog.biss.click/posts/2b2fb1a7/这篇文章详细介绍了在OpenWrt系统中安装OpenClash的过程。作者通过Web管理页面和终端命令两种方式进行了安装,并强调了推荐启用Smart内核。安装完成后,作者提到了配置步骤,包括下载内核和导入配置文件,最后表达了对于zashboard界面简洁美观的个人喜好。整篇文章内容连贯,条理清晰,为读者提供了完整的安装指南。Tue, 02 Sep 2025 21:00:00 GMTCudy TR3000刷入Openwrt系统https://blog.biss.click/posts/8802e397/https://blog.biss.click/posts/8802e397/这篇文章介绍了如何将Cudy TR3000路由器刷入OpenWrt系统。首先,作者提供了下载中间固件和OpenWrt固件的方法。接着,详细说明了刷入过程的步骤,包括访问不同的IP地址进行固件刷入,并提到了OpenWrt的默认登录密码是root。整个过程旨在使路由器能够支持科学上网功能。Sun, 31 Aug 2025 20:15:00 GMT搭建Owncloud并集成Onlyofficehttps://blog.biss.click/posts/59c8572a/https://blog.biss.click/posts/59c8572a/这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。Tue, 26 Aug 2025 14:27:00 GMT为博客添加CMS系统https://blog.biss.click/posts/3a26a97a/https://blog.biss.click/posts/3a26a97a/这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。Mon, 25 Aug 2025 22:03:00 GMT为1Panel添加自己想要的应用https://blog.biss.click/posts/bb18d851/https://blog.biss.click/posts/bb18d851/这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。Tue, 19 Aug 2025 10:43:16 GMT使用GitHub推送Hexo到服务器https://blog.biss.click/posts/ce1ec3fe/https://blog.biss.click/posts/ce1ec3fe/这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。Mon, 18 Aug 2025 17:10:18 GMTC# 基本语法https://blog.biss.click/posts/e9a8e898/https://blog.biss.click/posts/e9a8e898/这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。Sat, 16 Aug 2025 18:01:10 GMTC# 入门https://blog.biss.click/posts/bc56f789/https://blog.biss.click/posts/bc56f789/这篇文章指导初学者在Windows上安装C#开发环境:先下载Visual Studio社区版安装器并勾选“.NET桌面开发”工作负载,可自定义安装路径;随后通过HelloWorld演示C#基础结构,包含命名空间、类、Main入口及Console输出,帮助读者完成首个程序并熟悉默认模板。Fri, 15 Aug 2025 09:56:17 GMT自定义页脚(新)https://blog.biss.click/posts/c6143ad3/https://blog.biss.click/posts/c6143ad3/您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。Thu, 14 Aug 2025 17:05:27 GMT自定义分类条https://blog.biss.click/posts/33249733/https://blog.biss.click/posts/33249733/这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。Thu, 14 Aug 2025 16:25:49 GMT自定义导航栏https://blog.biss.click/posts/3e61a389/https://blog.biss.click/posts/3e61a389/这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。Wed, 13 Aug 2025 16:12:25 GMT利用 SiteMap 随机访问站内页面https://blog.biss.click/posts/b5866b9e/https://blog.biss.click/posts/b5866b9e/提供了一个很好的解决方案,通过使用 Hexo 的 sitemap 生成器和 JavaScript 代码来实现随机访问站内页面的功能 以下是你提供的步骤的详细解释:Tue, 12 Aug 2025 15:15:32 GMT博客添加AI总结https://blog.biss.click/posts/41b9aff7/https://blog.biss.click/posts/41b9aff7/这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。Tue, 12 Aug 2025 09:46:25 GMT利用插件自定义页脚菜单https://blog.biss.click/posts/a6ab0925/https://blog.biss.click/posts/a6ab0925/这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。Tue, 12 Aug 2025 09:19:16 GMT英语文章分享-去奋力生活吧,就好像死亡近在咫尺https://blog.biss.click/posts/f0a1a1f4/https://blog.biss.click/posts/f0a1a1f4/这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。Mon, 11 Aug 2025 20:59:46 GMT自定义右键菜单https://blog.biss.click/posts/8bdb35fb/https://blog.biss.click/posts/8bdb35fb/这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。Mon, 11 Aug 2025 16:02:12 GMT在侧边栏中添加欢迎信息https://blog.biss.click/posts/da17d00/https://blog.biss.click/posts/da17d00/这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。Sun, 10 Aug 2025 19:30:40 GMT配置说说页面https://blog.biss.click/posts/ad244066/https://blog.biss.click/posts/ad244066/这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方Sun, 10 Aug 2025 08:25:39 GMT部署历程https://blog.biss.click/posts/6e5f5039/https://blog.biss.click/posts/6e5f5039/这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。Sat, 02 Aug 2025 19:07:05 GMT \ No newline at end of file diff --git a/shuoshuo/index.html b/shuoshuo/index.html new file mode 100644 index 0000000..fb68224 --- /dev/null +++ b/shuoshuo/index.html @@ -0,0 +1,322 @@ + 说说 | Bi's Blog

    Bi's Blog

    说说

    胡言乱语 ing...

    Comments

    说说

    \ No newline at end of file diff --git a/sitemap-0.xml b/sitemap-0.xml new file mode 100644 index 0000000..41a1129 --- /dev/null +++ b/sitemap-0.xml @@ -0,0 +1 @@ +https://blog.biss.click/https://blog.biss.click/about/https://blog.biss.click/archives/https://blog.biss.click/archives/page/2/https://blog.biss.click/archives/page/3/https://blog.biss.click/categories/https://blog.biss.click/categories/life/https://blog.biss.click/categories/notes/https://blog.biss.click/categories/site-notes/https://blog.biss.click/categories/study/https://blog.biss.click/categories/tech/https://blog.biss.click/cc/https://blog.biss.click/cookie/https://blog.biss.click/go/https://blog.biss.click/link/https://blog.biss.click/page/2/https://blog.biss.click/page/3/https://blog.biss.click/posts/2b2fb1a7/https://blog.biss.click/posts/3a26a97a/https://blog.biss.click/posts/3e61a389/https://blog.biss.click/posts/3f8de22/https://blog.biss.click/posts/4c91a8f2/https://blog.biss.click/posts/5ed2f1e6/https://blog.biss.click/posts/6e5f5039/https://blog.biss.click/posts/6e3332b/https://blog.biss.click/posts/7baa41fc/https://blog.biss.click/posts/7e921903/https://blog.biss.click/posts/8bdb35fb/https://blog.biss.click/posts/9b2ef00a/https://blog.biss.click/posts/41b9aff7/https://blog.biss.click/posts/56f57c0b/https://blog.biss.click/posts/59c8572a/https://blog.biss.click/posts/66e66374/https://blog.biss.click/posts/69b16001/https://blog.biss.click/posts/437a5198/https://blog.biss.click/posts/5785bd01/https://blog.biss.click/posts/8712baaa/https://blog.biss.click/posts/8802e397/https://blog.biss.click/posts/34725d47/https://blog.biss.click/posts/7207243b/https://blog.biss.click/posts/33249733/https://blog.biss.click/posts/a6ab0925/https://blog.biss.click/posts/a31d95d9/https://blog.biss.click/posts/ad244066/https://blog.biss.click/posts/b5601a7e/https://blog.biss.click/posts/b5866b9e/https://blog.biss.click/posts/b57500e9/https://blog.biss.click/posts/b559997d/https://blog.biss.click/posts/bb18d851/https://blog.biss.click/posts/bc56f789/https://blog.biss.click/posts/c4477b0c/https://blog.biss.click/posts/c6143ad3/https://blog.biss.click/posts/ce1ec3fe/https://blog.biss.click/posts/d2c8521/https://blog.biss.click/posts/da17d00/https://blog.biss.click/posts/e8f95ead/https://blog.biss.click/posts/e9a8e898/https://blog.biss.click/posts/efe1e805/https://blog.biss.click/posts/f0a1a1f4/https://blog.biss.click/posts/f287c563/https://blog.biss.click/privacy/https://blog.biss.click/shuoshuo/https://blog.biss.click/tags/https://blog.biss.click/tags/%E5%9B%BE%E5%83%8F%E7%94%9F%E6%88%90/https://blog.biss.click/tags/%E7%94%9F%E6%B4%BB/https://blog.biss.click/tags/%E7%BD%91%E7%AB%99/https://blog.biss.click/tags/%E8%8B%B1%E8%AF%AD/https://blog.biss.click/tags/%E9%9A%8F%E7%AC%94/https://blog.biss.click/tags/1panel/https://blog.biss.click/tags/AdguardHome/https://blog.biss.click/tags/AI/https://blog.biss.click/tags/C%23/https://blog.biss.click/tags/Cloudflare%20Worker/https://blog.biss.click/tags/Cudy-TR3000/https://blog.biss.click/tags/gitea/https://blog.biss.click/tags/OpenAI/https://blog.biss.click/tags/Openclash/https://blog.biss.click/tags/opencv/https://blog.biss.click/tags/OpenGL/https://blog.biss.click/tags/Openwrt/https://blog.biss.click/tags/Umami/ \ No newline at end of file diff --git a/sitemap-index.xml b/sitemap-index.xml new file mode 100644 index 0000000..c4ed240 --- /dev/null +++ b/sitemap-index.xml @@ -0,0 +1 @@ +https://blog.biss.click/sitemap-0.xml \ No newline at end of file diff --git a/tags/1panel/index.html b/tags/1panel/index.html new file mode 100644 index 0000000..012eb96 --- /dev/null +++ b/tags/1panel/index.html @@ -0,0 +1,274 @@ + 1panel | Bi's Blog

    Bi's Blog

    #1panel

    共 1 篇文章

    为1Panel添加自己想要的应用

    这篇文章详细介绍了如何为1Panel添加自定义应用的过程,包括预准备工作、创建文件和配置应用声明文件等步骤。首先,作者强调了Fork仓库和创建新分支的重要性。接着,文章逐步讲解了如何在应用目录下创建所需的文件和子目录,如halo、logo.png、data.yml、README.md等。特别详细地说明了应用声明文件data.yml的构成,包括基本信息、应用标签、类型等关键配置。此外,还解释了关于端口字段和type字段的具体使用方法,提供了实际安装时填写form表单的示例。整个过程不仅清晰易懂,而且为开发者提供了一套完整的操作指南,有助于他们更好地理解和实施应用添加到1Panel的过程。

    \ No newline at end of file diff --git a/tags/AI/index.html b/tags/AI/index.html new file mode 100644 index 0000000..45f5e4f --- /dev/null +++ b/tags/AI/index.html @@ -0,0 +1,274 @@ + AI | Bi's Blog

    Bi's Blog

    #AI

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/AdguardHome/index.html b/tags/AdguardHome/index.html new file mode 100644 index 0000000..f5ecffc --- /dev/null +++ b/tags/AdguardHome/index.html @@ -0,0 +1,274 @@ + AdguardHome | Bi's Blog

    Bi's Blog

    #AdguardHome

    共 3 篇文章

    Adguard和Openclash共存

    这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

    在Openwrt上安装AdguardHome

    这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。

    \ No newline at end of file diff --git a/tags/C%23/index.html b/tags/C%23/index.html new file mode 100644 index 0000000..747e5cf --- /dev/null +++ b/tags/C%23/index.html @@ -0,0 +1,274 @@ + C# | Bi's Blog

    Bi's Blog

    #C#

    共 2 篇文章

    C# 基本语法

    这篇文章通过Rectangle示例讲解C#面向对象基础,展示类与对象如何封装数据及行为,并输出面积15.75。随后介绍using引入命名空间、class声明类、注释、成员变量与函数、实例化流程及标识符规则。重点解析C#9顶级语句,无需Main方法即可在文件顶层直接写代码,编译器自动生成入口,适合脚本化开发,示例演示变量、方法、LINQ与异常处理,简化小型项目结构。

    \ No newline at end of file diff --git a/tags/Cloudflare Worker/index.html b/tags/Cloudflare Worker/index.html new file mode 100644 index 0000000..0965d1b --- /dev/null +++ b/tags/Cloudflare Worker/index.html @@ -0,0 +1,274 @@ + Cloudflare Worker | Bi's Blog

    Bi's Blog

    #Cloudflare Worker

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/Cudy-TR3000/index.html b/tags/Cudy-TR3000/index.html new file mode 100644 index 0000000..cd30fef --- /dev/null +++ b/tags/Cudy-TR3000/index.html @@ -0,0 +1,274 @@ + Cudy-TR3000 | Bi's Blog

    Bi's Blog

    #Cudy-TR3000

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/OpenAI/index.html b/tags/OpenAI/index.html new file mode 100644 index 0000000..542bc36 --- /dev/null +++ b/tags/OpenAI/index.html @@ -0,0 +1,274 @@ + OpenAI | Bi's Blog

    Bi's Blog

    #OpenAI

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/OpenGL/index.html b/tags/OpenGL/index.html new file mode 100644 index 0000000..610190e --- /dev/null +++ b/tags/OpenGL/index.html @@ -0,0 +1,274 @@ + OpenGL | Bi's Blog

    Bi's Blog

    #OpenGL

    共 4 篇文章

    OpenGL- Cohen-Sutherland 编码裁剪算法

    这篇文章介绍了Cohen-Sutherland编码裁剪算法,这是一种在计算机图形学中常用的图像裁剪方法。裁剪的目的是从一个大场景中提取所需信息,例如在浏览地图时放大显示特定区域。Cohen-Sutherland算法通过空间划分和编码方案来判断线段与矩形窗口的位置关系,从而决定线段的哪部分在窗口内、哪部分在窗口外。算法的核心在于计算线段端点的区域编码,并根据编码值来确定线段的裁剪结果。文章还提供了具体的代码实现,包括初始化设置、窗口大小调整和主函数等,展示了如何在实际应用中使用该算法进行图像裁剪。

    OpenGL-直线的扫描转换

    这篇文章介绍了三种直线扫描转换算法:DDA算法、中点画线算法和Bresenham算法。 DDA算法通过计算直线起点和终点的坐标差值来确定步数和增量,依次绘制直线上的点。算法实现简单,适用于任意直线,但精度较低。 中点画线算法仅适用于斜率为0或1的直线,通过计算中点和斜率来确定直线上的像素点,效率较高。 Bresenham算法根据直线的斜率和误差项来确定像素点的位置,适用于任意直线,且精度高,但实现稍复杂。

    OpenGL-基础程序

    这篇文章详细介绍了OpenGL的基础程序,包括代码展示和代码解释两部分。代码展示部分呈现了一个简单的OpenGL程序,涵盖了初始化与窗口管理、状态设置与缓冲区操作、矩阵与坐标变换以及几何图形绘制等关键步骤。代码解释则将这些步骤按照功能类别进行了归纳,帮助读者更好地理解OpenGL的渲染流程。通过这个示例,读者可以初步掌握OpenGL的基本操作和概念。

    \ No newline at end of file diff --git a/tags/Openclash/index.html b/tags/Openclash/index.html new file mode 100644 index 0000000..05b241d --- /dev/null +++ b/tags/Openclash/index.html @@ -0,0 +1,274 @@ + Openclash | Bi's Blog

    Bi's Blog

    #Openclash

    共 2 篇文章

    Adguard和Openclash共存

    这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

    \ No newline at end of file diff --git a/tags/Openwrt/index.html b/tags/Openwrt/index.html new file mode 100644 index 0000000..ce99ad0 --- /dev/null +++ b/tags/Openwrt/index.html @@ -0,0 +1,274 @@ + Openwrt | Bi's Blog

    Bi's Blog

    #Openwrt

    共 4 篇文章

    Adguard和Openclash共存

    这篇文章介绍了如何在已经安装了 OpenWrt 系统并配置好 OpenClash 和 AdguardHome 的情况下,实现这两者的共存。首先简要说明了 AdguardHome 和 OpenClash 的原理,它们都是通过劫持 DNS 来达到特定目的。然后分别介绍了两种配置方式:一种是在 AdguardHome 中将上游 DNS 服务器设置为 127.0.0.1:7874,这种方式相对简单;另一种是将 OpenClash 中的 DNS 指向 AdguardHome,但可能存在拦截失败的情况。

    在Openwrt上安装AdguardHome

    这篇文章介绍了在OpenWrt上安装AdguardHome的过程。首先,作者提到由于luci-app-adguardhome软件源的问题,选择了其他途径获取软件包并进行安装。接着,详细说明了安装步骤,包括下载、上传和运行安装命令。在配置部分,作者强调了更新核心版本的重要性,并提供了配置AdguardHome的具体步骤,如关闭路由器自带DNS/DHCP服务器,并建议设置AdguardHome的DHCP服务器。最后,作者提醒用户在完成配置后点击“保存并应用”,并通过AdGuardHome Web界面进行验证。

    \ No newline at end of file diff --git a/tags/Umami/index.html b/tags/Umami/index.html new file mode 100644 index 0000000..9e12316 --- /dev/null +++ b/tags/Umami/index.html @@ -0,0 +1,274 @@ + Umami | Bi's Blog

    Bi's Blog

    #Umami

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/gitea/index.html b/tags/gitea/index.html new file mode 100644 index 0000000..aea3355 --- /dev/null +++ b/tags/gitea/index.html @@ -0,0 +1,274 @@ + gitea | Bi's Blog

    Bi's Blog

    #gitea

    共 3 篇文章

    自建renovate-bot

    这篇文章介绍了如何自建Renovate Bot以自动更新依赖,并通过Docker Compose进行部署。首先,文章提供了Renovate Bot的Docker Compose文件配置,包括使用特定平台(gitea)、设置Git API地址、生成Token以及启用自动发现功能。接着,建议创建一个专门用于管理Renovate Bot的账户,并为其生成登录Token。最后,通过在系统的crontab中设置定时任务,可以自动化运行更新流程。

    将博客仓库转移到gitea

    这篇文章详细介绍了如何将博客仓库从现有的平台转移到Gitea。首先,确保已经成功安装了Gitea。接着,文章重点讲解了action文件的修改,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到本地仓库以及Webhook的配置。这些步骤确保了博客能够顺利迁移到Gitea,并且保持了原有的功能和结构。需要注意的是,文章中的action文件包含了一些具体的指令和变量,如ref: master、cache-node-modules等,这些在实际操作时需要替换为具体的值。此外,文章还提到了Webhook的配置,这是一个可选但推荐的自定义事件触发器,用于在特定事件发生时自动触发部署流程。

    安装gitea

    这篇文章详细介绍了如何在2C2G的服务器上安装Gitea,包括获取二进制文件、创建用户和工作目录、创建系统服务、创建数据库以及安装Runner等步骤。Gitea作为一个轻量级的自助Git服务,适合用于托管网站源码。文章还提供了关于如何配置反向代理和数据库的信息,以及如何使用Docker运行Gitea Runner以实现GitHub Actions功能。通过这些步骤,用户可以在自己的服务器上成功部署Gitea,并利用其进行版本控制和自动化工作流程。

    \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..612384d --- /dev/null +++ b/tags/index.html @@ -0,0 +1,274 @@ + 标签 | Bi's Blog

    Bi's Blog

    标签

    共 18 个标签

    标签文章统计

    共 43 篇文章

    \ No newline at end of file diff --git a/tags/opencv/index.html b/tags/opencv/index.html new file mode 100644 index 0000000..4cf9a98 --- /dev/null +++ b/tags/opencv/index.html @@ -0,0 +1,274 @@ + opencv | Bi's Blog

    Bi's Blog

    #opencv

    共 3 篇文章

    opencv应用-算术运算

    这篇文章介绍了如何使用OpenCV进行图像的加法运算。首先,通过包含OpenCV和iostream头文件,并使用std命名空间,开始编写代码。主函数中,读取两张图片,检查是否成功加载,并确保它们的尺寸相同。如果尺寸不同,将第二张图片调整为与第一张相同的大小。然后,调用add函数对两幅图片进行加法运算,并将结果存储在result矩阵中。最后,显示原始图片和加法运算的结果,并等待按键关闭窗口。代码已上传至自建的git仓库。

    opencv应用-基础操作

    这篇文章介绍了使用C++和OpenCV库进行图像处理的基础操作,包括图片的读取与展示、像素的读取与修改、图像的基本变换(缩放、旋转、平移、翻转)、以及图像通道的分离与合并。文章详细讲解了每个操作的代码实现,并展示了如何通过OpenCV库提供的函数来完成这些任务。这对于初学者来说是非常有用的,因为它帮助他们理解如何使用OpenCV进行图像处理。

    \ No newline at end of file diff --git a/tags/图像生成/index.html b/tags/图像生成/index.html new file mode 100644 index 0000000..5e9cea8 --- /dev/null +++ b/tags/图像生成/index.html @@ -0,0 +1,274 @@ + 图像生成 | Bi's Blog

    Bi's Blog

    #图像生成

    共 1 篇文章

    \ No newline at end of file diff --git a/tags/生活/index.html b/tags/生活/index.html new file mode 100644 index 0000000..7379acb --- /dev/null +++ b/tags/生活/index.html @@ -0,0 +1,274 @@ + 生活 | Bi's Blog

    Bi's Blog

    #生活

    共 3 篇文章

    和朋友们的故事

    这篇文章讲述了作者与朋友们的故事,以及他通过挂号信和电子贺卡与朋友们保持联系的经历。作者描述了自己对这种传统通信方式的喜爱,尽管现在网络发达,但他仍然觉得挂号信具有独特的魅力。文章中提到了作者去年上半年开始给朋友们写信,下半年因忙碌而减少,年底时尝试使用AI工具创作电子贺卡。作者感慨大学时间的快速流逝,以及朋友们之间的疏远。尽管如此,作者认为这是必然的事情,并无悲伤之处,只是单纯的感慨。最后,作者表示下次有时间会在文章中更新更多内容。

    \ No newline at end of file diff --git a/tags/网站/index.html b/tags/网站/index.html new file mode 100644 index 0000000..f4433ae --- /dev/null +++ b/tags/网站/index.html @@ -0,0 +1,274 @@ + 网站 | Bi's Blog

    Bi's Blog

    #网站

    共 17 篇文章

    添加网站左上角菜单

    这篇文章详细介绍了如何在柳神的网站上添加一个自定义的左上角菜单,包括菜单的结构、样式以及响应式设计。作者提供了具体的HTML和CSS代码示例,展示了如何实现一个包含多个链接和功能的导航栏,同时确保在不同屏幕尺寸下的适配性。此外,还解释了一些关键的CSS属性和JavaScript交互效果,以实现平滑的菜单显示和隐藏。通过这些步骤,用户可以轻松地自定义和美化网站的顶部导航栏,提升用户体验。

    自定义字体

    这篇文章讨论了如何自定义网站字体,以提升网站的美观性和用户体验。作者首先提到了在移动端和电脑上使用woff或woff2字体能够完美显示的重要性,并提到谷歌字体库可能已经可以直接在中国大陆访问,这使得自定义字体的需求可能不再那么迫切。接着,文章详细介绍了如何在网上寻找并下载喜欢的字体,包括字体格式的转换方法。然后,展示了如何在CSS文件中添加字体,包括如何声明字体族和指定字体文件的路径。最后,文章还提供了关于其他字体格式的参考,并指导如何在主题文件的font配置区域修改字体设置。

    在侧边栏添加日历和倒计时

    实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。

    搭建Owncloud并集成Onlyoffice

    这篇文章详细介绍了如何搭建Owncloud网盘服务并集成Onlyoffice文档编辑器。首先,作者描述了搭建过程中的基本步骤,包括安装网盘和进行系统调优,其中强调了Redis缓存配置和Cron任务设置的重要性。接着,作者提到了在1panel应用商店安装Onlyoffice的过程,并说明了如何将其反向代理至个人域名。最后,文章指导用户在Owncloud中搜索并安装Onlyoffice插件,确保了整个集成过程的顺利完成。

    为博客添加CMS系统

    这篇文章介绍了如何为Hexo博客添加一个基于Sveltia CMS的CMS系统。首先,作者通过npm安装了Sveltia CMS,并在博客源码目录下创建了必要的文件和目录结构。接着,作者详细说明了如何在config.yml文件中配置CMS,包括字段定义、页面和集合的设置。由于作者使用的是自己的服务器,无法使用官方的认证系统,因此他还讲解了如何配置GitHub OAuth应用,并在Cloudflare上设置环境变量以支持CI/CD流程。最后,作者指出完成这些步骤后,可以通过访问博客的admin后台来管理内容。

    使用GitHub推送Hexo到服务器

    这篇文章介绍了如何使用GitHub推送Hexo到服务器,包括建立GitHub仓库、上传文件、创建Action以及配置环境变量等步骤。首先,文章提供了在香港直连GitHub的方法,并指导如何在本地安装git、配置用户名和邮箱、创建SSH Key并在GitHub上上传公钥。接着,文章详细说明了如何在自己的hexo根目录下初始化git仓库,并使用PowerShell将文件推送到新建的私有仓库。此外,文章还创建了一个Action,用于自动化部署过程,包括检查分支、缓存项目、安装Node和Hexo、安装依赖、清理文件树、生成静态文件并压缩、部署到服务器等环节。最后,文章列出了需要配置的环境变量,如服务器IP、用户名、密钥密码、私钥和SSH登录端口,并强调了测试的重要性,以确保整个部署流程无报错。

    自定义页脚(新)

    您提供的代码是一个自定义页脚的HTML和CSS代码 要在您的网站中使用这个自定义页脚,请按照以下步骤操作: 1. 首先,将提供的CSS代码复制到您网站的CSS文件中。如果您没有单独的CSS文件,可以在HTML文件的`<head>`部分添加`<style>`标签,并将CSS代码粘贴到其中。 2. 接下来,将提供的HTML代码复制到您网站的HTML文件中。将其放置在`<body>`标签内,最好在页面的底部。 3. 根据需要自定义页脚内容,例如更改版权信息、菜单链接、社交链接等。 4. 保存更改并在浏览器中查看您的网站,您应该能看到自定义页脚。

    自定义分类条

    这篇文章介绍了如何在博客系统中自定义分类栏的设计和实现过程。首先,通过Pug模板语言创建了一个名为categoryBar.pug的文件,用于定义分类栏的结构和样式。接着,在Stylus样式文件category-bar.styl中详细定义了分类栏的样式,包括内边距、背景颜色、边框圆角等,并设置了滚动条的样式。最后,在JavaScript文件main.js中添加了一个setCategoryBarActive函数,用于根据当前页面路径自动激活相应的分类项。此外,还讨论了如何将自定义的分类栏集成到主题中,并提供了相关的Pug混入代码。整个过程确保了分类栏的可定制性和易维护性。

    自定义导航栏

    这篇文章介绍了如何在最新版的Butterfly 5.4中自定义导航栏,包括PC端和PE端的修改方法以及CSS文件的调整。主要内容包括修改header\nav.pug和menu_item.pug文件中的内容,调整head.styl和sidebar.styl中的CSS样式,以及在CSS文件中添加新的样式规则。这些修改旨在实现导航栏的个性化设置,如菜单项的显示、位置调整、背景颜色变化等,以提升网站的用户体验和视觉效果。

    博客添加AI总结

    这篇文章介绍了如何在Hexo博客中添加AI摘要功能,作者寻找并尝试了多个AI摘要插件后,最终选择了hexo-ai-summary-liushen插件。安装过程中,作者详细说明了如何安装额外依赖,并在Hexo配置文件中添加了相关配置。文章还提供了关于内容清洗、摘要字段设置、日志等级、API接口配置、插件适配等方面的详细说明和配置示例。此外,作者还介绍了如何将AI摘要集成到Hexo主题中,并提供了相关的CSS样式和JavaScript动效代码,以实现更加逼真的摘要效果。最后,作者提醒用户在运行插件前注意备份,并介绍了如何处理可能出现的缓存问题。

    利用插件自定义页脚菜单

    这篇文章介绍了如何使用插件来自定义Hexo博客的页脚菜单。首先,通过npm安装了hexo-butterfly-footer-marcus插件,并根据需要安装了yamljs插件。接着,在博客的配置文件中添加了相关配置信息,包括页脚美化、图标链接、作者信息等。文章还提供了具体的参数释义和使用方法,指导读者如何填写配置项和修改源码来调整页脚样式和布局。最后,文章还提到了如何解决在黑暗模式下修改按钮层级的问题,并提供了相关的代码修改示例。

    自定义右键菜单

    这篇文章介绍了如何在自定义右键菜单中添加各种功能,包括返回、前进、刷新、滚动到页面不同位置、复制文本、搜索页面、打开链接、粘贴内容、复制本文地址、在新窗口打开链接、保存图片、随机文章浏览、切换深色模式、繁简转换、阅读模式以及隐私声明、Cookie协议和版权声明等。同时,文章还提供了创建Pug文件和JavaScript文件的步骤,详细说明了如何编写右键菜单的HTML结构和交互逻辑。

    在侧边栏中添加欢迎信息

    这篇文章介绍了如何在侧边栏中添加欢迎信息,包括申请腾讯地图API key、添加js脚本以及根据用户的地理位置显示个性化欢迎语。首先,文章指导读者如何申请腾讯地图API key,并提醒注意填写信任域名时不要包含'https://'。接着,文章说明了在主题的js目录下添加txmap.js脚本的过程,并提到了一个更完善的js脚本。最后,文章详细描述了如何使用获取到的地理位置信息,通过switch语句根据不同的国家、省份和城市显示相应的欢迎语。

    配置说说页面

    这段文字看起来是一个部署教程,主要是关于如何在服务器上部署一个名为“Moments”的应用,并将其与网站集成。以下是对这段文字的概括: 1. 介绍了硬件和软件的要求,包括服务器、域名、Docker环境和反向代理工具(如Nginx)。 2. 提供了Moments应用的部署方法,包括使用docker-compose进行部署和配置Nginx反向代理。 3. 介绍了如何在前端集成Moments应用,包括引入必要的CSS和JS文件,以及创建一个新的页面来显示Moments的内容。 需要注意的是,这段文字中的一些链接和代码可能已经过时或者不适用于所有场景,建议在实际操作时参考最新的官方

    部署历程

    这篇文章详细介绍了如何部署Hexo博客到Linux服务器,包括前提条件、本地环境准备、SSH密钥认证、配置步骤、工作流程以及故障排除提示。文章首先列出了部署所需满足的条件,然后逐步指导读者如何在服务器上安装Git、创建裸仓库、配置Git钩子以自动部署Hexo博客,并提供了验证部署成功的步骤。最后,文章还给出了一些常见问题的解决建议,帮助读者顺利完成部署。整个过程简单高效,适合需要自动化部署博客的用户。

    \ No newline at end of file diff --git a/tags/英语/index.html b/tags/英语/index.html new file mode 100644 index 0000000..0b6931c --- /dev/null +++ b/tags/英语/index.html @@ -0,0 +1,274 @@ + 英语 | Bi's Blog

    Bi's Blog

    #英语

    共 1 篇文章

    英语文章分享-去奋力生活吧,就好像死亡近在咫尺

    这篇文章讨论了在生活中追求意义的重要性,特别是在面对死亡时。作者作为一名临终关怀医生,见证了无数病人对未实现目标的遗憾。文章指出,许多人因感到精力不足或时间不够而未能追求自己的目标。然而,研究表明,拥有强烈目标感的人实际上更有活力,更容易被激励。目的不在于做出巨大贡献,而在于找到能激发内心火花的小目标,如阅读、散步或重拾儿时的爱好。文章强调,勇气是追求目标的关键,而不仅仅是勇敢。最后,作者警告说,如果不在有生之年追求目标,可能会在死亡时后悔莫及。

    \ No newline at end of file diff --git a/tags/随笔/index.html b/tags/随笔/index.html new file mode 100644 index 0000000..f8acc1a --- /dev/null +++ b/tags/随笔/index.html @@ -0,0 +1,274 @@ + 随笔 | Bi's Blog

    Bi's Blog

    #随笔

    共 1 篇文章

    \ No newline at end of file