feat(search): 重构搜索结果页至 v3.0
- **重构视图与组件**: 全面重构搜索结果页,引入 SearchResultView-搜索结果页3.0.vue,并按分类(如菜谱、文章、食品添加剂等)拆分和优化了展示组件。 - **优化目录结构**: 将相关组件迁移至 components/SearchResult-搜索结果/ 目录,并采用更清晰的中文命名。 - **更新路由**: 将 /search-result 路由指向新的 v3.0 视图。 - **新增文档**: 添加了 3.5-工作日志-搜索结果页重构-v3.0.md,详细记录了本次重构的内容。
This commit is contained in:
parent
aa2a7ae542
commit
a26d9cc069
@ -44,21 +44,21 @@ JECFA 分类标准
|
|||||||
|
|
||||||
一、分类体系及定义
|
一、分类体系及定义
|
||||||
A类(安全性较高)
|
A类(安全性较高)
|
||||||
|
A1类:毒理学资料完善,已制定正式每日允许摄入量(ADI值),允许按标准使用。
|
||||||
|
A2类:毒理学资料不完善,但已制定暂定ADI值,允许暂时使用。
|
||||||
|
|
||||||
A1类:毒理学资料完善,已制定正式每日允许摄入量(ADI值),允许按标准使用12。
|
|
||||||
A2类:毒理学资料不完善,但已制定暂定ADI值,允许暂时使用12。
|
|
||||||
B类(安全性待评估)
|
B类(安全性待评估)
|
||||||
|
B1类:JECFA曾评估但资料不足,未制定ADI值。
|
||||||
|
B2类:未经过JECFA安全性评价。
|
||||||
|
|
||||||
B1类:JECFA曾评估但资料不足,未制定ADI值12。
|
|
||||||
B2类:未经过JECFA安全性评价12。
|
|
||||||
C类(限制或禁用)
|
C类(限制或禁用)
|
||||||
|
C1类:经评估认为在食品中使用不安全,原则上禁止使用。
|
||||||
|
C2类:仅限特定食品中严格限制使用。
|
||||||
|
|
||||||
C1类:经评估认为在食品中使用不安全,原则上禁止使用12。
|
|
||||||
C2类:仅限特定食品中严格限制使用12。
|
|
||||||
二、安全性排序
|
二、安全性排序
|
||||||
从高到低依次为:A1 > A2 > B1 > B2 > C2 > C134。
|
从高到低依次为:A1 > A2 > B1 > B2 > C2 > C1。
|
||||||
|
|
||||||
三、典型示例
|
三、典型示例
|
||||||
A1类:常见防腐剂苯甲酸钠(正式ADI值0-5 mg/kg体重)15。
|
A1类:常见防腐剂苯甲酸钠(正式ADI值0-5 mg/kg体重)15。
|
||||||
C1类:部分工业用着色剂因致癌性被禁用23。
|
C1类:部分工业用着色剂因致癌性被禁用。
|
||||||
注:该分类为国际通用标准,具体应用需结合各国法规(如中国GB 2760-2024)调整67。
|
注:该分类为国际通用标准,具体应用需结合各国法规(如中国GB 2760-2024)调整67。
|
56
3-工作日志/3.5-工作日志-搜索结果页重构-v3.0.md
Normal file
56
3-工作日志/3.5-工作日志-搜索结果页重构-v3.0.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 搜索结果页重构日志 (v3.0)
|
||||||
|
|
||||||
|
**版本:** 3.0
|
||||||
|
**日期:** 2025-07-26
|
||||||
|
**负责人:** DevOps 工程师
|
||||||
|
|
||||||
|
## 1. 概述
|
||||||
|
|
||||||
|
本次更新对搜索结果页面进行了全面的重构,旨在提升代码的可维护性、可扩展性和用户体验。所有相关的旧版组件和视图均已废弃,并由全新的 v3.0 版本取代。
|
||||||
|
|
||||||
|
## 2. 主要变更
|
||||||
|
|
||||||
|
### 2.1. 视图层 (View)
|
||||||
|
|
||||||
|
- **删除旧视图**:
|
||||||
|
- `SearchResultView-搜索结果页.vue`
|
||||||
|
- `SearchResultView-搜索结果页-2.0.backup.vue`
|
||||||
|
- **新增新视图**:
|
||||||
|
- `SearchResultView-搜索结果页3.0.vue`
|
||||||
|
- **路由更新**:
|
||||||
|
- `/search-result` 路由已指向新的 `SearchResultView-搜索结果页3.0.vue`。
|
||||||
|
|
||||||
|
### 2.2. 组件层 (Component)
|
||||||
|
|
||||||
|
- **目录结构调整**:
|
||||||
|
- 旧的 `shihuashishuo-ui/src/components/SearchResult/` 目录已被删除。
|
||||||
|
- 所有新组件均迁移至 `shihuashishuo-ui/src/components/SearchResult-搜索结果/`,并采用更清晰的中文命名。
|
||||||
|
- **组件重构**:
|
||||||
|
- 删除了所有旧的搜索结果相关卡片和列表组件。
|
||||||
|
- 新增了以下组件,对不同类型的结果进行了更精细的拆分:
|
||||||
|
- `AllResultsList-所有结果列表.vue`
|
||||||
|
- `AdditiveCard-食品添加剂卡片.vue`
|
||||||
|
- `AdditiveList-食品添加剂列表.vue`
|
||||||
|
- `ArticleCard-资讯卡片.vue`
|
||||||
|
- `ArticleList-文章列表.vue`
|
||||||
|
- `MaterialCard-原料卡片.vue`
|
||||||
|
- `MaterialList-原料列表.vue`
|
||||||
|
- `PrepackagedCard-预包装食品卡片.vue`
|
||||||
|
- `PrepackagedList-预包装食品列表.vue`
|
||||||
|
- `RecipeCard-菜谱卡片-v2.vue`
|
||||||
|
- `RecipeList-菜谱列表.vue`
|
||||||
|
|
||||||
|
### 2.3. 静态资源 (Assets)
|
||||||
|
|
||||||
|
- **CSS 样式**:
|
||||||
|
- `shihuashishuo-ui/src/assets/main.css` 文件已更新,以适应新版页面的视觉风格。
|
||||||
|
- **图片资源**:
|
||||||
|
- 新增 `shihuashishuo-ui/src/assets/images/` 目录,用于存放页面所需的图片。
|
||||||
|
|
||||||
|
### 2.4. 参考资料
|
||||||
|
|
||||||
|
- `2-参考资料/2-食品添加剂分类.md` 文档内容已更新。
|
||||||
|
|
||||||
|
## 3. 结论
|
||||||
|
|
||||||
|
此次重构为搜索结果模块带来了更清晰的代码结构和更灵活的组件设计,为未来的功能迭代奠定了坚实的基础。
|
BIN
shihuashishuo-ui/src/assets/images/breakfast.jpg
Normal file
BIN
shihuashishuo-ui/src/assets/images/breakfast.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 407 KiB |
BIN
shihuashishuo-ui/src/assets/images/desserts.jpg
Normal file
BIN
shihuashishuo-ui/src/assets/images/desserts.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
BIN
shihuashishuo-ui/src/assets/images/tomatoes.png
Normal file
BIN
shihuashishuo-ui/src/assets/images/tomatoes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 583 KiB |
@ -16,8 +16,8 @@
|
|||||||
height: 100vh; /* Use viewport height to fill the screen */
|
height: 100vh; /* Use viewport height to fill the screen */
|
||||||
|
|
||||||
/* Add a border to clearly visualize the phone screen */
|
/* Add a border to clearly visualize the phone screen */
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc; /* Default border for mobile view */
|
||||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
box-shadow: 0 0 10px rgba(0,0,0,0.1); /* Default shadow for mobile view */
|
||||||
|
|
||||||
/* Ensure content inside scrolls if it overflows, not the whole page */
|
/* Ensure content inside scrolls if it overflows, not the whole page */
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -35,12 +35,13 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Media Query for iPad Air and Pro */
|
/* Media Query for iPad Air and Pro */
|
||||||
@media (min-width: 768px) and (max-width: 1024px) {
|
/* Media Query for tablets and desktops */
|
||||||
|
@media (min-width: 429px) {
|
||||||
#app {
|
#app {
|
||||||
max-width: 700px; /* Wider container for tablets */
|
/* On larger screens, behave like a web page, not a phone emulator */
|
||||||
height: 90vh; /* Adjust height to be less than full screen */
|
max-width: none; /* Allow the app to fill the full screen width */
|
||||||
margin: 5vh auto; /* Center vertically */
|
border: none;
|
||||||
border-radius: 20px; /* Add rounded corners for a card-like feel */
|
box-shadow: none;
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,0.15); /* Enhance shadow */
|
height: 100vh; /* Ensure it still fills the screen vertically */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="result-item additive-card">
|
<div class="result-item additive-card">
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
<img :src="item.imageUrl" alt="添加剂图片" class="product-image">
|
||||||
<div class="additive-details">
|
<div class="additive-details">
|
||||||
<h4 class="additive-title">
|
<h4 class="additive-title">
|
||||||
<span class="category-tag">[添加剂]</span>
|
<span class="category-tag">[添加剂]</span>
|
||||||
|
|
||||||
<span class="brand"></span>
|
<span class="brand"></span>
|
||||||
<span class="product-name">{{ item.name }}</span>
|
<span class="product-name">{{ item.name }}</span>
|
||||||
</h4>
|
</h4>
|
||||||
@ -45,17 +46,13 @@ defineProps({
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
object-fit: cover;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 12px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additive-details {
|
.additive-details {
|
||||||
@ -120,12 +117,12 @@ defineProps({
|
|||||||
.safety-level-1 { color: #07C160; font-weight: bold; }
|
.safety-level-1 { color: #07C160; font-weight: bold; }
|
||||||
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
||||||
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
||||||
.safety-level-4 { color: #ffe600; font-weight: bold; }
|
.safety-level-4 { color: #f2d025; font-weight: bold; }
|
||||||
.safety-level-5 { color: #FF7F50; font-weight: bold; }
|
.safety-level-5 { color: #FF7F50; font-weight: bold; }
|
||||||
.safety-level-6 { color: #ff0000; font-weight: bold; }
|
.safety-level-6 { color: #ff0000; font-weight: bold; }
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
@ -147,7 +144,7 @@ defineProps({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AdditiveCard from './AdditiveCard.vue';
|
import AdditiveCard from './AdditiveCard-食品添加剂卡片.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -29,6 +29,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -14,10 +14,11 @@
|
|||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
|
||||||
// Dynamically import card components for code splitting
|
// Dynamically import card components for code splitting
|
||||||
const PrepackagedCard = defineAsyncComponent(() => import('./PrepackagedCard.vue'));
|
const PrepackagedCard = defineAsyncComponent(() => import('./PrepackagedCard-预包装食品卡片.vue'));
|
||||||
const AdditiveCard = defineAsyncComponent(() => import('./AdditiveCard.vue'));
|
const AdditiveCard = defineAsyncComponent(() => import('./AdditiveCard-食品添加剂卡片.vue'));
|
||||||
const MaterialCard = defineAsyncComponent(() => import('./MaterialCard.vue'));
|
const MaterialCard = defineAsyncComponent(() => import('./MaterialCard-原料卡片.vue'));
|
||||||
const SummaryCard = defineAsyncComponent(() => import('./SummaryCard.vue'));
|
const RecipeCardV2 = defineAsyncComponent(() => import('./RecipeCard-菜谱卡片-v2.vue'));
|
||||||
|
const ArticleCard = defineAsyncComponent(() => import('./ArticleCard-资讯卡片.vue'));
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -33,8 +34,8 @@ const cardMap: { [key: string]: any } = {
|
|||||||
prepackaged: PrepackagedCard,
|
prepackaged: PrepackagedCard,
|
||||||
additive: AdditiveCard,
|
additive: AdditiveCard,
|
||||||
material: MaterialCard,
|
material: MaterialCard,
|
||||||
recipe: SummaryCard,
|
recipe: RecipeCardV2,
|
||||||
article: SummaryCard,
|
article: ArticleCard,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCardComponent = (category: string) => {
|
const getCardComponent = (category: string) => {
|
||||||
@ -48,6 +49,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -0,0 +1,173 @@
|
|||||||
|
<!-- Force reload to fix display issue -->
|
||||||
|
<template>
|
||||||
|
<div class="result-item article-card">
|
||||||
|
<div class="image-container">
|
||||||
|
<img :src="item.imageUrl" alt="资讯媒体" class="product-image">
|
||||||
|
<div v-if="item.mediaType === 'video'" class="video-icon-overlay">
|
||||||
|
<span class="video-icon"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="article-details">
|
||||||
|
<h4 class="article-title">
|
||||||
|
<span class="category-tag">[资讯]</span>
|
||||||
|
<span class="product-name">{{ item.title }}</span>
|
||||||
|
</h4>
|
||||||
|
<p class="article-summary">{{ item.summary }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.result-item {
|
||||||
|
padding: 15px 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.45);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin-left: 1px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
border-left: 11px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title .category-tag {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #9ca3af;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-summary {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.45);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin-left: 1px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
border-left: 11px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.image-container {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
.article-card {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.article-details {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="list-container">
|
<div class="list-container">
|
||||||
<SummaryCard
|
<ArticleCard
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:item="item"
|
:item="item"
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SummaryCard from './SummaryCard.vue';
|
import ArticleCard from './ArticleCard-资讯卡片.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -29,6 +29,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="result-item material-card">
|
<div class="result-item material-card">
|
||||||
<div class="product-image-placeholder"><span>食材图片</span></div>
|
<img :src="item.imageUrl" alt="食材图片" class="product-image">
|
||||||
<div class="material-details">
|
<div class="material-details">
|
||||||
<h4 class="material-title">
|
<h4 class="material-title">
|
||||||
<span class="category-tag">[食材]</span>
|
<span class="category-tag">[食材]</span>
|
||||||
@ -11,7 +11,7 @@
|
|||||||
<span>分类: {{ item.family }}</span>
|
<span>分类: {{ item.family }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="product-info-row nutrient-tags-container">
|
<div class="product-info-row nutrient-tags-container">
|
||||||
<span v-for="tag in item.nutrientTags" :key="tag.text" class="nutrient-tag" :class="tag.class">{{ tag.text }}</span>
|
<span v-for="tag in item.nutrientTags" :key="tag.text" class="nutrient-tag" :style="getTagStyle(tag.text)">{{ tag.text }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -25,6 +25,26 @@ defineProps({
|
|||||||
default: () => ({ nutrientTags: [] })
|
default: () => ({ nutrientTags: [] })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const colorPalette = [
|
||||||
|
{ backgroundColor: '#eefbf3', color: '#41ae7c' }, // Soft Green
|
||||||
|
{ backgroundColor: '#eef6fc', color: '#5dade2' }, // Soft Blue
|
||||||
|
{ backgroundColor: '#fdeeee', color: '#e67e7e' }, // Soft Red
|
||||||
|
{ backgroundColor: '#fef8e9', color: '#f3c567' }, // Soft Amber
|
||||||
|
{ backgroundColor: '#f4f4f4', color: '#a6a6a6' }, // Soft Stone
|
||||||
|
{ backgroundColor: '#fdeef5', color: '#e58dbb' }, // Soft Pink
|
||||||
|
{ backgroundColor: '#eaf8fe', color: '#6ecce9' }, // Soft Sky
|
||||||
|
];
|
||||||
|
|
||||||
|
const getTagStyle = (tagText: string) => {
|
||||||
|
if (!tagText) return colorPalette[0];
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < tagText.length; i++) {
|
||||||
|
hash += tagText.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const index = hash % colorPalette.length;
|
||||||
|
return colorPalette[index];
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@ -44,17 +64,13 @@ defineProps({
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
object-fit: cover;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 12px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-details {
|
.material-details {
|
||||||
@ -107,44 +123,8 @@ defineProps({
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nutrient-tag.tomato {
|
|
||||||
background-color: #fef2f2;
|
|
||||||
color: #b91c1c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nutrient-tag.antioxidant {
|
|
||||||
background-color: #eff6ff;
|
|
||||||
color: #1d4ed8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nutrient-tag.umami {
|
|
||||||
background-color: #f5f5f4;
|
|
||||||
color: #57534e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nutrient-tag.high-sodium {
|
|
||||||
background-color: #fffbeb;
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.material-card {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.material-details {
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
.product-info-row {
|
|
||||||
font-size: 11px;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
color: #b45309;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
|
||||||
.product-image-placeholder {
|
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MaterialCard from './MaterialCard.vue';
|
import MaterialCard from './MaterialCard-原料卡片.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -29,6 +29,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="result-item product-card">
|
<div class="result-item product-card">
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
<img :src="item.imageUrl" alt="商品图片" class="product-image">
|
||||||
<div class="product-details">
|
<div class="product-details">
|
||||||
<h4 class="product-title">
|
<h4 class="product-title">
|
||||||
<span class="category-tag">[{{ item.category || '预包装' }}]</span>
|
<span class="category-tag">[预包装]</span>
|
||||||
<span class="brand">{{ item.brand }}</span>
|
<span class="brand">{{ item.brand }}</span>
|
||||||
<span class="product-name">{{ item.name }}</span>
|
<span class="product-name">{{ item.name }}</span>
|
||||||
</h4>
|
</h4>
|
||||||
@ -50,17 +50,13 @@ defineProps({
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
object-fit: cover;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 12px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-details {
|
.product-details {
|
||||||
@ -115,7 +111,7 @@ defineProps({
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-left: auto;
|
/* margin-left: auto; */ /* Removed for clean left alignment */
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-info-row .count {
|
.product-info-row .count {
|
||||||
@ -134,31 +130,11 @@ defineProps({
|
|||||||
.safety-level-1 { color: #07C160; font-weight: bold; }
|
.safety-level-1 { color: #07C160; font-weight: bold; }
|
||||||
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
||||||
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
||||||
.prepackaged-safety-4, .safety-level-4 { color: #FF7F50; font-weight: bold; }
|
.prepackaged-safety-4, .safety-level-4 { color: #f2d025; font-weight: bold; }
|
||||||
.prepackaged-safety-5, .safety-level-5 { color: #ff0000; font-weight: bold; }
|
.prepackaged-safety-5, .safety-level-5 { color: #ff0000; font-weight: bold; }
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.product-image-placeholder {
|
.product-image {
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.product-card {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.product-details {
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
.product-info-row,
|
|
||||||
.product-info-row.risk-info {
|
|
||||||
font-size: 11px;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
.calories {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 380px) {
|
|
||||||
.product-image-placeholder {
|
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PrepackagedCard from './PrepackagedCard.vue';
|
import PrepackagedCard from './PrepackagedCard-预包装食品卡片.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -29,6 +29,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -0,0 +1,139 @@
|
|||||||
|
<!-- Force reload to fix display issue -->
|
||||||
|
<template>
|
||||||
|
<div class="result-item recipe-card">
|
||||||
|
<div class="image-container">
|
||||||
|
<img :src="item.imageUrl" alt="食谱媒体" class="product-image">
|
||||||
|
<div v-if="item.mediaType === 'video'" class="video-icon-overlay">
|
||||||
|
<span class="video-icon"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="recipe-details">
|
||||||
|
<h4 class="recipe-title">
|
||||||
|
<span class="category-tag">[食谱]</span>
|
||||||
|
<span class="product-name">{{ item.title }}</span>
|
||||||
|
</h4>
|
||||||
|
<p class="recipe-summary">{{ item.summary }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps({
|
||||||
|
item: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.result-item {
|
||||||
|
padding: 15px 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-container {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.45);
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-icon::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
margin-left: 1px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 7px solid transparent;
|
||||||
|
border-bottom: 7px solid transparent;
|
||||||
|
border-left: 11px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-title {
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-title .category-tag {
|
||||||
|
font-weight: normal;
|
||||||
|
color: #9ca3af;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-summary {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
/* Clamp the text to 2 lines */
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.image-container {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
.recipe-card {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.recipe-details {
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="list-container">
|
<div class="list-container">
|
||||||
<SummaryCard
|
<RecipeCardV2
|
||||||
v-for="item in items"
|
v-for="item in items"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:item="item"
|
:item="item"
|
||||||
@ -10,7 +10,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SummaryCard from './SummaryCard.vue';
|
import RecipeCardV2 from './RecipeCard-菜谱卡片-v2.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: {
|
items: {
|
||||||
@ -29,6 +29,6 @@ const onItemClick = (item: any) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.list-container {
|
.list-container {
|
||||||
/* Styles for the list container if needed */
|
padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -1,38 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="result-item">
|
|
||||||
<h4>[{{ item.category }}] {{ item.title }}</h4>
|
|
||||||
<p class="article-summary">{{ item.summary }}</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps({
|
|
||||||
item: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.result-item {
|
|
||||||
padding: 15px 0;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.result-item h4 {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.result-item p {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
.article-summary {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -94,7 +94,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/search-result',
|
path: '/search-result',
|
||||||
name: 'search-result',
|
name: 'search-result',
|
||||||
component: () => import('../views/核心体验页/SearchResultView-搜索结果页.vue'),
|
component: () => import('../views/核心体验页/SearchResultView-搜索结果页3.0.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/result/:id',
|
path: '/result/:id',
|
||||||
|
@ -1,909 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="search-result-view">
|
|
||||||
<header class="top-bar">
|
|
||||||
<button class="back-btn" @click="goBack"><</button>
|
|
||||||
<div class="search-bar-container">
|
|
||||||
<input type="text" v-model="searchQuery" @keyup.enter="performSearch" placeholder="食品 /成分 /食物 /菜谱 /问题" />
|
|
||||||
<div class="separator"></div>
|
|
||||||
<div class="search-button" @click="performSearch">搜索</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="tabs">
|
|
||||||
<button
|
|
||||||
v-for="tab in tabs"
|
|
||||||
:key="tab.id"
|
|
||||||
:class="{ active: activeTab === tab.id }"
|
|
||||||
@click="activeTab = tab.id"
|
|
||||||
>
|
|
||||||
{{ tab.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filters-bar">
|
|
||||||
<button class="filter-toggle-btn" @click="openFilterPanel">
|
|
||||||
<span>{{ activeFilterName }}</span>
|
|
||||||
<span class="arrow"></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter Panel Overlay -->
|
|
||||||
<div v-if="isPanelOpen" class="filter-panel-overlay" @click.self="cancelFilters">
|
|
||||||
<div class="filter-panel">
|
|
||||||
<div class="filter-group">
|
|
||||||
<h4>排序方式</h4>
|
|
||||||
<div class="tags">
|
|
||||||
<button
|
|
||||||
v-for="filter in filters"
|
|
||||||
:key="filter.id"
|
|
||||||
:class="{ active: tempActiveFilter === filter.id }"
|
|
||||||
@click="tempActiveFilter = filter.id"
|
|
||||||
>
|
|
||||||
{{ filter.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-group">
|
|
||||||
<h4>顺序</h4>
|
|
||||||
<div class="tags">
|
|
||||||
<button :class="{ active: tempSortOrder === 'desc' }" @click="tempSortOrder = 'desc'">从高到低</button>
|
|
||||||
<button :class="{ active: tempSortOrder === 'asc' }" @click="tempSortOrder = 'asc'">从低到高</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-actions">
|
|
||||||
<button class="btn-cancel" @click="cancelFilters">取消</button>
|
|
||||||
<button class="btn-confirm" @click="confirmFilters">确认</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<main class="results-list">
|
|
||||||
<div v-if="activeTab === 'additive'" class="safety-level-description">
|
|
||||||
安全等级划分参照 JECFA:A1 > A2 > B1 > B2 > C2 > C1。
|
|
||||||
</div>
|
|
||||||
<!-- Prepackaged Food Results -->
|
|
||||||
<!-- Example for Level 1 -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'prepackaged'" class="result-item product-card">
|
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
|
||||||
<div class="product-details">
|
|
||||||
<h4 class="product-title">
|
|
||||||
<span class="category-tag">[预包装]</span>
|
|
||||||
<span class="brand">某品牌</span>
|
|
||||||
<span class="product-name">有机纯牛奶</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>安全评级 <span class="safety-level-1">1 级 (最安全)</span></span>
|
|
||||||
<span>营养评级 <span class="score-high">高</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row risk-info">
|
|
||||||
<span>添加剂 <span class="count additive-count">0</span></span>
|
|
||||||
<span>高风险成分 <span class="count risk-count">0</span></span>
|
|
||||||
<span class="calories">热量 270千卡/100g</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Example for Level 2 -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'prepackaged'" class="result-item product-card">
|
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
|
||||||
<div class="product-details">
|
|
||||||
<h4 class="product-title">
|
|
||||||
<span class="category-tag">[预包装]</span>
|
|
||||||
<span class="brand">某品牌</span>
|
|
||||||
<span class="product-name">全麦面包</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>安全评级 <span class="safety-level-2">2 级 (较安全)</span></span>
|
|
||||||
<span>营养评级 <span class="score-mid">中</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row risk-info">
|
|
||||||
<span>添加剂 <span class="count additive-count">2</span></span>
|
|
||||||
<span>高风险成分 <span class="count risk-count">0</span></span>
|
|
||||||
<span class="calories">热量 350千卡/100g</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Example for Level 3 -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'prepackaged'" class="result-item product-card">
|
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
|
||||||
<div class="product-details">
|
|
||||||
<h4 class="product-title">
|
|
||||||
<span class="category-tag">[预包装]</span>
|
|
||||||
<span class="brand">某品牌</span>
|
|
||||||
<span class="product-name">番茄酱</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>安全评级 <span class="safety-level-3">3 级 (一般安全)</span></span>
|
|
||||||
<span>营养评级 <span class="score-mid">中</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row risk-info">
|
|
||||||
<span>添加剂 <span class="count additive-count">5</span></span>
|
|
||||||
<span>高风险成分 <span class="count risk-count">1</span></span>
|
|
||||||
<span class="calories">热量 420千卡/100g</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Example for Level 4 -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'prepackaged'" class="result-item product-card">
|
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
|
||||||
<div class="product-details">
|
|
||||||
<h4 class="product-title">
|
|
||||||
<span class="category-tag">[预包装]</span>
|
|
||||||
<span class="brand">子弟</span>
|
|
||||||
<span class="product-name">薯片 (蜂蜜黄油味)</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>安全评级 <span class="prepackaged-safety-4">4 级 (需警惕)</span></span>
|
|
||||||
<span>营养评级 <span class="score-low">低</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row risk-info">
|
|
||||||
<span>添加剂 <span class="count additive-count">8</span></span>
|
|
||||||
<span>高风险成分 <span class="count risk-count">2</span></span>
|
|
||||||
<span class="calories">热量 519千卡/100g</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Example for Level 5 -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'prepackaged'" class="result-item product-card">
|
|
||||||
<div class="product-image-placeholder"><span>商品图片</span></div>
|
|
||||||
<div class="product-details">
|
|
||||||
<h4 class="product-title">
|
|
||||||
<span class="category-tag">[预包装]</span>
|
|
||||||
<span class="brand">某品牌</span>
|
|
||||||
<span class="product-name">辣条</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>安全评级 <span class="prepackaged-safety-5">5 级 (风险较高)</span></span>
|
|
||||||
<span>营养评级 <span class="score-low">低</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row risk-info">
|
|
||||||
<span>添加剂 <span class="count additive-count">15</span></span>
|
|
||||||
<span>高风险成分 <span class="count risk-count">4</span></span>
|
|
||||||
<span class="calories">热量 600千卡/100g</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Additive Results - New Card Design -->
|
|
||||||
<!-- Additive Results - V3 Preview with 6 Levels -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">维生素 C (抗坏血酸)</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-1">A1 最高安全等级</span></span>
|
|
||||||
<span class="function-tag">抗氧化剂</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">一般认为安全,但极高剂量可能导致肠胃不适。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">苯甲酸钠</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-2">A2 较安全</span></span>
|
|
||||||
<span class="function-tag">防腐剂</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">在特定条件下可能与维生素C反应生成微量苯,长期过量摄入有潜在风险。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">诱惑红</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-3">B1 需警惕</span></span>
|
|
||||||
<span class="function-tag">色素</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">可能引起儿童多动症等过敏反应,在多国被限制或禁止使用。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">糖醇类甜味剂</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-4">B2 风险未知</span></span>
|
|
||||||
<span class="function-tag">新型甜味剂</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">长期摄入对健康的影响尚不完全明确,部分人群可能出现肠胃不适。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">黄原胶 (特定场景)</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-5">C2 严格限制</span></span>
|
|
||||||
<span class="function-tag">增稠剂</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">仅限在特定食品(如婴幼儿食品)中严格限量使用。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'additive'" class="result-item additive-card">
|
|
||||||
<div class="product-image-placeholder"><span>添加剂图片</span></div>
|
|
||||||
<div class="additive-details">
|
|
||||||
<h4 class="additive-title">
|
|
||||||
<span class="category-tag">[添加剂]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">苏丹红</span>
|
|
||||||
</h4>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span>安全等级: <span class="safety-level-6">C1 禁止使用</span></span>
|
|
||||||
<span class="function-tag">工业染料</span>
|
|
||||||
</div>
|
|
||||||
<div class="additive-info-row">
|
|
||||||
<span class="health-risk-warning">具有潜在致癌性,严禁在食品中添加。</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Food Material Results - New Card Design -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'material'" class="result-item material-card">
|
|
||||||
<div class="product-image-placeholder"><span>食材图片</span></div>
|
|
||||||
<div class="material-details">
|
|
||||||
<h4 class="material-title">
|
|
||||||
<span class="category-tag">[食材]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">西兰花</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>分类: 十字花科蔬菜</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row nutrient-tags-container">
|
|
||||||
<span class="nutrient-tag">富含维生素C</span>
|
|
||||||
<span class="nutrient-tag">高膳食纤维</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'material'" class="result-item material-card">
|
|
||||||
<div class="product-image-placeholder"><span>食材图片</span></div>
|
|
||||||
<div class="material-details">
|
|
||||||
<h4 class="material-title">
|
|
||||||
<span class="category-tag">[食材]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">番茄</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>分类: 茄科植物</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row nutrient-tags-container">
|
|
||||||
<span class="nutrient-tag tomato">富含番茄红素</span>
|
|
||||||
<span class="nutrient-tag antioxidant">抗氧化</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'material'" class="result-item material-card">
|
|
||||||
<div class="product-image-placeholder"><span>食材图片</span></div>
|
|
||||||
<div class="material-details">
|
|
||||||
<h4 class="material-title">
|
|
||||||
<span class="category-tag">[食材]</span>
|
|
||||||
<span class="brand"></span>
|
|
||||||
<span class="product-name">酱油</span>
|
|
||||||
</h4>
|
|
||||||
<div class="product-info-row">
|
|
||||||
<span>分类: 传统酿造调味品</span>
|
|
||||||
</div>
|
|
||||||
<div class="product-info-row nutrient-tags-container">
|
|
||||||
<span class="nutrient-tag umami">提鲜</span>
|
|
||||||
<span class="nutrient-tag high-sodium">高钠</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recipe Results -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'recipe'" class="result-item">
|
|
||||||
<h4>[食谱] 自制健康牛奶小面包</h4>
|
|
||||||
<p class="article-summary">一个简单易学的食谱,使用最少的添加剂,为家人制作美味又健康的面包。</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Article Results (Renamed to "资讯") -->
|
|
||||||
<div v-if="activeTab === 'all' || activeTab === 'article'" class="result-item">
|
|
||||||
<h4>牛奶过敏的宝宝应该怎么办?</h4>
|
|
||||||
<p class="article-summary">本文将详细介绍牛奶过敏的症状、原因以及应对方法...</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
const searchQuery = ref('');
|
|
||||||
const activeTab = ref('all');
|
|
||||||
|
|
||||||
// Filter and Sort State
|
|
||||||
const filters = ref([
|
|
||||||
{ id: 'comprehensive', name: '综合' },
|
|
||||||
{ id: 'safety', name: '安全评级' },
|
|
||||||
{ id: 'nutrition', name: '营养评级' },
|
|
||||||
{ id: 'latest', name: '最新发布' },
|
|
||||||
{ id: 'protein', name: '蛋白质' },
|
|
||||||
{ id: 'fat', name: '脂肪' },
|
|
||||||
{ id: 'carbs', name: '碳水化合物' },
|
|
||||||
]);
|
|
||||||
const activeFilter = ref('comprehensive');
|
|
||||||
const sortOrder = ref('desc'); // 'desc' or 'asc'
|
|
||||||
|
|
||||||
// Panel State
|
|
||||||
const isPanelOpen = ref(false);
|
|
||||||
const tempActiveFilter = ref(activeFilter.value);
|
|
||||||
const tempSortOrder = ref(sortOrder.value);
|
|
||||||
|
|
||||||
const activeFilterName = computed(() => {
|
|
||||||
const found = filters.value.find(f => f.id === activeFilter.value);
|
|
||||||
return found ? found.name : '综合';
|
|
||||||
});
|
|
||||||
|
|
||||||
const openFilterPanel = () => {
|
|
||||||
tempActiveFilter.value = activeFilter.value;
|
|
||||||
tempSortOrder.value = sortOrder.value;
|
|
||||||
isPanelOpen.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelFilters = () => {
|
|
||||||
isPanelOpen.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmFilters = () => {
|
|
||||||
activeFilter.value = tempActiveFilter.value;
|
|
||||||
sortOrder.value = tempSortOrder.value;
|
|
||||||
isPanelOpen.value = false;
|
|
||||||
// Here you would typically re-fetch data with new filters
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabs = ref([
|
|
||||||
{ id: 'all', name: '全部' },
|
|
||||||
{ id: 'prepackaged', name: '预包装' },
|
|
||||||
{ id: 'additive', name: '添加剂' },
|
|
||||||
{ id: 'material', name: '食材' },
|
|
||||||
{ id: 'recipe', name: '食谱' },
|
|
||||||
{ id: 'article', name: '资讯' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
searchQuery.value = (route.query.q as string) || '';
|
|
||||||
});
|
|
||||||
|
|
||||||
const goBack = () => {
|
|
||||||
router.push({ name: 'home' });
|
|
||||||
};
|
|
||||||
|
|
||||||
const performSearch = () => {
|
|
||||||
// In a real app, this would re-trigger the search
|
|
||||||
console.log('Searching for:', searchQuery.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const goToResult = (id: string) => {
|
|
||||||
router.push({ name: 'result', params: { id } });
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.search-result-view {
|
|
||||||
/* The padding is no longer needed here as the results-list is positioned absolutely. */
|
|
||||||
}
|
|
||||||
.top-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
.back-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
padding-right: 10px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.search-bar-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 25px;
|
|
||||||
padding: 8px 15px;
|
|
||||||
border: 1px solid #07C160;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-bar-container input {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
flex-grow: 1;
|
|
||||||
width: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
width: 1px;
|
|
||||||
height: 16px;
|
|
||||||
background-color: #e0e0e0;
|
|
||||||
margin: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-button {
|
|
||||||
color: #07C160;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
padding: 10px 0;
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
position: fixed;
|
|
||||||
top: 57px; /* Position below top-bar */
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 9;
|
|
||||||
}
|
|
||||||
.tabs button {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
cursor: pointer;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
}
|
|
||||||
.tabs button.active {
|
|
||||||
color: #22c55e;
|
|
||||||
border-bottom-color: #22c55e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters-bar {
|
|
||||||
display: flex;
|
|
||||||
padding: 8px 15px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
position: fixed;
|
|
||||||
top: 101px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-toggle-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-toggle-btn .arrow {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-left: 5px solid transparent;
|
|
||||||
border-right: 5px solid transparent;
|
|
||||||
border-top: 5px solid #666;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 142px; /* Position below the header bars */
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 99;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel {
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 20px 15px;
|
|
||||||
border-bottom-left-radius: 16px;
|
|
||||||
border-bottom-right-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group h4 {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags button {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
border: 1px solid #f7f7f7;
|
|
||||||
color: #333;
|
|
||||||
padding: 6px 14px;
|
|
||||||
border-radius: 16px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags button.active {
|
|
||||||
background-color: #e8f8f1;
|
|
||||||
border-color: #07c160;
|
|
||||||
color: #07c160;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-actions button {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 25px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-cancel {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
border: 1px solid #f7f7f7;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-confirm {
|
|
||||||
background-color: #07c160;
|
|
||||||
border: 1px solid #07c160;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-list {
|
|
||||||
position: absolute;
|
|
||||||
top: 142px; /* Position right below the filters-bar */
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px; /* Ensures consistency with the fixed headers */
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow-y: auto; /* Enables vertical scrolling for the list */
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
|
||||||
.result-item {
|
|
||||||
padding: 15px 0;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.result-item h4 {
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.result-item p {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
.article-summary {
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.score-high { color: #22c55e; }
|
|
||||||
.score-mid { color: orange; }
|
|
||||||
.score-low { color: #ef4444; }
|
|
||||||
|
|
||||||
.product-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-image-placeholder {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 12px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-item h4.product-title {
|
|
||||||
font-size: 14px !important; /* Force size with high specificity and !important */
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info-row.risk-info {
|
|
||||||
justify-content: flex-start; /* Align items to the start */
|
|
||||||
gap: 20px; /* Increase gap for risk info */
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info-row span {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-title .category-tag,
|
|
||||||
.additive-title .category-tag,
|
|
||||||
.material-title .category-tag {
|
|
||||||
font-weight: normal;
|
|
||||||
color: #9ca3af; /* Gray-400 */
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 8px; /* Reset margin slightly as min-width handles spacing */
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-title .brand {
|
|
||||||
font-weight: normal;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calories {
|
|
||||||
background-color: #f3f4f6; /* Gray-100 */
|
|
||||||
color: #4b5563; /* Gray-600 */
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info-row .count {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additive-count {
|
|
||||||
color: #FF7F50; /* Yellow for additives */
|
|
||||||
}
|
|
||||||
|
|
||||||
.risk-count {
|
|
||||||
color: #ff0000; /* Red for high-risk */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 6-Level Additive Safety Rating Styles */
|
|
||||||
.safety-level-1 { color: #07C160; font-weight: bold; } /* A1: 最安全 - 微信绿 */
|
|
||||||
.safety-level-2 { color: #90ee90; font-weight: bold; } /* A2: 较安全 - 浅绿色 */
|
|
||||||
.safety-level-3 { color: #1E90FF; font-weight: bold; } /* B1: 需警惕 - 蓝色 */
|
|
||||||
.safety-level-4 { color: #ffe600; font-weight: bold; } /* B2: 风险未知 - 金色 */
|
|
||||||
.safety-level-5 { color: #FF7F50; font-weight: bold; } /* C2: 严格限制 - 橙色 */
|
|
||||||
.safety-level-6 { color: #ff0000; font-weight: bold; } /* C1: 禁止使用 - 深红色 */
|
|
||||||
|
|
||||||
/* Prepackaged Food Specific Safety Rating Styles */
|
|
||||||
.prepackaged-safety-4 { color: #FF7F50; font-weight: bold; }
|
|
||||||
.prepackaged-safety-5 { color: #ff0000; font-weight: bold; }
|
|
||||||
|
|
||||||
/* Material Card Specific Styles */
|
|
||||||
.material-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.material-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-item h4.material-title {
|
|
||||||
font-size: 14px !important;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .material-family and .material-nutrients styles are no longer needed in this form */
|
|
||||||
/* They are replaced by reusing .product-info-row and the specific .nutrient-tag styles */
|
|
||||||
|
|
||||||
.nutrient-tag {
|
|
||||||
background-color: #f0fdf4; /* Green-50 */
|
|
||||||
color: #15803d; /* Green-700 */
|
|
||||||
padding: 3px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nutrient-tags-container {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Additive Card Specific Styles V2 */
|
|
||||||
.additive-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additive-details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-item h4.additive-title {
|
|
||||||
font-size: 14px !important;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additive-title .brand,
|
|
||||||
.material-title .brand {
|
|
||||||
/* This is a placeholder to match the structure of product-title */
|
|
||||||
/* It can be empty, but its presence helps in aligning items if needed */
|
|
||||||
font-weight: normal;
|
|
||||||
color: #666;
|
|
||||||
display: inline-block;
|
|
||||||
min-width: 0; /* No minimum width needed */
|
|
||||||
margin-right: 0; /* Remove extra margin for the placeholder */
|
|
||||||
}
|
|
||||||
|
|
||||||
.health-risk-warning {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666; /* Matching the color of other info text */
|
|
||||||
line-height: 1.4;
|
|
||||||
white-space: normal; /* Allow text to wrap */
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2; /* Show max 2 lines */
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.additive-info-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.function-tag {
|
|
||||||
background-color: #eef2ff; /* Indigo-50 */
|
|
||||||
color: #4338ca; /* Indigo-700 */
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
|
||||||
.product-image-placeholder {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
.product-card,
|
|
||||||
.additive-card {
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.product-details,
|
|
||||||
.additive-details {
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
.product-info-row,
|
|
||||||
.product-info-row.risk-info,
|
|
||||||
.additive-info-row {
|
|
||||||
font-size: 11px;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
.calories,
|
|
||||||
.function-tag {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
.health-risk-warning {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.safety-level-description {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 10px 0; /* Use padding on Y-axis only to avoid affecting horizontal alignment */
|
|
||||||
margin: 0 -20px 10px -20px; /* Use negative margin to extend the background to the edges */
|
|
||||||
padding-left: 20px; /* Re-apply left padding */
|
|
||||||
padding-right: 20px; /* Re-apply right padding */
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
z-index: 1; /* Ensure it stays above the scrolling content */
|
|
||||||
font-size: 16px;
|
|
||||||
color: #6b7280;
|
|
||||||
transform: scale(0.85);
|
|
||||||
transform-origin: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
@ -1,7 +1,10 @@
|
|||||||
|
<!-- Force reload to fix display issue and update image sources -->
|
||||||
<template>
|
<template>
|
||||||
<div class="search-result-view">
|
<div class="search-result-view">
|
||||||
<header class="top-bar">
|
<header class="top-bar">
|
||||||
<button class="back-btn" @click="goBack"><</button>
|
<button class="back-btn" @click="goBack">
|
||||||
|
<img src="@/assets/back-arrow-icon.svg" alt="Back" />
|
||||||
|
</button>
|
||||||
<div class="search-bar-container">
|
<div class="search-bar-container">
|
||||||
<input type="text" v-model="searchQuery" @keyup.enter="performSearch" placeholder="食品 /成分 /食物 /菜谱 /问题" />
|
<input type="text" v-model="searchQuery" @keyup.enter="performSearch" placeholder="食品 /成分 /食物 /菜谱 /问题" />
|
||||||
<div class="separator"></div>
|
<div class="separator"></div>
|
||||||
@ -25,43 +28,48 @@
|
|||||||
<span>{{ activeFilterName }}</span>
|
<span>{{ activeFilterName }}</span>
|
||||||
<span class="arrow"></span>
|
<span class="arrow"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
<!-- Filter Panel Overlay - Moved inside filters-bar -->
|
||||||
|
<div v-if="isPanelOpen" class="filter-panel-overlay" @click.self="cancelFilters">
|
||||||
<!-- Filter Panel Overlay -->
|
<div class="filter-panel">
|
||||||
<div v-if="isPanelOpen" class="filter-panel-overlay" @click.self="cancelFilters">
|
<div class="filter-group">
|
||||||
<div class="filter-panel">
|
<h4>排序方式</h4>
|
||||||
<div class="filter-group">
|
<div class="tags">
|
||||||
<h4>排序方式</h4>
|
<button
|
||||||
<div class="tags">
|
v-for="filter in filters"
|
||||||
<button
|
:key="filter.id"
|
||||||
v-for="filter in filters"
|
:class="{ active: tempActiveFilter === filter.id }"
|
||||||
:key="filter.id"
|
@click="tempActiveFilter = filter.id"
|
||||||
:class="{ active: tempActiveFilter === filter.id }"
|
>
|
||||||
@click="tempActiveFilter = filter.id"
|
{{ filter.name }}
|
||||||
>
|
</button>
|
||||||
{{ filter.name }}
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<h4>顺序</h4>
|
<h4>顺序</h4>
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<button :class="{ active: tempSortOrder === 'desc' }" @click="tempSortOrder = 'desc'">从高到低</button>
|
<button :class="{ active: tempSortOrder === 'desc' }" @click="tempSortOrder = 'desc'">从高到低</button>
|
||||||
<button :class="{ active: tempSortOrder === 'asc' }" @click="tempSortOrder = 'asc'">从低到高</button>
|
<button :class="{ active: tempSortOrder === 'asc' }" @click="tempSortOrder = 'asc'">从低到高</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="panel-actions">
|
<div class="panel-actions">
|
||||||
<button class="btn-cancel" @click="cancelFilters">取消</button>
|
<button class="btn-cancel" @click="cancelFilters">取消</button>
|
||||||
<button class="btn-confirm" @click="confirmFilters">确认</button>
|
<button class="btn-confirm" @click="confirmFilters">确认</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="results-list">
|
<main class="results-list">
|
||||||
<div v-if="activeTab === 'additive'" class="safety-level-description">
|
<div v-if="activeTab === 'additive'" class="safety-level-description">
|
||||||
安全等级划分参照 JECFA:A1 > A2 > B1 > B2 > C2 > C1。
|
安全等级:
|
||||||
|
<span class="safety-level-1">A1</span> >
|
||||||
|
<span class="safety-level-2">A2</span> >
|
||||||
|
<span class="safety-level-3">B1</span> >
|
||||||
|
<span class="safety-level-4">B2</span> >
|
||||||
|
<span class="safety-level-5">C2</span> >
|
||||||
|
<span class="safety-level-6">C1</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- Dynamic component for rendering lists -->
|
<!-- Dynamic component for rendering lists -->
|
||||||
<component
|
<component
|
||||||
@ -78,13 +86,21 @@ import { ref, computed, onMounted } from 'vue';
|
|||||||
import type { Component } from 'vue';
|
import type { Component } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
// Cache buster, updated to force reload and show new images
|
||||||
|
const cacheBuster = '2025-07-26T07:52:30.000Z'; // Force reload to fix sticky header background
|
||||||
|
|
||||||
// Import list components
|
// Import list components
|
||||||
import PrepackagedList from '@/components/SearchResult/PrepackagedList.vue';
|
import PrepackagedList from '@/components/SearchResult-搜索结果/PrepackagedList-预包装食品列表.vue';
|
||||||
import AdditiveList from '@/components/SearchResult/AdditiveList.vue';
|
import AdditiveList from '@/components/SearchResult-搜索结果/AdditiveList-食品添加剂列表.vue';
|
||||||
import MaterialList from '@/components/SearchResult/MaterialList.vue';
|
import MaterialList from '@/components/SearchResult-搜索结果/MaterialList-原料列表.vue';
|
||||||
import RecipeList from '@/components/SearchResult/RecipeList.vue';
|
import RecipeList from '@/components/SearchResult-搜索结果/RecipeList-菜谱列表.vue';
|
||||||
import ArticleList from '@/components/SearchResult/ArticleList.vue';
|
import ArticleList from '@/components/SearchResult-搜索结果/ArticleList-文章列表.vue';
|
||||||
import AllResultsList from '@/components/SearchResult/AllResultsList.vue';
|
import AllResultsList from '@/components/SearchResult-搜索结果/AllResultsList-所有结果列表.vue';
|
||||||
|
|
||||||
|
// Import local images
|
||||||
|
import breakfastImage from '@/assets/images/breakfast.jpg';
|
||||||
|
import dessertsImage from '@/assets/images/desserts.jpg';
|
||||||
|
import tomatoesImage from '@/assets/images/tomatoes.png';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -94,30 +110,32 @@ const activeTab = ref('all');
|
|||||||
// Mock Data
|
// Mock Data
|
||||||
const mockData = {
|
const mockData = {
|
||||||
prepackaged: [
|
prepackaged: [
|
||||||
{ id: 'p1', category: 'prepackaged', brand: '某品牌', name: '有机纯牛奶', safetyLevel: 1, safetyLevelText: '1 级 (最安全)', nutritionLevel: '高', nutritionLevelClass: 'score-high', additiveCount: 0, riskCount: 0, calories: 270 },
|
{ id: 'p1', category: 'prepackaged', brand: '某品牌', name: '有机纯牛奶', safetyLevel: 1, safetyLevelText: '1 级 (最安全)', nutritionLevel: '高', nutritionLevelClass: 'score-high', additiveCount: 0, riskCount: 0, calories: 270, imageUrl: breakfastImage },
|
||||||
{ id: 'p2', category: 'prepackaged', brand: '某品牌', name: '全麦面包', safetyLevel: 2, safetyLevelText: '2 级 (较安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 2, riskCount: 0, calories: 350 },
|
{ id: 'p2', category: 'prepackaged', brand: '某品牌', name: '全麦面包', safetyLevel: 2, safetyLevelText: '2 级 (较安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 2, riskCount: 0, calories: 350, imageUrl: breakfastImage },
|
||||||
{ id: 'p3', category: 'prepackaged', brand: '某品牌', name: '番茄酱', safetyLevel: 3, safetyLevelText: '3 级 (一般安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 5, riskCount: 1, calories: 420 },
|
{ id: 'p3', category: 'prepackaged', brand: '某品牌', name: '番茄酱', safetyLevel: 3, safetyLevelText: '3 级 (一般安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 5, riskCount: 1, calories: 420, imageUrl: tomatoesImage },
|
||||||
{ id: 'p4', category: 'prepackaged', brand: '子弟', name: '薯片 (蜂蜜黄油味)', safetyLevel: 4, safetyLevelText: '4 级 (需警惕)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 8, riskCount: 2, calories: 519 },
|
{ id: 'p4', category: 'prepackaged', brand: '子弟', name: '薯片 (蜂蜜黄油味)', safetyLevel: 4, safetyLevelText: '4 级 (需警惕)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 8, riskCount: 2, calories: 519, imageUrl: breakfastImage },
|
||||||
{ id: 'p5', category: 'prepackaged', brand: '某品牌', name: '辣条', safetyLevel: 5, safetyLevelText: '5 级 (风险较高)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 15, riskCount: 4, calories: 600 },
|
{ id: 'p5', category: 'prepackaged', brand: '某品牌', name: '辣条', safetyLevel: 5, safetyLevelText: '5 级 (风险较高)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 15, riskCount: 4, calories: 600, imageUrl: dessertsImage },
|
||||||
],
|
],
|
||||||
additive: [
|
additive: [
|
||||||
{ id: 'a1', category: 'additive', name: '维生素 C (抗坏血酸)', safetyLevel: 1, safetyLevelText: 'A1 最高安全等级', functionTag: '抗氧化剂', riskWarning: '一般认为安全,但极高剂量可能导致肠胃不适。' },
|
{ id: 'a1', category: 'additive', name: '维生素 C (抗坏血酸)', safetyLevel: 1, safetyLevelText: 'A1 最高安全等级', functionTag: '抗氧化剂', riskWarning: '一般认为安全,但极高剂量可能导致肠胃不适。', imageUrl: breakfastImage },
|
||||||
{ id: 'a2', category: 'additive', name: '苯甲酸钠', safetyLevel: 2, safetyLevelText: 'A2 较安全', functionTag: '防腐剂', riskWarning: '在特定条件下可能与维生素C反应生成微量苯,长期过量摄入有潜在风险。' },
|
{ id: 'a2', category: 'additive', name: '苯甲酸钠', safetyLevel: 2, safetyLevelText: 'A2 较安全', functionTag: '防腐剂', riskWarning: '在特定条件下可能与维生素C反应生成微量苯,长期过量摄入有潜在风险。', imageUrl: dessertsImage },
|
||||||
{ id: 'a3', category: 'additive', name: '诱惑红', safetyLevel: 3, safetyLevelText: 'B1 需警惕', functionTag: '色素', riskWarning: '可能引起儿童多动症等过敏反应,在多国被限制或禁止使用。' },
|
{ id: 'a3', category: 'additive', name: '诱惑红', safetyLevel: 3, safetyLevelText: 'B1 需警惕', functionTag: '色素', riskWarning: '可能引起儿童多动症等过敏反应,在多国被限制或禁止使用。', imageUrl: dessertsImage },
|
||||||
{ id: 'a4', category: 'additive', name: '糖醇类甜味剂', safetyLevel: 4, safetyLevelText: 'B2 风险未知', functionTag: '新型甜味剂', riskWarning: '长期摄入对健康的影响尚不完全明确,部分人群可能出现肠胃不适。' },
|
{ id: 'a4', category: 'additive', name: '糖醇类甜味剂', safetyLevel: 4, safetyLevelText: 'B2 风险未知', functionTag: '新型甜味剂', riskWarning: '长期摄入对健康的影响尚不完全明确,部分人群可能出现肠胃不适。', imageUrl: dessertsImage },
|
||||||
{ id: 'a5', category: 'additive', name: '黄原胶 (特定场景)', safetyLevel: 5, safetyLevelText: 'C2 严格限制', functionTag: '增稠剂', riskWarning: '仅限在特定食品(如婴幼儿食品)中严格限量使用。' },
|
{ id: 'a5', category: 'additive', name: '黄原胶 (特定场景)', safetyLevel: 5, safetyLevelText: 'C2 严格限制', functionTag: '增稠剂', riskWarning: '仅限在特定食品(如婴幼儿食品)中严格限量使用。', imageUrl: dessertsImage },
|
||||||
{ id: 'a6', category: 'additive', name: '苏丹红', safetyLevel: 6, safetyLevelText: 'C1 禁止使用', functionTag: '工业染料', riskWarning: '具有潜在致癌性,严禁在食品中添加。' },
|
{ id: 'a6', category: 'additive', name: '苏丹红', safetyLevel: 6, safetyLevelText: 'C1 禁止使用', functionTag: '工业染料', riskWarning: '具有潜在致癌性,严禁在食品中添加。', imageUrl: dessertsImage },
|
||||||
],
|
],
|
||||||
material: [
|
material: [
|
||||||
{ id: 'm1', category: 'material', name: '西兰花', family: '十字花科蔬菜', nutrientTags: [{text: '富含维生素C'}, {text: '高膳食纤维'}] },
|
{ id: 'm1', category: 'material', name: '西兰花', family: '十字花科蔬菜', nutrientTags: [{text: '富含维生素C'}, {text: '高膳食纤维'}], imageUrl: breakfastImage },
|
||||||
{ id: 'm2', category: 'material', name: '番茄', family: '茄科植物', nutrientTags: [{text: '富含番茄红素', class: 'tomato'}, {text: '抗氧化', class: 'antioxidant'}] },
|
{ id: 'm2', category: 'material', name: '番茄', family: '茄科植物', nutrientTags: [{text: '富含番茄红素', class: 'tomato'}, {text: '抗氧化', class: 'antioxidant'}], imageUrl: tomatoesImage },
|
||||||
{ id: 'm3', category: 'material', name: '酱油', family: '传统酿造调味品', nutrientTags: [{text: '提鲜', class: 'umami'}, {text: '高钠', class: 'high-sodium'}] },
|
{ id: 'm3', category: 'material', name: '酱油', family: '传统酿造调味品', nutrientTags: [{text: '提鲜', class: 'umami'}, {text: '高钠', class: 'high-sodium'}], imageUrl: dessertsImage },
|
||||||
],
|
],
|
||||||
recipe: [
|
recipe: [
|
||||||
{ id: 'r1', category: 'recipe', title: '自制健康牛奶小面包', summary: '一个简单易学的食谱,使用最少的添加剂,为家人制作美味又健康的面包。' }
|
{ id: 'r1', category: 'recipe', title: '丰盛的全麦贝果早餐', summary: '一个简单易学的食谱,使用最少的添加剂,为家人制作美味又健康的面包。', mediaType: 'image', imageUrl: breakfastImage },
|
||||||
|
{ id: 'r2', category: 'recipe', title: '一分钟学会做番茄炒蛋', summary: '看视频,轻松掌握这道国民家常菜的精髓,厨房新手也能零失败。', mediaType: 'video', imageUrl: tomatoesImage }
|
||||||
],
|
],
|
||||||
article: [
|
article: [
|
||||||
{ id: 'ar1', category: 'article', title: '牛奶过敏的宝宝应该怎么办?', summary: '本文将详细介绍牛奶过敏的症状、原因以及应对方法...' }
|
{ id: 'ar1', category: 'article', title: '牛奶过敏的宝宝应该怎么办?', summary: '本文将详细介绍牛奶过敏的症状、原因以及应对方法...', mediaType: 'image', imageUrl: breakfastImage },
|
||||||
|
{ id: 'ar2', category: 'article', title: '如何科学地给宝宝添加辅食', summary: '辅食添加的顺序、种类、注意事项,专家视频为您一次讲清。', mediaType: 'video', imageUrl: breakfastImage }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -227,30 +245,34 @@ const goToResult = (item: any) => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
/* Keep only layout and container styles */
|
/* Keep only layout and container styles */
|
||||||
.search-result-view {
|
.search-result-view {
|
||||||
/* The padding is no longer needed here as the results-list is positioned absolutely. */
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative; /* Restore positioning context */
|
||||||
}
|
}
|
||||||
.top-bar {
|
.top-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 20px;
|
padding: 15px 12px 12px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
position: fixed;
|
/* position: fixed; Removed */
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.back-btn {
|
.back-btn {
|
||||||
background: none;
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 24px;
|
background: none;
|
||||||
font-weight: bold;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-right: 10px;
|
margin-right: 8px; /* Add some space to the search bar */
|
||||||
color: #333;
|
}
|
||||||
|
|
||||||
|
.back-btn img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.search-bar-container {
|
.search-bar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -292,18 +314,13 @@ const goToResult = (item: any) => {
|
|||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
position: fixed;
|
/* position: fixed; Removed */
|
||||||
top: 57px; /* Position below top-bar */
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
}
|
}
|
||||||
.tabs button {
|
.tabs button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
@ -319,13 +336,8 @@ const goToResult = (item: any) => {
|
|||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
position: fixed;
|
|
||||||
top: 101px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 428px;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 8;
|
z-index: 8;
|
||||||
|
position: relative; /* Become a positioning context for the panel */
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-toggle-btn {
|
.filter-toggle-btn {
|
||||||
@ -348,13 +360,13 @@ const goToResult = (item: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-panel-overlay {
|
.filter-panel-overlay {
|
||||||
position: fixed;
|
position: absolute; /* Changed from fixed to be contained within the view */
|
||||||
top: 142px; /* Position below the header bars */
|
top: 100%; /* Position right below the filters-bar */
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 99;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
@ -401,17 +413,19 @@ const goToResult = (item: any) => {
|
|||||||
|
|
||||||
.panel-actions {
|
.panel-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
justify-content: center;
|
||||||
|
gap: 22px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding-top: 20px;
|
padding-top: 15px;
|
||||||
border-top: 1px solid #f0f0f0;
|
border-top: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-actions button {
|
.panel-actions button {
|
||||||
flex-grow: 1;
|
flex-grow: 0;
|
||||||
padding: 12px;
|
width: 100px;
|
||||||
|
padding: 8px;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -429,31 +443,29 @@ const goToResult = (item: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.results-list {
|
.results-list {
|
||||||
position: absolute;
|
flex-grow: 1; /* Allow this container to grow and fill available space */
|
||||||
top: 142px; /* Position right below the filters-bar */
|
overflow-y: auto; /* Enable scrolling ONLY for this container */
|
||||||
bottom: 0;
|
padding: 0; /* Remove padding from the container */
|
||||||
left: 0;
|
min-height: 0; /* Fix for flexbox overflow issue, preventing parent scrolling */
|
||||||
right: 0;
|
|
||||||
max-width: 428px; /* Ensures consistency with the fixed headers */
|
|
||||||
margin: 0 auto;
|
|
||||||
overflow-y: auto; /* Enables vertical scrolling for the list */
|
|
||||||
padding: 0 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.safety-level-description {
|
.safety-level-description {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
padding: 10px 0;
|
padding: 10px 20px; /* Add horizontal padding */
|
||||||
margin: 0 -20px 10px -20px;
|
margin-bottom: 10px; /* Add margin to separate from the list */
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-size: 16px;
|
font-size: 13px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
transform: scale(0.85);
|
|
||||||
transform-origin: left;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
/* Safety Level Colors */
|
||||||
|
.safety-level-1 { color: #07C160; font-weight: bold; }
|
||||||
|
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
||||||
|
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
||||||
|
.safety-level-4 { color: #f2d025; font-weight: bold; }
|
||||||
|
.safety-level-5 { color: #FF7F50; font-weight: bold; }
|
||||||
|
.safety-level-6 { color: #ff0000; font-weight: bold; }
|
||||||
</style>
|
</style>
|
471
shihuashishuo-ui/src/views/核心体验页/SearchResultView-搜索结果页3.0.vue
Normal file
471
shihuashishuo-ui/src/views/核心体验页/SearchResultView-搜索结果页3.0.vue
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
<!-- Force reload to fix display issue and update image sources -->
|
||||||
|
<template>
|
||||||
|
<div class="search-result-view">
|
||||||
|
<header class="top-bar">
|
||||||
|
<button class="back-btn" @click="goBack">
|
||||||
|
<img src="@/assets/back-arrow-icon.svg" alt="Back" />
|
||||||
|
</button>
|
||||||
|
<div class="search-bar-container">
|
||||||
|
<input type="text" v-model="searchQuery" @keyup.enter="performSearch" placeholder="食品 /成分 /食物 /菜谱 /问题" />
|
||||||
|
<div class="separator"></div>
|
||||||
|
<div class="search-button" @click="performSearch">搜索</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<button
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.id"
|
||||||
|
:class="{ active: activeTab === tab.id }"
|
||||||
|
@click="activeTab = tab.id"
|
||||||
|
>
|
||||||
|
{{ tab.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters-bar">
|
||||||
|
<button class="filter-toggle-btn" @click="openFilterPanel">
|
||||||
|
<span>{{ activeFilterName }}</span>
|
||||||
|
<span class="arrow"></span>
|
||||||
|
</button>
|
||||||
|
<!-- Filter Panel Overlay - Moved inside filters-bar -->
|
||||||
|
<div v-if="isPanelOpen" class="filter-panel-overlay" @click.self="cancelFilters">
|
||||||
|
<div class="filter-panel">
|
||||||
|
<div class="filter-group">
|
||||||
|
<h4>排序方式</h4>
|
||||||
|
<div class="tags">
|
||||||
|
<button
|
||||||
|
v-for="filter in filters"
|
||||||
|
:key="filter.id"
|
||||||
|
:class="{ active: tempActiveFilter === filter.id }"
|
||||||
|
@click="tempActiveFilter = filter.id"
|
||||||
|
>
|
||||||
|
{{ filter.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-group">
|
||||||
|
<h4>顺序</h4>
|
||||||
|
<div class="tags">
|
||||||
|
<button :class="{ active: tempSortOrder === 'desc' }" @click="tempSortOrder = 'desc'">从高到低</button>
|
||||||
|
<button :class="{ active: tempSortOrder === 'asc' }" @click="tempSortOrder = 'asc'">从低到高</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-actions">
|
||||||
|
<button class="btn-cancel" @click="cancelFilters">取消</button>
|
||||||
|
<button class="btn-confirm" @click="confirmFilters">确认</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="results-list">
|
||||||
|
<div v-if="activeTab === 'additive'" class="safety-level-description">
|
||||||
|
安全等级:
|
||||||
|
<span class="safety-level-1">A1</span> >
|
||||||
|
<span class="safety-level-2">A2</span> >
|
||||||
|
<span class="safety-level-3">B1</span> >
|
||||||
|
<span class="safety-level-4">B2</span> >
|
||||||
|
<span class="safety-level-5">C2</span> >
|
||||||
|
<span class="safety-level-6">C1</span>
|
||||||
|
</div>
|
||||||
|
<!-- Dynamic component for rendering lists -->
|
||||||
|
<component
|
||||||
|
:is="componentMap[activeTab]"
|
||||||
|
:items="filteredResults"
|
||||||
|
@item-click="goToResult"
|
||||||
|
/>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, onMounted } from 'vue';
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
// Cache buster, updated to force reload and show new images
|
||||||
|
const cacheBuster = '2025-07-26T07:52:30.000Z'; // Force reload to fix sticky header background
|
||||||
|
|
||||||
|
// Import list components
|
||||||
|
import PrepackagedList from '@/components/SearchResult-搜索结果/PrepackagedList-预包装食品列表.vue';
|
||||||
|
import AdditiveList from '@/components/SearchResult-搜索结果/AdditiveList-食品添加剂列表.vue';
|
||||||
|
import MaterialList from '@/components/SearchResult-搜索结果/MaterialList-原料列表.vue';
|
||||||
|
import RecipeList from '@/components/SearchResult-搜索结果/RecipeList-菜谱列表.vue';
|
||||||
|
import ArticleList from '@/components/SearchResult-搜索结果/ArticleList-文章列表.vue';
|
||||||
|
import AllResultsList from '@/components/SearchResult-搜索结果/AllResultsList-所有结果列表.vue';
|
||||||
|
|
||||||
|
// Import local images
|
||||||
|
import breakfastImage from '@/assets/images/breakfast.jpg';
|
||||||
|
import dessertsImage from '@/assets/images/desserts.jpg';
|
||||||
|
import tomatoesImage from '@/assets/images/tomatoes.png';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const searchQuery = ref('');
|
||||||
|
const activeTab = ref('all');
|
||||||
|
|
||||||
|
// Mock Data
|
||||||
|
const mockData = {
|
||||||
|
prepackaged: [
|
||||||
|
{ id: 'p1', category: 'prepackaged', brand: '某品牌', name: '有机纯牛奶', safetyLevel: 1, safetyLevelText: '1 级 (最安全)', nutritionLevel: '高', nutritionLevelClass: 'score-high', additiveCount: 0, riskCount: 0, calories: 270, imageUrl: breakfastImage },
|
||||||
|
{ id: 'p2', category: 'prepackaged', brand: '某品牌', name: '全麦面包', safetyLevel: 2, safetyLevelText: '2 级 (较安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 2, riskCount: 0, calories: 350, imageUrl: breakfastImage },
|
||||||
|
{ id: 'p3', category: 'prepackaged', brand: '某品牌', name: '番茄酱', safetyLevel: 3, safetyLevelText: '3 级 (一般安全)', nutritionLevel: '中', nutritionLevelClass: 'score-mid', additiveCount: 5, riskCount: 1, calories: 420, imageUrl: tomatoesImage },
|
||||||
|
{ id: 'p4', category: 'prepackaged', brand: '子弟', name: '薯片 (蜂蜜黄油味)', safetyLevel: 4, safetyLevelText: '4 级 (需警惕)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 8, riskCount: 2, calories: 519, imageUrl: breakfastImage },
|
||||||
|
{ id: 'p5', category: 'prepackaged', brand: '某品牌', name: '辣条', safetyLevel: 5, safetyLevelText: '5 级 (风险较高)', nutritionLevel: '低', nutritionLevelClass: 'score-low', additiveCount: 15, riskCount: 4, calories: 600, imageUrl: dessertsImage },
|
||||||
|
],
|
||||||
|
additive: [
|
||||||
|
{ id: 'a1', category: 'additive', name: '维生素 C (抗坏血酸)', safetyLevel: 1, safetyLevelText: 'A1 最高安全等级', functionTag: '抗氧化剂', riskWarning: '一般认为安全,但极高剂量可能导致肠胃不适。', imageUrl: breakfastImage },
|
||||||
|
{ id: 'a2', category: 'additive', name: '苯甲酸钠', safetyLevel: 2, safetyLevelText: 'A2 较安全', functionTag: '防腐剂', riskWarning: '在特定条件下可能与维生素C反应生成微量苯,长期过量摄入有潜在风险。', imageUrl: dessertsImage },
|
||||||
|
{ id: 'a3', category: 'additive', name: '诱惑红', safetyLevel: 3, safetyLevelText: 'B1 需警惕', functionTag: '色素', riskWarning: '可能引起儿童多动症等过敏反应,在多国被限制或禁止使用。', imageUrl: dessertsImage },
|
||||||
|
{ id: 'a4', category: 'additive', name: '糖醇类甜味剂', safetyLevel: 4, safetyLevelText: 'B2 风险未知', functionTag: '新型甜味剂', riskWarning: '长期摄入对健康的影响尚不完全明确,部分人群可能出现肠胃不适。', imageUrl: dessertsImage },
|
||||||
|
{ id: 'a5', category: 'additive', name: '黄原胶 (特定场景)', safetyLevel: 5, safetyLevelText: 'C2 严格限制', functionTag: '增稠剂', riskWarning: '仅限在特定食品(如婴幼儿食品)中严格限量使用。', imageUrl: dessertsImage },
|
||||||
|
{ id: 'a6', category: 'additive', name: '苏丹红', safetyLevel: 6, safetyLevelText: 'C1 禁止使用', functionTag: '工业染料', riskWarning: '具有潜在致癌性,严禁在食品中添加。', imageUrl: dessertsImage },
|
||||||
|
],
|
||||||
|
material: [
|
||||||
|
{ id: 'm1', category: 'material', name: '西兰花', family: '十字花科蔬菜', nutrientTags: [{text: '富含维生素C'}, {text: '高膳食纤维'}], imageUrl: breakfastImage },
|
||||||
|
{ id: 'm2', category: 'material', name: '番茄', family: '茄科植物', nutrientTags: [{text: '富含番茄红素', class: 'tomato'}, {text: '抗氧化', class: 'antioxidant'}], imageUrl: tomatoesImage },
|
||||||
|
{ id: 'm3', category: 'material', name: '酱油', family: '传统酿造调味品', nutrientTags: [{text: '提鲜', class: 'umami'}, {text: '高钠', class: 'high-sodium'}], imageUrl: dessertsImage },
|
||||||
|
],
|
||||||
|
recipe: [
|
||||||
|
{ id: 'r1', category: 'recipe', title: '丰盛的全麦贝果早餐', summary: '一个简单易学的食谱,使用最少的添加剂,为家人制作美味又健康的面包。', mediaType: 'image', imageUrl: breakfastImage },
|
||||||
|
{ id: 'r2', category: 'recipe', title: '一分钟学会做番茄炒蛋', summary: '看视频,轻松掌握这道国民家常菜的精髓,厨房新手也能零失败。', mediaType: 'video', imageUrl: tomatoesImage }
|
||||||
|
],
|
||||||
|
article: [
|
||||||
|
{ id: 'ar1', category: 'article', title: '牛奶过敏的宝宝应该怎么办?', summary: '本文将详细介绍牛奶过敏的症状、原因以及应对方法...', mediaType: 'image', imageUrl: breakfastImage },
|
||||||
|
{ id: 'ar2', category: 'article', title: '如何科学地给宝宝添加辅食', summary: '辅食添加的顺序、种类、注意事项,专家视频为您一次讲清。', mediaType: 'video', imageUrl: breakfastImage }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Component Mapping
|
||||||
|
const componentMap: Record<string, Component> = {
|
||||||
|
all: AllResultsList,
|
||||||
|
prepackaged: PrepackagedList,
|
||||||
|
additive: AdditiveList,
|
||||||
|
material: MaterialList,
|
||||||
|
recipe: RecipeList,
|
||||||
|
article: ArticleList,
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredResults = computed(() => {
|
||||||
|
switch (activeTab.value) {
|
||||||
|
case 'prepackaged':
|
||||||
|
return mockData.prepackaged;
|
||||||
|
case 'additive':
|
||||||
|
return mockData.additive;
|
||||||
|
case 'material':
|
||||||
|
return mockData.material;
|
||||||
|
case 'recipe':
|
||||||
|
return mockData.recipe;
|
||||||
|
case 'article':
|
||||||
|
return mockData.article;
|
||||||
|
case 'all':
|
||||||
|
return [
|
||||||
|
...mockData.prepackaged,
|
||||||
|
...mockData.additive,
|
||||||
|
...mockData.material,
|
||||||
|
...mockData.recipe,
|
||||||
|
...mockData.article
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Filter and Sort State
|
||||||
|
const filters = ref([
|
||||||
|
{ id: 'comprehensive', name: '综合' },
|
||||||
|
{ id: 'safety', name: '安全评级' },
|
||||||
|
{ id: 'nutrition', name: '营养评级' },
|
||||||
|
{ id: 'latest', name: '最新发布' },
|
||||||
|
{ id: 'protein', name: '蛋白质' },
|
||||||
|
{ id: 'fat', name: '脂肪' },
|
||||||
|
{ id: 'carbs', name: '碳水化合物' },
|
||||||
|
]);
|
||||||
|
const activeFilter = ref('comprehensive');
|
||||||
|
const sortOrder = ref('desc'); // 'desc' or 'asc'
|
||||||
|
|
||||||
|
// Panel State
|
||||||
|
const isPanelOpen = ref(false);
|
||||||
|
const tempActiveFilter = ref(activeFilter.value);
|
||||||
|
const tempSortOrder = ref(sortOrder.value);
|
||||||
|
|
||||||
|
const activeFilterName = computed(() => {
|
||||||
|
const found = filters.value.find(f => f.id === activeFilter.value);
|
||||||
|
return found ? found.name : '综合';
|
||||||
|
});
|
||||||
|
|
||||||
|
const openFilterPanel = () => {
|
||||||
|
tempActiveFilter.value = activeFilter.value;
|
||||||
|
tempSortOrder.value = sortOrder.value;
|
||||||
|
isPanelOpen.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelFilters = () => {
|
||||||
|
isPanelOpen.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmFilters = () => {
|
||||||
|
activeFilter.value = tempActiveFilter.value;
|
||||||
|
sortOrder.value = tempSortOrder.value;
|
||||||
|
isPanelOpen.value = false;
|
||||||
|
// Here you would typically re-fetch data with new filters
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabs = ref([
|
||||||
|
{ id: 'all', name: '全部' },
|
||||||
|
{ id: 'prepackaged', name: '预包装' },
|
||||||
|
{ id: 'additive', name: '添加剂' },
|
||||||
|
{ id: 'material', name: '食材' },
|
||||||
|
{ id: 'recipe', name: '食谱' },
|
||||||
|
{ id: 'article', name: '资讯' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
searchQuery.value = (route.query.q as string) || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const goBack = () => {
|
||||||
|
router.push({ name: 'home' });
|
||||||
|
};
|
||||||
|
|
||||||
|
const performSearch = () => {
|
||||||
|
// In a real app, this would re-trigger the search
|
||||||
|
console.log('Searching for:', searchQuery.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToResult = (item: any) => {
|
||||||
|
router.push({ name: 'result', params: { id: item.id } });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Keep only layout and container styles */
|
||||||
|
.search-result-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative; /* Restore positioning context */
|
||||||
|
}
|
||||||
|
.top-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px 12px 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
/* position: fixed; Removed */
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
.back-btn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 8px; /* Add some space to the search bar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.search-bar-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 25px;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border: 1px solid #07C160;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar-container input {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
width: 1px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button {
|
||||||
|
color: #07C160;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding: 10px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
/* position: fixed; Removed */
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
.tabs button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #6b7280;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
}
|
||||||
|
.tabs button.active {
|
||||||
|
color: #22c55e;
|
||||||
|
border-bottom-color: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-bar {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
z-index: 8;
|
||||||
|
position: relative; /* Become a positioning context for the panel */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggle-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-toggle-btn .arrow {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-top: 5px solid #666;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel-overlay {
|
||||||
|
position: absolute; /* Changed from fixed to be contained within the view */
|
||||||
|
top: 100%; /* Position right below the filters-bar */
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-panel {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px 15px;
|
||||||
|
border-bottom-left-radius: 16px;
|
||||||
|
border-bottom-right-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags button {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border: 1px solid #f7f7f7;
|
||||||
|
color: #333;
|
||||||
|
padding: 6px 14px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags button.active {
|
||||||
|
background-color: #e8f8f1;
|
||||||
|
border-color: #07c160;
|
||||||
|
color: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 22px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-actions button {
|
||||||
|
flex-grow: 0;
|
||||||
|
width: 100px;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border: 1px solid #f7f7f7;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-confirm {
|
||||||
|
background-color: #07c160;
|
||||||
|
border: 1px solid #07c160;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-list {
|
||||||
|
flex-grow: 1; /* Allow this container to grow and fill available space */
|
||||||
|
overflow-y: auto; /* Enable scrolling ONLY for this container */
|
||||||
|
padding: 0; /* Remove padding from the container */
|
||||||
|
min-height: 0; /* Fix for flexbox overflow issue, preventing parent scrolling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.safety-level-description {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 10px 20px; /* Add horizontal padding */
|
||||||
|
margin-bottom: 10px; /* Add margin to separate from the list */
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
/* Safety Level Colors */
|
||||||
|
.safety-level-1 { color: #07C160; font-weight: bold; }
|
||||||
|
.safety-level-2 { color: #90ee90; font-weight: bold; }
|
||||||
|
.safety-level-3 { color: #1E90FF; font-weight: bold; }
|
||||||
|
.safety-level-4 { color: #f2d025; font-weight: bold; }
|
||||||
|
.safety-level-5 { color: #FF7F50; font-weight: bold; }
|
||||||
|
.safety-level-6 { color: #ff0000; font-weight: bold; }
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user