|
|
@@ -125,35 +125,45 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- Selection List (Bottom on mobile, left on desktop) -->
|
|
|
- <div class="lg:col-span-5 lg:order-1 space-y-4 max-h-[500px] lg:max-h-[600px] overflow-y-auto pr-2 custom-scrollbar">
|
|
|
- <button
|
|
|
- v-for="scan in scans"
|
|
|
- :key="scan.id"
|
|
|
- @click="activeScan = scan; showRealPhoto = false"
|
|
|
- class="w-full text-left p-4 rounded-2xl border transition-all duration-300 flex gap-4 bg-card/20 hover:bg-card/45"
|
|
|
- :class="activeScan.id === scan.id ? 'border-primary/50 bg-primary/5 shadow-lg shadow-primary/5' : 'border-border/35 hover:border-border/60'"
|
|
|
- >
|
|
|
- <!-- Thumbnail -->
|
|
|
- <div class="w-20 h-20 rounded-xl bg-black/60 border border-white/5 overflow-hidden flex-shrink-0 relative">
|
|
|
- <img
|
|
|
- :src="`${RESOURCES_BASE_URL}/uploads/scans/${scan.folder_name}/thumbnail.webp`"
|
|
|
- class="w-full h-full object-cover"
|
|
|
- alt="Scan thumbnail"
|
|
|
- />
|
|
|
- </div>
|
|
|
- <!-- Text -->
|
|
|
- <div class="flex-1 min-w-0 flex flex-col justify-between py-1">
|
|
|
- <div>
|
|
|
- <h4 class="font-bold text-sm tracking-tight text-white truncate">{{ getLocalizedTitle(scan) }}</h4>
|
|
|
- <p class="text-xs text-muted-foreground leading-snug line-clamp-2 mt-1">{{ getLocalizedDescription(scan) }}</p>
|
|
|
+ <div class="lg:col-span-5 lg:order-1 space-y-4">
|
|
|
+ <div class="max-h-[420px] lg:max-h-[500px] overflow-y-auto pr-2 custom-scrollbar space-y-4">
|
|
|
+ <button
|
|
|
+ v-for="scan in scans"
|
|
|
+ :key="scan.id"
|
|
|
+ @click="activeScan = scan; showRealPhoto = false"
|
|
|
+ class="w-full text-left p-4 rounded-2xl border transition-all duration-300 flex gap-4 bg-card/20 hover:bg-card/45"
|
|
|
+ :class="activeScan.id === scan.id ? 'border-primary/50 bg-primary/5 shadow-lg shadow-primary/5' : 'border-border/35 hover:border-border/60'"
|
|
|
+ >
|
|
|
+ <!-- Thumbnail -->
|
|
|
+ <div class="w-20 h-20 rounded-xl bg-black/60 border border-white/5 overflow-hidden flex-shrink-0 relative">
|
|
|
+ <img
|
|
|
+ :src="`${RESOURCES_BASE_URL}/uploads/scans/${scan.folder_name}/thumbnail.webp`"
|
|
|
+ class="w-full h-full object-cover"
|
|
|
+ alt="Scan thumbnail"
|
|
|
+ />
|
|
|
</div>
|
|
|
- <div class="text-[10px] text-muted-foreground flex items-center gap-1 mt-2">
|
|
|
- <span class="font-mono text-primary/70">{{ scan.frames_count }} frames</span>
|
|
|
- <span>•</span>
|
|
|
- <span>Interactive</span>
|
|
|
+ <!-- Text -->
|
|
|
+ <div class="flex-1 min-w-0 flex flex-col justify-between py-1">
|
|
|
+ <div>
|
|
|
+ <h4 class="font-bold text-sm tracking-tight text-white truncate">{{ getLocalizedTitle(scan) }}</h4>
|
|
|
+ <p class="text-xs text-muted-foreground leading-snug line-clamp-2 mt-1">{{ getLocalizedDescription(scan) }}</p>
|
|
|
+ </div>
|
|
|
+ <div class="text-[10px] text-muted-foreground flex items-center gap-1 mt-2">
|
|
|
+ <span class="font-mono text-primary/70">{{ scan.frames_count }} frames</span>
|
|
|
+ <span>•</span>
|
|
|
+ <span>Interactive</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- </button>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <!-- Pagination -->
|
|
|
+ <div v-if="total > size" class="flex items-center justify-center gap-2 pt-2">
|
|
|
+ <button v-for="p in Math.ceil(total / size)" :key="p"
|
|
|
+ @click="changePage(p)"
|
|
|
+ :class="['w-7 h-7 rounded-lg font-bold text-[10px] transition-all p-0 flex items-center justify-center border', page === p ? 'bg-primary text-black border-primary shadow-glow' : 'bg-card border border-border/50 text-muted-foreground hover:border-primary/50']">
|
|
|
+ {{ p }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</section>
|
|
|
@@ -217,6 +227,9 @@ const scans = ref<any[]>([]);
|
|
|
const activeScan = ref<any>(null);
|
|
|
const showRealPhoto = ref(false);
|
|
|
const loading = ref(true);
|
|
|
+const page = ref(1);
|
|
|
+const total = ref(0);
|
|
|
+const size = ref(10);
|
|
|
|
|
|
const getIcon = (step: string) => {
|
|
|
switch (step) {
|
|
|
@@ -243,18 +256,34 @@ const getLocalizedDescription = (scan: any) => {
|
|
|
return scan.description_en;
|
|
|
};
|
|
|
|
|
|
-onMounted(async () => {
|
|
|
+const fetchScansList = async () => {
|
|
|
+ loading.value = true;
|
|
|
try {
|
|
|
- const fetchedScans = await getPublicScans();
|
|
|
- scans.value = fetchedScans || [];
|
|
|
+ const fetched = await getPublicScans(page.value, size.value);
|
|
|
+ scans.value = fetched.scans || [];
|
|
|
+ total.value = fetched.total || 0;
|
|
|
if (scans.value.length > 0) {
|
|
|
- activeScan.value = scans.value[0];
|
|
|
+ const exists = scans.value.some(s => s.id === activeScan.value?.id);
|
|
|
+ if (!exists) {
|
|
|
+ activeScan.value = scans.value[0];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ activeScan.value = null;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("Failed to load public scans portfolio:", error);
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
+};
|
|
|
+
|
|
|
+const changePage = (p: number) => {
|
|
|
+ page.value = p;
|
|
|
+ fetchScansList();
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ fetchScansList();
|
|
|
});
|
|
|
|
|
|
useHead({
|