fix: convert apps/website from submodule to regular directory

This commit is contained in:
yeuimu
2026-07-16 01:42:45 +08:00
parent f12ae12ad3
commit 9595030625
341 changed files with 50808 additions and 1 deletions
@@ -0,0 +1,148 @@
<script setup lang="ts">
import { computed } from 'vue';
import type { Product } from '~/composables/useProductCenter';
import { sortTagsByGroup } from '~/composables/useProductCenter';
const props = defineProps<{
product: Product;
showCountry?: boolean;
}>();
const sortedTags = computed(() => sortTagsByGroup(props.product.tags).slice(0, 4));
function formatPrice(price: string | null): string {
if (!price) return '—';
const num = Number(price);
if (!Number.isFinite(num)) return '—';
return `¥${num.toFixed(2)}`;
}
</script>
<template>
<article class="product-card">
<div class="product-media">
<img
v-if="product.image"
:src="product.image"
:alt="product.name"
class="product-img"
loading="lazy"
/>
<div v-else class="placeholder">
<i class="fa-solid fa-image"></i>
</div>
</div>
<div class="product-info">
<h3 class="product-title" :title="product.name">{{ product.name }}</h3>
<div v-if="sortedTags.length > 0 || showCountry" class="chips">
<span
v-for="tag in sortedTags"
:key="tag.id"
class="chip"
:style="{ color: tag.fontColor || tag.color || '#ff6a00', background: (tag.color || '#ff6a00') + '4d', borderColor: (tag.color || '#ff6a00') + '99' }"
>
{{ tag.name }}
</span>
<span v-if="showCountry" class="chip country-chip">
<img
v-if="product.country.icon"
:src="product.country.icon"
:alt="product.country.name"
class="country-flag"
/>
{{ product.country.name }}
</span>
</div>
<div class="price">{{ formatPrice(product.price) }}</div>
</div>
</article>
</template>
<style scoped>
.product-card {
overflow: hidden;
border-radius: 12px;
background: #fff;
border: 1px solid #eee;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.product-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 28px rgba(17, 17, 17, 0.06);
}
.product-media {
display: grid;
place-items: center;
width: 100%;
aspect-ratio: 1 / 1;
background: linear-gradient(180deg, #f8f8f8, #f0f0f0);
overflow: hidden;
}
.product-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.placeholder {
color: #ccc;
font-size: 32px;
}
.product-info {
padding: 14px;
}
.product-title {
margin: 0 0 12px;
font-size: 19px;
line-height: 1.4;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: #111;
}
.chips {
display: flex;
gap: 6px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.chip {
display: inline-flex;
align-items: center;
height: 24px;
padding: 0 8px;
border-radius: 5px;
color: #ff6a00;
background: #fff2e8;
font-size: 14px;
white-space: nowrap;
}
.country-chip {
color: #555;
background: #f3f4f6;
font-size: 14px;
}
.country-flag {
width: 16px;
height: 12px;
border-radius: 2px;
object-fit: cover;
margin-right: 4px;
}
.price {
color: #222;
font-size: 20px;
font-weight: 700;
}
</style>