wip: cart
This commit is contained in:
93
spa/src/components/DevConsoleOverlay.vue
Normal file
93
spa/src/components/DevConsoleOverlay.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="logs.length"
|
||||
ref="logContainer"
|
||||
class="fixed bottom-0 left-0 right-0 max-h-60 overflow-y-auto bg-white text-sm font-mono border-t border-gray-300 shadow-lg z-[9999] p-4 space-y-2"
|
||||
>
|
||||
<div
|
||||
v-for="(log, idx) in logs"
|
||||
:key="idx"
|
||||
:class="colorClass(log.type)"
|
||||
class="whitespace-pre-wrap"
|
||||
>
|
||||
[{{ log.type.toUpperCase() }}] {{ log.message }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, nextTick} from 'vue'
|
||||
|
||||
const logs = ref([])
|
||||
const logContainer = ref(null)
|
||||
|
||||
function pushLog(type, input) {
|
||||
let message = ''
|
||||
let details = ''
|
||||
|
||||
if (input instanceof Error) {
|
||||
message = input.message
|
||||
details = input.stack
|
||||
} else if (typeof input === 'string') {
|
||||
message = input
|
||||
} else {
|
||||
try {
|
||||
message = JSON.stringify(input, null, 2)
|
||||
} catch {
|
||||
message = String(input)
|
||||
}
|
||||
}
|
||||
|
||||
logs.value.push({ type, message, details })
|
||||
|
||||
nextTick(() => {
|
||||
const el = logContainer.value
|
||||
if (el) el.scrollTop = el.scrollHeight
|
||||
});
|
||||
}
|
||||
|
||||
function colorClass(type) {
|
||||
switch (type) {
|
||||
case 'error': return 'text-red-700'
|
||||
case 'warn': return 'text-yellow-700'
|
||||
case 'info': return 'text-blue-700'
|
||||
default: return 'text-gray-800'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (import.meta.env.PROD) return
|
||||
|
||||
// Backup originals
|
||||
const orig = {
|
||||
log: console.log,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
info: console.info,
|
||||
}
|
||||
|
||||
Object.entries(orig).forEach(([type, fn]) => {
|
||||
console[type] = (...args) => {
|
||||
pushLog(type, args.map(toText).join(' '))
|
||||
fn.apply(console, args)
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('error', (e) => {
|
||||
pushLog('error', e.error?.stack || `${e.message} at ${e.filename}:${e.lineno}:${e.colno}`)
|
||||
})
|
||||
|
||||
window.addEventListener('unhandledrejection', (e) => {
|
||||
pushLog('error', e.reason?.stack || e.reason?.message || String(e.reason))
|
||||
})
|
||||
})
|
||||
|
||||
function toText(v) {
|
||||
try {
|
||||
if (typeof v === 'string') return v
|
||||
return JSON.stringify(v, null, 2)
|
||||
} catch {
|
||||
return String(v)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
74
spa/src/components/ProductImageSwiper.vue
Normal file
74
spa/src/components/ProductImageSwiper.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<swiper
|
||||
:style="{
|
||||
'--swiper-navigation-color': '#fff',
|
||||
'--swiper-pagination-color': '#fff',
|
||||
}"
|
||||
:lazy="true"
|
||||
:pagination="pagination"
|
||||
:navigation="true"
|
||||
:modules="modules"
|
||||
class="mySwiper"
|
||||
>
|
||||
<swiper-slide v-for="image in images">
|
||||
<img
|
||||
:src="image.url"
|
||||
:alt="image.alt"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div
|
||||
class="swiper-lazy-preloader swiper-lazy-preloader-white"
|
||||
></div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Swiper, SwiperSlide} from 'swiper/vue';
|
||||
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/pagination';
|
||||
|
||||
import {Pagination} from 'swiper/modules';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Swiper,
|
||||
SwiperSlide,
|
||||
},
|
||||
|
||||
props: {
|
||||
images: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
}
|
||||
},
|
||||
|
||||
setup() {
|
||||
return {
|
||||
pagination: {
|
||||
clickable: true,
|
||||
dynamicBullets: true,
|
||||
},
|
||||
modules: [Pagination],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.product-swiper {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.swiper-slide {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user