|
|
@ -6,7 +6,6 @@ import { getShop, buyItem, type Shop, type ShopItemDisplay } from '@/api/shop' |
|
|
import { ElMessage } from 'element-plus' |
|
|
import { ElMessage } from 'element-plus' |
|
|
import StarBorder from '@/components/StarBorder/StarBorder.vue' |
|
|
import StarBorder from '@/components/StarBorder/StarBorder.vue' |
|
|
import Particles from '@/components/Particles/Particles.vue' |
|
|
import Particles from '@/components/Particles/Particles.vue' |
|
|
import { ArrowLeft } from '@element-plus/icons-vue' |
|
|
|
|
|
|
|
|
|
|
|
const router = useRouter() |
|
|
const router = useRouter() |
|
|
const characterStore = useCharacterStore() |
|
|
const characterStore = useCharacterStore() |
|
|
@ -115,120 +114,124 @@ onMounted(async () => { |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<template> |
|
|
<template> |
|
|
<div class="shop-container"> |
|
|
<div class="shop-page"> |
|
|
<Particles /> |
|
|
<Particles /> |
|
|
<StarBorder /> |
|
|
<StarBorder /> |
|
|
|
|
|
|
|
|
<div class="header"> |
|
|
<div class="page-container"> |
|
|
<button class="back-btn" @click="goBack"> |
|
|
<div class="page-header"> |
|
|
<span class="back-icon">←</span> |
|
|
<span class="back-btn" @click="goBack">← 返回</span> |
|
|
<span>返回</span> |
|
|
<span class="title">商店</span> |
|
|
</button> |
|
|
<span class="placeholder"></span> |
|
|
<h1 class="title">商店</h1> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="shop" class="shop-info"> |
|
|
|
|
|
<span class="refresh-time">商品刷新时间: {{ formatRefreshTime(shop.lastRefreshTime) }}</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="money-display"> |
|
|
<div class="money-display"> |
|
|
💰 {{ formatNumber(currentMoney) }} 灵石 |
|
|
💰 {{ formatNumber(currentMoney) }} 灵石 |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="shop" class="shop-info"> |
|
|
|
|
|
<span class="refresh-time">商品刷新时间: {{ formatRefreshTime(shop.lastRefreshTime) }}</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div v-if="loading" class="loading"> |
|
|
<div v-if="loading" class="loading"> |
|
|
加载中... |
|
|
加载中... |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div v-else-if="error" class="error"> |
|
|
<div v-else-if="error" class="error"> |
|
|
{{ error }} |
|
|
{{ error }} |
|
|
<button class="retry-btn" @click="fetchShop">重试</button> |
|
|
<button class="retry-btn" @click="fetchShop">重试</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div v-else-if="!shop || shop.items.length === 0" class="empty"> |
|
|
<div v-else-if="!shop || shop.items.length === 0" class="empty"> |
|
|
商店暂无可购商品,请联系管理员上架商品 |
|
|
商店暂无可购商品,请联系管理员上架商品 |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div v-else class="shop-grid"> |
|
|
<div v-else class="shop-grid"> |
|
|
<div |
|
|
<div |
|
|
v-for="item in shop.items" |
|
|
v-for="item in shop.items" |
|
|
:key="item.shopItemId" |
|
|
:key="item.shopItemId" |
|
|
class="shop-item" |
|
|
class="shop-item" |
|
|
:class="{ 'cannot-buy': !canBuy(item) }" |
|
|
:class="{ 'cannot-buy': !canBuy(item) }" |
|
|
> |
|
|
> |
|
|
<div class="item-icon-wrapper"> |
|
|
<div class="item-icon-wrapper"> |
|
|
<img |
|
|
<img |
|
|
v-if="getItemIcon(item)" |
|
|
v-if="getItemIcon(item)" |
|
|
:src="getItemIcon(item)" |
|
|
:src="getItemIcon(item)" |
|
|
class="item-icon-img" |
|
|
class="item-icon-img" |
|
|
:class="'rarity-' + (item.itemRarity || 1)" |
|
|
:class="'rarity-' + (item.itemRarity || 1)" |
|
|
/> |
|
|
/> |
|
|
<div v-else class="item-icon">{{ getItemEmoji(item.itemType) }}</div> |
|
|
<div v-else class="item-icon">{{ getItemEmoji(item.itemType) }}</div> |
|
|
<div v-if="item.itemRarity" class="rarity-badge" :style="{ color: rarityColorMap[item.itemRarity] }"> |
|
|
<div v-if="item.itemRarity" class="rarity-badge" :style="{ color: rarityColorMap[item.itemRarity] }"> |
|
|
{{ rarityMap[item.itemRarity] }} |
|
|
{{ rarityMap[item.itemRarity] }} |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="item-name">{{ item.itemName }}</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="item-price"> |
|
|
|
|
|
💰 {{ formatNumber(item.price) }} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="item-limit" v-if="item.dailyLimit > 0"> |
|
|
|
|
|
今日已购: {{ item.purchasedToday }} / {{ item.dailyLimit }} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<button |
|
|
|
|
|
class="buy-btn" |
|
|
|
|
|
:disabled="!canBuy(item) || buying === item.shopItemId" |
|
|
|
|
|
@click="handleBuy(item)" |
|
|
|
|
|
> |
|
|
|
|
|
{{ buying === item.shopItemId ? '购买中...' : getBuyButtonText(item) }} |
|
|
|
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="item-name">{{ item.itemName }}</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="item-price"> |
|
|
|
|
|
💰 {{ formatNumber(item.price) }} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="item-limit" v-if="item.dailyLimit > 0"> |
|
|
|
|
|
今日已购: {{ item.purchasedToday }} / {{ item.dailyLimit }} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<button |
|
|
|
|
|
class="buy-btn" |
|
|
|
|
|
:disabled="!canBuy(item) || buying === item.shopItemId" |
|
|
|
|
|
@click="handleBuy(item)" |
|
|
|
|
|
> |
|
|
|
|
|
{{ buying === item.shopItemId ? '购买中...' : getBuyButtonText(item) }} |
|
|
|
|
|
</button> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<style scoped lang="css"> |
|
|
<style scoped lang="css"> |
|
|
.shop-container { |
|
|
.shop-page { |
|
|
min-height: 100vh; |
|
|
min-height: 100vh; |
|
|
|
|
|
background: #000000; |
|
|
padding: 20px; |
|
|
padding: 20px; |
|
|
position: relative; |
|
|
position: relative; |
|
|
overflow-y: auto; |
|
|
overflow-y: auto; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.header { |
|
|
.page-header { |
|
|
display: flex; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
align-items: center; |
|
|
|
|
|
padding: 16px 0; |
|
|
margin-bottom: 20px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.page-container { |
|
|
|
|
|
max-width: 480px; |
|
|
|
|
|
margin: 0 auto; |
|
|
|
|
|
position: relative; |
|
|
|
|
|
z-index: 10; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
.back-btn { |
|
|
.back-btn { |
|
|
display: flex; |
|
|
color: #666666; |
|
|
align-items: center; |
|
|
font-size: 0.9rem; |
|
|
gap: 8px; |
|
|
|
|
|
padding: 8px 16px; |
|
|
|
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
|
|
|
border-radius: 8px; |
|
|
|
|
|
color: #cccccc; |
|
|
|
|
|
cursor: pointer; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
padding: 8px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.back-btn:hover { |
|
|
.back-btn:hover { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
color: #ffffff; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.back-icon { |
|
|
|
|
|
font-size: 1.2rem; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.title { |
|
|
.title { |
|
|
font-size: 1.5rem; |
|
|
|
|
|
color: #ffffff; |
|
|
color: #ffffff; |
|
|
margin: 0; |
|
|
font-size: 1.1rem; |
|
|
|
|
|
font-weight: 500; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.placeholder { |
|
|
|
|
|
width: 60px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.money-display { |
|
|
.money-display { |
|
|
@ -238,12 +241,15 @@ onMounted(async () => { |
|
|
border-radius: 8px; |
|
|
border-radius: 8px; |
|
|
color: #ffd700; |
|
|
color: #ffd700; |
|
|
font-size: 1rem; |
|
|
font-size: 1rem; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
margin-bottom: 20px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.shop-info { |
|
|
.shop-info { |
|
|
margin-bottom: 20px; |
|
|
margin-bottom: 20px; |
|
|
color: #888888; |
|
|
color: #888888; |
|
|
font-size: 0.9rem; |
|
|
font-size: 0.9rem; |
|
|
|
|
|
text-align: center; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.refresh-time { |
|
|
.refresh-time { |
|
|
@ -278,15 +284,15 @@ onMounted(async () => { |
|
|
|
|
|
|
|
|
.shop-grid { |
|
|
.shop-grid { |
|
|
display: grid; |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); |
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); |
|
|
gap: 16px; |
|
|
gap: 12px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.shop-item { |
|
|
.shop-item { |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
background: rgba(255, 255, 255, 0.05); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
|
border-radius: 12px; |
|
|
border-radius: 12px; |
|
|
padding: 16px; |
|
|
padding: 12px; |
|
|
display: flex; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
align-items: center; |
|
|
@ -305,23 +311,23 @@ onMounted(async () => { |
|
|
|
|
|
|
|
|
.item-icon-wrapper { |
|
|
.item-icon-wrapper { |
|
|
position: relative; |
|
|
position: relative; |
|
|
margin-bottom: 12px; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.item-icon-img { |
|
|
.item-icon-img { |
|
|
width: 64px; |
|
|
width: 48px; |
|
|
height: 64px; |
|
|
height: 48px; |
|
|
object-fit: contain; |
|
|
object-fit: contain; |
|
|
border-radius: 8px; |
|
|
border-radius: 8px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.item-icon { |
|
|
.item-icon { |
|
|
width: 64px; |
|
|
width: 48px; |
|
|
height: 64px; |
|
|
height: 48px; |
|
|
display: flex; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
justify-content: center; |
|
|
font-size: 2rem; |
|
|
font-size: 1.5rem; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border-radius: 8px; |
|
|
border-radius: 8px; |
|
|
} |
|
|
} |
|
|
@ -333,43 +339,43 @@ onMounted(async () => { |
|
|
|
|
|
|
|
|
.rarity-badge { |
|
|
.rarity-badge { |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
bottom: -8px; |
|
|
bottom: -6px; |
|
|
left: 50%; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
transform: translateX(-50%); |
|
|
font-size: 0.7rem; |
|
|
font-size: 0.65rem; |
|
|
font-weight: bold; |
|
|
font-weight: bold; |
|
|
white-space: nowrap; |
|
|
white-space: nowrap; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.item-name { |
|
|
.item-name { |
|
|
color: #ffffff; |
|
|
color: #ffffff; |
|
|
font-size: 0.95rem; |
|
|
font-size: 0.85rem; |
|
|
margin-bottom: 8px; |
|
|
margin-bottom: 6px; |
|
|
min-height: 40px; |
|
|
min-height: 36px; |
|
|
display: flex; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
align-items: center; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.item-price { |
|
|
.item-price { |
|
|
color: #ffd700; |
|
|
color: #ffd700; |
|
|
font-size: 1rem; |
|
|
font-size: 0.9rem; |
|
|
margin-bottom: 8px; |
|
|
margin-bottom: 6px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.item-limit { |
|
|
.item-limit { |
|
|
color: #888888; |
|
|
color: #888888; |
|
|
font-size: 0.8rem; |
|
|
font-size: 0.75rem; |
|
|
margin-bottom: 12px; |
|
|
margin-bottom: 8px; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.buy-btn { |
|
|
.buy-btn { |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
padding: 10px 16px; |
|
|
padding: 8px 12px; |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
border: none; |
|
|
border: none; |
|
|
border-radius: 8px; |
|
|
border-radius: 6px; |
|
|
color: #ffffff; |
|
|
color: #ffffff; |
|
|
font-size: 0.95rem; |
|
|
font-size: 0.85rem; |
|
|
cursor: pointer; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
} |
|
|
|