refactor(portal): extract PortalLayout so Nav/Footer persist across routes
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled

将 Landing/Docs/Pricing 的 Nav + Footer 提取到共享 PortalLayout。
Router 改为嵌套结构,路由切换时 router-view 内容变化,Nav 本身
不重挂载,消除切页时的 UI 抖动(真·SPA 行为)。

- new: components/layout/PortalLayout.vue(Nav + router-view + Footer)
- router: /、/docs、/pricing 作为 PortalLayout 的子路由
- i18n: 新增 portal.nav.* 命名空间;删除重复的 docs.nav.* / pricing.nav.* / landing.nav.*
- router: scrollBehavior 支持 hash 锚点跳转(offset 80px 绕开 sticky nav)
- router-link 使用 active-class/exact-active-class prop 替代硬编码 class="active"
This commit is contained in:
mini
2026-04-23 12:52:07 +08:00
parent 779005e1cd
commit e7f3fe5b4d
7 changed files with 174 additions and 168 deletions

View File

@@ -1,28 +1,5 @@
<template>
<div class="puro-page">
<div class="bg-glow soft"></div>
<nav class="nav">
<div class="container nav-inner">
<router-link to="/" class="brand">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" fill="rgba(34, 211, 238, 0.08)"/>
</svg>
<span>PURO AI</span>
</router-link>
<div class="nav-links">
<router-link to="/">{{ $t('docs.nav.products') }}</router-link>
<router-link to="/pricing">{{ $t('docs.nav.pricing') }}</router-link>
<router-link to="/docs" class="active">{{ $t('docs.nav.docs') }}</router-link>
</div>
<div class="nav-cta">
<PuroLocaleSwitcher />
<router-link to="/login" class="btn btn-ghost">{{ $t('docs.nav.login') }}</router-link>
<router-link to="/register" class="btn btn-primary">{{ $t('docs.nav.signup') }}</router-link>
</div>
</div>
</nav>
<div>
<section class="docs-hero container">
<h1>{{ $t('docs.hero.title') }}</h1>
<p class="subtitle">{{ $t('docs.hero.subtitle') }}</p>
@@ -256,7 +233,6 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
const { t } = useI18n()

View File

@@ -1,30 +1,5 @@
<template>
<div class="puro-page">
<div class="bg-glow"></div>
<div class="grain"></div>
<!-- NAV -->
<nav class="nav">
<div class="container nav-inner">
<router-link to="/" class="brand">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" fill="rgba(34, 211, 238, 0.08)"/>
</svg>
<span>PURO AI</span>
</router-link>
<div class="nav-links">
<a href="#features">{{ $t('landing.nav.products') }}</a>
<router-link to="/pricing">{{ $t('landing.nav.pricing') }}</router-link>
<router-link to="/docs">{{ $t('landing.nav.docs') }}</router-link>
</div>
<div class="nav-cta">
<PuroLocaleSwitcher />
<router-link to="/login" class="btn btn-ghost">{{ $t('landing.nav.login') }}</router-link>
<router-link to="/register" class="btn btn-primary">{{ $t('landing.nav.signup') }}</router-link>
</div>
</div>
</nav>
<div>
<!-- HERO -->
<section class="hero container">
<div class="hero-eyebrow">
@@ -304,45 +279,10 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
</div>
</section>
<!-- Footer -->
<footer class="puro-footer">
<div class="container footer-grid">
<div class="footer-brand">
<div class="brand">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" fill="rgba(34, 211, 238, 0.08)"/>
</svg>
<span>PURO AI</span>
</div>
<p class="footer-tagline">{{ $t('landing.footer.tagline1') }}<br>{{ $t('landing.footer.tagline2') }}</p>
<p class="footer-meta">© 2026 puro.im · MIT License<br>fork of Wei-Shaw/sub2api</p>
<div class="footer-status"><span class="dot-green"></span>all systems operational</div>
</div>
<div class="footer-col">
<div class="footer-col-title">{{ $t('landing.footer.colProducts') }}</div>
<a href="/docs">{{ $t('landing.footer.linkDocs') }}</a>
<a href="#features">{{ $t('landing.footer.linkFeatures') }}</a>
<a href="https://git.puro.im/purovps/sub2api/commits/branch/main" target="_blank" rel="noopener noreferrer">{{ $t('landing.footer.linkChangelog') }}</a>
</div>
<div class="footer-col">
<div class="footer-col-title">{{ $t('landing.footer.colAccount') }}</div>
<router-link to="/login">{{ $t('landing.footer.linkLogin') }}</router-link>
<router-link to="/register">{{ $t('landing.footer.linkRegister') }}</router-link>
<a href="/dashboard">Dashboard</a>
</div>
<div class="footer-col">
<div class="footer-col-title">{{ $t('landing.footer.colContact') }}</div>
<a href="mailto:admin@puro.im">admin@puro.im</a>
<a href="https://git.puro.im" target="_blank" rel="noopener noreferrer">git.puro.im</a>
<a href="https://git.puro.im/purovps/sub2api" target="_blank" rel="noopener noreferrer">GitHub </a>
</div>
</div>
</footer>
</div>
</template>
<script setup lang="ts">
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
</script>
<style scoped>

View File

@@ -1,31 +1,5 @@
<template>
<div class="puro-page">
<div class="bg-glow"></div>
<div class="grain"></div>
<!-- NAV -->
<nav class="nav">
<div class="container nav-inner">
<router-link to="/" class="brand">
<svg class="hex" viewBox="0 0 24 24" fill="none">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" stroke="currentColor" stroke-width="1.8" fill="rgba(34, 211, 238, 0.08)"/>
<path d="M12 7L17 9.5V14.5L12 17L7 14.5V9.5L12 7Z" fill="currentColor"/>
</svg>
PURO AI
</router-link>
<div class="nav-links">
<router-link to="/">{{ $t('pricing.nav.products') }}</router-link>
<router-link to="/pricing" class="active">{{ $t('pricing.nav.pricing') }}</router-link>
<router-link to="/docs">{{ $t('pricing.nav.docs') }}</router-link>
</div>
<div class="nav-cta">
<PuroLocaleSwitcher />
<router-link to="/login" class="btn btn-ghost">{{ $t('pricing.nav.login') }}</router-link>
<router-link to="/register" class="btn btn-primary">{{ $t('pricing.nav.signup') }}</router-link>
</div>
</div>
</nav>
<div>
<!-- HERO -->
<section class="hero">
<div class="section-kicker" style="margin-bottom:14px;">{{ $t('pricing.hero.kicker') }}</div>
@@ -362,7 +336,6 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
import PricingCalculator from '@/components/puro/PricingCalculator.vue'
const customAmt = ref(50)