anon-ui/src/pages/index/index.vue
2026-05-03 19:36:05 +08:00

344 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<home-hero-card
:eyebrow="HOME_HERO.eyebrow"
:title="HOME_HERO.title"
:subtitle="HOME_HERO.subtitle"
:highlights="HOME_HERO.highlights"
:image="HOME_HERO.image"
:action-text="HOME_HERO.actionText"
:secondary-text="HOME_HERO.secondaryText"
@action="navigateTo(HOME_HERO.actionRoute)"
@secondary="navigateTo(HOME_HERO.secondaryRoute)"
/>
<view class="section-block">
<view class="section-header">
<text class="section-title">选择表达方式</text>
<text class="section-desc">按场景选择,先把话说清楚</text>
</view>
<home-feature-card
v-for="feature in HOME_PRIMARY_FEATURES"
:key="feature.id"
:title="feature.title"
:description="feature.description"
:icon-path="feature.iconPath"
:button-text="feature.buttonText"
:icon-class="feature.iconClass"
:button-class="feature.buttonClass"
:tag="feature.tag"
@action="handleFeatureTap(feature)"
/>
</view>
<tui-tips ref="tuiTips" position="center" backgroundColor="rgba(0, 0, 0, 0.8)" color="#fff" :size="30"></tui-tips>
<view class="scenario-section">
<view class="section-header">
<text class="section-title">适合这些时刻</text>
<text class="section-desc">告白、道歉、祝福都需要分寸感</text>
</view>
<view class="scenario-list">
<view class="scenario-card" v-for="scenario in HOME_SCENARIOS" :key="scenario.title">
<view class="scenario-dot"></view>
<view class="scenario-copy">
<text class="scenario-title">{{ scenario.title }}</text>
<text class="scenario-desc">{{ scenario.description }}</text>
</view>
</view>
</view>
</view>
<view class="step-section">
<view class="section-header">
<text class="section-title">三步送达</text>
<text class="section-desc">发送前还能再次预览确认</text>
</view>
<view class="step-list">
<view class="step-item" v-for="(step, index) in HOME_STEPS" :key="step">
<text class="step-index">{{ index + 1 }}</text>
<text class="step-text">{{ step }}</text>
</view>
</view>
</view>
<view class="section-block additional-cards">
<view class="section-header">
<text class="section-title">更多工具</text>
<text class="section-desc">让表达更准时、更稳妥</text>
</view>
<home-feature-card
v-for="(feature, index) in additionalCards"
:key="feature.id"
:title="feature.title"
:description="feature.description"
:icon-path="feature.iconPath"
:button-text="feature.buttonText"
:icon-class="getIconClass(index)"
:button-class="getButtonClass(index)"
:tag="feature.tag"
@action="handleFeatureTap(feature)"
/>
<inline-state v-if="isLoading" text="加载中..." type="loading" />
<inline-state v-if="loadError" :text="loadError" type="error" retry-text="重试" @retry="fetchAdditionalCards" />
</view>
<view class="guardrail-bar">
<text class="guardrail-item" v-for="item in HOME_GUARDRAILS" :key="item">{{ item }}</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { onPullDownRefresh } from "@dcloudio/uni-app";
import InlineState from "@/components/app/InlineState.vue";
import HomeFeatureCard from "@/components/home/HomeFeatureCard.vue";
import HomeHeroCard from "@/components/home/HomeHeroCard.vue";
import TuiTips from "@/components/thorui/tui-tips/tui-tips.vue";
import {
HOME_ADDITIONAL_FEATURES,
HOME_FEATURE_STYLE_CLASSES,
HOME_GUARDRAILS,
HOME_HERO,
HOME_PRIMARY_FEATURES,
HOME_SCENARIOS,
HOME_STEPS,
type HomeFeatureCardConfig,
} from "@/config/home";
import { useSwitchTab } from "@/stores/switchTab";
const additionalCards = ref<HomeFeatureCardConfig[]>([]);
const isLoading = ref<boolean>(false);
const loadError = ref<string>("");
const tuiTips = ref<any>(null);
const getIconClass = (index: number): string => {
const classes = HOME_FEATURE_STYLE_CLASSES.icon;
return classes[index % classes.length];
};
const getButtonClass = (index: number): string => {
const classes = HOME_FEATURE_STYLE_CLASSES.button;
return classes[index % classes.length];
};
const showTip = (message: string, duration = 2000): void => {
tuiTips.value?.showTips({
msg: message,
duration,
});
};
const navigateTo = (route?: string): void => {
if (!route) return;
uni.navigateTo({
url: route,
});
};
const handleFeatureTap = (feature: HomeFeatureCardConfig): void => {
if (feature.disabledTip) {
showTip(feature.disabledTip);
return;
}
navigateTo(feature.route);
};
const fetchAdditionalCards = (): void => {
isLoading.value = true;
loadError.value = "";
setTimeout(() => {
additionalCards.value = HOME_ADDITIONAL_FEATURES;
isLoading.value = false;
}, 1000);
};
const initLastPage = (): void => {
const switchTab = useSwitchTab();
const currentPages = getCurrentPages();
const lastPage = currentPages[currentPages.length - 1];
const page = lastPage ? String(lastPage.route) : "";
switchTab.setLastPage(page);
};
onMounted(() => {
initLastPage();
fetchAdditionalCards();
});
onPullDownRefresh(() => {
fetchAdditionalCards();
uni.stopPullDownRefresh();
});
</script>
<style>
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
background:
linear-gradient(180deg, rgba(255, 244, 250, 0.98) 0%, rgba(239, 248, 255, 0.94) 46%, #f8f8fb 100%),
linear-gradient(120deg, rgba(255, 210, 228, 0.26), rgba(196, 231, 255, 0.22));
position: relative;
padding: 24rpx 24rpx 44rpx;
box-sizing: border-box;
}
.section-block,
.scenario-section,
.step-section {
margin-top: 30rpx;
}
.section-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 18rpx;
padding: 0 6rpx;
}
.section-title {
color: #20273a;
font-size: 32rpx;
font-weight: 700;
line-height: 1.3;
}
.section-desc {
color: #8b94a3;
font-size: 23rpx;
line-height: 1.4;
margin-left: 16rpx;
text-align: right;
}
.scenario-list {
background:
linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(250, 253, 255, 0.82)),
linear-gradient(90deg, rgba(255, 205, 223, 0.18), rgba(202, 235, 255, 0.18));
border-radius: 26rpx;
padding: 8rpx 24rpx;
box-shadow: 0 12rpx 32rpx rgba(83, 96, 130, 0.07);
border: 1rpx solid rgba(255, 255, 255, 0.84);
}
.scenario-card {
display: flex;
align-items: flex-start;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f2f5;
}
.scenario-card:last-child {
border-bottom: none;
}
.scenario-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: linear-gradient(135deg, #ff7fa0, #7fc8ff);
margin-top: 13rpx;
margin-right: 18rpx;
box-shadow: 0 0 0 8rpx rgba(255, 127, 160, 0.12);
flex-shrink: 0;
}
.scenario-copy {
display: flex;
flex-direction: column;
}
.scenario-title {
color: #253044;
font-size: 29rpx;
font-weight: 600;
line-height: 1.35;
}
.scenario-desc {
color: #7b8493;
font-size: 24rpx;
line-height: 1.5;
margin-top: 8rpx;
}
.step-list {
display: flex;
justify-content: space-between;
}
.step-item {
flex: 1;
min-height: 156rpx;
margin-right: 16rpx;
padding: 22rpx 18rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.84);
box-shadow: 0 12rpx 30rpx rgba(83, 96, 130, 0.07);
border: 1rpx solid rgba(255, 255, 255, 0.84);
box-sizing: border-box;
}
.step-item:last-child {
margin-right: 0;
}
.step-index {
display: flex;
align-items: center;
justify-content: center;
width: 38rpx;
height: 38rpx;
border-radius: 50%;
color: #fff;
background: linear-gradient(135deg, #ff82a2, #73bfff);
font-size: 22rpx;
font-weight: 700;
line-height: 38rpx;
box-shadow: 0 8rpx 16rpx rgba(115, 191, 255, 0.18);
}
.step-text {
display: block;
margin-top: 18rpx;
color: #3a4354;
font-size: 24rpx;
line-height: 1.45;
}
.additional-cards {
margin-top: 32rpx;
}
.guardrail-bar {
display: flex;
flex-wrap: wrap;
margin-top: 24rpx;
padding: 18rpx 20rpx;
border-radius: 24rpx;
background: rgba(255, 255, 255, 0.66);
border: 1rpx solid rgba(255, 255, 255, 0.82);
box-shadow: 0 8rpx 22rpx rgba(83, 96, 130, 0.05);
}
.guardrail-item {
color: #7b8493;
font-size: 22rpx;
line-height: 1.3;
margin-right: 20rpx;
margin-bottom: 6rpx;
}
</style>