344 lines
8.4 KiB
Vue
344 lines
8.4 KiB
Vue
<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>
|