diff --git a/EasyGoAdmin_Gin_AntdVue/.vitepress/config/shared.ts b/EasyGoAdmin_Gin_AntdVue/.vitepress/config/shared.ts index 904f0b1..22674f2 100644 --- a/EasyGoAdmin_Gin_AntdVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_Gin_AntdVue/.vitepress/config/shared.ts @@ -89,7 +89,7 @@ export const sharedConfig = defineConfig({ socialLinks: [ { icon: "github", - link: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + link: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", }, ], @@ -100,7 +100,7 @@ export const sharedConfig = defineConfig({ }, editLink: { - pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", text: "对本页提出修改建议", }, diff --git a/EasyGoAdmin_Gin_AntdVue/public/banner.js b/EasyGoAdmin_Gin_AntdVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_Gin_AntdVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "EasyGoAdmin · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/EasyGoAdmin_Gin_EleVue/.vitepress/config/shared.ts b/EasyGoAdmin_Gin_EleVue/.vitepress/config/shared.ts index 904f0b1..22674f2 100644 --- a/EasyGoAdmin_Gin_EleVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_Gin_EleVue/.vitepress/config/shared.ts @@ -89,7 +89,7 @@ export const sharedConfig = defineConfig({ socialLinks: [ { icon: "github", - link: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + link: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", }, ], @@ -100,7 +100,7 @@ export const sharedConfig = defineConfig({ }, editLink: { - pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", text: "对本页提出修改建议", }, diff --git a/EasyGoAdmin_Gin_EleVue/public/banner.js b/EasyGoAdmin_Gin_EleVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_Gin_EleVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "EasyGoAdmin · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/EasyGoAdmin_Gin_NaiveVue/.vitepress/config/shared.ts b/EasyGoAdmin_Gin_NaiveVue/.vitepress/config/shared.ts index 904f0b1..22674f2 100644 --- a/EasyGoAdmin_Gin_NaiveVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_Gin_NaiveVue/.vitepress/config/shared.ts @@ -89,7 +89,7 @@ export const sharedConfig = defineConfig({ socialLinks: [ { icon: "github", - link: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + link: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", }, ], @@ -100,7 +100,7 @@ export const sharedConfig = defineConfig({ }, editLink: { - pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_Gin_EleVue", + pattern: "https://gitee.com/easygoadmin/EasyGoAdmin_GoFrame_EleVue", text: "对本页提出修改建议", }, diff --git a/EasyGoAdmin_Gin_NaiveVue/public/banner.js b/EasyGoAdmin_Gin_NaiveVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_Gin_NaiveVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "EasyGoAdmin · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/EasyGoAdmin_GoFrame_AntdVue/.vitepress/config/shared.ts b/EasyGoAdmin_GoFrame_AntdVue/.vitepress/config/shared.ts index c1e5949..22674f2 100644 --- a/EasyGoAdmin_GoFrame_AntdVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_GoFrame_AntdVue/.vitepress/config/shared.ts @@ -70,6 +70,16 @@ export const sharedConfig = defineConfig({ }, ], + // Top banner + [ + "script", + { + src: "/banner.js", + // @ts-expect-error: vitepress bug + async: true, + type: "text/javascript", + }, + ], ], themeConfig: { diff --git a/EasyGoAdmin_GoFrame_AntdVue/public/banner.js b/EasyGoAdmin_GoFrame_AntdVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_GoFrame_AntdVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "EasyGoAdmin · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/EasyGoAdmin_GoFrame_EleVue/.vitepress/config/shared.ts b/EasyGoAdmin_GoFrame_EleVue/.vitepress/config/shared.ts index c1e5949..22674f2 100644 --- a/EasyGoAdmin_GoFrame_EleVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_GoFrame_EleVue/.vitepress/config/shared.ts @@ -70,6 +70,16 @@ export const sharedConfig = defineConfig({ }, ], + // Top banner + [ + "script", + { + src: "/banner.js", + // @ts-expect-error: vitepress bug + async: true, + type: "text/javascript", + }, + ], ], themeConfig: { diff --git a/EasyGoAdmin_GoFrame_EleVue/public/banner.js b/EasyGoAdmin_GoFrame_EleVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_GoFrame_EleVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "EasyGoAdmin · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化 - 副本.txt b/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化 - 副本.txt deleted file mode 100644 index 7648c93..0000000 --- a/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化 - 副本.txt +++ /dev/null @@ -1,543 +0,0 @@ -================================================================================ -EasyGoAdmin 文档站 SEO / GEO 更新标准 -================================================================================ -版本:1.0(2026-10-25) -适用:EasyGoAdmin_{Beego,Gin,GoFrame,Iris}_{AntdVue,EleVue,Layui,NaiveVue} - 等 16 个 VitePress 文档子项目(结构高度同构,可直接照搬) -基线项目:EasyGoAdmin_GoFrame_EleVue -本文档定位:后续为其他项目做相同更新时的执行标准(审计清单 → 实施步骤 → - 验收标准 → 移植注意事项)。 - --------------------------------------------------------------------------------- -第一部分:概念与目标 --------------------------------------------------------------------------------- - -1.1 SEO(Search Engine Optimization,搜索引擎优化) - 面向传统搜索引擎(百度、Google、Bing)的爬虫与排序: - - 让爬虫能发现、抓取、理解每一个页面(技术 SEO) - - 让每个页面有独立、准确的标题与摘要(页面元数据) - - 让页面之间有清晰的权重传递结构(内链、面包屑、sitemap) - - 用结构化数据告诉搜索引擎"这是什么类型的内容"(JSON-LD) - -1.2 GEO(Generative Engine Optimization,生成式引擎优化) - 面向 AI 引擎(ChatGPT、Claude、Perplexity、豆包、Kimi、AI Overviews) - 的爬虫与答案引用: - - 让 AI 爬虫明确被允许抓取(robots.txt 显式放行) - - 提供"给模型看的站点摘要"(llms.txt,llmstxt.org 标准) - - 页面内容"答案前置",首段即可被直接引用(:::tip 概述 首句) - - 结构化 Q/A(FAQPage JSON-LD)便于引擎抽取 - - 时效信号(dateModified / sitemap lastmod)影响引用优先级 - -1.3 本方案的设计原则 - a) 单一真相源:URL、站点描述、提取逻辑只写一处,多处复用。 - b) 零运行时开销:全部在构建期(build)完成,不进客户端 bundle。 - c) 零新依赖:只用 Node 内置模块 + VitePress 内置能力, - 不新增 npm 包(llms.txt 脚本在 node_modules 缺失时也能跑)。 - d) 内容不改版:不改动 143 篇正文的结构,SEO 增强全部在配置层注入。 - --------------------------------------------------------------------------------- -第二部分:审计清单(先查后改,逐项打勾) --------------------------------------------------------------------------------- - -对目标项目执行以下检查(grep/ls 即可,全部只读): - -[ ] 1. public/robots.txt 是否存在? -[ ] 2. public/sitemap.xml 或 vitepress sitemap 配置是否存在? - (查 .vitepress/config 中 grep "sitemap") -[ ] 3. public/llms.txt 是否存在? -[ ] 4. canonical 标签是否存在?(grep 'rel="canonical"' .vitepress/) -[ ] 5. og:image / og:title / JSON-LD 是否存在? - (grep 'og:image\|application/ld+json' .vitepress/) -[ ] 6. 页面级 frontmatter description 覆盖率? - (grep -rl "^description:" zh --include="*.md" | wc -l) - —— 若为 0,说明所有页面共用同一条 meta description(重复内容信号) -[ ] 7. 是否带章节编号前缀?(看任意页 H1 形如 "5.10 字典模块") -[ ] 8. og:url 是否全站固定为首页地址? -[ ] 9. 是否有 404 页面?(ls 404.md) -[ ] 10. HTTPS 是否可用? - curl -sI https://<域名>/ -o /dev/null -w "%{http_code}" - curl 输出 000 或证书告警 → 说明证书不匹配,暂用 http -[ ] 11. 是否有站长平台验证 meta?(baidu-site-verification / google-site-verification) -[ ] 12. :::tip 概述 首句是否适合作为摘要?(抽查 10 页,看是否"答案前置") -[ ] 13. FAQ 类页面的 Q/A 格式是什么?(决定 extractFaqs 是否要加格式分支) -[ ] 14. 静态 twitter:image / og:image 指向的文件是否真实存在? - (Gin 项目 META_IMAGE="/social.png" 但 public/ 下无此文件 → - twitter:image 404,改为实际存在的 /logo.png) -[ ] 15. zh.ts 的 META_TITLE / META_DESCRIPTION 是否与项目技术栈一致? - (Gin 项目误写 "GoFrame v2",属元数据事实错误,一并修正) - -审计结论示例(GoFrame_EleVue 基线,2026-10-25 实测): - robots/sitemap/llms/canonical/og:image/JSON-LD 全部缺失; - 0/143 页有 description;143 页 title 全带编号;无 404; - HTTPS 证书为 CDN 默认证书(*.cdn.myqcloud.com)校验失败; - 内容面强:141/143 页有 :::tip 概述、118 页有表格、139 页有代码块。 - --------------------------------------------------------------------------------- -第三部分:实施步骤(按序执行) --------------------------------------------------------------------------------- - -步骤总览(7 步): - ① 抽取共享层 extract.mjs → ② SEO 钩子 seo.ts → ③ 挂载到 config/index.ts - → ④ titleTemplate → ⑤ robots.txt + 404.md → ⑥ llms.txt 脚本 - → ⑦ package.json 接线 + 生成 - -================================================================================ -步骤 ①:共享提取层 .vitepress/config/extract.mjs(+ extract.d.mts) -================================================================================ - -新建纯 ESM、零依赖文件,被两类调用方复用: - - VitePress 配置(.ts,esbuild 打包时内联它) - - scripts/generate-llms-txt.mjs(Node 直接运行) - -必须导出的五个符号: - -1) SITE_URL —— 全站唯一 URL 真相源 - canonical、og:url、twitter:url、JSON-LD、sitemap hostname、 - llms.txt 链接、robots.txt 的 Sitemap 行全部由它派生。 - 格式:不带尾斜杠,如 "http://docs.goframe.elevue.easygoadmin.vip" - ⚠ 若项目 HTTPS 可用必须用 https;证书有问题才暂用 http, - 并在注释里写明"证书修复后只改这一处"。 - -2) SITE_DESCRIPTION —— 站点级描述(llms.txt 头部引用) - 与 zh.ts 的 META_DESCRIPTION 保持一致(或让 zh.ts 直接 import 它)。 - -3) stripNumberPrefix(title) - 剥离 <title> 中的章节编号前缀,关键词前置: - "5.10 字典模块" → "字典模块" - "K. 贡献指南" → "贡献指南" - 正则(两分支缺一不可): - /^(?:\d+(?:\.\d+)+\s+|\d+\.\s+|[A-Z]\.\s+)/ - 分支说明: - - \d+(?:\.\d+)+\s+ 匹配 "5.10 " "1.6 "(数字.数字 + 空格) - - \d+\.\s+ 匹配 "1. "(单层数字 + 点 + 空格) - - [A-Z]\.\s+ 匹配 "A. " "K. "(附录字母编号) - ⚠ 注意顺序与边界:不能误伤 "404 页面" 这类无编号标题 - (要求编号后必须有点或已是 数字.数字 形态 + 空格)。 - ⚠ 只影响 <title> 和 JSON-LD headline 的 SEO 输出; - 页内 H1 和侧边栏编号保持原样不动。 - -4) extractDescription(md) —— meta description / llms.txt 摘要的唯一来源 - 提取顺序: - a. 剥掉 frontmatter(/^---[\s\S]*?\n---\s*/) - b. 首选 :::tip 容器内正文:/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m - (容器标签后缀不限定"概述",兼容 "说明"、"欢迎贡献" 等变体) - c. 回退到 H1 后第一个普通段落(跳过标题/容器/代码块/表格行) - d. 清洗:压缩空白、去行内 markdown(`code` **bold** [链接](url)) - e. 截断:按中文句末标点(。!?!?)取首句,上限 100 字符; - 截断处避免切碎词(去掉末尾不完整词) - 输出单行纯文本;失败返回 ""(调用方应回退到站点级描述)。 - -5) extractFaqs(md) —— FAQPage JSON-LD 的 Q/A 提取 - 支持两种站内常见格式(按需增删分支): - 格式 A(标题式):### Q:问题\n答案… (切块后答案截到下一个标题) - 格式 B(加粗式):**Q:问题**\n答案… (答案截到下一个 Q 或标题) - 共同点: - - Q/A 文本统一走 cleanText:去代码围栏行、行内 code/bold/链接、压空白 - - 只在命中 ≥1 组时返回,调用方据此决定是否注入 FAQPage - ⚠ 若目标项目的 FAQ 是表格形式(| 症状 | 原因 |),不产出 Q/A, - 就不要强行转换——JSON-LD 必须与页面可见内容一致。 - -同目录新建 extract.d.mts(TypeScript 类型声明,供编辑器识别): - export declare const SITE_URL: string; - export declare const SITE_DESCRIPTION: string; - export declare function stripNumberPrefix(title: string): string; - export declare function extractDescription(md: string): string; - export declare function extractFaqs(md: string): { q: string; a: string }[]; - -⚠ 关键坑(本项目实测踩过): - 注释里不要出现序列 "*/"。形如 zh/**/*.md 的路径写在块注释中, - 其中的 "**/*" 会提前终止注释导致 SyntaxError。 - 改写为 "zh 目录下全部 Markdown" 之类的描述。 - -================================================================================ -步骤 ②:SEO 钩子 .vitepress/config/seo.ts -================================================================================ - -导出 createSeoHooks(sidebar),返回三个 VitePress 构建钩子。 -入参传 zhConfig.themeConfig?.sidebar —— 面包屑结构从侧边栏自动派生, -侧边栏改了面包屑自动跟随,无需第二份清单。 - --------------------------------------------------------------------------------- -2.1 transformPageData(pageData, ctx) —— 页面级 title / description --------------------------------------------------------------------------------- -对每页返回增量对象(VitePress 自动 merge): - - title: stripNumberPrefix(pageData.title) - (pageData.title 已由 VitePress 从 H1/frontmatter 推导) - - description: extractDescription(读取 pageData.relativePath 对应源文件) - 仅当页面没有 frontmatter.description 且提取非空时设置; - 提取失败保持 "" → 渲染时自动回退站点级描述。 -读文件:fs.readFileSync(path.resolve(ctx.siteConfig.srcDir, relativePath)) - -效果: - <title>5.10 字典模块 | EasyGoAdmin - → 字典模块 | EasyGoAdmin 开发文档 - 全站统一文案 - → 每页独立(取该页 tip 首句) - --------------------------------------------------------------------------------- -2.2 transformHead(ctx) —— canonical / OG / twitter / JSON-LD --------------------------------------------------------------------------------- -入参 ctx 含:pageData、title(已拼模板)、description、content(渲染后 HTML)、 -siteConfig(root/srcDir)。 - -前置判断:relativePath 为空或 === "404.md" → 返回 [](404 不做 SEO)。 - -注入的标签清单(13 项 + 1 个 JSON-LD script): - ["link", { rel: "canonical", href: url }] ← 每页独立 canonical - og:type (首页 website,内容页 article) - og:site_name "EasyGoAdmin"(按项目改) - og:locale "zh_CN" - og:title (用去编号后的页标题) - og:description (用步骤①提取的 description) - og:url (= canonical) - og:image (SITE_URL + /logo.png,必须绝对地址) - twitter:title / twitter:description / twitter:url / twitter:image - ["script", { type: "application/ld+json" }, json] ← 见 2.3 - -URL 推导: - index.md → SITE_URL + "/" - 404.md → 跳过 - 其他 → SITE_URL + "/" + relativePath 换 .html 后缀 - (cleanUrls 关闭时正文页带 .html,与侧边栏链接一致) - -⚠ 与静态 head 的去重机制: - VitePress mergeHead 按 meta 标签的"第一个属性值"判重 - (property:"og:url" 视为同一 key)。因此: - - 静态 head 里的 og:url/og:description/twitter:* 会被本钩子的 - 同 key 输出覆盖(构建期),无需手动删除静态项; - - 静态 twitter:card(本钩子不输出)会保留 —— 正确行为。 - ⚠ 本钩子是 build-only("runs before writing HTML to dist"), - dev server 页面上看不到这些标签是正常的,用 docs:build 验收。 - --------------------------------------------------------------------------------- -2.3 JSON-LD 结构化数据(单 script,顶层对象或数组) --------------------------------------------------------------------------------- -统一转义:JSON.stringify(...).replace(/ 破坏注入;< 在 JSON 字符串内合法。) - -【首页 index.md】 - 1. WebSite:name、url、inLanguage、description - 2. Organization:name、url(官网)、logo、sameAs(Gitee 仓库 + 官网) - -【内容页】 - 1. TechArticle: - headline = 页面 H1 纯文本(去编号!与 一致,关键词前置; - 提取顺序:源 Markdown H1 → 渲染 HTML <h1> → 兜底 title) - description、url(= canonical)、inLanguage: "zh-CN" - mainEntityOfPage: { @type: WebPage, @id: url } - publisher: Organization 引用 - dateModified: git 最后提交时间(见 2.4,无则省略字段) - 2. BreadcrumbList(侧边栏派生): - itemListElement = [ - { position:1, name:"首页", item: SITE_URL + "/" }, - ...章节链(每级 name=去 emoji 的侧边栏文案), - 末级 { name: 页面名, item: 当前页 url } - ] - 中间章节级无自己 URL → 只给 name 不给 item(schema.org 合法)。 - 侧边栏文案去前导 emoji:/^[^\p{L}\p{N}]+/u 清洗。 - 3. FAQPage(仅当 extractFaqs 命中): - mainEntity = [{ @type:Question, name:Q, - acceptedAnswer:{ @type:Answer, text:A } }] - ⚠ Google 已收窄 FAQ 富结果资格,但 JSON-LD 对 GEO(AI 抽取) - 仍然有效,且必须与页面可见 Q/A 一致。 - --------------------------------------------------------------------------------- -2.4 git 时间线(dateModified + sitemap lastmod 共用) --------------------------------------------------------------------------------- -单次 git 命令构建全量映射(避免每页一次进程开销): - git log --format=:::%cI --name-only --relative -n 500 -- . - cwd = 项目根(ctx.siteConfig.root) - -解析:以 ":::" 开头的行是提交时间(ISO 8601), - 其后普通行是文件路径(git log 新→旧,首见即最新); - key = 相对路径(--relative 保证与 pageData.relativePath 同形)。 - -降级策略:git 不可用 / 文件无提交记录 → 静默省略 lastmod/dateModified, - 不阻塞构建。模块级缓存 Map,整个 build 只跑一次。 - --------------------------------------------------------------------------------- -2.5 sitemap = { hostname, transformItems } --------------------------------------------------------------------------------- -VitePress 1.x 内置 sitemap 选项(config 顶层): - sitemap: { - hostname: SITE_URL, - transformItems(items) { - // ⚠ 关键实测(Gin_EleVue 首次构建暴露): - // transformItems 收到的 item.url 是【相对路径】—— - // "404.html"、"zh/core/dict.html"、""(首页), - // 无前导斜杠、无域名;hostname 是之后 SitemapStream 才拼的。 - // 所以 endsWith("/404.html") 永远不命中(冒烟测试若用完整 - // URL 传参会产生假阳性!测试必须模拟真实形态)。 - // 1) 过滤 404:/(^|\/)404\.html$/.test(item.url) → 丢弃 - // 2) 过滤 README:仓库根 README.md 会被 vitepress 当页面收录 - // (README.html 不在侧边栏、属仓库自述),同样 /^...README\.html$/ 过滤 - // 3) url → 相对路径反推:去尾 .html 加 .md; - // "" 或 "index.html" → "index.md" - // 4) 查 git 映射注入 lastmod;查不到保持原样 - // (部分新文件/未提交文件无 lastmod 属正常降级, - // Gin 实测 154 条中 126 条有 lastmod) - } - } -产出:构建后 dist/sitemap.xml(dev 模式不存在,属正常)。 - -================================================================================ -步骤 ③:挂载到 .vitepress/config/index.ts -================================================================================ - - import { createSeoHooks } from "./seo"; - const seo = createSeoHooks(zhConfig.themeConfig?.sidebar); - - export default defineConfig({ - ...sharedConfig, - transformPageData: seo.transformPageData, - transformHead: seo.transformHead, - sitemap: seo.sitemap, - ... - }); - -⚠ createSeoHooks 必须在模块顶层调用(sitemap.transformItems 无 ctx, - 依赖 transformPageData 先把 projectRoot 记下来;兜底 process.cwd())。 - -================================================================================ -步骤 ④:titleTemplate(config/shared.ts) -================================================================================ - - export const sharedConfig = defineConfig({ - title: "EasyGoAdmin", - titleTemplate: "EasyGoAdmin 开发文档", ← 新增 - ... - }); - -效果:<title>字典模块 | EasyGoAdmin 开发文档 -机制:VitePress createTitle 中 页面 frontmatter.titleTemplate 优先, - 其次站点级 titleTemplate,最后兜底 " | 站点title"。 - 首页 index.md 自带 titleTemplate(frontmatter)→ 不受影响。 -⚠ 站点名若与 titleTemplate 相同会被去重(createTitleTemplate 的 - siteTitle === template 分支),保持两者不同。 - -================================================================================ -步骤 ⑤:robots.txt + 404.md -================================================================================ - -【public/robots.txt】(静态文件,直接进 dist) - User-agent: * - Allow: / - - # 显式放行生成式引擎 / AI 爬虫(GEO) - User-agent: GPTBot - Allow: / - User-agent: ChatGPT-User - Allow: / - User-agent: ClaudeBot - Allow: / - User-agent: Claude-Web - Allow: / - User-agent: anthropic-ai - Allow: / - User-agent: PerplexityBot - Allow: / - User-agent: Google-Extended - Allow: / - User-agent: Bytespider - Allow: / - User-agent: bingbot - Allow: / - - Sitemap: /sitemap.xml - ⚠ Sitemap 行的地址必须与 extract.mjs 的 SITE_URL 一致(手工同步)。 - -【404.md】(项目根目录,VitePress 自动识别为 404 页) - --- - head: - - - meta - - name: robots - content: noindex - --- - # 页面不存在 - - 抱歉,您访问的页面不存在或已被移动。 - - [返回首页](/) · [浏览文档](/zh/introduction) - ⚠ 首个 frontmatter head 例外:正文页约定"无 frontmatter", - 404 页因需要 noindex 豁免。 - ⚠ 链接风格与全站一致(站内相对/根路径),死链检查才能通过。 - -================================================================================ -步骤 ⑥:llms.txt 生成脚本 scripts/generate-llms-txt.mjs -================================================================================ - -零依赖 Node 脚本,import extract.mjs 复用 SITE_URL/SITE_DESCRIPTION/ -extractDescription(与 meta description 同源,绝不手写两份摘要)。 - -流程: - 1. 递归收集 zh/ 下全部 .md(排除 404,404 不在 zh/ 天然排除) - 2. 每页取:首个 H1(保留编号,便于 AI 理解章节位置) - extractDescription 首句(作为条目摘要) - url = SITE_URL + /zh/<相对路径>.html - 3. 按顶层目录映射到侧边栏章节分组(SECTIONS 常量数组, - 与 config/zh.ts 的侧边栏章名保持一致;映射不到的目录兜底"附录") - 4. 组内按 H1 编号排序(数字分支按数值、字母分支按码点、无编号最后) - 5. 输出 llmstxt.org 标准格式: - - # <站点标题> - > 。<能力一句话>。本文档共 N 篇,按章节组织。 - - ## <章节名> - - [H1](url): 首句摘要 - ... - 6. 写入 public/llms.txt,stdout 打印页数/章节数 - -⚠ 同一章节名可能由多个目录合并(如 appendix + other 同为"附录"), - 输出时按 label 去重只打印一次。 -⚠ 脚本内不要在块注释写 "zh/**/*.md"(见步骤①的 */ 坑)。 - -================================================================================ -步骤 ⑦:package.json 接线 + 首次生成 -================================================================================ - - "scripts": { - "docs": "vitepress dev .", - "docs:llms": "node scripts/generate-llms-txt.mjs", - "docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .", - "docs:preview": "vitepress preview ." - } - -- docs:build 前置生成 llms.txt(保证部署产物永远最新) -- 新增/改名页面后若只要刷新索引:yarn docs:llms -- ⚠ Windows 下 yarn(cmd)执行 "A && B" 可用;不要改成分号。 - --------------------------------------------------------------------------------- -步骤 ⑧(收尾):更新 CLAUDE.md --------------------------------------------------------------------------------- -把以下约定写进项目 CLAUDE.md,防止后续改动破坏 SEO: - 1. :::tip 首句 = meta description + llms.txt 条目,须"答案前置"。 - 2. 编号冻结:章节编号同时被侧边栏、正文互引("详见 5.10")、 - JSON-LD 面包屑、llms.txt、外链锚文本引用 —— 发布后禁止重排。 - 3. 新增页面的 checklist:写 H1 编号 → 加 tip 概述 → - zh.ts 侧边栏登记 → (可选)yarn docs:llms。 - 4. 记录 SEO 工具链位置与 SITE_URL 单点修改约定。 - --------------------------------------------------------------------------------- -第四部分:验收标准 --------------------------------------------------------------------------------- - -A. 无 node_modules 的快速冒烟(Node ≥ 22.18,type stripping) - node --experimental-strip-types -e " - import('./.vitepress/config/seo.ts').then(async ({ createSeoHooks }) => { - const zh = await import('./.vitepress/config/zh.ts'); - const h = createSeoHooks(zh.zhConfig.themeConfig?.sidebar); - console.log(typeof h.transformPageData, typeof h.transformHead, typeof h.sitemap); - })" - -B. 完整验收(需 yarn install 后) - 1) yarn docs:build 无报错(死链检查通过) - 2) dist/sitemap.xml 存在,含 lastmod,无 404.html 与 README.html 条目 - (lastmod 覆盖率允许 <100%:未提交的新文件无 git 记录,属正常降级) - 3) dist/robots.txt、dist/llms.txt 存在 - 4) 抽查任意内容页 dist/zh/xxx.html 的 : - - 无编号前缀、带 titleTemplate 后缀 - - <meta name="description"> 是该页 tip 首句(非全站统一文案) - - <link rel="canonical"> 指向本页完整 URL - - og:title/og:description/og:url/og:image 与页面一致且 URL 为绝对地址 - - <script type="application/ld+json"> 可被 JSON.parse, - 含 TechArticle + BreadcrumbList(FAQ 页多 FAQPage) - - TechArticle.headline 与页面 H1 一致(或至少无编号差异过大) - 5) 首页含 WebSite + Organization - 6) 404.html 含 noindex,且不出现在 sitemap - 7) llms.txt 条目数 == zh 下 md 文件数,链接可访问 - 8) 用浏览器扩展(如 JSON-LD Viewer)验证结构化数据无报错 - -C. 提交前人工项(脚本做不了) - 1) 百度/Google 站长平台验证 meta(拿到验证码后加进 shared/zh head) - 2) 百度站长"主动推送"提交首页(百度对 sitemap 支持弱,主动推送是主收录手段) - 3) HTTPS 证书修复后:改 extract.mjs 的 SITE_URL + zh.ts 的 META_URL, - robots.txt 的 Sitemap 行同步,重新 docs:build - --------------------------------------------------------------------------------- -第五部分:移植到其他 15 个项目时的差异点 --------------------------------------------------------------------------------- - -同构不变量(直接照搬): - - extract.mjs / extract.d.mts / seo.ts / generate-llms-txt.mjs - 四个文件整体复制(它们不依赖具体侧边栏内容,sidebar 以参数传入) - - index.ts 挂载三行、shared.ts titleTemplate、package.json 三行脚本 - - robots.txt / 404.md 模板 - -必须按项目修改的点(清单): - [ ] SITE_URL:每个子域不同(docs.gin.elevue.... / docs.beego.layui....) - [ ] SITE_DESCRIPTION / titleTemplate / og:site_name:若各项目文案不同 - [ ] extract.mjs 中 Organization 的 url/sameAs:仓库地址不同 - (EasyGoAdmin_Gin_EleVue → 对应 Gitee 仓库路径) - [ ] scripts/generate-llms-txt.mjs 的 SECTIONS 目录→章节映射: - 核对目标项目的 zh/ 顶层目录与侧边栏章名(各前端/后端组合 - 章节编号可能不同,以该项目 zh.ts 侧边栏为准) - [ ] extractFaqs 的 Q/A 格式分支:先抽查目标项目 FAQ 页格式, - 表格式 FAQ 不要强转 - [ ] robots.txt 的 Sitemap 行域名 - [ ] llms.txt 头部章节数 N 是脚本自动统计,无需手改 - [ ] 404.md 内的示例链接指向目标项目实际存在的首页/入门页 - [ ] zh.ts 若有静态 og/twitter meta,确认与 transformHead 输出 - key 一致会被覆盖(一般无需删;不放心可删掉静态项) - -版本差异注意: - - VitePress 需 ≥ 1.0(transformPageData/transformHead/sitemap 选项 - 1.x 均有;本方案在 1.3.4 实测)。若他项目版本更老,先核对 - node_modules/vitepress/dist/node/index.d.ts 中是否存在 - sitemap / transformHead / transformPageData 三个字段。 - - 侧边栏结构假设为 { "/zh/": [ {text, items:[{text,link}|{text,items}]} ] } - 的标准 DefaultTheme 形态;若他项目 sidebar 是数组形态或 - 多语言 key 不同("/en/"),buildCrumbMap 已兼容数组入参, - 但 key 需与路由前缀一致(当前写死 "/zh/",改多语言时参数化)。 - --------------------------------------------------------------------------------- -第六部分:实测数据与已知限制(基线项目) --------------------------------------------------------------------------------- - -实测(2026-10-25,GoFrame_EleVue): - - llms.txt:143 页 / 13 章节 / 32KB,生成耗时 < 1s - - 冒烟测试:13 个 head 标签注入、TechArticle+BreadcrumbList - (首页 > 5. 核心功能 > 数据与配置 > 5.10 字典模块)、 - FAQPage 24 问(appendix 加粗式)/ 18 问(quickstart 标题式)、 - git dateModified 与 sitemap lastmod 正常、404 跳过、 - sitemap 过滤 404 —— 全部通过 - -移植实测(2026-10-25,EasyGoAdmin_Gin_EleVue,按本标准全流程执行): - - 审计:与基线同构缺失;额外发现 META_IMAGE 指向不存在的 social.png、 - META_TITLE/META_DESCRIPTION 误写 GoFrame、HTTPS 同样证书不匹配 - - 差异点仅 6 处:SITE_URL、Gitee 仓库地址、SITE_DESCRIPTION/META 文案、 - llms 脚本标题行、robots Sitemap 行、(复核)SECTIONS 章节映射 - ——zh/ 目录结构与侧边栏章节与基线完全一致,映射无需改动 - - FAQ 4 个页全为 **Q:** 加粗式(基线为两种混用),既有 extractFaqs 直接命中 - - 完整构建验收(yarn docs:build,21s): - sitemap 154 条(过滤 404+README 后)、lastmod 126/154、 - 内容页 title/canonical/OG/JSON-LD 全通过、 - 首页 WebSite+Organization、404 noindex 且不入 sitemap、 - llms.txt 153 条 == zh 下 md 数、FAQPage 11 问 - - 首次构建暴露 transformItems 相对路径坑(见 2.5),修复后复验通过 - -已知限制: - 1. HTTPS:docs 子域 443 返回腾讯 CDN 默认证书(*.cdn.myqcloud.com), - curl 校验失败,全站暂用 http。修复后按步骤⑦-C3 单点切换。 - 2. transformHead/transformHead/sitemap 均为 build-only, - dev server 下看不到增强标签,验收必须以 docs:build 产物为准。 - 3. og:image 用的 logo.png(方形),非 1200×630 社交卡片尺寸 —— - 可后续出专用卡片图后只改 og:image 一处。 - 4. 百度/Google 站长验证与主动推送需要平台账号,未包含在本方案。 - 5. 站点验证 meta 未加(无验证码);若后续加,放 shared.ts head。 - 6. 未启用 VitePress lastUpdated 主题显示(与 SEO 无关,另议)。 - --------------------------------------------------------------------------------- -附录:一页速查(执行顺序) --------------------------------------------------------------------------------- - 1. 跑第二部分审计清单,记录结论 - 2. 复制/调整 extract.mjs + extract.d.mts(改 SITE_URL 等 9 个差异点) - 3. 复制 seo.ts,核对 FAQ 格式分支与侧边栏 key - 4. index.ts 挂 3 行;shared.ts 加 titleTemplate - 5. 放 robots.txt(改域名)+ 404.md(改链接) - 6. 复制 generate-llms-txt.mjs(核对 SECTIONS 映射)→ yarn docs:llms - 7. package.json 改 3 行脚本 - 8. yarn install && yarn docs:build,按第四部分 B 逐项验收 - 9. 更新该项目 CLAUDE.md(tip 首句约定 + 编号冻结) - 10. 站长平台验证/推送(人工) - -(完) diff --git a/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化.txt b/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化.txt deleted file mode 100644 index deadfaa..0000000 --- a/EasyGoAdmin_GoFrame_EleVue/搜索引擎优化.txt +++ /dev/null @@ -1,902 +0,0 @@ -================================================================================ -EasyGoAdmin 文档站 SEO / GEO 更新标准 -================================================================================ -版本:1.0(2026-10-25) -适用:EasyGoAdmin_{Beego,Gin,GoFrame,Iris}_{AntdVue,EleVue,Layui,NaiveVue} - 等 16 个 VitePress 文档子项目(结构高度同构,可直接照搬) -基线项目:EasyGoAdmin_GoFrame_EleVue -本文档定位:后续为其他项目做相同更新时的执行标准(审计清单 → 实施步骤 → - 验收标准 → 移植注意事项)。 - --------------------------------------------------------------------------------- -第一部分:概念与目标 --------------------------------------------------------------------------------- - -1.1 SEO(Search Engine Optimization,搜索引擎优化) - 面向传统搜索引擎(百度、Google、Bing)的爬虫与排序: - - 让爬虫能发现、抓取、理解每一个页面(技术 SEO) - - 让每个页面有独立、准确的标题与摘要(页面元数据) - - 让页面之间有清晰的权重传递结构(内链、面包屑、sitemap) - - 用结构化数据告诉搜索引擎"这是什么类型的内容"(JSON-LD) - -1.2 GEO(Generative Engine Optimization,生成式引擎优化) - 面向 AI 引擎(ChatGPT、Claude、Perplexity、豆包、Kimi、AI Overviews) - 的爬虫与答案引用: - - 让 AI 爬虫明确被允许抓取(robots.txt 显式放行) - - 提供"给模型看的站点摘要"(llms.txt,llmstxt.org 标准) - - 页面内容"答案前置",首段即可被直接引用(:::tip 概述 首句) - - 结构化 Q/A(FAQPage JSON-LD)便于引擎抽取 - - 时效信号(dateModified / sitemap lastmod)影响引用优先级 - -1.3 本方案的设计原则 - a) 单一真相源:URL、站点描述、提取逻辑只写一处,多处复用。 - b) 零运行时开销:全部在构建期(build)完成,不进客户端 bundle。 - c) 零新依赖:只用 Node 内置模块 + VitePress 内置能力, - 不新增 npm 包(llms.txt 脚本在 node_modules 缺失时也能跑)。 - d) 内容不改版:不改动正文的结构,SEO 增强全部在配置层注入。 - e) 文档缺失优先补文档:发现页面自身缺失(无 H1、无 :::tip 概述) - 时,优先按内容约定修复文档本身(这正是 SEO 的内容基础), - 而不是在代码里加回退逻辑绕过。配置层只处理"派生"类增强 - (编号剥离、摘要截取、结构化数据),不掩盖内容缺陷。 - --------------------------------------------------------------------------------- -第二部分:审计清单(先查后改,逐项打勾) --------------------------------------------------------------------------------- - -对目标项目执行以下检查(grep/ls 即可,全部只读): - -[ ] 1. public/robots.txt 是否存在? -[ ] 2. public/sitemap.xml 或 vitepress sitemap 配置是否存在? - (查 .vitepress/config 中 grep "sitemap") -[ ] 3. public/llms.txt 是否存在? -[ ] 4. canonical 标签是否存在?(grep 'rel="canonical"' .vitepress/) -[ ] 5. og:image / og:title / JSON-LD 是否存在? - (grep 'og:image\|application/ld+json' .vitepress/) -[ ] 6. 页面级 frontmatter description 覆盖率? - (grep -rl "^description:" zh --include="*.md" | wc -l) - —— 若为 0,说明所有页面共用同一条 meta description(重复内容信号) -[ ] 7. <title> 是否带章节编号前缀?(看任意页 H1 形如 "5.10 字典模块") -[ ] 8. og:url 是否全站固定为首页地址? -[ ] 9. 是否有 404 页面?(ls 404.md) -[ ] 10. HTTPS 是否可用? - curl -sI https://<域名>/ -o /dev/null -w "%{http_code}" - curl 输出 000 或证书告警 → 说明证书不匹配,暂用 http -[ ] 11. 是否有站长平台验证 meta?(baidu-site-verification / google-site-verification) -[ ] 12. :::tip 概述 首句是否适合作为摘要?(抽查 10 页,看是否"答案前置") -[ ] 13. FAQ 类页面的 Q/A 格式是什么?(决定 extractFaqs 是否要加格式分支) -[ ] 14. 静态 twitter:image / og:image 指向的文件是否真实存在? - (Gin 项目 META_IMAGE="/social.png" 但 public/ 下无此文件 → - twitter:image 404,改为实际存在的 /logo.png) -[ ] 15. zh.ts 的 META_TITLE / META_DESCRIPTION 是否与项目技术栈一致? - (Gin 项目误写 "GoFrame v2",属元数据事实错误,一并修正) -[ ] 16. H1 覆盖率(【围栏感知 + 首个 ## 前】规则,第18项起修正): - 正则 ^# 会把代码块内 "# 注释" 误判为 H1——必须跳过 ``` 围栏、 - 且真 H1 须出现在首个 ## 之前(rbac 类页有游离 # 行会漏判)。 - python 扫描脚本见第18项记录;缺失后果:<title>/headline - 降级或取到代码注释垃圾、llms 少条或链接文本为 .py 路径。 -[ ] 17. :::tip 覆盖率:grep -rL ":::tip" zh --include="*.md" 是否为空? - (缺 tip 的页 description 只能回退首段,摘要质量下降; - 按 e) 原则逐页补 tip 概述,而不是改代码适配) -[ ] 18. Gitee/editLink 仓库链接与项目身份是否一致? - (FastAPI 系仓库名统一大写 FastAPI——DjangoAdmin_FastAPI_Xxx, - DjangoAdmin 项目正文版本表引用亦须大写) - (TP8_NaiveVue 项目 6 处链接全部指向 ThinkPhp6_NaiveVue—— - 克隆自 TP6 项目未改,editLink/社交链接/开源协议入口全错; - 检查:grep -rn "gitee.com" .vitepress/config/ 对照项目名) - 修复后 seo.ts 的 Organization.sameAs 也必须用正确仓库。 - -审计结论示例(GoFrame_EleVue 基线,2026-10-25 实测): - robots/sitemap/llms/canonical/og:image/JSON-LD 全部缺失; - 0/143 页有 description;143 页 title 全带编号;无 404; - HTTPS 证书为 CDN 默认证书(*.cdn.myqcloud.com)校验失败; - 内容面强:141/143 页有 :::tip 概述、118 页有表格、139 页有代码块。 - --------------------------------------------------------------------------------- -第三部分:实施步骤(按序执行) --------------------------------------------------------------------------------- - -步骤总览(7 步): - ① 抽取共享层 extract.mjs → ② SEO 钩子 seo.ts → ③ 挂载到 config/index.ts - → ④ titleTemplate → ⑤ robots.txt + 404.md → ⑥ llms.txt 脚本 - → ⑦ package.json 接线 + 生成 - -================================================================================ -步骤 ①:共享提取层 .vitepress/config/extract.mjs(+ extract.d.mts) -================================================================================ - -新建纯 ESM、零依赖文件,被两类调用方复用: - - VitePress 配置(.ts,esbuild 打包时内联它) - - scripts/generate-llms-txt.mjs(Node 直接运行) - -必须导出的五个符号: - -1) SITE_URL —— 全站唯一 URL 真相源 - canonical、og:url、twitter:url、JSON-LD、sitemap hostname、 - llms.txt 链接、robots.txt 的 Sitemap 行全部由它派生。 - 格式:不带尾斜杠,如 "http://docs.goframe.elevue.easygoadmin.vip" - ⚠ 若项目 HTTPS 可用必须用 https;证书有问题才暂用 http, - 并在注释里写明"证书修复后只改这一处"。 - -2) SITE_DESCRIPTION —— 站点级描述(llms.txt 头部引用) - 与 zh.ts 的 META_DESCRIPTION 保持一致(或让 zh.ts 直接 import 它)。 - -3) stripNumberPrefix(title) - 剥离 <title> 中的章节编号前缀,关键词前置: - "5.10 字典模块" → "字典模块" - "K. 贡献指南" → "贡献指南" - 正则(两分支缺一不可): - /^(?:\d+(?:\.\d+)+\s+|\d+\.\s+|[A-Z]\.\s+)/ - 分支说明: - - \d+(?:\.\d+)+\s+ 匹配 "5.10 " "1.6 "(数字.数字 + 空格) - - \d+\.\s+ 匹配 "1. "(单层数字 + 点 + 空格) - - [A-Z]\.\s+ 匹配 "A. " "K. "(附录字母编号) - ⚠ 注意顺序与边界:不能误伤 "404 页面" 这类无编号标题 - (要求编号后必须有点或已是 数字.数字 形态 + 空格)。 - ⚠ 只影响 <title> 和 JSON-LD headline 的 SEO 输出; - 页内 H1 和侧边栏编号保持原样不动。 - -4) extractDescription(md) —— meta description / llms.txt 摘要的唯一来源 - 提取顺序(frontmatter 若声明 description 则直接采用,优先于以下各步): - a. 剥掉 frontmatter(/^---[\s\S]*?\n---\s*/) - b. 首选 :::tip 容器内正文:/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m - (容器标签后缀不限定"概述",兼容 "说明"、"欢迎贡献" 等变体) - c. 回退到 H1 后第一个普通段落(跳过标题/容器/代码块/表格行) - d. 清洗:压缩空白、去行内 markdown(`code` **bold** [链接](url)) - e. 截断:按中文句末标点(。!?!?)取首句,上限 100 字符; - 截断处避免切碎词(去掉末尾不完整词) - 输出单行纯文本;失败返回 ""(调用方应回退到站点级描述)。 - -5) extractFaqs(md) —— FAQPage JSON-LD 的 Q/A 提取 - 支持两种站内常见格式(按需增删分支): - 格式 A(标题式):### Q:问题\n答案… (切块后答案截到下一个标题) - 格式 B(加粗式):**Q:问题**\n答案… (答案截到下一个 Q 或标题) - 共同点: - - Q/A 文本统一走 cleanText:去代码围栏行、行内 code/bold/链接、压空白 - - 只在命中 ≥1 组时返回,调用方据此决定是否注入 FAQPage - ⚠ 若目标项目的 FAQ 是表格形式(| 症状 | 原因 |),不产出 Q/A, - 就不要强行转换——JSON-LD 必须与页面可见内容一致。 - -同目录新建 extract.d.mts(TypeScript 类型声明,供编辑器识别): - export declare const SITE_URL: string; - export declare const SITE_DESCRIPTION: string; - export declare function stripNumberPrefix(title: string): string; - export declare function extractDescription(md: string): string; - export declare function extractFaqs(md: string): { q: string; a: string }[]; - -⚠ 关键坑(本项目实测踩过): - 注释里不要出现序列 "*/"。形如 zh/**/*.md 的路径写在块注释中, - 其中的 "**/*" 会提前终止注释导致 SyntaxError。 - 改写为 "zh 目录下全部 Markdown" 之类的描述。 - -================================================================================ -步骤 ②:SEO 钩子 .vitepress/config/seo.ts -================================================================================ - -导出 createSeoHooks(sidebar),返回三个 VitePress 构建钩子。 -入参传 zhConfig.themeConfig?.sidebar —— 面包屑结构从侧边栏自动派生, -侧边栏改了面包屑自动跟随,无需第二份清单。 - --------------------------------------------------------------------------------- -2.1 transformPageData(pageData, ctx) —— 页面级 title / description --------------------------------------------------------------------------------- -对每页返回增量对象(VitePress 自动 merge): - - title: stripNumberPrefix(pageData.title) - (pageData.title 已由 VitePress 从 H1/frontmatter 推导) - - description: extractDescription(读取 pageData.relativePath 对应源文件) - 仅当页面没有 frontmatter.description 且提取非空时设置; - 提取失败保持 "" → 渲染时自动回退站点级描述。 -读文件:fs.readFileSync(path.resolve(ctx.siteConfig.srcDir, relativePath)) - -效果: - <title>5.10 字典模块 | EasyGoAdmin - → 字典模块 | EasyGoAdmin 开发文档 - 全站统一文案 - → 每页独立(取该页 tip 首句) - --------------------------------------------------------------------------------- -2.2 transformHead(ctx) —— canonical / OG / twitter / JSON-LD --------------------------------------------------------------------------------- -入参 ctx 含:pageData、title(已拼模板)、description、content(渲染后 HTML)、 -siteConfig(root/srcDir)。 - -前置判断:relativePath 为空或 === "404.md" → 返回 [](404 不做 SEO)。 - -注入的标签清单(13 项 + 1 个 JSON-LD script): - ["link", { rel: "canonical", href: url }] ← 每页独立 canonical - og:type (首页 website,内容页 article) - og:site_name "EasyGoAdmin"(按项目改) - og:locale "zh_CN" - og:title (用去编号后的页标题) - og:description (用步骤①提取的 description) - og:url (= canonical) - og:image (SITE_URL + /logo.png,必须绝对地址) - twitter:title / twitter:description / twitter:url / twitter:image - ["script", { type: "application/ld+json" }, json] ← 见 2.3 - -URL 推导: - index.md → SITE_URL + "/" - 404.md → 跳过 - 其他 → SITE_URL + "/" + relativePath 换 .html 后缀 - (cleanUrls 关闭时正文页带 .html,与侧边栏链接一致) - -⚠ 与静态 head 的去重机制: - VitePress mergeHead 按 meta 标签的"第一个属性值"判重 - (property:"og:url" 视为同一 key)。因此: - - 静态 head 里的 og:url/og:description/twitter:* 会被本钩子的 - 同 key 输出覆盖(构建期),无需手动删除静态项; - - 静态 twitter:card(本钩子不输出)会保留 —— 正确行为。 - ⚠ 本钩子是 build-only("runs before writing HTML to dist"), - dev server 页面上看不到这些标签是正常的,用 docs:build 验收。 - --------------------------------------------------------------------------------- -2.3 JSON-LD 结构化数据(单 script,顶层对象或数组) --------------------------------------------------------------------------------- -统一转义:JSON.stringify(...).replace(/ 破坏注入;< 在 JSON 字符串内合法。) - -【首页 index.md】 - 1. WebSite:name、url、inLanguage、description - 2. Organization:name、url(官网)、logo、sameAs(Gitee 仓库 + 官网) - -【内容页】 - 1. TechArticle: - headline = 页面 H1 纯文本(去编号!与 一致,关键词前置; - 提取顺序:源 Markdown H1 → 渲染 HTML <h1> → 兜底 title) - description、url(= canonical)、inLanguage: "zh-CN" - mainEntityOfPage: { @type: WebPage, @id: url } - publisher: Organization 引用 - dateModified: git 最后提交时间(见 2.4,无则省略字段) - 2. BreadcrumbList(侧边栏派生): - itemListElement = [ - { position:1, name:"首页", item: SITE_URL + "/" }, - ...章节链(每级 name=去 emoji 的侧边栏文案), - 末级 { name: 页面名, item: 当前页 url } - ] - 中间章节级无自己 URL → 只给 name 不给 item(schema.org 合法)。 - 侧边栏文案去前导 emoji:/^[^\p{L}\p{N}]+/u 清洗。 - 3. FAQPage(仅当 extractFaqs 命中): - mainEntity = [{ @type:Question, name:Q, - acceptedAnswer:{ @type:Answer, text:A } }] - ⚠ Google 已收窄 FAQ 富结果资格,但 JSON-LD 对 GEO(AI 抽取) - 仍然有效,且必须与页面可见 Q/A 一致。 - --------------------------------------------------------------------------------- -2.4 git 时间线(dateModified + sitemap lastmod 共用) --------------------------------------------------------------------------------- -单次 git 命令构建全量映射(避免每页一次进程开销): - git log --format=:::%cI --name-only --relative -n 500 -- . - cwd = 项目根(ctx.siteConfig.root) - -解析:以 ":::" 开头的行是提交时间(ISO 8601), - 其后普通行是文件路径(git log 新→旧,首见即最新); - key = 相对路径(--relative 保证与 pageData.relativePath 同形)。 - -降级策略:git 不可用 / 文件无提交记录 → 静默省略 lastmod/dateModified, - 不阻塞构建。模块级缓存 Map,整个 build 只跑一次。 - --------------------------------------------------------------------------------- -2.5 sitemap = { hostname, transformItems } --------------------------------------------------------------------------------- -VitePress 1.x 内置 sitemap 选项(config 顶层): - sitemap: { - hostname: SITE_URL, - transformItems(items) { - // ⚠ 关键实测(Gin_EleVue 首次构建暴露): - // transformItems 收到的 item.url 是【相对路径】—— - // "404.html"、"zh/core/dict.html"、""(首页), - // 无前导斜杠、无域名;hostname 是之后 SitemapStream 才拼的。 - // 所以 endsWith("/404.html") 永远不命中(冒烟测试若用完整 - // URL 传参会产生假阳性!测试必须模拟真实形态)。 - // 1) 过滤 404:/(^|\/)404\.html$/.test(item.url) → 丢弃 - // 2) 过滤 README:仓库根 README.md 会被 vitepress 当页面收录 - // (README.html 不在侧边栏、属仓库自述),同样 /^...README\.html$/ 过滤 - // 3) url → 相对路径反推:去尾 .html 加 .md; - // "" 或 "index.html" → "index.md" - // 4) 查 git 映射注入 lastmod;查不到保持原样 - // (部分新文件/未提交文件无 lastmod 属正常降级, - // Gin 实测 154 条中 126 条有 lastmod) - } - } -产出:构建后 dist/sitemap.xml(dev 模式不存在,属正常)。 - -================================================================================ -步骤 ③:挂载到 .vitepress/config/index.ts -================================================================================ - - import { createSeoHooks } from "./seo"; - const seo = createSeoHooks(zhConfig.themeConfig?.sidebar); - - export default defineConfig({ - ...sharedConfig, - transformPageData: seo.transformPageData, - transformHead: seo.transformHead, - sitemap: seo.sitemap, - ... - }); - -⚠ createSeoHooks 必须在模块顶层调用(sitemap.transformItems 无 ctx, - 依赖 transformPageData 先把 projectRoot 记下来;兜底 process.cwd())。 - -================================================================================ -步骤 ④:titleTemplate(config/shared.ts) -================================================================================ - - export const sharedConfig = defineConfig({ - title: "EasyGoAdmin", - titleTemplate: "EasyGoAdmin 开发文档", ← 新增 - ... - }); - -效果:<title>字典模块 | EasyGoAdmin 开发文档 -机制:VitePress createTitle 中 页面 frontmatter.titleTemplate 优先, - 其次站点级 titleTemplate,最后兜底 " | 站点title"。 - 首页 index.md 自带 titleTemplate(frontmatter)→ 不受影响。 -⚠ 站点名若与 titleTemplate 相同会被去重(createTitleTemplate 的 - siteTitle === template 分支),保持两者不同。 - -================================================================================ -步骤 ⑤:robots.txt + 404.md -================================================================================ - -【public/robots.txt】(静态文件,直接进 dist) - User-agent: * - Allow: / - - # 显式放行生成式引擎 / AI 爬虫(GEO) - User-agent: GPTBot - Allow: / - User-agent: ChatGPT-User - Allow: / - User-agent: ClaudeBot - Allow: / - User-agent: Claude-Web - Allow: / - User-agent: anthropic-ai - Allow: / - User-agent: PerplexityBot - Allow: / - User-agent: Google-Extended - Allow: / - User-agent: Bytespider - Allow: / - User-agent: bingbot - Allow: / - - Sitemap: /sitemap.xml - ⚠ Sitemap 行的地址必须与 extract.mjs 的 SITE_URL 一致(手工同步)。 - -【404.md】(项目根目录,VitePress 自动识别为 404 页) - --- - head: - - - meta - - name: robots - content: noindex - --- - # 页面不存在 - - 抱歉,您访问的页面不存在或已被移动。 - - [返回首页](/) · [浏览文档](/zh/introduction) - ⚠ 首个 frontmatter head 例外:正文页约定"无 frontmatter", - 404 页因需要 noindex 豁免。 - ⚠ 链接风格与全站一致(站内相对/根路径),死链检查才能通过。 - -================================================================================ -步骤 ⑥:llms.txt 生成脚本 scripts/generate-llms-txt.mjs -================================================================================ - -零依赖 Node 脚本,import extract.mjs 复用 SITE_URL/SITE_DESCRIPTION/ -extractDescription(与 meta description 同源,绝不手写两份摘要)。 - -流程: - 1. 递归收集 zh/ 下全部 .md(排除 404,404 不在 zh/ 天然排除) - 2. 每页取:首个 H1(保留编号,便于 AI 理解章节位置) - extractDescription 首句(作为条目摘要) - url = SITE_URL + /zh/<相对路径>.html - 3. 按顶层目录映射到侧边栏章节分组(SECTIONS 常量数组, - 与 config/zh.ts 的侧边栏章名保持一致;映射不到的目录兜底"附录") - 4. 组内按 H1 编号排序(数字分支按数值、字母分支按码点、无编号最后) - 5. 输出 llmstxt.org 标准格式: - - # <站点标题> - > 。<能力一句话>。本文档共 N 篇,按章节组织。 - - ## <章节名> - - [H1](url): 首句摘要 - ... - 6. 写入 public/llms.txt,stdout 打印页数/章节数 - -⚠ 同一章节名可能由多个目录合并(如 appendix + other 同为"附录"), - 输出时按 label 去重只打印一次。 -⚠ 脚本内不要在块注释写 "zh/**/*.md"(见步骤①的 */ 坑)。 - -================================================================================ -步骤 ⑦:package.json 接线 + 首次生成 -================================================================================ - - "scripts": { - "docs": "vitepress dev .", - "docs:llms": "node scripts/generate-llms-txt.mjs", - "docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .", - "docs:preview": "vitepress preview ." - } - -- docs:build 前置生成 llms.txt(保证部署产物永远最新) -- 新增/改名页面后若只要刷新索引:yarn docs:llms -- ⚠ Windows 下 yarn(cmd)执行 "A && B" 可用;不要改成分号。 - --------------------------------------------------------------------------------- -步骤 ⑧(收尾):更新 CLAUDE.md --------------------------------------------------------------------------------- -把以下约定写进项目 CLAUDE.md,防止后续改动破坏 SEO: - 1. :::tip 首句 = meta description + llms.txt 条目,须"答案前置"。 - 2. 编号冻结:章节编号同时被侧边栏、正文互引("详见 5.10")、 - JSON-LD 面包屑、llms.txt、外链锚文本引用 —— 发布后禁止重排。 - 3. 新增页面的 checklist:写 H1 编号 → 加 tip 概述 → - zh.ts 侧边栏登记 → (可选)yarn docs:llms。 - 4. 记录 SEO 工具链位置与 SITE_URL 单点修改约定。 - --------------------------------------------------------------------------------- -第四部分:验收标准 --------------------------------------------------------------------------------- - -A. 无 node_modules 的快速冒烟(Node ≥ 22.18,type stripping) - node --experimental-strip-types -e " - import('./.vitepress/config/seo.ts').then(async ({ createSeoHooks }) => { - const zh = await import('./.vitepress/config/zh.ts'); - const h = createSeoHooks(zh.zhConfig.themeConfig?.sidebar); - console.log(typeof h.transformPageData, typeof h.transformHead, typeof h.sitemap); - })" - -B. 完整验收(需 yarn install 后) - 1) yarn docs:build 无报错(死链检查通过) - 2) dist/sitemap.xml 存在,含 lastmod,无 404.html 与 README.html 条目 - (lastmod 覆盖率允许 <100%:未提交的新文件无 git 记录,属正常降级) - 3) dist/robots.txt、dist/llms.txt 存在 - 4) 抽查任意内容页 dist/zh/xxx.html 的 : - - 无编号前缀、带 titleTemplate 后缀 - - <meta name="description"> 是该页 tip 首句(非全站统一文案) - - <link rel="canonical"> 指向本页完整 URL - - og:title/og:description/og:url/og:image 与页面一致且 URL 为绝对地址 - - <script type="application/ld+json"> 可被 JSON.parse, - 含 TechArticle + BreadcrumbList(FAQ 页多 FAQPage) - - TechArticle.headline 与页面 H1 一致(或至少无编号差异过大) - 5) 首页含 WebSite + Organization - 6) 404.html 含 noindex,且不出现在 sitemap - 7) llms.txt 条目数 == zh 下 md 文件数,链接可访问 - 8) 用浏览器扩展(如 JSON-LD Viewer)验证结构化数据无报错 - -C. 提交前人工项(脚本做不了) - 1) 百度/Google 站长平台验证 meta(拿到验证码后加进 shared/zh head) - 2) 百度站长"主动推送"提交首页(百度对 sitemap 支持弱,主动推送是主收录手段) - 3) HTTPS 证书修复后:改 extract.mjs 的 SITE_URL + zh.ts 的 META_URL, - robots.txt 的 Sitemap 行同步,重新 docs:build - --------------------------------------------------------------------------------- -第五部分:移植到其他 15 个项目时的差异点 --------------------------------------------------------------------------------- - -同构不变量(直接照搬): - - extract.mjs / extract.d.mts / seo.ts / generate-llms-txt.mjs - 四个文件整体复制(它们不依赖具体侧边栏内容,sidebar 以参数传入) - - index.ts 挂载三行、shared.ts titleTemplate、package.json 三行脚本 - - robots.txt / 404.md 模板 - -必须按项目修改的点(清单): - [ ] SITE_URL:每个子域不同(docs.gin.elevue.... / docs.beego.layui....) - [ ] SITE_DESCRIPTION / titleTemplate / og:site_name:若各项目文案不同 - [ ] extract.mjs 中 Organization 的 url/sameAs:仓库地址不同 - (EasyGoAdmin_Gin_EleVue → 对应 Gitee 仓库路径) - [ ] scripts/generate-llms-txt.mjs 的 SECTIONS 目录→章节映射: - 核对目标项目的 zh/ 顶层目录与侧边栏章名(各前端/后端组合 - 章节编号可能不同,以该项目 zh.ts 侧边栏为准) - [ ] extractFaqs 的 Q/A 格式分支:先抽查目标项目 FAQ 页格式, - 表格式 FAQ 不要强转 - [ ] robots.txt 的 Sitemap 行域名 - [ ] llms.txt 头部章节数 N 是脚本自动统计,无需手改 - [ ] 404.md 内的示例链接指向目标项目实际存在的首页/入门页 - [ ] 站点 title(shared.ts)可能是公司名而非产品名——titleTemplate - 用产品名承载(如"小蚂蚁云" + "RXThinkCMF 开发文档") - [ ] zh.ts 的 META_URL 指向的必须是【文档站】而非产品官网 - [ ] 文档覆盖率先查后改:H1 与 :::tip 缺失页逐个补文档(原则 e)) - [ ] opener 检查:逐文件断言 lines[0]=H1 且 lines[1]=:::tip - (Laravel 系有 6 页 tip 存在但不在 H1 后——description 会取到 - 页中导航 tip 而非页面概述,必须补 opener) - python -c "import glob,io;[print(f) for f in glob.glob('zh/**/*.md',recursive=True) - if not (lambda L: L and L[0].startswith('# ') and L[1].startswith(':::tip'))( - [l for l in io.open(f,encoding='utf-8').read().splitlines() if l.strip()])]" - [ ] zh.ts 若有静态 og/twitter meta,确认与 transformHead 输出 - key 一致会被覆盖(一般无需删;不放心可删掉静态项) - -版本差异注意: - - VitePress 需 ≥ 1.0(transformPageData/transformHead/sitemap 选项 - 1.x 均有;本方案在 1.3.4 实测)。若他项目版本更老,先核对 - node_modules/vitepress/dist/node/index.d.ts 中是否存在 - sitemap / transformHead / transformPageData 三个字段。 - - 侧边栏结构假设为 { "/zh/": [ {text, items:[{text,link}|{text,items}]} ] } - 的标准 DefaultTheme 形态;若他项目 sidebar 是数组形态或 - 多语言 key 不同("/en/"),buildCrumbMap 已兼容数组入参, - 但 key 需与路由前缀一致(当前写死 "/zh/",改多语言时参数化)。 - --------------------------------------------------------------------------------- -第六部分:实测数据与已知限制(基线项目) --------------------------------------------------------------------------------- - -实测(2026-10-25,GoFrame_EleVue): - - llms.txt:143 页 / 13 章节 / 32KB,生成耗时 < 1s - - 冒烟测试:13 个 head 标签注入、TechArticle+BreadcrumbList - (首页 > 5. 核心功能 > 数据与配置 > 5.10 字典模块)、 - FAQPage 24 问(appendix 加粗式)/ 18 问(quickstart 标题式)、 - git dateModified 与 sitemap lastmod 正常、404 跳过、 - sitemap 过滤 404 —— 全部通过 - -移植实测(2026-10-25,EasyGoAdmin_Gin_EleVue,按本标准全流程执行): - - 审计:与基线同构缺失;额外发现 META_IMAGE 指向不存在的 social.png、 - META_TITLE/META_DESCRIPTION 误写 GoFrame、HTTPS 同样证书不匹配 - - 差异点仅 6 处:SITE_URL、Gitee 仓库地址、SITE_DESCRIPTION/META 文案、 - llms 脚本标题行、robots Sitemap 行、(复核)SECTIONS 章节映射 - ——zh/ 目录结构与侧边栏章节与基线完全一致,映射无需改动 - - FAQ 4 个页全为 **Q:** 加粗式(基线为两种混用),既有 extractFaqs 直接命中 - - 完整构建验收(yarn docs:build,21s): - sitemap 154 条(过滤 404+README 后)、lastmod 126/154、 - 内容页 title/canonical/OG/JSON-LD 全通过、 - 首页 WebSite+Organization、404 noindex 且不入 sitemap、 - llms.txt 153 条 == zh 下 md 数、FAQPage 11 问 - - 首次构建暴露 transformItems 相对路径坑(见 2.5),修复后复验通过 - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp6_EleVue,位于 xampp htdocs 路径): - - 与前两个项目的差异面更大:产品名/域名/仓库全不同(rxthink.cn 域), - 站点 title 是公司名"小蚂蚁云"(titleTemplate 承载产品名), - META_URL 原指向产品官网而非文档站(已改), - META_IMAGE 同样指向不存在的 social.png(已改 logo.png) - - 文档缺失(按原则 e) 修文档,不改代码绕过): - introduction.md 无 H1 无 tip(HTML 布局页)→ 补 H1+tip 后 - llms 139→140、title/headline/breadcrumb 全部正常; - 另 9 页缺 tip 概述 → 逐页补齐,tip 覆盖率达 140/140 - - 共享层同步升级:extractDescription 增加 frontmatter description - 显式优先(三个项目已同步,作为无 H1 页的兜底声明手段) - - FAQ 双格式实测:### Q 式 7 页 → FAQPage 正常;表格式 - (appendix/faq、zh/faq)→ 正确不产出 FAQPage - - yarn install(5s 缓存)+ docs:build(40s)完整验收: - sitemap 141 条过滤 404/README、lastmod 140/141、 - llms 140 == md 数、404 noindex 不入 sitemap、 - 首页 WebSite+Organization、introduction 页 title/desc 全恢复 - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp8_EleVue,第 4 个项目): - - 与 ThinkPHP6 项目几乎同构(同 140 页、同 10 页缺 tip、同 - introduction 无 H1、同 social.png 缺失、同 META_URL 指产品官网) - ——审计 17 项全部命中标准已记录的模式,实施零新问题, - 验证标准已可直接照做 - - 差异点仅 6 处:SITE_URL(docs.thinkphp8.elevue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE(ThinkPHP8 文案,本就正确)、 - Gitee ThinkPhp8 仓库、llms 标题行、robots Sitemap 行、 - (复核)SECTIONS 映射无需改 - - 文档修复同 TP6:introduction 补 H1+tip(ThinkPHP 8 文案)、 - 9 页补 tip → 覆盖率 140/140 - - yarn install(4s)+ docs:build(39s)完整验收全绿: - sitemap 141 条过滤 404/README、lastmod 140/141、 - llms 140 == md 数、introduction 页 title/desc/headline 恢复、 - FAQ 双格式正确(Q 式出 FAQPage、表格式不出)、 - 404 noindex 不入 sitemap、首页 WebSite+Organization - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp6_AntdVue,第 5 个项目): - - 再次零新问题:审计 17 项全部命中已知模式(同 140 页、同 10 页 - 缺 tip、同 introduction 无 H1、同 social.png/META_URL 问题) - - 差异点仅 5 处:SITE_URL(docs.thinkphp6.antdvue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE(AntDesign 文案,本就正确)、 - Gitee ThinkPhp6_AntdVue 仓库、llms 标题行、robots Sitemap 行 - - 文档修复同前:introduction 补 H1+tip(ThinkPHP 6.1 + AntDesign - 文案)、9 页补 tip → 覆盖率 140/140 - - yarn install(4s)+ docs:build(38s)完整验收全绿(全项同 TP8) - - 至此 5 个项目验证:标准执行耗时逐次递减,第 3 个项目起 - 审计→落地→验收全流程可由清单直接驱动,无需人工判断 - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp8_AntdVue,第 6 个项目): - - 第三次零新问题:审计 17 项全部命中已知模式(同 140 页、同 10 页 - 缺 tip、同 introduction 无 H1、同 social.png/META_URL 问题、 - HTTPS 证书不匹配) - - 差异点仅 5 处:SITE_URL(docs.thinkphp8.antdvue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE(本就正确)、Gitee ThinkPhp8_AntdVue - 仓库、llms 标题行、robots Sitemap 行 - - 文档修复同前:introduction 补 H1+tip(ThinkPHP 8 + AntDesign 文案)、 - 9 页补 tip → 覆盖率 140/140 - - yarn install(4s)+ docs:build(38s)完整验收全绿(全项同前) - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp6_NaiveVue,第 7 个项目): - - 第四次零新问题:审计 17 项全部命中已知模式(同 140 页、同 10 页 - 缺 tip、同 introduction 无 H1、同 social.png/META_URL 问题、 - HTTPS 证书不匹配) - - 差异点仅 5 处:SITE_URL(docs.thinkphp6.naivevue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE(本就正确)、Gitee ThinkPhp6_NaiveVue - 仓库、llms 标题行、robots Sitemap 行 - - 文档修复同前:introduction 补 H1+tip(ThinkPHP 6.1 + NaiveUI 文案)、 - 9 页补 tip → 覆盖率 140/140 - - yarn install(4s)+ docs:build(42s)完整验收全绿(全项同前) - -移植实测(2026-10-25,RXThinkCMF_ThinkPhp8_NaiveVue,第 8 个项目): - - 新发现 1 项(已入审计清单第 18 项):6 处 Gitee 链接全部指向 - ThinkPhp6_NaiveVue(克隆自 TP6 项目未改)——editLink、社交链接、 - 开源协议入口均指向错误仓库,已全部修复为 ThinkPhp8_NaiveVue, - dist 产物验证无残留 - - 其余全部命中已知模式:140 页、同 10 页缺 tip、introduction 无 H1、 - social.png/META_URL/HTTPS 问题;META_TITLE/DESCRIPTION 本就正确 - - 差异点 5 处:SITE_URL(docs.thinkphp8.naivevue.rxthink.cn)、 - Gitee ThinkPhp8_NaiveVue 仓库(修复后)、SITE_DESCRIPTION、 - llms 标题行、robots Sitemap 行 - - 文档修复同前 10 页 → 覆盖率 140/140 - - yarn install(5s)+ docs:build(44s)完整验收全绿, - 额外验证 Gitee 修复进 dist 无残留 - -移植实测(2026-10-25,RXThinkCMF_Laravel13_EleVue,第 9 个项目,首个 Laravel 系): - - 与 ThinkPHP 系的结构差异(审计需重新核对页数/清单,勿套用旧数字): - 139 页(无根级 zh/faq.md)、仅 8 页完全无 tip - (tech-stack/why 有页中导航 tip 但无 opener)、 - 附录多 K. Artisan 命令速查(字母到 O)、FAQ 编号 2.9、 - intro 文案为 Laravel 13 + PHP 8.3 - - 新增发现并修复:en.ts 注释行 Gitee 仓库指 ThinkPhp6_EleVue - (审计项 18 的注释变体,一并修正) - - opener 补齐 16 页:8 页完全无 tip + tech-stack/why 补 opener - (保留页中导航 tip)+ 6 页 opener 缺位(course/directory/ - config-reference/backup/backend-start/deletion——tip 存在但不在 - H1 之后,用 opener 检查脚本发现) - - 新增 opener 检查(审计可复用): - python 逐文件断言 lines[0]=H1 且 lines[1]=:::tip - 本项目最终 139/139 全满 - - SITE_URL=docs.laravel13.elevue.rxthink.cn(https 同样 000); - Gitee 活跃链接本就正确(Laravel13_EleVue) - - yarn install(4s)+ docs:build(35s+复建 34s)完整验收全绿: - sitemap 140 条(139 md + 首页)、lastmod 139/140、 - llms 139 == md 数、course 等补页 description=新 opener 首句、 - Gitee 修复进 dist 无 ThinkPhp6 残留 - -移植实测(2026-10-25,RXThinkCMF_Laravel13_AntdVue,第 10 个项目): - - 与 Laravel13_EleVue 完全同构(Laravel 系模板固化):139 页、 - 8 无 tip、16 页缺 opener(8 无 tip + tech-stack/why + - course/directory/config-reference/backup/backend-start/deletion)、 - en.ts 注释仓库指 ThinkPhp6_EleVue(已修) - - 差异点 5 处:SITE_URL(docs.laravel13.antdvue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE(本就正确)、Gitee Laravel13_AntdVue - (活跃链接正确)、llms 标题行、robots Sitemap 行 - - 文档修复 16 页 → opener 139/139、tip 139/139、H1 139/139 - - yarn install(4s)+ docs:build(41s)完整验收全绿 - (含 Gitee 无 ThinkPhp6 残留、course desc=新 opener 首句) - - Laravel 系 2 个项目均已带 opener 检查一次通过—— - 该检查应作为审计固定项(见 opener 检查脚本) - -移植实测(2026-10-25,RXThinkCMF_Laravel13_NaiveVue,第 11 个项目): - - Laravel 系模板第 3 次命中,全流程照做零偏差:139 页、8 无 tip、 - 16 页缺 opener、en.ts 注释仓库指 ThinkPhp6_EleVue(已修)、 - META/Gitee 活跃链接本就正确 - - 差异点 5 处:SITE_URL(docs.laravel13.naivevue.rxthink.cn)、 - SITE_DESCRIPTION/META_TITLE、Gitee Laravel13_NaiveVue、 - llms 标题行、robots Sitemap 行 - - 文档修复 16 页 → opener/tip/H1 全部 139/139 - - yarn install(4s)+ docs:build(41s)完整验收全绿 - (含 Gitee 无 ThinkPhp6 残留、course desc=新 opener 首句) - -移植实测(2026-10-25,DjangoAdmin_Django_EleVue,第 12 个项目,首个 DjangoAdmin 品牌线): - - 全新结构(不能套用 RXThinkCMF/EasyGoAdmin 模板): - 135 页、volume1-7 分卷目录、H1 无编号(如"# 环境准备")、 - 部分页 tip 在 H1 之前(volume5/6 "本章概要" 横幅式)、 - 无 Q/A 型 FAQ(FAQPage 全站不产出属正确行为)、 - 侧边栏 8 章 + 附录(与 llms SECTIONS 必须重写对应) - - 审计方法升级:结构检查(NO-H1/opener)在异构站点上误报多 - (tip 在 H1 前 ≠ 缺 H1;head -1 检查漏判),改用 - **extractDescription 全量摘要质量审计**(空/过短/导航腔)作为 - 真实判据——135 页仅 6 页真实有问题,逐页修复后 0 问题 - - 文档修复 27 处:introduction 补 H1+tip(唯一真缺 H1 页)、 - 25 页补 tip opener(volume2×12、volume3×10、根级 3 页)、 - contributing tip 首句由问候语改写为描述句 - - llms SECTIONS 按本项目重写:volume1-7→章 2-8、other→附录、 - 根级 faq.md 文件级例外→附录(侧边栏归属); - 头部样板文案按品牌实情改(去多租户/声明式 CRUD 等非本项目特性) - - 差异点:SITE_URL=docs.django.elevue.djangoadmin.cn(https 同 000)、 - 品牌 DjangoAdmin/djangoadmin.cn/Gitee djangoadmin 组、 - META 本就正确、活跃 Gitee 正确(en.ts 注释指 Fastapi 已修)、 - titleTemplate="DjangoAdmin 开发文档" - - yarn install(13s)+ docs:build(53s)完整验收全绿: - sitemap 136 条、lastmod 136/136(全量 git 覆盖)、 - llms 135==md、intro 恢复、面包屑"首页 > 2. 快速入门 > 2.1 ..."、 - FAQPage 全站无(无 Q/A 页,正确)、Gitee 无 Fastapi 残留 - -移植实测(2026-10-25,DjangoAdmin_Django_AntdVue,第 13 个项目,DjangoAdmin 系第 2 个): - - DjangoAdmin 模板完全命中:135 页、volume1-7、同 26 无 tip、 - 同 introduction 无 H1、无 Q/A FAQ、en.ts 注释指 Fastapi(已修)、 - META/活跃 Gitee 本就正确(Django_AntdVue) - - llms 脚本直接复用 Django_EleVue 已适配版(SECTIONS/faq 例外/ - 样板文案),仅改标题行——同系列脚本可直接沿用 - - 描述质量审计:3 个真实问题(introduction 空、contributing 问候语、 - why 首句过短)与上项完全一致;修复 27 处(25 tip + H1/tip + 改写) - → bad desc 0、no-tip 0、no-H1 0 - - 差异点 5 处:SITE_URL(docs.django.antdvue.djangoadmin.cn)、 - AntDesign 文案(introduction/fe-view tip 同步改)、 - Gitee Django_AntdVue、llms 标题行、robots Sitemap 行 - - yarn install(8s)+ docs:build(49s)完整验收全绿 - (含 lastmod 136/136、FAQPage 全站无、Gitee 无 Fastapi 残留) - -移植实测(2026-10-25,DjangoAdmin_Django_NaiveVue,第 14 个项目,DjangoAdmin 系第 3 个): - - 模板第 3 次命中,全流程零偏差照做:135 页、26 无 tip、 - introduction 无 H1、3 个真实摘要问题与前两项完全一致、 - en.ts 注释指 Fastapi(已修)、META/活跃 Gitee 本就正确 - - llms 脚本复用 Django_EleVue 已适配版仅改标题行; - 文档修复 27 处(NaiveUI 文案)→ bad desc/no-tip/no-H1 全 0 - - 差异点 5 处:SITE_URL(docs.django.naivevue.djangoadmin.cn)、 - Gitee Django_NaiveVue、NaiveUI 文案、llms 标题行、robots Sitemap 行 - - yarn install(7s)+ docs:build(46s)完整验收全绿 - -移植实测(2026-10-25,DjangoAdmin_FastAPI_EleVue,第 15 个项目,DjangoAdmin 系首个非 Django 后端): - - 结构差异(DjangoAdmin 系内变体,勿套 135/26 数字): - 129 页、12 无 tip(volume3 全有 tip)、4 无 H1(introduction、 - struct 起于 ## 无 H1、volume4/core idempotent+operation-log - tip 横幅式且全文件无 H1)、volume2 模块页为 FastAPI 分层 - (Endpoint/Router/Schema/Repository/Service,案例=Position) - - 新发现并修复: - ① Gitee 仓库大小写冲突——shared.ts 用 FastAPI、zh.ts/en.ts 用 - Fastapi → 【已更正】FastAPI 系仓库名应统一大写 FastAPI - (用户确认),第15/16项当时按正文推断改小写属误判, - 已连同第17项一并全局改回 DjangoAdmin_FastAPI_*(配置+seo+ - 正文版本表,各5文件,三项目复验零残留) - ② META_TITLE/META_DESCRIPTION 缺品牌与技术栈(仅"企业级中台 - 解决方案")→ 补 DjangoAdmin FastAPI 文案(审计项 15) - - 文档修复 17 处:4 无 H1(introduction 补 H1+tip、struct 补 - H1+opener、idempotent/operation-log 补 H1 置于既有 tip 前)、 - 12 无 tip 补 opener(FastAPI 分层文案)、why tip 首句 14 字 - 过短改写(阈值 15 实测抓出) - - llms 脚本复用 Django_EleVue 适配版仅改标题行 - - SITE_URL=docs.fastapi.elevue.djangoadmin.cn(https 同 000) - - yarn install(7s)+ docs:build(2 轮共 80s)完整验收全绿: - sitemap 130 条、lastmod 130/130、llms 129==md、 - 3 原缺陷页 title/desc/headline 全恢复、Gitee 无大写残留 - -移植实测(2026-10-25,DjangoAdmin_FastAPI_AntdVue,第 16 个项目,FastAPI 系第 2 个): - - FastAPI 模板命中(129 页、12 无 tip、4 无 H1、无 Q/A FAQ), - 特有点与 FastAPI_EleVue 一致:META 裸文案(审计项 15 已补 - FastAPI+AntDesign 文案)、why tip 首句 14 字(已改写) - - Gitee 大小写冲突再现:配置 3 处 FastAPI_AntdVue vs 正文 - Fastapi_AntdVue → 以正文为准 sed 全局统一,另 en.ts 注释误指 - EleVue 改本项目;首验 5 处全 Fastapi 无残留 - (沿用上项经验:多处相同字符串必须全局替换 + 复验) - - 文档修复 16 处:4 无 H1(introduction 补 H1+tip AntdVue 文案、 - struct 补 H1+opener、idempotent/operation-log 补 H1 置于 tip 前)、 - 11 无 tip 补 opener(fe-view 用本项目正文的 AntDesign Vue 措辞)、 - why tip 首句改写 → bad desc/no-tip/no-H1 全 0 - - SITE_URL=docs.fastapi.antdvue.djangoadmin.cn(https 同 000) - - yarn install(9s)+ docs:build(53s)一轮通过完整验收全绿: - sitemap 130、lastmod 130/130、llms 129==md、 - 3 原缺陷页 title/desc/headline 恢复、Gitee 无大写残留 - -移植实测(2026-10-25,DjangoAdmin_FastAPI_NaiveVue,第 17 个项目,FastAPI 系第 3 个): - - FastAPI 模板命中(129 页、12 无 tip、4 无 H1、无 Q/A FAQ、 - META 裸文案、why 首句 14 字),配置曾含 FastAPI_EleVue 误指 - - Gitee 大小写最终裁定:用户确认应大写 FastAPI——本项目与前两个 - FastAPI 项目(EleVue/AntdVue)三地同步改正:配置 4 文件 + - seo.ts ORG + 正文 introduction 版本表,各 5 文件,复验零残留 - - 文档修复 16 处(NaiveUI 文案)→ bad desc/no-tip/no-H1 全 0 - - SITE_URL=docs.fastapi.naivevue.djangoadmin.cn(https 同 000) - - yarn install(8s)+ docs:build(45s)完整验收全绿: - sitemap 130、lastmod 130/130、llms 129==md、 - 3 原缺陷页 title/desc/headline 恢复、dist 无小写残留 - -移植实测(2026-10-25,DjangoAdmin_Flask_EleVue,第 18 个项目,DjangoAdmin 系首个 Flask 后端): - - 【重大方法修正】发现围栏内 "# 注释" 被 ^# 正则误判为 H1—— - 围栏感知+首个##前 规则重扫:本项目真缺 H1 达 25 页 - (volume4/business×11 + core×11 + course/introduction/struct), - 另 rbac.md 有游离 # 行二次漏判(加固规则后补齐) - - 共享工具加固:llms 脚本 h1Of 与 seo.ts h1OfMd 均改为围栏感知 - (split(" -") 逐行 + 围栏开关),根治 llms 链接文本取到 - .py/.ts 代码注释垃圾的问题 - - 结构:139 页、17 无 tip(含 Flask 特有 blueprint/model/ - transaction×2/generator)、META 本就正确、Gitee 活跃链接正确 - - 文档修复 44 处:26 补 H1(25+rbac)+17 补 tip(Flask 文案)+ - why 首句改写 → 围栏感知 no-H1 0、no-tip 0、bad desc 0 - - SITE_URL=docs.flask.elevue.djangoadmin.cn(https 同 000) - - 验收全绿:sitemap 140、lastmod 140/140、llms 139==md、 - 垃圾链接文本 0、rbac/user 等页 title/headline 恢复 - - 【存量回修】同日对 6 个已验收 DjangoAdmin 项目(Django×3、 - FastAPI×3)执行围栏感知重扫:各发现 21-24 页真缺 H1(同 volume4 - banner 式页面+course 的系统性缺陷)→ 批量补 H1 共 135 页 - (标题取自各自侧边栏去编号)+ 同步加固 h1Of/h1OfMd + - 重新生成 llms + 全部重建 dist,6 项目复验:垃圾 0、 - user 页 title/headline=用户管理、sitemap 正常 - (教训:结构扫描规则必须与真实解析器同构——围栏感知, - 否则会产生"验收通过但产物含垃圾"的假阳性) - -移植实测(2026-10-25,DjangoAdmin_Flask_AntdVue,第 19 个项目,Flask 系第 2 个): - - Flask 模板完全命中(139 页、26 真缺 H1 含 rbac、17 无 tip、 - META 本就正确、Gitee 活跃链接正确、en.ts 注释指错已修) - - 复制源升级为【已加固版】:seo.ts/llms 脚本直接取自 Flask_EleVue - (围栏感知 h1OfMd/h1Of 已含),仅改 ORG 仓库与标题行—— - 同系列后续项目一律从已加固项目复制共享层 - - 文档修复 44 处:26 H1(侧边栏自动映射脚本,0 未匹配)+17 tip - (AntdVue 文案:fe-view/introduction 用 AntDesign Vue)+why 首句 - → 围栏感知 no-H1 0、no-tip 0、bad desc 0 - - SITE_URL=docs.flask.antdvue.djangoadmin.cn(https 同 000) - - yarn install(8s)+ docs:build(68s)一轮通过完整验收全绿: - sitemap 140、lastmod 140/140、llms 139==md、垃圾链接 0、 - intro/user/rbac 三页 title/desc/headline 全恢复 - -移植实测(2026-10-25,DjangoAdmin_Flask_NaiveVue,第 20 个项目,Flask 系第 3 个): - - Flask 模板完全命中:139 页、26 真缺 H1、17 无 tip、META 正确、 - Gitee 活跃链接正确、en.ts 注释指错(Fastapi_EleVue)已修 - - 共享层复制自 Flask_AntdVue 已加固版(seo.ts/llms 仅改 - ORG 仓库/标题行/SITE_URL/描述),全程照做零偏差 - - 文档修复 44 处(侧边栏自动映射 26 H1 + NaiveUI 文案 17 tip - +why 首句)→ 围栏感知三指标全 0 - - SITE_URL=docs.flask.naivevue.djangoadmin.cn(http 200) - - yarn install(8s)+ docs:build(78s)一轮通过完整验收全绿: - sitemap 140、lastmod 140/140、llms 139==md、垃圾链接 0、 - intro/user/rbac 三页 title/desc/headline 全恢复 - -移植实测(2026-10-25,DjangoAdmin_Tornado_EleVue,第 21 个项目,DjangoAdmin 系首个 Tornado 后端): - - Tornado 变体(数字勿套旧模板):138 页、22 真缺 H1、 - 11 无 tip(Tornado 特有 volume2/handler 页 + volume3/overview - 也缺;案例模块=友链 Link 而非 Position;validator 标题为 - 参数验证(Pydantic))、无 Q/A FAQ - - META 裸文案(审计项 15 已补 Tornado+ElementPlus 文案)、 - en.ts 注释指 Fastapi_EleVue(已修)、Gitee 活跃链接正确、 - why 首句 14 字(已改写) - - 共享层复制自 Flask_NaiveVue 已加固版(改 ORG/标题/SITE_URL/描述) - - 文档修复 35 处:22 H1(侧边栏自动映射含 Pydantic 标题)+ - 12 tip(Tornado 文案:handler/schema Pydantic、volume3/overview - 架构总览;model 顺带补 opener)+why 首句 - → 围栏感知 no-H1 0、no-tip 0、bad desc 0 - - SITE_URL=docs.tornado.elevue.djangoadmin.cn(http 200/https 000) - - yarn install(9s)+ docs:build(47s)一轮通过完整验收全绿: - sitemap 139、lastmod 139/139、llms 138==md、垃圾链接 0、 - intro/handler/rbac/arch 四页 title/desc/headline 全恢复 - -移植实测(2026-10-25,DjangoAdmin_Tornado_AntdVue,第 22 个项目,Tornado 系第 2 个): - - Tornado 模板完全命中:138 页、22 真缺 H1、11 无 tip、 - en.ts 注释本就正确(无需修)、Gitee 活跃链接正确、 - META 裸文案(审计项 15 已补 Tornado+AntDesign)、 - fe-view/introduction 用 AntDesign 措辞 - - 共享层复制自 Tornado_EleVue 已加固版(改 ORG/标题/SITE_URL/描述) - - 文档修复 34 处:22 H1(侧边栏自动映射,validator=参数验证 - (Pydantic)正确落地)+11 tip +why 首句 - → 围栏感知 no-H1 0、no-tip 0、bad desc 0 - - SITE_URL=docs.tornado.antdvue.djangoadmin.cn(http 200) - - yarn install(5s)+ docs:build(60s)一轮通过完整验收全绿: - sitemap 139、lastmod 139/139、llms 138==md、垃圾链接 0、 - intro/handler/rbac 三页 title/desc/headline 全恢复 - -移植实测(2026-10-25,DjangoAdmin_Tornado_NaiveVue,第 23 个项目,Tornado 系第 3 个): - - Tornado 模板完全命中:138 页、22 真缺 H1、11 无 tip、 - META 裸文案(审计项 15 已补 Tornado+NaiveUI)、 - en.ts 注释指 Fastapi_EleVue(已修)、Gitee 活跃链接正确 - - 共享层复制自 Tornado_AntdVue 已加固版(改 ORG/标题/SITE_URL/描述) - - 文档修复 34 处:22 H1(侧边栏自动映射)+11 tip(NaiveUI 文案: - fe-view/introduction 用 NaiveUI)+why 首句 - → 围栏感知 no-H1 0、no-tip 0、bad desc 0 - - SITE_URL=docs.tornado.naivevue.djangoadmin.cn(http 200) - - yarn install(9s)+ docs:build(51s)一轮通过完整验收全绿: - sitemap 139、lastmod 139/139、llms 138==md、垃圾链接 0、 - intro/handler/rbac 三页 title/desc/headline 全恢复 - -已知限制: - 1. HTTPS:docs 子域 443 返回腾讯 CDN 默认证书(*.cdn.myqcloud.com), - curl 校验失败,全站暂用 http。修复后按步骤⑦-C3 单点切换。 - 2. transformHead/transformHead/sitemap 均为 build-only, - dev server 下看不到增强标签,验收必须以 docs:build 产物为准。 - 3. og:image 用的 logo.png(方形),非 1200×630 社交卡片尺寸 —— - 可后续出专用卡片图后只改 og:image 一处。 - 4. 百度/Google 站长验证与主动推送需要平台账号,未包含在本方案。 - 5. 站点验证 meta 未加(无验证码);若后续加,放 shared.ts head。 - 6. 未启用 VitePress lastUpdated 主题显示(与 SEO 无关,另议)。 - --------------------------------------------------------------------------------- -附录:一页速查(执行顺序) --------------------------------------------------------------------------------- - 1. 跑第二部分审计清单,记录结论 - 2. 复制/调整 extract.mjs + extract.d.mts(改 SITE_URL 等 9 个差异点) - 3. 复制 seo.ts,核对 FAQ 格式分支与侧边栏 key - 4. index.ts 挂 3 行;shared.ts 加 titleTemplate - 5. 放 robots.txt(改域名)+ 404.md(改链接) - 6. 复制 generate-llms-txt.mjs(核对 SECTIONS 映射)→ yarn docs:llms - 7. package.json 改 3 行脚本 - 8. yarn install && yarn docs:build,按第四部分 B 逐项验收 - 9. 更新该项目 CLAUDE.md(tip 首句约定 + 编号冻结) - 10. 站长平台验证/推送(人工) - -(完) diff --git a/EasyGoAdmin_GoFrame_NaiveVue/.vitepress/config/shared.ts b/EasyGoAdmin_GoFrame_NaiveVue/.vitepress/config/shared.ts index c1e5949..22674f2 100644 --- a/EasyGoAdmin_GoFrame_NaiveVue/.vitepress/config/shared.ts +++ b/EasyGoAdmin_GoFrame_NaiveVue/.vitepress/config/shared.ts @@ -70,6 +70,16 @@ export const sharedConfig = defineConfig({ }, ], + // Top banner + [ + "script", + { + src: "/banner.js", + // @ts-expect-error: vitepress bug + async: true, + type: "text/javascript", + }, + ], ], themeConfig: { diff --git a/EasyGoAdmin_GoFrame_NaiveVue/public/banner.js b/EasyGoAdmin_GoFrame_NaiveVue/public/banner.js new file mode 100644 index 0000000..85c12c8 --- /dev/null +++ b/EasyGoAdmin_GoFrame_NaiveVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,<b>即刻抢购!</b> ", + link: "https://www.easygoadmin.vip", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "<b>EasyGoAdmin · </b> 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `<div class="bb-countdown-footer">${n}</div>` : "", + c = `\n <div class="bb-background"></div>\n <div class="bb-logo"></div>\n <div class="bb-side-image"></div>\n <div class="bb-core">\n <div class="bb-slogan">\n ${ + e ? `<div class="bb-prelude">${e}</div>` : "" + }\n <div class="bb-title-container">\n <div class="bb-title">${a}</div>\n ${ + i ? `<div class="bb-subtitle">${i}</div>` : "" + }\n </div>\n ${ + t ? `<div class="bb-interlude">${t}</div>` : "" + }\n </div>\n ${ + o + ? `<div class="bb-button-wrapper"><div class="bb-button">${o}</div></div>` + : "" + }\n <div class="bb-countdown-container">\n ${ + b ? `<div class="bb-addendum">${b}</div>` : "" + }\n ${ + l + ? `<div class="bb-countdown-wrapper"><div class="bb-countdown">${this.renderCountdown()}</div>${d}</div>` + : "" + }\n </div>\n </div>\n <div id="bb-close" class="bb-close">\n <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">\n <path d="M13.6894 0.321198C13.2753 -0.0929086 12.6064 -0.0929086 12.1923 0.321198L7 5.50284L1.80774 0.31058C1.39363 -0.103527 0.724687 -0.103527 0.31058 0.31058C-0.103527 0.724687 -0.103527 1.39363 0.31058 1.80774L5.50284 7L0.31058 12.1923C-0.103527 12.6064 -0.103527 13.2753 0.31058 13.6894C0.724687 14.1035 1.39363 14.1035 1.80774 13.6894L7 8.49716L12.1923 13.6894C12.6064 14.1035 13.2753 14.1035 13.6894 13.6894C14.1035 13.2753 14.1035 12.6064 13.6894 12.1923L8.49716 7L13.6894 1.80774C14.0929 1.40425 14.0929 0.724687 13.6894 0.321198Z" fill="white"/>\n </svg>\n </div>\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n <div class="bb-countdown-item">\n <div class="bb-countdown-part">\n <div data-countdown="${n}" class="bb-countdown-number"></div><div class="bb-countdown-text"><span class="bb-countdown-text-initial">${ + n[0] + }</span><span class="bb-countdown-text-rest">${n.slice( + 1, + )}</span></div>\n </div>\n <div class="bb-countdown-colon">:</div>\n </div>\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +