增加website

This commit is contained in:
2026-09-14 22:24:55 +08:00
parent bb8a6936e4
commit 7e6a3e969f
57 changed files with 10568 additions and 1 deletions
+26
View File
@@ -0,0 +1,26 @@
<script setup lang="ts">
import SiteHeader from "./SiteHeader.vue";
import SiteFooter from "./SiteFooter.vue";
</script>
<template>
<div class="site">
<SiteHeader />
<main class="site__main">
<RouterView />
</main>
<SiteFooter />
</div>
</template>
<style scoped lang="less">
.site {
display: flex;
flex-direction: column;
min-height: 100vh;
&__main {
flex: 1;
}
}
</style>
+397
View File
@@ -0,0 +1,397 @@
<script setup lang="ts">
import { footerColumns, policyLinks, siteInfo } from "@/config/site";
</script>
<template>
<footer class="site-footer">
<div class="container site-footer__top">
<!-- 品牌与联系方式 -->
<div class="footer-brand">
<RouterLink to="/" class="footer-brand__logo">
<span class="footer-brand__mark">{{ siteInfo.brandMark }}</span>
<span class="footer-brand__name">{{ siteInfo.fullName }}</span>
</RouterLink>
<ul class="footer-info">
<li>
<span>地址</span>{{ siteInfo.address }}(邮编
{{ siteInfo.postalCode }})
</li>
<li>
<span>备案</span>京ICP备 00000000 号-1 | 京公网安备
11010800000000 号
</li>
<li>
<span>联系</span>服务热线:{{ siteInfo.phone }} · 商务合作:{{
siteInfo.email
}}
</li>
</ul>
</div>
<!-- 链接分组 -->
<div class="footer-links">
<div
v-for="column in footerColumns"
:key="column.title"
class="footer-links__column"
>
<h3>{{ column.title }}</h3>
<ul>
<li v-for="link in column.links" :key="link.path">
<RouterLink :to="link.path">{{ link.label }}</RouterLink>
</li>
</ul>
</div>
</div>
</div>
<div class="site-footer__bottom">
<div class="container site-footer__bottom-inner">
<p class="copyright">{{ siteInfo.copyright }}</p>
<div class="footer-end">
<ul class="policy-links">
<li v-for="link in policyLinks" :key="link.path">
<RouterLink :to="link.path">{{ link.label }}</RouterLink>
</li>
</ul>
<ul class="social-links">
<li>
<a href="#" aria-label="微博" title="微博">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M10.5 20c-4 0-7.5-2-7.5-4.6 0-1.6 1-3.4 2.7-5C7.6 9 9.7 8 11.4 8c.8 0 1.4.2 1.7.6.4.5.2 1.2-.1 1.9 0 0 .9-.4 1.5-.4 1 0 1.4.6 1.4 1.4 0 .5-.2 1-.6 1.5 1.3.4 2.2 1.3 2.2 2.4C17.5 17.6 14.6 20 10.5 20Z"
fill="none"
stroke="currentColor"
stroke-width="1.6"
/>
</svg>
</a>
</li>
<li>
<a href="#" aria-label="微信" title="微信">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M9 4.5c-3.6 0-6.5 2.4-6.5 5.4 0 1.7.9 3.2 2.4 4.2l-.6 2 2.3-1.2c.7.2 1.5.3 2.4.3h.5"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M21.5 15.2c0-2.6-2.5-4.7-5.6-4.7s-5.6 2.1-5.6 4.7 2.5 4.7 5.6 4.7c.7 0 1.4-.1 2-.3l2 1-.5-1.7c1.3-.9 2.1-2.2 2.1-3.7Z"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linejoin="round"
/>
</svg>
</a>
</li>
<li>
<a href="#" aria-label="哔哩哔哩" title="哔哩哔哩">
<svg viewBox="0 0 24 24" aria-hidden="true">
<rect
x="3"
y="7"
width="18"
height="13"
rx="3.5"
fill="none"
stroke="currentColor"
stroke-width="1.6"
/>
<path
d="M7.5 4.5 9.5 7M16.5 4.5 14.5 7M8.5 12v3M15.5 12v3"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</a>
</li>
<li>
<a href="#" aria-label="抖音" title="抖音">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M14 3.5v10.2a3.6 3.6 0 1 1-3.6-3.6c.3 0 .6 0 .9.1"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
/>
<path
d="M14 3.5c.5 2.3 2.1 3.8 4.4 4"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
/>
</svg>
</a>
</li>
<li>
<a href="#" aria-label="邮箱" title="邮箱">
<svg viewBox="0 0 24 24" aria-hidden="true">
<rect
x="3"
y="5.5"
width="18"
height="13"
rx="2.5"
fill="none"
stroke="currentColor"
stroke-width="1.6"
/>
<path
d="m4.5 8 7.5 5 7.5-5"
fill="none"
stroke="currentColor"
stroke-width="1.6"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
</template>
<style scoped lang="less">
.site-footer {
background: @navy-900;
color: rgba(255, 255, 255, 0.62);
&__top {
display: flex;
justify-content: space-between;
gap: 60px;
padding-top: 62px;
padding-bottom: 52px;
}
&__bottom {
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
&__bottom-inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
min-height: 74px;
padding-top: 12px;
padding-bottom: 12px;
}
}
/* ---------- 左侧品牌 ---------- */
.footer-brand {
max-width: 720px;
&__logo {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 26px;
}
&__mark {
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.12);
color: @white;
font-size: 14px;
font-weight: 700;
}
&__name {
font-size: 19px;
font-weight: 700;
color: @white;
letter-spacing: 0.02em;
}
}
.footer-info {
li {
position: relative;
padding-left: 52px;
font-size: 13.5px;
line-height: 2;
color: rgba(255, 255, 255, 0.55);
span {
position: absolute;
left: 0;
top: 0;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
}
}
/* ---------- 右侧链接 ---------- */
.footer-links {
display: flex;
gap: 88px;
flex-shrink: 0;
&__column {
h3 {
margin-bottom: 16px;
font-size: 14px;
font-weight: 700;
color: @white;
letter-spacing: 0.06em;
}
li + li {
margin-top: 9px;
}
a {
font-size: 13.5px;
color: rgba(255, 255, 255, 0.55);
transition: color 0.2s ease;
&:hover {
color: @white;
}
}
}
}
/* ---------- 底部 ---------- */
.copyright {
font-size: 12px;
letter-spacing: 0.04em;
color: rgba(255, 255, 255, 0.4);
}
.footer-end {
display: flex;
align-items: center;
gap: 34px;
}
.policy-links {
display: flex;
align-items: center;
li {
display: flex;
align-items: center;
& + li::before {
content: "";
width: 1px;
height: 11px;
margin: 0 14px;
background: rgba(255, 255, 255, 0.2);
}
}
a {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
&:hover {
color: @white;
}
}
}
.social-links {
display: flex;
align-items: center;
gap: 10px;
a {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.16);
color: rgba(255, 255, 255, 0.7);
transition: all 0.25s ease;
&:hover {
background: rgba(255, 255, 255, 0.14);
border-color: transparent;
color: @white;
}
}
svg {
width: 18px;
height: 18px;
}
}
/* ---------- 响应式 ---------- */
@media (max-width: 1200px) {
.footer-links {
gap: 56px;
}
}
@media (max-width: 992px) {
.site-footer__top {
flex-direction: column;
gap: 44px;
padding-top: 48px;
padding-bottom: 40px;
}
.footer-links {
gap: 48px;
}
}
@media (max-width: 768px) {
.footer-info li {
padding-left: 0;
line-height: 1.9;
margin-bottom: 8px;
span {
position: static;
margin-right: 8px;
}
}
.footer-links {
flex-direction: column;
gap: 32px;
}
.site-footer__bottom-inner {
flex-direction: column;
align-items: flex-start;
gap: 16px;
padding-top: 20px;
padding-bottom: 24px;
}
.footer-end {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
}
</style>
+373
View File
@@ -0,0 +1,373 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { navItems, siteInfo, utilityLinks } from "@/config/site";
const route = useRoute();
const stuck = ref(false);
const menuOpen = ref(false);
const onScroll = () => {
stuck.value = window.scrollY > 8;
};
onMounted(() => {
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
});
onBeforeUnmount(() => {
window.removeEventListener("scroll", onScroll);
});
// 路由变化时收起移动端菜单
watch(
() => route.fullPath,
() => {
menuOpen.value = false;
}
);
</script>
<template>
<header class="site-header" :class="{ 'is-stuck': stuck }">
<!-- 顶部工具条 -->
<div class="header-util">
<div class="container header-util__inner">
<p class="header-util__slogan">{{ siteInfo.slogan }}</p>
<ul class="util-links">
<li v-for="item in utilityLinks" :key="item.path">
<RouterLink :to="item.path">{{ item.label }}</RouterLink>
</li>
</ul>
</div>
</div>
<!-- 主导航 -->
<div class="header-main">
<div class="container header-main__inner">
<RouterLink to="/" class="brand" aria-label="返回首页">
<span class="brand__mark">{{ siteInfo.brandMark }}</span>
<span class="brand__text">
<strong>{{ siteInfo.brandName }}</strong>
<em>{{ siteInfo.brandSub }}</em>
</span>
</RouterLink>
<nav class="main-nav">
<RouterLink
v-for="item in navItems"
:key="item.path"
:to="item.path"
class="main-nav__link"
>
{{ item.label }}
</RouterLink>
</nav>
<button
class="menu-toggle"
type="button"
:class="{ 'is-open': menuOpen }"
:aria-expanded="menuOpen"
aria-label="展开导航菜单"
@click="menuOpen = !menuOpen"
>
<span></span>
<span></span>
<span></span>
</button>
</div>
</div>
<!-- 移动端下拉菜单 -->
<transition name="drawer">
<nav v-show="menuOpen" class="mobile-nav">
<div class="container">
<RouterLink
v-for="item in navItems"
:key="item.path"
:to="item.path"
class="mobile-nav__link"
>
{{ item.label }}
</RouterLink>
<div class="mobile-nav__util">
<RouterLink
v-for="item in utilityLinks"
:key="item.path"
:to="item.path"
>
{{ item.label }}
</RouterLink>
</div>
</div>
</nav>
</transition>
</header>
</template>
<style scoped lang="less">
.site-header {
position: sticky;
top: 0;
z-index: 100;
background: @white;
transition: box-shadow 0.3s ease;
&.is-stuck {
box-shadow: 0 8px 30px rgba(11, 20, 48, 0.09);
}
}
/* ---------- 顶部工具条 ---------- */
.header-util {
background: #f8fafd;
border-bottom: 1px solid @line;
&__inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
}
&__slogan {
font-size: 13px;
color: @ink-500;
letter-spacing: 0.02em;
}
}
.util-links {
display: flex;
align-items: center;
li {
display: flex;
align-items: center;
& + li::before {
content: "";
width: 1px;
height: 11px;
margin: 0 16px;
background: @ink-300;
}
}
a {
font-size: 13px;
color: @ink-600;
transition: color 0.2s ease;
&:hover {
color: @blue-600;
}
}
}
/* ---------- 主导航 ---------- */
.header-main {
&__inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88px;
}
}
.brand {
display: flex;
align-items: center;
gap: 12px;
flex-shrink: 0;
&__mark {
display: flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 11px;
background: linear-gradient(140deg, @navy-700, @navy-900);
color: @white;
font-size: 16px;
font-weight: 700;
letter-spacing: 0.02em;
}
&__text {
display: flex;
flex-direction: column;
line-height: 1.15;
strong {
font-size: 20px;
font-weight: 700;
color: @ink-900;
letter-spacing: 0.01em;
}
em {
font-style: normal;
font-size: 10px;
font-weight: 500;
letter-spacing: 0.34em;
color: @ink-400;
text-transform: uppercase;
}
}
}
.main-nav {
display: flex;
align-items: center;
margin-left: auto;
&__link {
position: relative;
padding: 0 26px;
font-size: 17px;
font-weight: 500;
color: @ink-800;
line-height: 88px;
letter-spacing: 0.02em;
transition: color 0.22s ease;
&::after {
content: "";
position: absolute;
left: 26px;
right: 26px;
bottom: 24px;
height: 2px;
background: @blue-600;
transform: scaleX(0);
transform-origin: left center;
transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}
&:hover,
&.router-link-active {
color: @blue-600;
&::after {
transform: scaleX(1);
}
}
}
}
.menu-toggle {
display: none;
width: 30px;
height: 30px;
margin-left: 22px;
flex-direction: column;
justify-content: center;
gap: 6px;
span {
display: block;
height: 2px;
width: 100%;
background: @ink-800;
border-radius: 2px;
transition: transform 0.3s ease, opacity 0.2s ease;
}
&.is-open {
span:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
span:nth-child(2) {
opacity: 0;
}
span:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
}
}
/* ---------- 移动端菜单 ---------- */
.mobile-nav {
display: none;
border-top: 1px solid @line;
background: @white;
padding: 12px 0 20px;
&__link {
display: block;
padding: 15px 0;
font-size: 16px;
font-weight: 500;
color: @ink-800;
border-bottom: 1px solid @line;
}
&__util {
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
padding-top: 16px;
a {
font-size: 13px;
color: @ink-500;
}
}
}
.drawer-enter-active,
.drawer-leave-active {
transition: opacity 0.25s ease, transform 0.25s ease;
}
.drawer-enter-from,
.drawer-leave-to {
opacity: 0;
transform: translateY(-10px);
}
/* ---------- 响应式 ---------- */
@media (max-width: 1200px) {
.main-nav__link {
padding: 0 16px;
font-size: 16px;
&::after {
left: 16px;
right: 16px;
}
}
}
@media (max-width: 992px) {
.header-util__slogan {
display: none;
}
.header-util__inner {
justify-content: flex-end;
height: 38px;
}
.header-main__inner {
height: 70px;
}
.main-nav {
display: none;
}
.menu-toggle {
display: flex;
}
.mobile-nav {
display: block;
}
}
</style>