sync: 同步最新呼籁官网代码到仓库

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
刘涛
2026-03-22 21:57:35 +08:00
共同撰写人 Claude Sonnet 4.6
当前提交 a25f3a77ce
共修改 319 个文件,包含 46379 行新增和 0 行删除
+55
查看文件
@@ -0,0 +1,55 @@
<template>
<nav v-if="items.length" class="breadcrumb" aria-label="面包屑导航">
<ol class="breadcrumb-list">
<li class="breadcrumb-item">
<NuxtLink to="/" class="breadcrumb-link">首页</NuxtLink>
</li>
<li v-for="(item, index) in items" :key="index" class="breadcrumb-item">
<span class="breadcrumb-sep" aria-hidden="true">/</span>
<NuxtLink v-if="item.to" :to="item.to" class="breadcrumb-link">{{ item.text }}</NuxtLink>
<span v-else class="breadcrumb-current" aria-current="page">{{ item.text }}</span>
</li>
</ol>
</nav>
</template>
<script setup>
defineProps({
items: { type: Array, default: () => [] },
})
</script>
<style lang="less" scoped>
.breadcrumb-list {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: @space-xs;
}
.breadcrumb-item {
display: flex;
align-items: center;
gap: @space-xs;
}
.breadcrumb-sep {
color: @color-text-muted;
font-size: @font-size-sm;
}
.breadcrumb-link {
font-size: @font-size-sm;
color: @color-text-muted;
text-decoration: none;
&:hover {
color: @color-primary;
}
}
.breadcrumb-current {
font-size: @font-size-sm;
color: @color-text-secondary;
}
</style>
+26
查看文件
@@ -0,0 +1,26 @@
<template>
<div class="content-card" :class="{ 'content-card--hoverable': hoverable }">
<slot />
</div>
</template>
<script setup>
defineProps({
hoverable: { type: Boolean, default: true },
})
</script>
<style lang="less" scoped>
.content-card {
background: @color-bg-white;
border: 1px solid @color-border;
border-radius: @border-radius-lg;
padding: @space-xl;
transition: box-shadow 0.3s cubic-bezier(0.23, 1, 0.32, 1), transform 0.3s cubic-bezier(0.23, 1, 0.32, 1);
&--hoverable:hover {
box-shadow: @shadow-md;
transform: translateY(-3px);
}
}
</style>
+37
查看文件
@@ -0,0 +1,37 @@
<template>
<NuxtLink :to="to" class="internal-link">
<span>{{ text }}</span>
<span v-if="arrow" class="internal-link-arrow" aria-hidden="true">&rarr;</span>
</NuxtLink>
</template>
<script setup>
defineProps({
to: { type: String, required: true },
text: { type: String, required: true },
arrow: { type: Boolean, default: true },
})
</script>
<style lang="less" scoped>
.internal-link {
display: inline-flex;
align-items: center;
gap: @space-sm;
font-size: @font-size-base;
color: @color-primary;
text-decoration: none;
font-weight: @font-weight-medium;
transition: gap @transition-fast;
&:hover {
gap: @space-md;
color: @color-primary-light;
}
}
.internal-link-arrow {
font-size: @font-size-lg;
transition: transform @transition-fast;
}
</style>
+54
查看文件
@@ -0,0 +1,54 @@
<template>
<div class="qr-block" :class="[`qr-block--${size}`]">
<img :src="image" :alt="`${label}二维码`" class="qr-image" loading="lazy" />
<span class="qr-label">{{ label }}</span>
</div>
</template>
<script setup>
defineProps({
image: { type: String, required: true },
label: { type: String, required: true },
size: { type: String, default: 'normal' },
})
</script>
<style lang="less" scoped>
.qr-block {
display: flex;
flex-direction: column;
align-items: center;
gap: @space-xs;
}
.qr-image {
border-radius: @border-radius-sm;
background: @color-bg-gray;
object-fit: contain;
.qr-block--small & {
width: 80px;
height: 80px;
}
.qr-block--normal & {
width: 140px;
height: 140px;
}
.qr-block--large & {
width: 180px;
height: 180px;
}
}
.qr-label {
font-size: @font-size-xs;
color: @color-text-muted;
.qr-block--normal &,
.qr-block--large & {
font-size: @font-size-sm;
}
}
</style>
+160
查看文件
@@ -0,0 +1,160 @@
<template>
<section class="section-cta" :class="`section-cta--${bg}`">
<div class="section-cta-bg" />
<div class="container text-center section-cta-inner">
<h2 class="section-cta-title">{{ title }}</h2>
<p class="section-cta-desc">{{ description }}</p>
<div class="section-cta-links">
<NuxtLink
v-for="(link, index) in links"
:key="link.to"
:to="link.to"
class="cta-btn"
:class="index === 0 ? 'cta-btn--primary' : 'cta-btn--outline'"
>
{{ link.text }}
</NuxtLink>
</div>
</div>
</section>
</template>
<script setup>
defineProps({
title: { type: String, required: true },
description: { type: String, required: true },
links: { type: Array, required: true },
bg: { type: String, default: 'green' },
})
</script>
<style lang="less" scoped>
.section-cta {
position: relative;
overflow: hidden;
.section-padding();
}
.section-cta--green {
background: linear-gradient(135deg, @color-primary-dark 0%, @color-primary 50%, @color-primary-light 100%);
}
.section-cta--warm {
background: linear-gradient(135deg, @color-bg-warm 0%, @color-accent-bg 100%);
}
.section-cta-bg {
position: absolute;
inset: 0;
background: radial-gradient(ellipse at 20% 50%, rgba(255, 255, 255, 0.08) 0%, transparent 60%),
radial-gradient(ellipse at 80% 50%, rgba(255, 255, 255, 0.05) 0%, transparent 60%);
pointer-events: none;
}
.section-cta-inner {
position: relative;
z-index: 1;
}
.section-cta-title {
.section-cta--green & {
color: #fff;
}
&::after {
display: none;
}
}
.section-cta-desc {
margin-top: @space-md;
margin-bottom: @space-xl;
font-size: @font-size-md;
line-height: @line-height-relaxed;
max-width: 560px;
margin-left: auto;
margin-right: auto;
.section-cta--green & {
color: rgba(255, 255, 255, 0.8);
}
.section-cta--warm & {
color: @color-text-secondary;
}
}
.section-cta-links {
display: flex;
justify-content: center;
gap: @space-md;
flex-wrap: wrap;
}
.cta-btn {
display: inline-flex;
align-items: center;
padding: 14px @space-xl;
border-radius: @border-radius-md;
font-size: @font-size-base;
font-weight: @font-weight-medium;
text-decoration: none;
transition: all @transition-base;
.respond-md({
padding: 16px 36px;
font-size: @font-size-md;
});
&--primary {
.section-cta--green & {
background: #fff;
color: @color-primary;
border: 1px solid #fff;
&:hover {
background: rgba(255, 255, 255, 0.9);
color: @color-primary-dark;
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
}
.section-cta--warm & {
background: @color-primary;
color: #fff;
border: 1px solid @color-primary;
&:hover {
background: @color-primary-light;
border-color: @color-primary-light;
color: #fff;
}
}
}
&--outline {
.section-cta--green & {
border: 1px solid rgba(255, 255, 255, 0.5);
color: #fff;
&:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.8);
color: #fff;
}
}
.section-cta--warm & {
background: transparent;
color: @color-primary;
border: 1px solid @color-primary;
&:hover {
background: @color-primary;
color: #fff;
}
}
}
}
</style>
+74
查看文件
@@ -0,0 +1,74 @@
<template>
<div class="section-title" :class="[`section-title--${align}`]">
<span v-if="label" class="section-title-label">{{ label }}</span>
<component :is="tag" class="section-title-text">{{ title }}</component>
<p v-if="subtitle" class="section-title-sub">{{ subtitle }}</p>
</div>
</template>
<script setup>
defineProps({
title: { type: String, required: true },
subtitle: { type: String, default: '' },
label: { type: String, default: '' },
tag: { type: String, default: 'h2' },
align: { type: String, default: 'center' },
})
</script>
<style lang="less" scoped>
.section-title {
margin-bottom: @space-xl;
.respond-md({
margin-bottom: @space-2xl;
});
&--center {
text-align: center;
}
&--left {
text-align: left;
}
}
.section-title-label {
display: inline-block;
font-size: @font-size-xs;
font-weight: @font-weight-bold;
color: @color-primary;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: @space-sm;
}
.section-title-text {
color: @color-text-primary;
// 标题下方品牌色渐变短线装饰
&::after {
content: '';
display: block;
width: 48px;
height: 3px;
background: linear-gradient(90deg, @color-primary, @color-primary-light);
border-radius: 2px;
margin-top: @space-md;
}
.section-title--center & {
&::after {
margin-left: auto;
margin-right: auto;
}
}
}
.section-title-sub {
margin-top: @space-md;
font-size: @font-size-md;
color: @color-text-muted;
line-height: @line-height-relaxed;
}
</style>
+83
查看文件
@@ -0,0 +1,83 @@
<template>
<div class="trust-badge" :class="{ 'trust-badge--dark': dark }">
<div class="trust-badge-value">
<span class="trust-number">{{ value }}</span>
<span v-if="unit" class="trust-unit">{{ unit }}</span>
</div>
<div class="trust-badge-label">{{ label }}</div>
<div v-if="attribution" class="trust-badge-note">{{ attribution }}</div>
</div>
</template>
<script setup>
defineProps({
value: { type: String, required: true },
unit: { type: String, default: '' },
label: { type: String, required: true },
attribution: { type: String, default: '' },
dark: { type: Boolean, default: false },
})
</script>
<style lang="less" scoped>
.trust-badge {
text-align: center;
padding: @space-lg @space-md;
}
.trust-badge-value {
display: flex;
align-items: baseline;
justify-content: center;
gap: @space-xs;
}
.trust-number {
font-size: @font-size-3xl;
font-weight: @font-weight-bold;
color: @color-primary;
letter-spacing: -1px;
.respond-md({
font-size: @font-size-display;
});
.trust-badge--dark & {
color: #fff;
}
}
.trust-unit {
font-size: @font-size-base;
color: @color-primary-light;
.respond-md({
font-size: @font-size-md;
});
.trust-badge--dark & {
color: @color-primary-lighter;
}
}
.trust-badge-label {
margin-top: @space-sm;
font-size: @font-size-sm;
color: @color-text-secondary;
font-weight: @font-weight-medium;
.trust-badge--dark & {
color: rgba(255, 255, 255, 0.6);
}
}
.trust-badge-note {
margin-top: @space-xs;
font-size: @font-size-xs;
color: @color-text-muted;
.trust-badge--dark & {
color: rgba(255, 255, 255, 0.35);
}
}
</style>