food-select.vue 7.27 KB
<template>
  <view class="page">
    <view class="header-hero">
      <view class="top-bar">
        <view class="top-left" @click="goBack">
          <AppIcon name="chevronLeft" size="sm" color="white" />
        </view>
        <view class="top-center">
          <text class="page-title">{{ getLabelTypeName(labelType) }}</text>
          <text class="page-sub">{{ t('labels.selectFood') }}</text>
        </view>
        <view class="top-right" @click="isMenuOpen = true">
          <AppIcon name="menu" size="sm" color="white" />
        </view>
      </view>
    </view>

    <view class="search-bar">
      <view class="search-icon-wrap">
        <AppIcon name="search" size="sm" color="gray" />
      </view>
      <input
        v-model="searchTerm"
        class="search-input"
        :placeholder="t('labels.searchFood')"
        placeholder-class="placeholder"
      />
    </view>

    <view class="content">
      <view v-if="filteredFoods.length === 0" class="empty">
        <view class="empty-icon">
          <AppIcon name="search" size="lg" color="gray" />
        </view>
        <text class="empty-title">{{ t('labels.noFoodFound') }}</text>
        <text class="empty-desc">{{ t('labels.noFoodDesc') }}</text>
      </view>
      <view v-else class="categories">
        <view v-for="cat in categoryKeys" :key="cat" class="category">
          <text class="category-title">{{ t(cat) }}</text>
          <view class="food-grid">
            <view
              v-for="food in getFoodsByCategory(cat)"
              :key="food.id"
              class="food-card"
              @click="goPreview(food.id)"
            >
              <view class="food-img-wrap">
                <image :src="food.image" class="food-img" mode="aspectFill" />
              </view>
              <text class="food-name">{{ t(food.nameKey) }}</text>
              <text class="food-desc">{{ t(food.descKey) }}</text>
            </view>
          </view>
        </view>
      </view>
    </view>

    <SideMenu v-model="isMenuOpen" />
  </view>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import AppIcon from '../../components/AppIcon.vue'
import SideMenu from '../../components/SideMenu.vue'

const { t } = useI18n()
const labelType = ref('')
const searchTerm = ref('')
const isMenuOpen = ref(false)

const allFoods = [
  { id: 'food-001', nameKey: 'food.chickenBreast', descKey: 'food.chickenBreast.desc', image: 'https://images.unsplash.com/photo-1532550907401-a500c9a57435?w=400', categoryKey: 'category.meat' },
  { id: 'food-002', nameKey: 'food.caesarSalad', descKey: 'food.caesarSalad.desc', image: 'https://images.unsplash.com/photo-1546793665-c74683f339c1?w=400', categoryKey: 'category.salads' },
  { id: 'food-004', nameKey: 'food.beefPatties', descKey: 'food.beefPatties.desc', image: 'https://images.unsplash.com/photo-1607623488235-e2e6794c30b5?w=400', categoryKey: 'category.meat' },
  { id: 'food-005', nameKey: 'food.marinaraSauce', descKey: 'food.marinaraSauce.desc', image: 'https://images.unsplash.com/photo-1621996346565-e3dbc646d9a9?w=400', categoryKey: 'category.sauces' },
  { id: 'food-007', nameKey: 'food.brownie', descKey: 'food.brownie.desc', image: 'https://images.unsplash.com/photo-1606313564200-e75d5e30476c?w=400', categoryKey: 'category.desserts' },
  { id: 'food-008', nameKey: 'food.shrimpPasta', descKey: 'food.shrimpPasta.desc', image: 'https://images.unsplash.com/photo-1563379926898-05f4575a45d8?w=400', categoryKey: 'category.prepared' },
  { id: 'food-009', nameKey: 'food.iceCream', descKey: 'food.iceCream.desc', image: 'https://images.unsplash.com/photo-1563805042-7684c019e1cb?w=400', categoryKey: 'category.frozen' },
]

const filteredFoods = computed(() => {
  const s = searchTerm.value.toLowerCase()
  if (!s) return allFoods
  return allFoods.filter((f) => {
    const n = t(f.nameKey).toLowerCase()
    const c = t(f.categoryKey).toLowerCase()
    return n.includes(s) || c.includes(s)
  })
})

const categoryKeys = computed(() => Array.from(new Set(filteredFoods.value.map((f) => f.categoryKey))))
const getFoodsByCategory = (cat: string) => filteredFoods.value.filter((f) => f.categoryKey === cat)

onLoad((opts: any) => {
  labelType.value = (opts && opts.labelType) || 'nutrition'
})

const goBack = () => uni.navigateBack()
const getLabelTypeName = (type: string) => t('labelType.' + type + '.name')
const goPreview = (foodId: string) => {
  uni.navigateTo({ url: '/pages/labels/preview?labelType=' + labelType.value + '&foodId=' + foodId })
}
</script>

<style scoped>
.page { min-height: 100vh; background: #f9fafb; }

.header-hero {
  background: linear-gradient(135deg, var(--theme-primary), var(--theme-primary-dark));
  padding: 48rpx 32rpx 24rpx;
}
.top-bar { height: 96rpx; display: flex; align-items: center; justify-content: space-between; }
.top-left, .top-right { width: 64rpx; height: 64rpx; border-radius: 999rpx; background: rgba(255,255,255,0.15); display: flex; align-items: center; justify-content: center; }
.top-center { flex: 1; text-align: center; }
.page-title { font-size: 34rpx; font-weight: 600; color: #fff; display: block; }
.page-sub { font-size: 24rpx; color: rgba(255,255,255,0.85); }

.search-bar {
  position: relative;
  display: flex;
  align-items: center;
  padding: 24rpx 48rpx;
  background: #fff;
  border-bottom: 1rpx solid #e5e7eb;
}
.search-icon-wrap { position: absolute; left: 72rpx; z-index: 1; }
.search-input {
  flex: 1;
  height: 80rpx;
  padding-left: 72rpx;
  padding-right: 24rpx;
  background: #f3f4f6;
  border-radius: 16rpx;
  font-size: 28rpx;
}

.content {
  padding: 48rpx;
  max-width: 960rpx;
  margin: 0 auto;
}

.empty { display: flex; flex-direction: column; align-items: center; padding: 96rpx 48rpx; }
.empty-icon { width: 160rpx; height: 160rpx; background: #f3f4f6; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-bottom: 32rpx; }
.empty-title { font-size: 32rpx; font-weight: 600; color: #111827; margin-bottom: 16rpx; }
.empty-desc { font-size: 28rpx; color: #6b7280; text-align: center; }

.category { margin-bottom: 48rpx; }
.category-title { font-size: 28rpx; font-weight: 600; color: #111827; display: block; margin-bottom: 16rpx; text-transform: uppercase; letter-spacing: 0.5rpx; }

.food-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 16rpx;
  row-gap: 16rpx;
}

.food-card {
  background: #fff;
  padding: 16rpx;
  border-radius: 16rpx;
  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
  cursor: pointer;
}

.food-card:active { background: #fafafa; }

.food-img-wrap {
  width: 100%;
  position: relative;
  padding-top: 100%;
  border-radius: 10rpx;
  background: #f3f4f6;
  margin-bottom: 16rpx;
  overflow: hidden;
}

.food-img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; }
.food-name { font-size: 24rpx; font-weight: 600; color: #111827; display: block; margin-bottom: 4rpx; overflow: hidden; text-overflow: ellipsis; }
.food-desc { font-size: 22rpx; color: #6b7280; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.4; }

@media (min-width: 768px) {
  .content { padding: 48rpx 64rpx; }
  .food-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 24rpx; row-gap: 24rpx; }
  .food-card { padding: 20rpx; }
}
</style>