TalkofFood_Design/shihuashishuo-ui/src/views/核心体验页/HomeView-首页-2.1.backup-合并前.vue
L.star cb4848234f refactor(views): 核心体验页面结构重构与UI迭代
将所有核心用户体验相关的视图组件(HomeView, ScanView, SearchView 等)迁移至新的 'src/views/核心体验页/' 目录,以优化项目结构。

更新 router/index.ts 中的路由配置,以匹配新的文件路径。

对 HomeView-首页-2.0.vue 进行了密集的UI迭代,包括将扫码与分析卡片合并,并进行了大量的CSS微调。

新增了多个原型页面(ScanView, SearchView)和备份文件,记录开发过程。
2025-07-23 18:34:05 +08:00

620 lines
15 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>
<div class="home-view">
<!-- 1. Top Status Bar -->
<header class="top-bar">
<div class="greeting">晚上好, 王女士</div>
<div class="message-icon" @click="goToMessages">
<img src="@/assets/message-icon.svg" alt="Messages" />
</div>
</header>
<!-- Search Bar Section -->
<section class="search-section" @click="() => goToSearch()">
<div class="search-bar-container">
<input type="text" placeholder="疾病 / 症状 / 药品 / 问题" readonly>
<div class="separator"></div>
<div class="search-button">搜索</div>
</div>
</section>
<!-- Hot Searches Section -->
<section class="hot-searches-section">
<span v-for="term in hotSearches" :key="term" class="hot-search-tag" @click.stop="goToSearch(term)">
{{ term }}
</span>
</section>
<!-- Banner Section -->
<section class="banner-section">
<div class="banner-wrapper" :style="bannerStyle">
<div v-for="item in bannerItems" :key="item.id" class="banner-slide">
<img :src="item.imageUrl" :alt="item.alt" />
</div>
</div>
<div class="banner-dots">
<span v-for="(item, index) in bannerItems" :key="item.id"
:class="{ active: index === currentIndex }"
@click="goToSlide(index)"></span>
</div>
</section>
<!-- 2. Hero Section -->
<main class="hero-section">
<div class="scan-cta" @click="goToScan">
<div class="scan-icon">📷</div>
<span>扫码/拍照</span>
</div>
</main>
<!-- 3. Personalized Dynamic Card (Restored) -->
<section class="dynamic-card-section">
<div class="dynamic-card">
<div class="card-text">
<p>本周您已分析 <strong>8</strong> 种食品,成功为家人避开 <strong>4</strong> 个高风险成分!</p>
</div>
<div class="card-avatar">
<img src="https://images.unsplash.com/photo-1527980965255-d3b416303d12?q=80&w=1780&auto=format&fit=crop" alt="User Avatar" />
</div>
</div>
</section>
<!-- 4. Health Dashboard (v2.5 Visual Upgrade) -->
<section class="health-dashboard">
<div class="dashboard-item nutrition-tracker">
<h4>每日营养</h4>
<div class="nutrition-details">
<div>
<p>蛋白质</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.protein }}</strong>
<span>/ {{ healthDashboardData.nutrition.proteinGoal }}g</span>
</div>
</div>
<div>
<p>脂肪</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.fat }}</strong>
<span>/ {{ healthDashboardData.nutrition.fatGoal }}g</span>
</div>
</div>
<div>
<p>碳水化合物</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.carbs }}</strong>
<span>/ {{ healthDashboardData.nutrition.carbsGoal }}g</span>
</div>
</div>
<div>
<p>钙</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.calcium }}</strong>
<span>/ {{ healthDashboardData.nutrition.calciumGoal }}mg</span>
</div>
</div>
<div>
<p>维生素C</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.vitaminC }}</strong>
<span>/ {{ healthDashboardData.nutrition.vitaminCGoal }}mg</span>
</div>
</div>
<div>
<p>维生素D</p>
<div class="nutrition-value">
<strong>{{ healthDashboardData.nutrition.vitaminD }}</strong>
<span>/ {{ healthDashboardData.nutrition.vitaminDGoal }}µg</span>
</div>
</div>
</div>
</div>
<div class="dashboard-item progress-card">
<!-- Calorie Progress Ring -->
<div class="progress-ring">
<svg class="progress-ring__svg" :width="ringSize" :height="ringSize">
<circle class="progress-ring__circle-bg" :r="radius" :cx="center" :cy="center" />
<circle class="progress-ring__circle" :stroke-dasharray="circumference" :stroke-dashoffset="calorieProgressOffset" :r="radius" :cx="center" :cy="center" />
</svg>
<div class="progress-ring__text">
<p>热量摄入</p>
<strong>{{ healthDashboardData.calories.current }}</strong>
<span>/ {{ healthDashboardData.calories.goal }} kcal</span>
</div>
</div>
<!-- Water Progress Ring -->
<div class="progress-ring">
<svg class="progress-ring__svg" :width="ringSize" :height="ringSize">
<circle class="progress-ring__circle-bg" :r="radius" :cx="center" :cy="center" />
<circle class="progress-ring__circle water" :stroke-dasharray="circumference" :stroke-dashoffset="waterProgressOffset" :r="radius" :cx="center" :cy="center" />
</svg>
<div class="progress-ring__text">
<p>饮水</p>
<strong>{{ healthDashboardData.water.current / 1000 }}</strong>
<span>/ {{ healthDashboardData.water.goal / 1000 }} L</span>
</div>
</div>
</div>
</section>
<!-- 5. Content Feed -->
<section class="feed-section">
<div v-for="item in feedItems" :key="item.id" class="feed-card" @click="goTo(item.link)">
<div v-if="item.type === 'discover'" class="discover-card">
<div class="card-content">
<span class="card-tag">发现</span>
<h3>{{ item.title }}</h3>
<p>{{ item.summary }}</p>
</div>
<img :src="item.imageUrl" :alt="item.title" class="card-image">
</div>
<div v-if="item.type === 'recipe'" class="recipe-card">
<img :src="item.imageUrl" :alt="item.title" class="card-image-full">
<div class="card-overlay">
<span class="card-tag">为你推荐的菜谱</span>
<h3>{{ item.title }}</h3>
</div>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useRouter } from 'vue-router';
const bannerItems = ref([
{ id: 1, imageUrl: 'https://images.unsplash.com/photo-1546069901-ba9599a7e63c?q=80&w=2080&auto=format&fit=crop', alt: 'Healthy Salad' },
{ id: 2, imageUrl: 'https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?q=80&w=1887&auto=format&fit=crop', alt: 'Fresh Berries' },
{ id: 3, imageUrl: 'https://images.unsplash.com/photo-1482049016688-2d3e1b311543?q=80&w=1910&auto=format&fit=crop', alt: 'Avocado Toast' },
]);
const currentIndex = ref(0);
let intervalId: number;
const hotSearches = ref(['无糖酸奶', '酱油', '儿童零食', '高钙牛奶']);
const healthDashboardData = ref({
nutrition: {
protein: 30, proteinGoal: 60,
fat: 20, fatGoal: 50,
carbs: 150, carbsGoal: 300,
calcium: 500, calciumGoal: 1000,
vitaminC: 40, vitaminCGoal: 90,
vitaminD: 5, vitaminDGoal: 15
},
calories: { current: 800, goal: 1800 },
water: { current: 1000, goal: 2000 },
});
// Progress Ring Computations
const ringSize = 120;
const strokeWidth = 10;
const center = ringSize / 2;
const radius = center - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
const calorieProgressOffset = computed(() => {
const progress = healthDashboardData.value.calories.current / healthDashboardData.value.calories.goal;
return circumference * (1 - progress);
});
const waterProgressOffset = computed(() => {
const progress = healthDashboardData.value.water.current / healthDashboardData.value.water.goal;
return circumference * (1 - progress);
});
const feedItems = ref([
{ id: 'd1', type: 'discover', title: "警惕这5种'儿童酱油'其实是钠含量炸弹", summary: '深度评测10款热门儿童酱油结果令人震惊...', imageUrl: 'https://images.unsplash.com/photo-1598134493282-85b82454f754?q=80&w=1887&auto=format&fit=crop', link: { name: 'discover' } },
{ id: 'r1', type: 'recipe', title: '适合减脂期的你:牛油果鸡胸肉沙拉', imageUrl: 'https://images.unsplash.com/photo-1505253716362-af78f6d38348?q=80&w=1887&auto=format&fit=crop', link: { name: 'kitchen' } },
]);
const bannerStyle = computed(() => ({
transform: `translateX(-${currentIndex.value * 100}%)`
}));
const goToSlide = (index: number) => {
currentIndex.value = index;
clearInterval(intervalId);
intervalId = window.setInterval(nextSlide, 3000);
};
const nextSlide = () => {
currentIndex.value = (currentIndex.value + 1) % bannerItems.value.length;
};
onMounted(() => {
intervalId = window.setInterval(nextSlide, 3000);
});
onUnmounted(() => {
clearInterval(intervalId);
});
const router = useRouter();
const goToMessages = () => {
router.push({ name: 'messages' });
};
const goToScan = () => {
router.push({ name: 'scan' });
};
const goToSearch = (query?: string) => {
if (query) {
router.push({ name: 'search', query: { q: query } });
} else {
router.push({ name: 'search' });
}
};
const goTo = (link: object) => {
router.push(link);
};
</script>
<style scoped>
.home-view {
padding: 20px;
box-sizing: border-box;
background-color: var(--color-background);
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
}
.search-section {
margin-top: 20px;
}
.search-bar-container {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 25px;
padding: 10px 15px;
border: 1px solid #07C160;
cursor: pointer;
max-width: 90%;
margin: 0 auto;
}
.search-bar-container input {
border: none;
outline: none;
background: transparent;
flex-grow: 1;
width: 0;
font-size: 14px;
color: #333;
pointer-events: none;
}
.separator {
width: 1px;
height: 16px;
background-color: #e0e0e0;
margin: 0 12px;
}
.search-button {
color: #07C160;
font-weight: bold;
font-size: 15px;
}
.hot-searches-section {
display: flex;
justify-content: flex-start;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
max-width: 90%;
margin-left: auto;
margin-right: auto;
}
.hot-search-tag {
background-color: #f0f0f0;
color: #555;
padding: 5px 12px;
border-radius: 15px;
font-size: 10px;
cursor: pointer;
}
.banner-section {
width: 100%;
margin-top: 15px;
overflow: hidden;
position: relative;
border-radius: 8px;
}
.banner-wrapper {
display: flex;
transition: transform 0.5s ease-in-out;
}
.banner-slide {
flex-shrink: 0;
width: 100%;
height: 150px;
}
.banner-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.banner-dots {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 8px;
}
.banner-dots span {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
}
.banner-dots span.active {
background-color: white;
}
.greeting {
font-size: 18px;
font-weight: bold;
}
.message-icon img {
width: 24px;
height: 24px;
}
.hero-section {
display: flex;
justify-content: center;
padding: 20px 0;
}
.scan-cta {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 160px;
height: 160px;
border-radius: 50%;
background: var(--color-background-soft);
cursor: pointer;
border: 2px solid #07C160;
box-shadow: 0 2px 8px rgba(7, 193, 96, 0.15);
}
.scan-icon {
font-size: 60px;
margin-bottom: 8px;
}
.scan-cta span {
font-size: 16px;
font-weight: bold;
}
.dynamic-card-section {
margin-bottom: 20px;
}
.dynamic-card {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--color-background-soft);
border-radius: 8px;
padding: 15px;
font-size: 14px;
width: 90%;
max-width: 350px;
border: 1px solid #07C160;
margin: 0 auto;
}
.card-text {
flex-grow: 1;
text-align: left;
margin-right: 15px;
}
.card-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.dynamic-card strong {
color: var(--color-primary-accent);
}
.health-dashboard {
display: flex;
flex-direction: column;
gap: 15px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 12px;
border: 1px solid #e0e0e0;
}
.dashboard-item {
background-color: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.dashboard-item h4 {
font-size: 14px;
color: #333;
margin: 0 0 10px 0;
text-align: left;
}
.nutrition-details {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
text-align: center;
}
.nutrition-details p {
margin: 0 0 5px 0;
font-size: 12px;
color: #666;
}
.nutrition-value {
line-height: 1;
}
.nutrition-value strong {
font-size: 18px;
font-weight: 600;
color: #111827;
}
.nutrition-value span {
font-size: 12px;
color: #6b7280;
margin-left: 2px;
}
.progress-card {
display: flex;
justify-content: space-around;
align-items: center;
}
.progress-ring {
position: relative;
text-align: center;
}
.progress-ring__svg {
transform: rotate(-90deg);
}
.progress-ring__circle-bg {
fill: none;
stroke: #e6e6e6;
stroke-width: 10;
}
.progress-ring__circle {
fill: none;
stroke: #07C160;
stroke-width: 10;
stroke-linecap: round;
transition: stroke-dashoffset 0.35s;
}
.progress-ring__circle.water {
stroke: #007bff;
}
.progress-ring__text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.progress-ring__text p {
margin: 0;
font-size: 12px;
color: #666;
}
.progress-ring__text strong {
font-size: 20px;
color: #333;
}
.progress-ring__text span {
font-size: 12px;
color: #999;
}
.feed-section {
margin-top: 30px;
display: flex;
flex-direction: column;
gap: 20px;
}
.feed-card {
border-radius: 12px;
overflow: hidden;
background-color: var(--color-background-soft);
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.discover-card {
display: flex;
align-items: center;
padding: 15px;
gap: 15px;
}
.discover-card .card-content { flex: 1; }
.discover-card .card-image {
width: 80px;
height: 80px;
border-radius: 8px;
object-fit: cover;
}
.recipe-card { position: relative; color: white; }
.recipe-card .card-image-full {
width: 100%;
height: 180px;
object-fit: cover;
}
.recipe-card .card-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0) 50%);
padding: 15px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.card-tag {
background-color: rgba(7, 193, 96, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: bold;
align-self: flex-start;
margin-bottom: 8px;
}
h3 {
font-size: 16px;
font-weight: bold;
margin: 0 0 5px 0;
}
.discover-card p {
font-size: 14px;
color: var(--color-text-secondary);
margin: 0;
}
</style>