- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
25 lines
2.0 KiB
Vue
25 lines
2.0 KiB
Vue
<script setup lang="ts">
|
||
import type { Product } from '~/composables/useProductCenter';
|
||
defineProps<{ products: Product[]; loading: boolean; showCountry?: boolean }>();
|
||
const emit = defineEmits<{ 'clear-filters': []; 'view-all': [] }>();
|
||
</script>
|
||
|
||
<template>
|
||
<div v-if="loading" class="product-grid"><ProductCardSkeleton v-for="index in 8" :key="index" /></div>
|
||
<div v-else-if="!products.length" class="empty">
|
||
<img src="/product-empty.svg" alt="" />
|
||
<h2>未找到相关商品</h2>
|
||
<p>当前筛选条件下暂无匹配产品,可尝试调整关键词或减少筛选条件。</p>
|
||
<div class="empty-actions"><button type="button" class="view-all" @click="emit('view-all')">查看全部商品</button><button type="button" class="clear" @click="emit('clear-filters')">清空筛选</button></div>
|
||
</div>
|
||
<div v-else class="product-grid"><ProductCard v-for="product in products" :key="product.id" :product="product" :show-country="showCountry" /></div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px 24px; }
|
||
.empty { display: flex; flex-direction: column; align-items: center; min-height: 600px; padding-top: 86px; color: rgba(0,0,0,.6); text-align: center; }.empty img { width: 260px; height: 260px; object-fit: contain; }.empty h2 { margin: 12px 0 10px; color: #000; font-size: 22px; font-weight: 600; }.empty p { margin: 0; font-size: 14px; }.empty-actions { display: flex; gap: 24px; margin-top: 34px; }.empty-actions button { height: 38px; padding: 0 16px; border: 0; border-radius: 60px; font-size: 16px; cursor: pointer; }.empty-actions .view-all { color: #ff6800; background: rgba(255,104,0,.06); }.empty-actions .clear { min-width: 128px; color: #fff; background: #ff6800; }
|
||
@media (max-width: 1180px) { .product-grid { gap: 16px; } }
|
||
@media (max-width: 760px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
||
@media (max-width: 430px) { .product-grid { grid-template-columns: minmax(0, 1fr); } }
|
||
</style>
|