- 后台全面引入 Naive UI 组件库,统一 UI 规范 - 前台 usePublicApi 切换到 API 调用(GET /api/content/[key]) - 前后台数据源统一(site_content 表) - 产品线统一管理(tour/camp/course 三套编辑器) - 产品数据归一化(itinerary/faq/pricing 格式统一) - 表单提交 API 改写入 submissions 表 - 新增 submissions 标记已读 API - site-content PUT 支持 upsert - 修复前台 bug(stories 路由冲突、占位符警告、selector breadcrumb) - 消除 PageHero 类型警告(useSEO reactive 修复) - 25 个前台页面全部 HTTP 200,展示效果不变 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
821 行
20 KiB
Vue
821 行
20 KiB
Vue
<template>
|
||
<div class="page-selector">
|
||
<LayoutPageHero
|
||
:title="seo.h1"
|
||
subtitle="5个问题 · 1分钟 · 找到最适合你的版本"
|
||
:breadcrumbs="[{ text: '产品线', to: '/products' }, { text: '选版工具' }]"
|
||
background-image="/images/seasons/summer-hero.jpg"
|
||
/>
|
||
|
||
<!-- TL;DR -->
|
||
<section class="page-tldr" aria-label="选版工具摘要">
|
||
<div class="container">
|
||
<div class="page-tldr-inner">
|
||
<p>呼伦贝尔行程选版工具:回答5个简单问题,1分钟内为你推荐最适合的产品版本。根据出行天数、人群、偏好智能匹配。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-if="selectorData" class="selector-section">
|
||
<div class="container">
|
||
<div class="selector-wrap">
|
||
|
||
<!-- 问答流程 -->
|
||
<template v-if="!showResult">
|
||
<!-- 进度条 -->
|
||
<div class="progress-bar-wrap">
|
||
<div class="progress-bar-track">
|
||
<div class="progress-bar-fill" :style="{ width: progressPercent + '%' }" />
|
||
</div>
|
||
<span class="progress-label">{{ currentStepIndex + 1 }} / {{ visibleQuestions.length }}</span>
|
||
</div>
|
||
|
||
<!-- 问题卡片 -->
|
||
<Transition name="step" mode="out-in">
|
||
<div :key="currentQuestion.id" class="question-card">
|
||
<p class="question-hint">{{ currentQuestion.hint }}</p>
|
||
<h2 class="question-text">{{ currentQuestion.text }}</h2>
|
||
<div class="options-grid" :class="{ 'options-grid--3': currentQuestion.options.length === 3 }">
|
||
<button
|
||
v-for="opt in currentQuestion.options"
|
||
:key="opt.value"
|
||
class="option-btn"
|
||
:class="{ 'option-btn--selected': answers[currentQuestion.id] === opt.value }"
|
||
@click="selectAnswer(currentQuestion.id, opt.value)"
|
||
>
|
||
<span class="option-label">{{ opt.label }}</span>
|
||
<span class="option-desc">{{ opt.desc }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 导航按钮(非第一步才显示返回) -->
|
||
<div class="step-nav">
|
||
<button v-if="currentStepIndex > 0" class="btn-back" @click="goBack">
|
||
← 上一步
|
||
</button>
|
||
<span v-else />
|
||
<button
|
||
v-if="answers[currentQuestion.id]"
|
||
class="btn-next"
|
||
@click="goNext"
|
||
>
|
||
{{ isLastQuestion ? '查看推荐' : '下一步 →' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
</template>
|
||
|
||
<!-- 推荐结果 -->
|
||
<Transition name="step" mode="out-in">
|
||
<div v-if="showResult" key="result" class="result-wrap">
|
||
<div class="result-header">
|
||
<p class="result-eyebrow">根据你的回答,我们推荐</p>
|
||
<div class="result-tag">{{ recommended.tag }}</div>
|
||
<h2 class="result-name">{{ recommended.name }}</h2>
|
||
<p class="result-audience">{{ recommended.audience }}</p>
|
||
</div>
|
||
|
||
<p class="result-desc">{{ recommended.description }}</p>
|
||
|
||
<!-- 推荐理由 -->
|
||
<div class="match-reasons">
|
||
<h3 class="match-reasons-title">为什么推荐这个版本</h3>
|
||
<ul class="match-reasons-list">
|
||
<li v-for="(reason, i) in matchReasons" :key="i" class="match-reason-item">
|
||
<span class="match-reason-icon">✓</span>
|
||
<span>{{ reason }}</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- 产品亮点 -->
|
||
<div class="result-highlights">
|
||
<h3 class="result-highlights-title">行程亮点</h3>
|
||
<ul class="highlights-list">
|
||
<li v-for="(h, i) in recommended.highlights" :key="i" class="highlight-item">
|
||
<span class="highlight-dot" />
|
||
<span>{{ h }}</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- 操作区 -->
|
||
<div class="result-actions">
|
||
<NuxtLink :to="`/products/${recommended.id}`" class="btn-primary">
|
||
查看详细行程
|
||
</NuxtLink>
|
||
<NuxtLink to="/customize" class="btn-cta">
|
||
在线定制这条行程
|
||
</NuxtLink>
|
||
</div>
|
||
|
||
<!-- 换一个 -->
|
||
<div class="alternate-wrap">
|
||
<p class="alternate-label">不太满意?</p>
|
||
<button class="btn-alternate" @click="showAlternate">
|
||
换一个版本看看 →
|
||
</button>
|
||
<button class="btn-restart" @click="restart">
|
||
重新回答
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 备选结果展示 -->
|
||
<Transition name="fade">
|
||
<div v-if="alternateIndex > 0 && rankedProducts[alternateIndex]" class="alternate-result">
|
||
<p class="alternate-eyebrow">备选推荐</p>
|
||
<div class="alternate-tag">{{ rankedProducts[alternateIndex].tag }}</div>
|
||
<h3 class="alternate-name">{{ rankedProducts[alternateIndex].name }}</h3>
|
||
<p class="alternate-desc">{{ rankedProducts[alternateIndex].description }}</p>
|
||
<div class="alternate-actions">
|
||
<NuxtLink :to="`/products/${rankedProducts[alternateIndex].id}`" class="btn-outline">
|
||
查看这个版本
|
||
</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
|
||
<!-- 全系列入口 -->
|
||
<div class="all-versions">
|
||
<p>想自己对比所有版本?</p>
|
||
<NuxtLink to="/products" class="btn-link">查看全系列6个版本 →</NuxtLink>
|
||
</div>
|
||
</div>
|
||
</Transition>
|
||
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- 底部CTA -->
|
||
<CommonSectionCTA
|
||
title="还有疑问?直接问定制师"
|
||
description="告诉我们你的家庭情况,定制师为你1对1选版本、排行程"
|
||
:links="[{ to: '/customize', text: '在线定制' }, { to: '/contact', text: '联系定制师' }]"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
|
||
|
||
const { data: selectorData } = await usePublicApi('selector')
|
||
const { data: productsData } = await usePublicApi('products')
|
||
|
||
const seo = useSEO('selector')
|
||
useScrollReveal()
|
||
|
||
// JSON-LD: Quiz + ItemList schema
|
||
useHead({
|
||
script: [
|
||
{
|
||
type: 'application/ld+json',
|
||
innerHTML: JSON.stringify({
|
||
'@context': 'https://schema.org',
|
||
'@type': 'Quiz',
|
||
name: '呼伦贝尔行程版本选择工具',
|
||
description: '5个问题帮你找到最适合的呼伦贝尔行程版本。根据出行天数、人群、孩子年龄、预算和偏好,从额吉的故乡V9系列6个版本中精准推荐。',
|
||
url: 'https://1814.love/selector',
|
||
about: {
|
||
'@type': 'TouristTrip',
|
||
name: '呼伦贝尔家庭定制游',
|
||
touristType: '家庭亲子游客',
|
||
},
|
||
numberOfQuestions: 5,
|
||
provider: {
|
||
'@type': 'TravelAgency',
|
||
name: '呼籁旅行',
|
||
url: 'https://1814.love',
|
||
},
|
||
}),
|
||
},
|
||
{
|
||
type: 'application/ld+json',
|
||
innerHTML: JSON.stringify({
|
||
'@context': 'https://schema.org',
|
||
'@type': 'ItemList',
|
||
name: '额吉的故乡V9系列行程版本',
|
||
description: '呼籁旅行呼伦贝尔亲子游6个版本,5天4晚至7天6晚,覆盖森林版、草原版、亲子版、探索版、旷野版',
|
||
url: 'https://1814.love/selector',
|
||
numberOfItems: productsData.value?.versions?.length || 0,
|
||
itemListElement: (productsData.value?.versions || []).map((v, i) => ({
|
||
'@type': 'ListItem',
|
||
position: i + 1,
|
||
name: v.name,
|
||
description: v.audience,
|
||
url: `https://1814.love/products/${v.id}`,
|
||
})),
|
||
}),
|
||
},
|
||
],
|
||
})
|
||
|
||
useBreadcrumbSchema([
|
||
{ text: '首页', to: '/' },
|
||
{ text: '产品线', to: '/products' },
|
||
{ text: '帮我选行程' },
|
||
])
|
||
|
||
const questions = computed(() => selectorData.value?.questions || [])
|
||
const scoring = computed(() => selectorData.value?.scoring || {})
|
||
const reasonsMap = computed(() => selectorData.value?.matchReasons || {})
|
||
const allProducts = computed(() => productsData.value?.versions || [])
|
||
|
||
// 状态
|
||
const answers = ref({})
|
||
const currentStepIndex = ref(0)
|
||
const showResult = ref(false)
|
||
const alternateIndex = ref(0)
|
||
|
||
// 过滤出当前应显示的问题(根据条件)
|
||
const visibleQuestions = computed(() => {
|
||
return questions.value.filter((q) => {
|
||
if (!q.condition) return true
|
||
return answers.value[q.condition.key] === q.condition.value
|
||
})
|
||
})
|
||
|
||
const currentQuestion = computed(() => visibleQuestions.value[currentStepIndex.value])
|
||
const isLastQuestion = computed(() => currentStepIndex.value === visibleQuestions.value.length - 1)
|
||
const progressPercent = computed(() =>
|
||
Math.round(((currentStepIndex.value) / visibleQuestions.value.length) * 100)
|
||
)
|
||
|
||
// 计分逻辑
|
||
const scores = computed(() => {
|
||
const totals = {}
|
||
for (const [questionId, questionScores] of Object.entries(scoring.value)) {
|
||
const answer = answers.value[questionId]
|
||
if (!answer) continue
|
||
const productScores = questionScores[answer] || {}
|
||
for (const [productId, score] of Object.entries(productScores)) {
|
||
totals[productId] = (totals[productId] || 0) + score
|
||
}
|
||
}
|
||
return totals
|
||
})
|
||
|
||
// 按得分排序
|
||
const rankedProducts = computed(() => {
|
||
return [...allProducts.value].sort(
|
||
(a, b) => (scores.value[b.id] || 0) - (scores.value[a.id] || 0)
|
||
)
|
||
})
|
||
|
||
const recommended = computed(() => rankedProducts.value[0])
|
||
|
||
// 生成匹配原因
|
||
const matchReasons = computed(() => {
|
||
if (!recommended.value) return []
|
||
const pid = recommended.value.id
|
||
const reasonMap = reasonsMap.value[pid] || {}
|
||
const result = []
|
||
|
||
// 找匹配答案的原因
|
||
for (const [qid, answer] of Object.entries(answers.value)) {
|
||
const key = `${qid}:${answer}`
|
||
if (reasonMap[key] && result.length < 3) {
|
||
result.push(reasonMap[key])
|
||
}
|
||
}
|
||
|
||
// 补充默认原因
|
||
if (result.length === 0 && reasonMap.default) {
|
||
result.push(reasonMap.default)
|
||
}
|
||
|
||
return result.slice(0, 3)
|
||
})
|
||
|
||
// 操作方法
|
||
function selectAnswer(questionId, value) {
|
||
answers.value = { ...answers.value, [questionId]: value }
|
||
// 移动端自动前进
|
||
if (window.innerWidth < 768) {
|
||
setTimeout(() => goNext(), 200)
|
||
}
|
||
}
|
||
|
||
function goNext() {
|
||
if (isLastQuestion.value) {
|
||
showResult.value = true
|
||
} else {
|
||
currentStepIndex.value++
|
||
}
|
||
}
|
||
|
||
function goBack() {
|
||
if (currentStepIndex.value > 0) {
|
||
currentStepIndex.value--
|
||
}
|
||
}
|
||
|
||
function showAlternate() {
|
||
if (alternateIndex.value < rankedProducts.value.length - 1) {
|
||
alternateIndex.value++
|
||
}
|
||
}
|
||
|
||
function restart() {
|
||
answers.value = {}
|
||
currentStepIndex.value = 0
|
||
showResult.value = false
|
||
alternateIndex.value = 0
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.selector-section {
|
||
padding: @space-3xl 0;
|
||
background: @color-bg-warm;
|
||
}
|
||
|
||
.selector-wrap {
|
||
max-width: 680px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
// 进度条
|
||
.progress-bar-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: @space-md;
|
||
margin-bottom: @space-2xl;
|
||
}
|
||
|
||
.progress-bar-track {
|
||
flex: 1;
|
||
height: 4px;
|
||
background: @color-border;
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-bar-fill {
|
||
height: 100%;
|
||
background: @color-primary;
|
||
border-radius: 2px;
|
||
transition: width 0.4s ease;
|
||
}
|
||
|
||
.progress-label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
// 问题卡片
|
||
.question-card {
|
||
background: @color-bg-white;
|
||
border-radius: 16px;
|
||
padding: @space-2xl;
|
||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.06);
|
||
|
||
.respond-md({
|
||
padding: @space-3xl;
|
||
});
|
||
}
|
||
|
||
.question-hint {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
margin: 0 0 @space-sm;
|
||
}
|
||
|
||
.question-text {
|
||
font-size: @font-size-xl;
|
||
font-weight: @font-weight-bold;
|
||
color: @color-text-primary;
|
||
margin: 0 0 @space-xl;
|
||
line-height: @line-height-tight;
|
||
|
||
.respond-md({
|
||
font-size: @font-size-2xl;
|
||
});
|
||
}
|
||
|
||
// 选项网格
|
||
.options-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: @space-md;
|
||
margin-bottom: @space-xl;
|
||
|
||
&--3 {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
}
|
||
|
||
.option-btn {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: @space-xs;
|
||
padding: @space-md @space-lg;
|
||
background: @color-bg-gray;
|
||
border: 2px solid @color-border;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: all @transition-base;
|
||
text-align: left;
|
||
width: 100%;
|
||
|
||
&:hover {
|
||
border-color: @color-primary-light;
|
||
background: @color-primary-lighter;
|
||
}
|
||
|
||
&--selected {
|
||
border-color: @color-primary;
|
||
background: @color-primary-lighter;
|
||
|
||
.option-label {
|
||
color: @color-primary;
|
||
}
|
||
}
|
||
}
|
||
|
||
.option-label {
|
||
font-size: @font-size-base;
|
||
font-weight: @font-weight-medium;
|
||
color: @color-text-primary;
|
||
transition: color @transition-base;
|
||
}
|
||
|
||
.option-desc {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-muted;
|
||
line-height: @line-height-normal;
|
||
}
|
||
|
||
// 步骤导航
|
||
.step-nav {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn-back {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: @space-sm 0;
|
||
transition: color @transition-base;
|
||
|
||
&:hover {
|
||
color: @color-text-primary;
|
||
}
|
||
}
|
||
|
||
.btn-next {
|
||
background: @color-primary;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: @space-sm @space-xl;
|
||
font-size: @font-size-base;
|
||
font-weight: @font-weight-medium;
|
||
cursor: pointer;
|
||
transition: background @transition-base;
|
||
|
||
&:hover {
|
||
background: @color-primary-light;
|
||
}
|
||
}
|
||
|
||
// 结果区域
|
||
.result-wrap {
|
||
background: @color-bg-white;
|
||
border-radius: 16px;
|
||
padding: @space-2xl;
|
||
box-shadow: 0 2px 20px rgba(0, 0, 0, 0.06);
|
||
|
||
.respond-md({
|
||
padding: @space-3xl;
|
||
});
|
||
}
|
||
|
||
.result-header {
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.result-eyebrow {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
margin: 0 0 @space-sm;
|
||
}
|
||
|
||
.result-tag {
|
||
display: inline-block;
|
||
background: @color-primary;
|
||
color: white;
|
||
font-size: @font-size-xs;
|
||
font-weight: @font-weight-medium;
|
||
padding: 3px 10px;
|
||
border-radius: 20px;
|
||
margin-bottom: @space-sm;
|
||
}
|
||
|
||
.result-name {
|
||
font-size: @font-size-2xl;
|
||
font-weight: @font-weight-bold;
|
||
color: @color-text-primary;
|
||
margin: 0 0 @space-xs;
|
||
line-height: @line-height-tight;
|
||
|
||
.respond-md({
|
||
font-size: @font-size-3xl;
|
||
});
|
||
}
|
||
|
||
.result-audience {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
margin: 0;
|
||
}
|
||
|
||
.result-desc {
|
||
font-size: @font-size-base;
|
||
color: @color-text-secondary;
|
||
line-height: @line-height-loose;
|
||
margin: 0 0 @space-xl;
|
||
padding-bottom: @space-xl;
|
||
border-bottom: 1px solid @color-border;
|
||
}
|
||
|
||
// 匹配原因
|
||
.match-reasons {
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.match-reasons-title,
|
||
.result-highlights-title {
|
||
font-size: @font-size-base;
|
||
font-weight: @font-weight-medium;
|
||
color: @color-text-primary;
|
||
margin: 0 0 @space-md;
|
||
}
|
||
|
||
.match-reasons-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-sm;
|
||
}
|
||
|
||
.match-reason-item {
|
||
display: flex;
|
||
gap: @space-sm;
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
line-height: @line-height-normal;
|
||
}
|
||
|
||
.match-reason-icon {
|
||
color: @color-primary;
|
||
font-weight: @font-weight-bold;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
// 亮点
|
||
.result-highlights {
|
||
margin-bottom: @space-xl;
|
||
padding-bottom: @space-xl;
|
||
border-bottom: 1px solid @color-border;
|
||
}
|
||
|
||
.highlights-list {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-sm;
|
||
}
|
||
|
||
.highlight-item {
|
||
display: flex;
|
||
gap: @space-sm;
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
line-height: @line-height-normal;
|
||
}
|
||
|
||
.highlight-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: @color-accent;
|
||
flex-shrink: 0;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
// 操作按钮
|
||
.result-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: @space-md;
|
||
margin-bottom: @space-xl;
|
||
|
||
.respond-md({
|
||
flex-direction: row;
|
||
});
|
||
}
|
||
|
||
.btn-primary {
|
||
display: inline-block;
|
||
background: @color-bg-white;
|
||
color: @color-primary;
|
||
border: 2px solid @color-primary;
|
||
border-radius: 8px;
|
||
padding: @space-md @space-xl;
|
||
font-size: @font-size-base;
|
||
font-weight: @font-weight-medium;
|
||
text-decoration: none;
|
||
text-align: center;
|
||
transition: all @transition-base;
|
||
|
||
&:hover {
|
||
background: @color-primary-lighter;
|
||
}
|
||
}
|
||
|
||
.btn-cta {
|
||
display: inline-block;
|
||
background: @color-primary;
|
||
color: white;
|
||
border-radius: 8px;
|
||
padding: @space-md @space-xl;
|
||
font-size: @font-size-base;
|
||
font-weight: @font-weight-medium;
|
||
text-decoration: none;
|
||
text-align: center;
|
||
transition: background @transition-base;
|
||
|
||
&:hover {
|
||
background: @color-primary-light;
|
||
}
|
||
}
|
||
|
||
// 换一个
|
||
.alternate-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: @space-md;
|
||
flex-wrap: wrap;
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.alternate-label {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
margin: 0;
|
||
}
|
||
|
||
.btn-alternate {
|
||
font-size: @font-size-sm;
|
||
color: @color-primary;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
text-decoration: underline;
|
||
transition: color @transition-base;
|
||
|
||
&:hover {
|
||
color: @color-primary-light;
|
||
}
|
||
}
|
||
|
||
.btn-restart {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
transition: color @transition-base;
|
||
|
||
&:hover {
|
||
color: @color-text-secondary;
|
||
}
|
||
}
|
||
|
||
// 备选结果
|
||
.alternate-result {
|
||
background: @color-bg-gray;
|
||
border-radius: 12px;
|
||
padding: @space-lg;
|
||
margin-bottom: @space-xl;
|
||
}
|
||
|
||
.alternate-eyebrow {
|
||
font-size: @font-size-xs;
|
||
color: @color-text-muted;
|
||
margin: 0 0 @space-xs;
|
||
}
|
||
|
||
.alternate-tag {
|
||
display: inline-block;
|
||
background: @color-accent;
|
||
color: white;
|
||
font-size: @font-size-xs;
|
||
font-weight: @font-weight-medium;
|
||
padding: 2px 8px;
|
||
border-radius: 20px;
|
||
margin-bottom: @space-xs;
|
||
}
|
||
|
||
.alternate-name {
|
||
font-size: @font-size-lg;
|
||
font-weight: @font-weight-bold;
|
||
color: @color-text-primary;
|
||
margin: 0 0 @space-sm;
|
||
}
|
||
|
||
.alternate-desc {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-secondary;
|
||
line-height: @line-height-normal;
|
||
margin: 0 0 @space-md;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.alternate-actions {
|
||
display: flex;
|
||
}
|
||
|
||
.btn-outline {
|
||
display: inline-block;
|
||
background: transparent;
|
||
color: @color-primary;
|
||
border: 1px solid @color-primary;
|
||
border-radius: 6px;
|
||
padding: @space-sm @space-lg;
|
||
font-size: @font-size-sm;
|
||
text-decoration: none;
|
||
transition: all @transition-base;
|
||
|
||
&:hover {
|
||
background: @color-primary-lighter;
|
||
}
|
||
}
|
||
|
||
// 全系列入口
|
||
.all-versions {
|
||
text-align: center;
|
||
padding-top: @space-lg;
|
||
border-top: 1px solid @color-border;
|
||
|
||
p {
|
||
font-size: @font-size-sm;
|
||
color: @color-text-muted;
|
||
margin: 0 0 @space-xs;
|
||
}
|
||
}
|
||
|
||
.btn-link {
|
||
font-size: @font-size-sm;
|
||
color: @color-primary;
|
||
text-decoration: none;
|
||
|
||
&:hover {
|
||
text-decoration: underline;
|
||
}
|
||
}
|
||
|
||
// 过渡动画
|
||
.step-enter-active,
|
||
.step-leave-active {
|
||
transition: opacity 0.25s ease, transform 0.25s ease;
|
||
}
|
||
|
||
.step-enter-from {
|
||
opacity: 0;
|
||
transform: translateY(16px);
|
||
}
|
||
|
||
.step-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(-16px);
|
||
}
|
||
|
||
.fade-enter-active,
|
||
.fade-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.fade-enter-from,
|
||
.fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
</style>
|