333 lines
10 KiB
Vue
333 lines
10 KiB
Vue
<template>
|
|
<Transition name="overlay">
|
|
<div v-if="visible" class="sell-overlay">
|
|
<!-- Header -->
|
|
<div class="sell-header">
|
|
<div class="header-left">
|
|
<div class="header-icon">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
|
|
</svg>
|
|
</div>
|
|
<h2>{{ dealerLabel }}</h2>
|
|
</div>
|
|
<button class="close-btn" @click="close">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="sell-body">
|
|
<!-- Sell Progress Bar (active sell timer) -->
|
|
<div v-if="sellProgress && !sellProgress.isComplete" class="sell-progress-bar">
|
|
<div class="progress-bar-left">
|
|
<img :src="getItemImage(sellProgress.itemName)" @error="onImgError" class="progress-bar-img" />
|
|
</div>
|
|
<div class="progress-bar-center">
|
|
<div class="progress-bar-label">
|
|
<span class="progress-bar-name">{{ sellProgress.itemLabel }} verkaufen</span>
|
|
<span class="progress-bar-money">${{ sellProgress.totalMoney }}</span>
|
|
</div>
|
|
<div class="progress-bar-track">
|
|
<div class="progress-bar-fill" :style="{ width: progressPercent + '%' }"></div>
|
|
</div>
|
|
</div>
|
|
<div class="progress-bar-time">
|
|
{{ formatTime(remainingTime) }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Collect Bar (money ready to pick up) -->
|
|
<div v-if="pendingCollect" class="collect-bar">
|
|
<div class="collect-bar-top">
|
|
<img :src="getItemImage(pendingCollect.itemName)" @error="onImgError" class="collect-bar-img" />
|
|
<div class="collect-bar-info">
|
|
<span class="collect-bar-name">{{ pendingCollect.itemLabel }} - Geld bereit!</span>
|
|
<span class="collect-bar-amount">{{ pendingCollect.amount }}x verkauft</span>
|
|
</div>
|
|
</div>
|
|
<button class="collect-btn" @click="collectMoney">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
|
|
${{ pendingCollect.totalMoney }} abholen
|
|
</button>
|
|
</div>
|
|
|
|
<Transition name="fade" mode="out-in">
|
|
<!-- Landing Tile -->
|
|
<div v-if="showLanding" key="landing" class="sell-landing">
|
|
<div
|
|
class="dealer-tile"
|
|
:style="tileStyle"
|
|
@click="enterItems"
|
|
>
|
|
<div class="dealer-tile-bg" :style="tileBgStyle"></div>
|
|
<div v-if="!dealerImage" class="dealer-tile-gradient"></div>
|
|
<div v-if="!dealerImage" class="dealer-tile-fallback-icon" :style="{ color: dealerColor || 'var(--accent)' }">
|
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
|
|
</svg>
|
|
</div>
|
|
<div class="dealer-tile-content">
|
|
<div class="dealer-tile-icon" :style="{ color: dealerColor || 'var(--accent)' }">
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>
|
|
</svg>
|
|
</div>
|
|
<span class="dealer-tile-label">{{ dealerLabel }}</span>
|
|
<span class="dealer-tile-count">{{ prices.length }} {{ prices.length === 1 ? 'Item' : 'Items' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sell Menu -->
|
|
<div v-else key="menu">
|
|
<SellMenu
|
|
:prices="prices"
|
|
:playerItems="playerItems"
|
|
:selectedItem="selectedItem"
|
|
:isSelling="!!sellProgress && !sellProgress.isComplete"
|
|
@select="selectedItem = $event"
|
|
@sell="onSell"
|
|
@backToLanding="backToLanding"
|
|
/>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
import SellMenu from './components/SellMenu.vue'
|
|
|
|
const visible = ref(false)
|
|
const dealerLabel = ref('')
|
|
const dealerId = ref('')
|
|
const dealerImage = ref('')
|
|
const dealerColor = ref('')
|
|
const showLanding = ref(true)
|
|
const prices = ref([])
|
|
const playerItems = ref([])
|
|
const selectedItem = ref(null)
|
|
const sellProgress = ref(null)
|
|
const pendingCollect = ref(null)
|
|
|
|
let progressTimer = null
|
|
|
|
const tileStyle = computed(() => {
|
|
const vars = {}
|
|
if (dealerColor.value) {
|
|
vars['--tile-color'] = dealerColor.value
|
|
vars['--tile-glow'] = dealerColor.value + '40'
|
|
}
|
|
return vars
|
|
})
|
|
|
|
const tileBgStyle = computed(() => {
|
|
if (dealerImage.value) {
|
|
return { backgroundImage: `url(${dealerImage.value})` }
|
|
}
|
|
return {}
|
|
})
|
|
|
|
function enterItems() {
|
|
showLanding.value = false
|
|
}
|
|
|
|
function backToLanding() {
|
|
showLanding.value = true
|
|
selectedItem.value = null
|
|
}
|
|
|
|
const progressPercent = computed(() => {
|
|
if (!sellProgress.value || !sellProgress.value.totalDuration) return 0
|
|
const elapsed = sellProgress.value.elapsed || 0
|
|
return Math.min((elapsed / sellProgress.value.totalDuration) * 100, 100)
|
|
})
|
|
|
|
const remainingTime = computed(() => {
|
|
if (!sellProgress.value) return 0
|
|
return Math.max(0, sellProgress.value.totalDuration - (sellProgress.value.elapsed || 0))
|
|
})
|
|
|
|
function formatTime(seconds) {
|
|
if (!seconds || seconds <= 0) return '0s'
|
|
const m = Math.floor(seconds / 60)
|
|
const s = Math.ceil(seconds % 60)
|
|
if (m > 0) return m + 'm ' + s + 's'
|
|
return s + 's'
|
|
}
|
|
|
|
function startProgressTimer() {
|
|
if (progressTimer) clearInterval(progressTimer)
|
|
progressTimer = setInterval(() => {
|
|
if (!sellProgress.value) { clearInterval(progressTimer); return }
|
|
const sp = sellProgress.value
|
|
const newElapsed = (sp.elapsed || 0) + 0.5
|
|
const isComplete = newElapsed >= sp.totalDuration
|
|
sellProgress.value = { ...sp, elapsed: newElapsed, isComplete }
|
|
if (isComplete) {
|
|
clearInterval(progressTimer)
|
|
progressTimer = null
|
|
// Move to pendingCollect
|
|
pendingCollect.value = {
|
|
itemName: sp.itemName,
|
|
itemLabel: sp.itemLabel,
|
|
amount: sp.amount,
|
|
totalMoney: sp.totalMoney,
|
|
}
|
|
sellProgress.value = null
|
|
}
|
|
}, 500)
|
|
}
|
|
|
|
function onSell(data) {
|
|
fetch(`https://${GetParentResourceName()}/sellItems`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ itemName: data.itemName, amount: data.amount }),
|
|
})
|
|
}
|
|
|
|
function collectMoney() {
|
|
fetch(`https://${GetParentResourceName()}/collectMoney`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({}),
|
|
})
|
|
}
|
|
|
|
function close() {
|
|
visible.value = false
|
|
selectedItem.value = null
|
|
fetch(`https://${GetParentResourceName()}/close`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({}),
|
|
})
|
|
}
|
|
|
|
function GetParentResourceName() {
|
|
return window.GetParentResourceName ? window.GetParentResourceName() : 'mercy-sell'
|
|
}
|
|
|
|
function getItemImage(name) {
|
|
return `https://cfx-nui-codem-inventory/html/itemimages/${name || 'example'}.png`
|
|
}
|
|
|
|
function onImgError(e) {
|
|
if (!e.target.src.endsWith('example.png')) e.target.src = './items/example.png'
|
|
}
|
|
|
|
function handleMessage(event) {
|
|
const data = event.data
|
|
switch (data.type) {
|
|
case 'open':
|
|
visible.value = true
|
|
dealerLabel.value = data.dealerLabel || 'Dealer'
|
|
dealerId.value = data.dealerId || ''
|
|
dealerImage.value = data.dealerImage || ''
|
|
dealerColor.value = data.dealerColor || ''
|
|
showLanding.value = true
|
|
prices.value = data.prices || []
|
|
playerItems.value = data.playerItems || []
|
|
selectedItem.value = null
|
|
break
|
|
|
|
case 'close':
|
|
visible.value = false
|
|
selectedItem.value = null
|
|
break
|
|
|
|
case 'sellStarted':
|
|
sellProgress.value = {
|
|
itemName: data.itemName,
|
|
itemLabel: data.itemLabel,
|
|
amount: data.amount,
|
|
totalMoney: data.totalMoney,
|
|
totalDuration: data.totalDuration,
|
|
elapsed: 0,
|
|
isComplete: false,
|
|
}
|
|
pendingCollect.value = null
|
|
selectedItem.value = null
|
|
showLanding.value = false
|
|
startProgressTimer()
|
|
break
|
|
|
|
case 'sellStatus':
|
|
if (data.isComplete) {
|
|
// Timer done, show collect button
|
|
sellProgress.value = null
|
|
pendingCollect.value = {
|
|
itemName: data.itemName,
|
|
itemLabel: data.itemLabel,
|
|
amount: data.amount,
|
|
totalMoney: data.totalMoney,
|
|
}
|
|
} else {
|
|
sellProgress.value = {
|
|
itemName: data.itemName,
|
|
itemLabel: data.itemLabel,
|
|
amount: data.amount,
|
|
totalMoney: data.totalMoney,
|
|
totalDuration: data.totalDuration,
|
|
elapsed: data.elapsed,
|
|
isComplete: false,
|
|
}
|
|
pendingCollect.value = null
|
|
showLanding.value = false
|
|
startProgressTimer()
|
|
}
|
|
break
|
|
|
|
case 'sellComplete':
|
|
// Server confirmed collection
|
|
pendingCollect.value = null
|
|
sellProgress.value = null
|
|
if (progressTimer) { clearInterval(progressTimer); progressTimer = null }
|
|
break
|
|
|
|
case 'sellFailed':
|
|
break
|
|
|
|
case 'updateInventory':
|
|
playerItems.value = data.playerItems || []
|
|
break
|
|
|
|
case 'updatePrices':
|
|
prices.value = data.prices || []
|
|
break
|
|
}
|
|
}
|
|
|
|
function handleKeydown(e) {
|
|
if (e.key === 'Escape' && visible.value) {
|
|
if (selectedItem.value) {
|
|
// Detail → List
|
|
selectedItem.value = null
|
|
e.stopPropagation()
|
|
} else if (!showLanding.value) {
|
|
// List → Landing
|
|
backToLanding()
|
|
e.stopPropagation()
|
|
} else {
|
|
// Landing → Close
|
|
close()
|
|
}
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('message', handleMessage)
|
|
window.addEventListener('keydown', handleKeydown)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('message', handleMessage)
|
|
window.removeEventListener('keydown', handleKeydown)
|
|
if (progressTimer) clearInterval(progressTimer)
|
|
})
|
|
</script>
|