Compare commits
3
Commits
4a4857fa82
...
ff74dd1e9b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff74dd1e9b | ||
|
|
7ebb1ab648 | ||
|
|
640b55f685 |
@@ -1,68 +1,274 @@
|
|||||||
<!--
|
|
||||||
* @Descripttion:
|
|
||||||
* @version: 1.0.0
|
|
||||||
* @Author: LyMy
|
|
||||||
* @Date: 2025-04-11 16:51:41
|
|
||||||
* @LastEditors: LyMy
|
|
||||||
* @LastEditTime: 2025-04-11 17:32:02
|
|
||||||
* @FilePath: \go_fish_web\src\pages\equipments\Equipments.vue
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<div class="equipments-page">
|
<div class="equipments-page">
|
||||||
<h1>查看装备</h1>
|
<div class="content">
|
||||||
<el-table :data="equipmentList" style="width: 100%">
|
<div class="title-container">
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
<h1 class="title">我的装备库</h1>
|
||||||
<el-table-column prop="name" label="名称" />
|
<p class="subtitle">管理你的钓鱼装备</p>
|
||||||
<el-table-column prop="type" label="类型" width="150" />
|
</div>
|
||||||
<el-table-column prop="description" label="描述" />
|
|
||||||
<el-table-column prop="quantity" label="数量" width="150" />
|
<div class="equipment-grid">
|
||||||
<el-table-column label="是否已装备" width="120">
|
<div
|
||||||
<template #default="scope">
|
v-for="item in equipmentList"
|
||||||
<el-tag type="success" v-if="scope.row.isEquipped">已装备</el-tag>
|
:key="item.id"
|
||||||
<el-tag type="info" v-else>未装备</el-tag>
|
class="equipment-card"
|
||||||
</template>
|
>
|
||||||
</el-table-column>
|
<div class="equipment-icon">
|
||||||
<el-table-column label="操作" width="120">
|
<span v-if="item.name.indexOf('竿') != -1" class="emoji-icon"
|
||||||
<template #default="scope">
|
>🎣</span
|
||||||
<el-button type="primary" size="small" @click="handleEquip(scope.row.id)" :disabled="scope.row.isEquipped">
|
>
|
||||||
装备
|
<span v-else-if="item.name === '蚯蚓'" class="emoji-icon">🪱</span>
|
||||||
</el-button>
|
<span v-else-if="item.name === '积分'" class="emoji-icon">🏳</span>
|
||||||
</template>
|
<span v-else-if="item.name === '渔具箱'" class="emoji-icon"
|
||||||
</el-table-column>
|
>🧰</span
|
||||||
</el-table>
|
>
|
||||||
|
<span v-else class="emoji-icon">📦</span>
|
||||||
|
</div>
|
||||||
|
<div class="equipment-info">
|
||||||
|
<h3 class="equipment-name">{{ item.name }}</h3>
|
||||||
|
<div class="equipment-type">{{ item.type }}</div>
|
||||||
|
<p class="equipment-description">{{ item.description }}</p>
|
||||||
|
<div class="equipment-stats">
|
||||||
|
<span class="quantity">数量: {{ item.quantity }}</span>
|
||||||
|
<el-tag
|
||||||
|
:type="item.isEquipped ? 'success' : 'info'"
|
||||||
|
class="status-tag"
|
||||||
|
effect="dark"
|
||||||
|
>
|
||||||
|
{{ item.isEquipped ? "已装备" : "未装备" }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
class="equip-button"
|
||||||
|
:class="{ 'is-equipped': item.isEquipped }"
|
||||||
|
@click="handleEquip(item.id, item.quantity)"
|
||||||
|
:disabled="item.isEquipped || item.quantity <= 0"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
item.isEquipped
|
||||||
|
? "已装备"
|
||||||
|
: item.quantity <= 0
|
||||||
|
? "数量不足"
|
||||||
|
: "装备"
|
||||||
|
}}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="back-button">
|
||||||
|
<el-button
|
||||||
|
@click="$router.push({ name: 'Home' })"
|
||||||
|
size="small"
|
||||||
|
type="default"
|
||||||
|
icon="ArrowLeft"
|
||||||
|
>
|
||||||
|
返回首页
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from "vue";
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from "element-plus";
|
||||||
import { getEquipments, equip } from '@/api/equipments/equipments'
|
import { getEquipments, equip } from "@/api/equipments/equipments";
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
const equipmentList = ref([])
|
const equipmentList = ref([]);
|
||||||
|
|
||||||
// 初始化加载
|
// 初始化加载
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const res = await getEquipments()
|
const res = await getEquipments();
|
||||||
equipmentList.value = res || []
|
equipmentList.value = res || [];
|
||||||
})
|
});
|
||||||
|
|
||||||
// 点击装备
|
// 点击装备
|
||||||
const handleEquip = async (id) => {
|
const handleEquip = async (id, quantity) => {
|
||||||
try {
|
try {
|
||||||
await equip(id)
|
if (quantity <= 0) {
|
||||||
ElMessage.success('装备成功!🎯')
|
ElMessage.warning("装备数量不足,无法装备!");
|
||||||
// 重新加载数据
|
return;
|
||||||
const res = await getEquipments()
|
|
||||||
equipmentList.value = res || []
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error('装备失败!')
|
|
||||||
}
|
}
|
||||||
}
|
await equip(id);
|
||||||
|
ElMessage.success("装备成功!🎯");
|
||||||
|
// 重新加载数据
|
||||||
|
const res = await getEquipments();
|
||||||
|
equipmentList.value = res || [];
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error("装备失败!");
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.equipments-page {
|
.equipments-page {
|
||||||
text-align: center;
|
min-height: 100vh;
|
||||||
|
background: var(--bg-gradient-primary);
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-shadow: var(--glow-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: var(--glow-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-card {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 20px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 8px 32px rgba(var(--primary-rgb), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
margin: 0 auto 15px;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(var(--primary-rgb), 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-card:hover .equipment-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: rgba(var(--primary-rgb), 0.2);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-name {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-type {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-description {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0 0 15px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-button {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equip-button.is-equipped {
|
||||||
|
background-color: var(--success-color);
|
||||||
|
border-color: var(--success-color);
|
||||||
|
opacity: 0.8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-card {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-name {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.equipment-description {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,91 +1,159 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="equipments-page">
|
<div class="fishbaskets-page">
|
||||||
<h1>🎣 我的鱼篓</h1>
|
<div class="content">
|
||||||
|
<div class="title-container">
|
||||||
|
<h1 class="title">🎣 我的鱼篓</h1>
|
||||||
|
<p class="subtitle">管理你的渔获</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 处理按钮 -->
|
<!-- 处理按钮 -->
|
||||||
<div style="text-align: right; margin-bottom: 20px;">
|
<div class="action-buttons">
|
||||||
<el-button type="primary" @click="fetchFishList">刷新</el-button>
|
<el-button type="primary" @click="fetchFishList" icon="Refresh"
|
||||||
<el-button type="primary" @click="handleProcessFish">一键处理普通鱼</el-button>
|
>刷新</el-button
|
||||||
</div>
|
>
|
||||||
|
<el-button type="success" @click="handleProcessFish" icon="Check"
|
||||||
|
>一键处理普通鱼</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-tabs v-model="activeTab" type="card">
|
<el-tabs v-model="activeTab" type="card" class="custom-tabs">
|
||||||
<el-tab-pane label="所有鱼" name="all" />
|
<el-tab-pane label="所有鱼" name="all" />
|
||||||
<el-tab-pane label="普通鱼" name="ordinary" />
|
<el-tab-pane label="普通鱼" name="ordinary" />
|
||||||
<el-tab-pane label="稀有鱼" name="rare" />
|
<el-tab-pane label="稀有鱼" name="rare" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<el-table :data="filteredFish" style="width: 100%;" height="calc(100vh - 240px)" border>
|
<el-table
|
||||||
<el-table-column prop="id" label="id" align="center" width="100" />
|
:data="filteredFish"
|
||||||
<el-table-column prop="name" label="鱼名" align="center" width="200" />
|
class="custom-table"
|
||||||
<el-table-column prop="weight" label="重量" align="right" width="150" />
|
height="calc(100vh - 300px)"
|
||||||
<el-table-column prop="description" label="描述" />
|
border
|
||||||
<el-table-column label="稀有度" align="center" width="150">
|
v-loading="loading"
|
||||||
<template #default="{ row }">
|
element-loading-text="正在加载..."
|
||||||
<el-tag :type="row.isRare ? 'danger' : 'info'">
|
element-loading-background="rgba(255, 255, 255, 0.1)"
|
||||||
{{ row.isRare ? '稀有' : '普通' }}
|
>
|
||||||
</el-tag>
|
<el-table-column prop="id" label="ID" align="center" width="100" />
|
||||||
</template>
|
<el-table-column prop="name" label="鱼名" align="center" width="200">
|
||||||
</el-table-column>
|
<template #default="{ row }">
|
||||||
<!-- 出售按钮 -->
|
<span class="fish-name">{{ row.name }}</span>
|
||||||
<el-table-column label="操作" align="center" width="180">
|
</template>
|
||||||
<template #default="{ row }">
|
</el-table-column>
|
||||||
<el-button :loading="loading" type="primary" @click="handleFish(row.id)">处理</el-button>
|
<el-table-column prop="weight" label="重量" align="right" width="150">
|
||||||
<el-button v-if="row.isRare" type="danger" @click="handleSell(row)">出售</el-button>
|
<template #default="{ row }">
|
||||||
</template>
|
<span class="fish-weight">{{ row.weight }}</span>
|
||||||
</el-table-column>
|
</template>
|
||||||
</el-table>
|
</el-table-column>
|
||||||
|
<el-table-column prop="description" label="描述">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="fish-description">{{ row.description }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="稀有度" align="center" width="150">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag
|
||||||
|
:type="row.isRare ? 'danger' : 'info'"
|
||||||
|
effect="dark"
|
||||||
|
class="rarity-tag"
|
||||||
|
>
|
||||||
|
{{ row.isRare ? "稀有" : "普通" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<el-button
|
||||||
|
:loading="loading"
|
||||||
|
type="primary"
|
||||||
|
@click="handleFish(row.id)"
|
||||||
|
size="small"
|
||||||
|
>处理</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="row.isRare"
|
||||||
|
type="danger"
|
||||||
|
@click="handleSell(row)"
|
||||||
|
size="small"
|
||||||
|
>出售</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<el-empty
|
||||||
|
v-if="!filteredFish.length"
|
||||||
|
description="暂无渔获"
|
||||||
|
class="empty-state"
|
||||||
|
>
|
||||||
|
<template #image>
|
||||||
|
<span class="empty-icon">🎣</span>
|
||||||
|
</template>
|
||||||
|
</el-empty>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from "vue";
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
import { myFishBaskets, handleFishById, sellFish, autoHandleFish } from '@/api/fishbaskets/fishbaskets'
|
import {
|
||||||
import { ElMessage, ElInput, ElMessageBox } from 'element-plus'
|
myFishBaskets,
|
||||||
|
handleFishById,
|
||||||
|
sellFish,
|
||||||
|
autoHandleFish,
|
||||||
|
} from "@/api/fishbaskets/fishbaskets";
|
||||||
|
import { ElMessage, ElInput, ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false);
|
||||||
const activeTab = ref('all')
|
const activeTab = ref("all");
|
||||||
const fishList = ref([])
|
const fishList = ref([]);
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const fetchFishList = async () => {
|
const fetchFishList = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await myFishBaskets()
|
loading.value = true;
|
||||||
if (res) {
|
const res = await myFishBaskets();
|
||||||
fishList.value = res;
|
if (res) {
|
||||||
}
|
fishList.value = res;
|
||||||
} catch (err) {
|
|
||||||
ElMessage.warning('请检查网络或重试 🌐')
|
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
ElMessage.warning("请检查网络或重试 🌐");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 根据选中的标签筛选数据
|
// 根据选中的标签筛选数据
|
||||||
const filteredFish = computed(() => {
|
const filteredFish = computed(() => {
|
||||||
switch (activeTab.value) {
|
switch (activeTab.value) {
|
||||||
case 'all':
|
case "all":
|
||||||
return fishList.value;
|
return fishList.value;
|
||||||
case 'ordinary':
|
case "ordinary":
|
||||||
return fishList.value.filter(f => !f.isRare);
|
return fishList.value.filter((f) => !f.isRare);
|
||||||
case 'rare':
|
case "rare":
|
||||||
return fishList.value.filter(f => f.isRare);
|
return fishList.value.filter((f) => f.isRare);
|
||||||
}
|
default:
|
||||||
})
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 一键处理鱼的接口调用
|
// 一键处理鱼的接口调用
|
||||||
const handleProcessFish = async () => {
|
const handleProcessFish = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await autoHandleFish()
|
loading.value = true;
|
||||||
if (res) {
|
const res = await autoHandleFish();
|
||||||
ElMessage.success(res + ' 🎉')
|
if (res) {
|
||||||
fetchFishList()
|
ElMessage.success(res + " 🎉");
|
||||||
}
|
await fetchFishList();
|
||||||
} catch (err) {
|
|
||||||
console.log('====================================');
|
|
||||||
console.log(err);
|
|
||||||
console.log('====================================');
|
|
||||||
ElMessage.warning('处理失败,请稍后再试 ⏳')
|
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
ElMessage.warning("处理失败,请稍后再试 ⏳");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 手动处理鱼
|
// 手动处理鱼
|
||||||
const handleFish = async (fishId) => {
|
const handleFish = async (fishId) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -111,41 +179,171 @@ const handleFish = async (fishId) => {
|
|||||||
|
|
||||||
// 处理出售操作
|
// 处理出售操作
|
||||||
const handleSell = async (fish) => {
|
const handleSell = async (fish) => {
|
||||||
const { value: price } = await ElMessageBox.prompt('请输入出售价格', `出售鱼 (${fish.name})`, {
|
try {
|
||||||
confirmButtonText: '确认',
|
const { value: price } = await ElMessageBox.prompt(
|
||||||
cancelButtonText: '取消',
|
"请输入出售价格",
|
||||||
|
`出售鱼 (${fish.name})`,
|
||||||
|
{
|
||||||
|
confirmButtonText: "确认",
|
||||||
|
cancelButtonText: "取消",
|
||||||
inputPattern: /^\d+(\.\d{1,2})?$/,
|
inputPattern: /^\d+(\.\d{1,2})?$/,
|
||||||
inputErrorMessage: '请输入合法的价格'
|
inputErrorMessage: "请输入合法的价格",
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (price) {
|
if (price) {
|
||||||
try {
|
const res = await sellFish({ FishBagId: fish.id, Points: price });
|
||||||
const res = await sellFish({ FishBagId: fish.id, Points: price })
|
if (res.success) {
|
||||||
if (res.success) {
|
ElMessage.success(res.message + " 🎉");
|
||||||
ElMessage.success(res.message + ' 🎉')
|
await fetchFishList();
|
||||||
fetchFishList()
|
} else {
|
||||||
} else {
|
ElMessage.error("出售失败 " + res.message + " 😢");
|
||||||
ElMessage.error('出售失败 ' + res.message + ' 😢');
|
}
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error('出售失败,请稍后再试 🛒')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
if (err !== "cancel") {
|
||||||
|
ElMessage.error("出售失败,请稍后再试 🛒");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchFishList()
|
fetchFishList();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.equipments-page {
|
.fishbaskets-page {
|
||||||
text-align: center;
|
min-height: 100vh;
|
||||||
margin-top: 50px;
|
background: var(--bg-gradient-primary);
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.content {
|
||||||
margin-top: 20px;
|
max-width: 1200px;
|
||||||
font-size: 16px;
|
margin: 0 auto;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-shadow: var(--glow-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: var(--glow-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
:deep(.el-tabs__header) {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__nav) {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
&.is-active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table {
|
||||||
|
height: calc(100vh - 250px);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
:deep(.el-table__header-wrapper) {
|
||||||
|
th {
|
||||||
|
background-color: rgba(var(--primary-rgb), 0.1);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__body-wrapper) {
|
||||||
|
td {
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-table__row) {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-weight {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rarity-tag {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
margin-top: 40px;
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-table {
|
||||||
|
:deep(.el-table__body-wrapper) {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+332
-150
@@ -1,25 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="fishing-page">
|
<div class="fishing-page">
|
||||||
<div class="fishing-container">
|
<div class="fishing-container">
|
||||||
<div class="fishing-area">
|
<div class="fishing-area">
|
||||||
<div class="fishing-animation" :class="{ 'fishing-active': isFishing }">
|
<div
|
||||||
<div class="fishing-scene" @click="handleFish"></div>
|
class="fishing-animation"
|
||||||
<div class="ripple"></div>
|
:class="{
|
||||||
</div>
|
'fishing-active': isFishing,
|
||||||
<div class="result" v-if="resultMessage || nextPullTime">
|
reeling: isReeling,
|
||||||
<el-alert v-if="resultMessage" :title="resultMessage" :type="currentCatch.length>0 ? 'success' : 'info'"
|
}"
|
||||||
:closable="false" show-icon />
|
>
|
||||||
<el-alert v-if="nextPullTime" :title="`下次可钓鱼时间:${nextPullTime}`" type="warning" :closable="false" show-icon />
|
<div class="fishing-scene" @click="handleFish"></div>
|
||||||
</div>
|
<div class="ripple"></div>
|
||||||
</div>
|
|
||||||
<div class="log-area">
|
|
||||||
<FishingLog :logs="fishingLogs" />
|
|
||||||
<div class="back-button">
|
|
||||||
<el-button @click="goHome" size="small" type="default" icon="ArrowLeft">返回</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="result" v-if="resultMessage || nextPullTime">
|
||||||
|
<el-alert
|
||||||
|
v-if="resultMessage"
|
||||||
|
:title="resultMessage"
|
||||||
|
:type="currentCatch.length > 0 ? 'success' : 'info'"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
<el-alert
|
||||||
|
v-if="nextPullTime"
|
||||||
|
:title="`下次可钓鱼时间:${nextPullTime}`"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="log-area">
|
||||||
|
<FishingLog :logs="fishingLogs" />
|
||||||
|
<div class="back-button">
|
||||||
|
<el-button
|
||||||
|
@click="goHome"
|
||||||
|
size="small"
|
||||||
|
type="default"
|
||||||
|
icon="ArrowLeft"
|
||||||
|
>返回</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -35,199 +58,358 @@ const nextPullTime = ref("");
|
|||||||
const isFishing = ref(false);
|
const isFishing = ref(false);
|
||||||
const currentCatch = ref([]); // 改为数组
|
const currentCatch = ref([]); // 改为数组
|
||||||
const fishingLogs = ref([]);
|
const fishingLogs = ref([]);
|
||||||
|
const isWaiting = ref(false);
|
||||||
|
const isReeling = ref(false);
|
||||||
|
const fishEscapeTimer = ref(null);
|
||||||
|
|
||||||
// 从localStorage加载缓存的钓鱼日志
|
// 从localStorage加载缓存的钓鱼日志
|
||||||
const loadCachedLogs = () => {
|
const loadCachedLogs = () => {
|
||||||
const cachedLogs = localStorage.getItem("fishingLogs");
|
const cachedLogs = localStorage.getItem("fishingLogs");
|
||||||
if (cachedLogs) {
|
if (cachedLogs) {
|
||||||
fishingLogs.value = JSON.parse(cachedLogs);
|
fishingLogs.value = JSON.parse(cachedLogs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 保存钓鱼日志到localStorage
|
// 保存钓鱼日志到localStorage
|
||||||
const saveLogs = () => {
|
const saveLogs = () => {
|
||||||
localStorage.setItem("fishingLogs", JSON.stringify(fishingLogs.value));
|
localStorage.setItem("fishingLogs", JSON.stringify(fishingLogs.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生成随机等待时间(1-3秒)
|
||||||
|
const generateWaitTime = () => {
|
||||||
|
return Math.floor(Math.random() * 2000) + 1000;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成随机逃跑概率(10%)
|
||||||
|
const willFishEscape = () => {
|
||||||
|
return Math.random() < 0.1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成随机提竿失败概率(20%)
|
||||||
|
const willReelFail = () => {
|
||||||
|
return Math.random() < 0.2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件挂载时加载缓存的日志
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCachedLogs();
|
loadCachedLogs();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清除定时器
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
localStorage.removeItem("fishingLogs");
|
if (fishEscapeTimer.value) {
|
||||||
|
clearTimeout(fishEscapeTimer.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFish = async () => {
|
const handleFish = async () => {
|
||||||
if (isFishing.value) return;
|
// 下杆前校验:如果设置了下次钓鱼时间,则判断当前时间是否在等待中
|
||||||
|
if (nextPullTime.value) {
|
||||||
isFishing.value = true;
|
// 尝试将 nextPullTime 转换为 Date 对象
|
||||||
currentCatch.value = [];
|
const nextTime = new Date(nextPullTime.value);
|
||||||
resultMessage.value = "正在钓鱼中...";
|
if (Date.now() < nextTime.getTime()) {
|
||||||
|
ElMessage.warning(
|
||||||
try {
|
`还未到下杆时间,请在 ${nextTime.toLocaleTimeString()} 后尝试`
|
||||||
const res = await fishingClick();
|
);
|
||||||
if (res.success && res.data) {
|
return;
|
||||||
const items = res.data.items || [];
|
|
||||||
if (res.data.nextPullTime) {
|
|
||||||
nextPullTime.value = new Date(res.data.nextPullTime).toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items.length > 0) {
|
|
||||||
currentCatch.value = items;
|
|
||||||
|
|
||||||
// 构建展示信息
|
|
||||||
const messageList = items.map(fish =>
|
|
||||||
`「${fish.isRare ? "稀有的" : ""}${fish.fishName}」,重量 ${fish.weight}`
|
|
||||||
);
|
|
||||||
resultMessage.value = `你钓到了:\n${messageList.join("\n")}`;
|
|
||||||
ElMessage.success("钓鱼成功!🎣");
|
|
||||||
|
|
||||||
// 添加所有钓到的鱼到日志
|
|
||||||
const newLogs = items.map(fish => ({
|
|
||||||
time: new Date(),
|
|
||||||
fishName: fish.fishName,
|
|
||||||
weight: fish.weight,
|
|
||||||
isRare: fish.isRare,
|
|
||||||
}));
|
|
||||||
fishingLogs.value.unshift(...newLogs);
|
|
||||||
saveLogs();
|
|
||||||
} else {
|
|
||||||
resultMessage.value = "什么也没钓到,下次好运 🍀";
|
|
||||||
ElMessage.warning("什么也没钓到,下次好运 🍀");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resultMessage.value = res.message || "钓鱼失败";
|
|
||||||
if (res.data?.nextPullTime) {
|
|
||||||
nextPullTime.value = new Date(res.data.nextPullTime).toLocaleString();
|
|
||||||
}
|
|
||||||
ElMessage.error(res.message || "钓鱼失败 🎣");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
resultMessage.value = "钓鱼出错,请稍后重试";
|
|
||||||
ElMessage.error("钓鱼出错,请稍后重试 🌊");
|
|
||||||
console.error(err);
|
|
||||||
} finally {
|
|
||||||
isFishing.value = false;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 如果正在钓鱼且还处于等待上钩阶段,不允许操作
|
||||||
|
if (isFishing.value && !isWaiting.value) return;
|
||||||
|
|
||||||
|
// 第一阶段:开始钓鱼
|
||||||
|
if (!isWaiting.value) {
|
||||||
|
isFishing.value = true;
|
||||||
|
resultMessage.value = "放下鱼竿,等待不知好歹的🐟";
|
||||||
|
|
||||||
|
// 随机等待时间后判断是否有鱼上钩
|
||||||
|
setTimeout(() => {
|
||||||
|
if (willFishEscape()) {
|
||||||
|
resultMessage.value = "鱼儿警觉地游走了... 🐟";
|
||||||
|
isFishing.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isWaiting.value = true;
|
||||||
|
resultMessage.value = "鱼儿上钩啦!速速收杆!速速速速速速";
|
||||||
|
|
||||||
|
isReeling.value = true;
|
||||||
|
|
||||||
|
// 设置逃跑计时器
|
||||||
|
fishEscapeTimer.value = setTimeout(() => {
|
||||||
|
if (isWaiting.value && isFishing.value) {
|
||||||
|
isWaiting.value = false;
|
||||||
|
isFishing.value = false;
|
||||||
|
resultMessage.value = "鱼儿挣脱逃走了!";
|
||||||
|
}
|
||||||
|
}, 5000); // 5秒内必须完成提竿
|
||||||
|
}, generateWaitTime());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除逃跑计时器
|
||||||
|
if (fishEscapeTimer.value) {
|
||||||
|
clearTimeout(fishEscapeTimer.value);
|
||||||
|
fishEscapeTimer.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二阶段:达到目标点击次数,判断是否提竿成功
|
||||||
|
isWaiting.value = false;
|
||||||
|
isReeling.value = false;
|
||||||
|
|
||||||
|
if (willReelFail()) {
|
||||||
|
isFishing.value = false;
|
||||||
|
resultMessage.value = "提竿时机不对,鱼儿溜走了!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用钓鱼接口
|
||||||
|
currentCatch.value = [];
|
||||||
|
resultMessage.value = "正在钓鱼中...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fishingClick();
|
||||||
|
if (res.success && res.data) {
|
||||||
|
const items = res.data.items || [];
|
||||||
|
if (res.data.nextPullTime) {
|
||||||
|
nextPullTime.value = new Date(res.data.nextPullTime).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length > 0) {
|
||||||
|
currentCatch.value = items;
|
||||||
|
|
||||||
|
// 构建展示信息
|
||||||
|
const messageList = items.map(
|
||||||
|
(fish) =>
|
||||||
|
`「${fish.isRare ? "稀有的" : ""}${fish.fishName}」,重量 ${
|
||||||
|
fish.weight
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
resultMessage.value = `你钓到了:\n${messageList.join("\n")}`;
|
||||||
|
// 根据钓到的鱼的数量显示不同的消息和动画效果
|
||||||
|
if (items.length > 1) {
|
||||||
|
ElMessage({
|
||||||
|
message: `太棒了!一次钓到${items.length}条鱼!🎣✨`,
|
||||||
|
type: "success",
|
||||||
|
duration: 5000,
|
||||||
|
showClose: true,
|
||||||
|
customClass: "multi-catch-message",
|
||||||
|
});
|
||||||
|
// 添加额外的动画类
|
||||||
|
document
|
||||||
|
.querySelector(".fishing-animation")
|
||||||
|
.classList.add("multi-catch");
|
||||||
|
setTimeout(() => {
|
||||||
|
document
|
||||||
|
.querySelector(".fishing-animation")
|
||||||
|
.classList.remove("multi-catch");
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
ElMessage.success("钓鱼成功!🎣");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加所有钓到的鱼到日志
|
||||||
|
const newLogs = items.map((fish) => ({
|
||||||
|
time: new Date(),
|
||||||
|
fishName: fish.fishName,
|
||||||
|
weight: fish.weight,
|
||||||
|
isRare: fish.isRare,
|
||||||
|
}));
|
||||||
|
fishingLogs.value.unshift(...newLogs);
|
||||||
|
saveLogs();
|
||||||
|
} else {
|
||||||
|
resultMessage.value = "什么也没钓到,下次好运 🍀";
|
||||||
|
ElMessage.warning("什么也没钓到,下次好运 🍀");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resultMessage.value = res.message || "钓鱼失败";
|
||||||
|
if (res.data?.nextPullTime) {
|
||||||
|
nextPullTime.value = new Date(res.data.nextPullTime).toLocaleString();
|
||||||
|
}
|
||||||
|
ElMessage.error(res.message || "钓鱼失败 🎣");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
resultMessage.value = "钓鱼出错,请稍后重试";
|
||||||
|
ElMessage.error("钓鱼出错,请稍后重试 🌊");
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
isFishing.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goHome = () => {
|
const goHome = () => {
|
||||||
router.push("/");
|
router.push("/");
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fishing-page {
|
.fishing-page {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-container {
|
.fishing-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 400px;
|
grid-template-columns: 1fr 400px;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
min-height: calc(100vh - 40px);
|
min-height: calc(100vh - 40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button {
|
.back-button {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button .el-button {
|
.back-button .el-button {
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
background: #409eff;
|
background: #409eff;
|
||||||
border: 1px solid #409eff;
|
border: 1px solid #409eff;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button .el-button:hover {
|
.back-button .el-button:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.3);
|
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.3);
|
||||||
background: #66b1ff;
|
background: #66b1ff;
|
||||||
border-color: #66b1ff;
|
border-color: #66b1ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-area {
|
.fishing-area {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-animation {
|
.fishing-animation {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-scene {
|
.fishing-scene {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: url("@/assets/fishing-scene.svg") no-repeat center;
|
background: url("@/assets/fishing-scene.svg") no-repeat center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-active .fishing-scene {
|
.fishing-active .fishing-scene {
|
||||||
animation: scene-active 3s ease-in-out infinite;
|
animation: scene-active 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reeling .fishing-scene {
|
||||||
|
animation: reel 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ripple {
|
.ripple {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
background: rgba(255, 255, 255, 0.7);
|
background: rgba(255, 255, 255, 0.7);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
left: 60%;
|
left: 60%;
|
||||||
top: 70%;
|
top: 70%;
|
||||||
transform: translate(-50%, -50%) scale(0);
|
transform: translate(-50%, -50%) scale(0);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fishing-active .ripple {
|
.fishing-active .ripple {
|
||||||
animation: ripple 2s infinite;
|
animation: ripple 2s infinite;
|
||||||
}
|
|
||||||
|
|
||||||
.fishing-controls {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.result {
|
.result {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes ripple {
|
@keyframes ripple {
|
||||||
0% {
|
0% {
|
||||||
transform: translate(-50%, -50%) scale(0);
|
transform: translate(-50%, -50%) scale(0);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: translate(-50%, -50%) scale(4);
|
transform: translate(-50%, -50%) scale(4);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scene-active {
|
@keyframes scene-active {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
transform: scale(1);
|
transform: scale(1) rotate(0deg);
|
||||||
}
|
filter: brightness(1);
|
||||||
50% {
|
}
|
||||||
transform: scale(1.02);
|
25% {
|
||||||
}
|
transform: scale(1.05) rotate(-2deg);
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: scale(1.05) rotate(2deg);
|
||||||
|
filter: brightness(1.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reel {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: rotate(-8deg) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 多条鱼时的特殊动画效果 */
|
||||||
|
@keyframes multi-catch {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
filter: brightness(1) contrast(1);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: scale(1.15) rotate(-5deg);
|
||||||
|
filter: brightness(1.3) contrast(1.1);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: scale(1.15) rotate(5deg);
|
||||||
|
filter: brightness(1.3) contrast(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-catch {
|
||||||
|
animation: multi-catch 1s ease-in-out 3 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-catch-message {
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
background: linear-gradient(45deg, #4caf50, #2196f3) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fishing-scene:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fishing-animation:not(.fishing-active) .fishing-scene:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+240
-230
@@ -1,279 +1,289 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<el-card class="login-card">
|
<el-card class="login-card">
|
||||||
<h1 class="title">钓鱼大冒险</h1>
|
<h1 class="title">钓鱼大冒险</h1>
|
||||||
|
|
||||||
<!-- 登录表单 -->
|
<!-- 登录表单 -->
|
||||||
<el-form v-if="isLogin" :model="form" :rules="rules" ref="formRef" @submit.prevent="handleLogin" label-width="90px">
|
<el-form
|
||||||
<el-row :gutter="24">
|
v-if="isLogin"
|
||||||
<el-col :span="24">
|
:model="form"
|
||||||
<el-form-item prop="Name" label="用户名">
|
:rules="rules"
|
||||||
<el-input v-model="form.Name" placeholder="请输入用户名"></el-input>
|
ref="formRef"
|
||||||
</el-form-item>
|
@submit.prevent="handleLogin"
|
||||||
</el-col>
|
label-width="90px"
|
||||||
</el-row>
|
>
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="24">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item prop="Password" label="密码">
|
<el-form-item prop="Name" label="用户名">
|
||||||
<el-input v-model="form.Password" type="password" placeholder="请输入密码"></el-input>
|
<el-input
|
||||||
</el-form-item>
|
v-model="form.Name"
|
||||||
</el-col>
|
placeholder="请输入用户名"
|
||||||
</el-row>
|
></el-input>
|
||||||
<el-row :gutter="24">
|
</el-form-item>
|
||||||
<el-col :span="24">
|
</el-col>
|
||||||
<el-form-item label="">
|
</el-row>
|
||||||
<el-button type="primary" block native-type="submit">登录</el-button>
|
<el-row :gutter="24">
|
||||||
</el-form-item>
|
<el-col :span="24">
|
||||||
</el-col>
|
<el-form-item prop="Password" label="密码">
|
||||||
</el-row>
|
<el-input
|
||||||
</el-form>
|
v-model="form.Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="">
|
||||||
|
<el-button type="primary" block native-type="submit"
|
||||||
|
>登录</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
<!-- 注册表单 -->
|
<!-- 注册表单 -->
|
||||||
<el-form v-if="!isLogin" :model="form" :rules="rules" ref="formRef" @submit.prevent="handleRegister" label-width="90px">
|
<el-form
|
||||||
<el-row :gutter="24">
|
v-if="!isLogin"
|
||||||
<el-col :span="24">
|
:model="form"
|
||||||
<el-form-item prop="Name" label="用户名">
|
:rules="rules"
|
||||||
<el-input v-model="form.Name" placeholder="请输入用户名"></el-input>
|
ref="formRef"
|
||||||
</el-form-item>
|
@submit.prevent="handleRegister"
|
||||||
</el-col>
|
label-width="90px"
|
||||||
</el-row>
|
>
|
||||||
<el-row :gutter="24">
|
<el-row :gutter="24">
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-form-item prop="Email" label="邮箱">
|
<el-form-item prop="Name" label="用户名">
|
||||||
<el-input v-model="form.Email" placeholder="请输入邮箱"></el-input>
|
<el-input
|
||||||
</el-form-item>
|
v-model="form.Name"
|
||||||
</el-col>
|
placeholder="请输入用户名"
|
||||||
</el-row>
|
></el-input>
|
||||||
<el-row :gutter="24">
|
</el-form-item>
|
||||||
<el-col :span="24">
|
</el-col>
|
||||||
<el-form-item prop="Password" label="密码">
|
</el-row>
|
||||||
<el-input v-model="form.Password" type="password" placeholder="请输入密码"></el-input>
|
<el-row :gutter="24">
|
||||||
</el-form-item>
|
<el-col :span="24">
|
||||||
</el-col>
|
<el-form-item prop="Email" label="邮箱">
|
||||||
</el-row>
|
<el-input
|
||||||
<el-row :gutter="24">
|
v-model="form.Email"
|
||||||
<el-col :span="24">
|
placeholder="请输入邮箱"
|
||||||
<el-form-item label="">
|
></el-input>
|
||||||
<el-button type="primary" block native-type="submit">注册</el-button>
|
</el-form-item>
|
||||||
</el-form-item>
|
</el-col>
|
||||||
</el-col>
|
</el-row>
|
||||||
</el-row>
|
<el-row :gutter="24">
|
||||||
</el-form>
|
<el-col :span="24">
|
||||||
|
<el-form-item prop="Password" label="密码">
|
||||||
|
<el-input
|
||||||
|
v-model="form.Password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="24">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="">
|
||||||
|
<el-button type="primary" block native-type="submit"
|
||||||
|
>注册</el-button
|
||||||
|
>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
<!-- 切换按钮 -->
|
<!-- 切换按钮 -->
|
||||||
<div class="toggle-link">
|
<div class="toggle-link">
|
||||||
<span v-if="isLogin">没有账号?</span>
|
<span v-if="isLogin">没有账号?</span>
|
||||||
<span v-if="!isLogin">已经有账号?</span>
|
<span v-if="!isLogin">已经有账号?</span>
|
||||||
<el-button type="text" @click="toggleForm">{{ isLogin ? '注册' : '登录' }}</el-button>
|
<el-button type="text" @click="toggleForm">{{
|
||||||
</div>
|
isLogin ? "注册" : "登录"
|
||||||
</el-card>
|
}}</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from "vue";
|
||||||
import { login, register } from '@/api/login/login' // 假设你有注册接口
|
import { login, register } from "@/api/login/login"; // 假设你有注册接口
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const formRef = ref(null)
|
const formRef = ref(null);
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
Name: '',
|
Name: "",
|
||||||
Password: ''
|
Password: "",
|
||||||
})
|
});
|
||||||
|
|
||||||
const isLogin = ref(true) // 控制当前显示的是登录还是注册表单
|
const isLogin = ref(true); // 控制当前显示的是登录还是注册表单
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
Password: [
|
Password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
Name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||||
],
|
};
|
||||||
Name: [
|
|
||||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleLogin () {
|
async function handleLogin() {
|
||||||
await formRef.value.validate(async (valid) => {
|
await formRef.value.validate(async (valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
try {
|
||||||
const res = await login({
|
const res = await login({
|
||||||
Name: form.value.Name,
|
Name: form.value.Name,
|
||||||
Password: form.value.Password
|
Password: form.value.Password,
|
||||||
})
|
});
|
||||||
if (res && res.token) {
|
if (res && res.token) {
|
||||||
localStorage.setItem('token', res.token)
|
localStorage.setItem("token", res.token);
|
||||||
router.push('/')
|
router.push("/");
|
||||||
} else {
|
|
||||||
ElMessage.error('登录失败,未返回token 🔑')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
//ElMessage.error('登录失败,请检查网络或重试')
|
|
||||||
console.error('登录错误:', err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning('请完整填写表单')
|
ElMessage.error("登录失败,未返回token 🔑");
|
||||||
}
|
}
|
||||||
})
|
} catch (err) {
|
||||||
}
|
//ElMessage.error('登录失败,请检查网络或重试')
|
||||||
|
console.error("登录错误:", err);
|
||||||
async function handleRegister () {
|
}
|
||||||
await formRef.value.validate(async (valid) => {
|
} else {
|
||||||
if (valid) {
|
ElMessage.warning("请完整填写表单");
|
||||||
try {
|
|
||||||
const res = await register({
|
|
||||||
Name: form.value.Name,
|
|
||||||
Email: form.value.Email,
|
|
||||||
Password: form.value.Password
|
|
||||||
})
|
|
||||||
if (res) {
|
|
||||||
ElMessage.success('注册成功 😀😀')
|
|
||||||
toggleForm() // 注册成功后切换到登录表单
|
|
||||||
} else {
|
|
||||||
ElMessage.error('注册失败 😢😢')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error('注册失败,请检查网络或重试 📶')
|
|
||||||
console.error('注册失败:', err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('请完整填写表单')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleForm () {
|
|
||||||
isLogin.value = !isLogin.value // 切换登录和注册表单
|
|
||||||
form.value = {
|
|
||||||
Name: '',
|
|
||||||
Password: ''
|
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields() // 重置表单验证状态
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRegister() {
|
||||||
|
await formRef.value.validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
try {
|
||||||
|
const res = await register({
|
||||||
|
Name: form.value.Name,
|
||||||
|
Email: form.value.Email,
|
||||||
|
Password: form.value.Password,
|
||||||
|
});
|
||||||
|
if (res) {
|
||||||
|
ElMessage.success("注册成功 😀😀");
|
||||||
|
toggleForm(); // 注册成功后切换到登录表单
|
||||||
|
} else {
|
||||||
|
ElMessage.error("注册失败 😢😢");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error("注册失败,请检查网络或重试 📶");
|
||||||
|
console.error("注册失败:", err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning("请完整填写表单");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleForm() {
|
||||||
|
isLogin.value = !isLogin.value; // 切换登录和注册表单
|
||||||
|
form.value = {
|
||||||
|
Name: "",
|
||||||
|
Password: "",
|
||||||
|
};
|
||||||
|
formRef.value?.resetFields(); // 重置表单验证状态
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.login-page {
|
.login-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, #0d47a1 0%, #1565c0 100%);
|
background: var(--bg-gradient-secondary);
|
||||||
padding: 20px;
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 500px;
|
max-width: 420px;
|
||||||
padding: 30px;
|
background: var(--bg-card);
|
||||||
background-color: rgba(255, 255, 255, 0.92);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 20px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25), 0 0 15px rgba(0, 0, 0, 0.1);
|
box-shadow: var(--shadow-lg);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(10px);
|
||||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card:hover {
|
.login-card:hover {
|
||||||
transform: translateY(-8px);
|
transform: translateY(-5px);
|
||||||
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.18), 0 0 15px rgba(0, 0, 0, 0.08);
|
/* box-shadow: var(--shadow-lg), var(--glow-secondary); */
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 40px;
|
color: var(--text-primary);
|
||||||
font-size: 2.8rem;
|
font-size: 2em;
|
||||||
background: linear-gradient(45deg, #0d47a1, #1976d2);
|
margin-bottom: 1.5em;
|
||||||
-webkit-background-clip: text;
|
text-shadow: var(--glow-secondary);
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 3px;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input {
|
:deep(.el-input__wrapper) {
|
||||||
width: 100%;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input :deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper:hover) {
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
border-radius: 12px;
|
border-color: var(--primary-color);
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.2), var(--glow-secondary);
|
||||||
padding: 8px 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input :deep(.el-input__wrapper:hover) {
|
:deep(.el-input__wrapper.is-focus) {
|
||||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
|
background: rgba(255, 255, 255, 0.3);
|
||||||
transform: translateY(-2px);
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 15px rgba(255, 255, 255, 0.3), var(--glow-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button {
|
:deep(.el-input__inner) {
|
||||||
width: 100%;
|
color: var(--text-primary);
|
||||||
height: 48px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 1.2px;
|
|
||||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button:not(.el-button--text) {
|
:deep(.el-input__inner::placeholder) {
|
||||||
background: linear-gradient(45deg, #0d47a1, #1976d2);
|
color: var(--text-muted);
|
||||||
border: none;
|
|
||||||
box-shadow: 0 4px 15px rgba(13, 71, 161, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button:not(.el-button--text):hover {
|
:deep(.el-form-item__label) {
|
||||||
transform: translateY(-3px);
|
color: var(--text-secondary);
|
||||||
box-shadow: 0 8px 25px rgba(30, 136, 229, 0.5);
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--primary) {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
border-color: var(--primary-dark);
|
||||||
|
height: 40px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--primary:hover) {
|
||||||
|
background: var(--primary-dark);
|
||||||
|
border-color: var(--primary-dark);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: var(--glow-primary);
|
||||||
|
color: var(--text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--text) {
|
||||||
|
color: var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button--text:hover) {
|
||||||
|
color: var(--primary-light);
|
||||||
|
text-shadow: var(--glow-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-link {
|
.toggle-link {
|
||||||
margin-top: 35px;
|
text-align: center;
|
||||||
text-align: center;
|
margin-top: 1em;
|
||||||
padding: 20px 16px 0;
|
color: var(--text-secondary);
|
||||||
border-top: 1px solid rgba(235, 238, 245, 0.8);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-link span {
|
|
||||||
margin-right: 8px;
|
|
||||||
color: #606266;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-link .el-button {
|
|
||||||
padding: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
background: none;
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
margin-left: 4px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-link .el-button:hover {
|
|
||||||
color: #1e88e5;
|
|
||||||
transform: scale(1.08);
|
|
||||||
text-shadow: 0 0 10px rgba(30, 136, 229, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-form-item {
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.login-card {
|
|
||||||
padding: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
margin-bottom: 35px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+293
-98
@@ -1,131 +1,326 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home-page">
|
<div class="home-page">
|
||||||
<!-- 粒子背景 -->
|
<!-- 粒子背景 -->
|
||||||
<Particles id="tsparticles" :options="particlesOptions" />
|
<Particles id="tsparticles" :options="particlesOptions" />
|
||||||
|
|
||||||
<!-- 页面内容 -->
|
<!-- 页面内容 -->
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1 class="title">欢迎来到钓鱼大冒险</h1>
|
<div class="title-container">
|
||||||
|
<h1 class="title">欢迎来到钓鱼大冒险</h1>
|
||||||
|
<p class="subtitle">开启你的海洋探索之旅</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-card class="menu-card">
|
<div class="menu-grid">
|
||||||
<el-row :gutter="20" justify="center">
|
<div
|
||||||
<el-col :span="8" v-for="item in menuItems" :key="item.name">
|
v-for="item in menuItems"
|
||||||
<el-button type="primary" size="large" class="menu-btn" @click="navigateTo(item.route)" plain>
|
:key="item.name"
|
||||||
{{ item.label }}
|
class="menu-item"
|
||||||
</el-button>
|
@click="navigateTo(item.route)"
|
||||||
</el-col>
|
>
|
||||||
</el-row>
|
<div class="menu-card">
|
||||||
</el-card>
|
<div class="menu-icon" v-html="item.icon"></div>
|
||||||
|
<h3 class="menu-title">{{ item.label }}</h3>
|
||||||
|
<p class="menu-description">{{ item.description }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ label: '🎒 查看装备', route: 'equipments' },
|
{
|
||||||
{ label: '🎣 拉竿钓鱼', route: 'fishing' },
|
label: "查看装备",
|
||||||
{ label: '🐟 查看鱼篓', route: 'fishbaskets' },
|
route: "equipments",
|
||||||
{ label: '🛒 道具商店', route: 'shop' },
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M20 7h-5V4c0-1.1-.9-2-2-2h-2C9.9 2 9 2.9 9 4v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2zM9 12c.83 0 1.5.67 1.5 1.5S9.83 15 9 15s-1.5-.67-1.5-1.5S8.17 12 9 12zm6 6H9v-.75c0-1 2-1.5 3-1.5s3 .5 3 1.5V18z"/></svg>`,
|
||||||
{ label: '💰 市场信息', route: 'market' },
|
description: "管理你的钓鱼装备和道具",
|
||||||
{ label: '🏆 游戏排名', route: 'ranking' }
|
},
|
||||||
]
|
{
|
||||||
|
label: "拉竿钓鱼",
|
||||||
|
route: "fishing",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M12 12.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zm0 4a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm1-7.5V5.7c3.4-.4 6-3.2 6-6.7h-2c0 2.8-2.2 5-5 5S7 1.8 7 -1H5c0 3.5 2.6 6.3 6 6.7V9H9v2h6V9h-2z"/></svg>`,
|
||||||
|
description: "开始一场精彩的钓鱼冒险",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "查看鱼篓",
|
||||||
|
route: "fishbaskets",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M18.5 11c.17 0 .34.01.5.03V7c0-1.1-.9-2-2-2h-1V3c0-.55-.45-1-1-1h-6c-.55 0-1 .45-1 1v2H7c-1.1 0-2 .9-2 2v4.03c.16-.02.33-.03.5-.03.83 0 1.58.34 2.12.88.54-.54 1.29-.88 2.12-.88.83 0 1.58.34 2.12.88.54-.54 1.29-.88 2.12-.88M9 4h6v1H9zm10 16H5c-.55 0-1-.45-1-1v-8.51c.42-.34.75-.77.93-1.27.18-.5.19-1.03.03-1.53-.22-.67-.74-1.19-1.41-1.41-.21-.07-.43-.11-.65-.13V7c0-.55.45-1 1-1h1v2c0 .55.45 1 1 1h8c.55 0 1-.45 1-1V6h1c.55 0 1 .45 1 1v.14c-.22.02-.44.06-.65.13-.67.22-1.19.74-1.41 1.41-.16.5-.15 1.03.03 1.53.18.5.51.93.93 1.27V19c0 .55-.45 1-1 1z"/></svg>`,
|
||||||
|
description: "查看你的渔获和战利品",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "道具商店",
|
||||||
|
route: "shop",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zM1 2v2h2l3.6 7.59-1.35 2.45c-.16.28-.25.61-.25.96 0 1.1.9 2 2 2h12v-2H7.42c-.14 0-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.08-.14.12-.31.12-.48 0-.55-.45-1-1-1H5.21l-.94-2H1zm16 16c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg>`,
|
||||||
|
description: "购买钓鱼装备和特殊道具",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "市场信息",
|
||||||
|
route: "market",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z"/></svg>`,
|
||||||
|
description: "了解最新的市场行情和价格",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "游戏排名",
|
||||||
|
route: "ranking",
|
||||||
|
icon: `<svg viewBox="0 0 24 24" width="32" height="32"><path fill="currentColor" d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM7 10.82C5.84 10.4 5 9.3 5 8V7h2v3.82zM19 8c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>`,
|
||||||
|
description: "查看玩家排行榜和成就",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const navigateTo = (page) => {
|
const navigateTo = (page) => {
|
||||||
router.push({ name: page })
|
router.push({ name: page });
|
||||||
}
|
};
|
||||||
|
|
||||||
const particlesOptions = {
|
const particlesOptions = {
|
||||||
background: {
|
background: {
|
||||||
color: { value: "#001f3f" }
|
color: { value: "transparent" },
|
||||||
|
},
|
||||||
|
particles: {
|
||||||
|
number: { value: 50, density: { enable: true, area: 800 } },
|
||||||
|
color: {
|
||||||
|
value: [
|
||||||
|
"var(--primary-color)",
|
||||||
|
"var(--primary-dark)",
|
||||||
|
"var(--text-primary)",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
particles: {
|
shape: { type: ["circle", "star"] },
|
||||||
number: { value: 40, density: { enable: true, area: 800 } },
|
opacity: {
|
||||||
color: { value: "#00d9ff" },
|
value: 0.6,
|
||||||
shape: { type: "circle" },
|
random: true,
|
||||||
opacity: {
|
anim: {
|
||||||
value: 0.7,
|
enable: true,
|
||||||
random: true,
|
speed: 0.3,
|
||||||
anim: {
|
opacity_min: 0.2,
|
||||||
enable: true,
|
sync: false,
|
||||||
speed: 0.5,
|
},
|
||||||
opacity_min: 0.3,
|
|
||||||
sync: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
value: 6,
|
|
||||||
random: true,
|
|
||||||
anim: {
|
|
||||||
enable: true,
|
|
||||||
speed: 2,
|
|
||||||
size_min: 0.3,
|
|
||||||
sync: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move: {
|
|
||||||
enable: true,
|
|
||||||
speed: 1.5,
|
|
||||||
direction: "top",
|
|
||||||
outModes: { default: "out" }
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
interactivity: {
|
size: {
|
||||||
events: {
|
value: 4,
|
||||||
onHover: { enable: true, mode: "bubble" },
|
random: true,
|
||||||
resize: true
|
anim: {
|
||||||
},
|
enable: true,
|
||||||
modes: {
|
speed: 1,
|
||||||
bubble: {
|
size_min: 0.2,
|
||||||
distance: 150,
|
sync: false,
|
||||||
size: 12,
|
},
|
||||||
duration: 2,
|
|
||||||
opacity: 1,
|
|
||||||
speed: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
detectRetina: true
|
move: {
|
||||||
}
|
enable: true,
|
||||||
|
speed: 1,
|
||||||
|
direction: "none",
|
||||||
|
random: true,
|
||||||
|
straight: false,
|
||||||
|
outModes: { default: "bounce" },
|
||||||
|
attract: {
|
||||||
|
enable: true,
|
||||||
|
rotateX: 600,
|
||||||
|
rotateY: 1200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
enable: true,
|
||||||
|
distance: 150,
|
||||||
|
color: "var(--primary-color)",
|
||||||
|
opacity: 0.2,
|
||||||
|
width: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interactivity: {
|
||||||
|
events: {
|
||||||
|
onHover: { enable: true, mode: ["grab", "bubble"] },
|
||||||
|
onClick: { enable: true, mode: "push" },
|
||||||
|
resize: true,
|
||||||
|
},
|
||||||
|
modes: {
|
||||||
|
grab: {
|
||||||
|
distance: 200,
|
||||||
|
links: { opacity: 0.8 },
|
||||||
|
},
|
||||||
|
bubble: {
|
||||||
|
distance: 200,
|
||||||
|
size: 6,
|
||||||
|
duration: 2,
|
||||||
|
opacity: 0.8,
|
||||||
|
speed: 3,
|
||||||
|
},
|
||||||
|
push: {
|
||||||
|
quantity: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
detectRetina: true,
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.home-page {
|
.home-page {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--bg-gradient-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tsparticles {
|
#tsparticles {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
max-width: 800px;
|
width: 100%;
|
||||||
margin: 60px auto;
|
max-width: 1200px;
|
||||||
text-align: center;
|
margin: 20px auto;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title-container {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 2.5rem;
|
font-size: 3rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 30px;
|
color: var(--text-primary);
|
||||||
color: #ffffff;
|
margin-bottom: 15px;
|
||||||
|
text-shadow: var(--glow-primary);
|
||||||
|
animation: glow 2s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: -10px;
|
||||||
|
text-shadow: var(--glow-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
padding: 8px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-card {
|
.menu-card {
|
||||||
background-color: rgba(255, 255, 255, 0.85);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
border-radius: 12px;
|
backdrop-filter: blur(15px);
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 20px 16px;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 150px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.menu-btn {
|
|
||||||
width: 100%;
|
.menu-icon {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 12px;
|
||||||
font-size: 16px;
|
color: var(--primary-color);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 10px;
|
||||||
|
background: rgba(var(--primary-rgb), 0.1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-description {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 12px 40px rgba(var(--primary-rgb), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-card:hover .menu-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
color: var(--primary-dark);
|
||||||
|
background: rgba(var(--primary-rgb), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-description {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
opacity: 0.9;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-card:hover .menu-description {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 6px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-card {
|
||||||
|
min-height: 140px;
|
||||||
|
padding: 16px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-description {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+318
-125
@@ -1,163 +1,356 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="market-page">
|
<div class="market-page">
|
||||||
<h1>🛒 京海鱼市交易中心</h1>
|
<div class="market-header">
|
||||||
|
<h1 class="title">🛒 京海鱼市交易中心</h1>
|
||||||
<el-tabs v-model="activeTab" @tab-click="handleTabChange" type="border-card">
|
|
||||||
<el-tab-pane label="鱼市" name="all" />
|
|
||||||
<el-tab-pane label="我的市场" name="mine" />
|
|
||||||
<el-tab-pane label="交易记录" name="history" />
|
|
||||||
</el-tabs>
|
|
||||||
|
|
||||||
<!-- 鱼市表格 -->
|
|
||||||
<el-table v-if="activeTab === 'all'" :data="marketList" style="width: 100%" height="calc(100vh - 110px)" border>
|
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
|
||||||
<el-table-column prop="fishName" label="鱼名" />
|
|
||||||
<el-table-column prop="weight" label="重量" align="right" width="150" />
|
|
||||||
<el-table-column prop="sellerName" label="卖家" width="200" />
|
|
||||||
<el-table-column prop="points" label="价格(点数)" width="150" align="right" />
|
|
||||||
<el-table-column label="操作" width="150">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="success" @click="buyFish(row)">购买</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 我的市场表格 -->
|
|
||||||
<el-table v-if="activeTab === 'mine'" :data="myMarketList" style="width: 100%" height="calc(100vh - 110px)" border>
|
|
||||||
<el-table-column prop="id" label="ID" width="80" />
|
|
||||||
<el-table-column prop="fishName" label="鱼名" />
|
|
||||||
<el-table-column prop="points" label="挂售价格" align="right" />
|
|
||||||
<el-table-column label="操作">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button type="danger" @click="removeMyListing(row)">下架</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 历史记录 -->
|
|
||||||
<el-card v-if="activeTab === 'history'" class="history-box">
|
|
||||||
<div v-if="historyList.length">
|
|
||||||
<p v-for="(item, index) in historyList" :key="index">{{ item }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<el-empty description="暂无交易记录" />
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="market-controls">
|
||||||
|
<el-tabs
|
||||||
|
v-model="activeTab"
|
||||||
|
@tab-click="handleTabChange"
|
||||||
|
type="border-card"
|
||||||
|
>
|
||||||
|
<el-tab-pane label="鱼市" name="all" />
|
||||||
|
<el-tab-pane label="我的市场" name="mine" />
|
||||||
|
<el-tab-pane label="交易记录" name="history" />
|
||||||
|
</el-tabs>
|
||||||
|
|
||||||
|
<div v-if="activeTab !== 'history'" class="search-filters">
|
||||||
|
<div class="search-box">
|
||||||
|
<el-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="搜索鱼类..."
|
||||||
|
prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="sort-control">
|
||||||
|
<el-select v-model="sortBy" placeholder="排序方式">
|
||||||
|
<el-option label="价格从低到高" value="priceAsc" />
|
||||||
|
<el-option label="价格从高到低" value="priceDesc" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 鱼市展示 -->
|
||||||
|
<div v-if="activeTab === 'all'" class="market-grid">
|
||||||
|
<el-empty v-if="filteredMarketList.length === 0" description="暂无商品" />
|
||||||
|
<el-card
|
||||||
|
v-for="item in filteredMarketList"
|
||||||
|
:key="item.id"
|
||||||
|
class="fish-card"
|
||||||
|
>
|
||||||
|
<div class="fish-icon">🐟</div>
|
||||||
|
<h3 class="fish-name">{{ item.fishName }}</h3>
|
||||||
|
<div class="seller-info">
|
||||||
|
<span class="seller-label">卖家:</span>
|
||||||
|
<span class="seller-name">{{ item.sellerName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="price-tag">
|
||||||
|
<span class="points-icon">💰</span>
|
||||||
|
<span class="points-value">{{ item.points }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<el-button type="primary" @click="buyFish(item)">购买</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 我的市场 -->
|
||||||
|
<div v-if="activeTab === 'mine'" class="market-grid">
|
||||||
|
<el-empty
|
||||||
|
v-if="filteredMyMarketList.length === 0"
|
||||||
|
description="暂无挂售商品"
|
||||||
|
/>
|
||||||
|
<el-card
|
||||||
|
v-for="item in filteredMyMarketList"
|
||||||
|
:key="item.id"
|
||||||
|
class="fish-card my-listing"
|
||||||
|
>
|
||||||
|
<div class="fish-icon">🐟</div>
|
||||||
|
<h3 class="fish-name">{{ item.fishName }}</h3>
|
||||||
|
<div class="price-tag">
|
||||||
|
<span class="points-icon">💰</span>
|
||||||
|
<span class="points-value">{{ item.points }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-action">
|
||||||
|
<el-button type="danger" @click="removeMyListing(item)"
|
||||||
|
>下架</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 历史记录 -->
|
||||||
|
<el-card v-if="activeTab === 'history'" class="history-box">
|
||||||
|
<div v-if="historyList.length" class="history-list">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in historyList"
|
||||||
|
:key="index"
|
||||||
|
class="history-item"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-else description="暂无交易记录" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { getAllMarketplace, getMyMarketplace, getMarketplaceHistory, buyFromMarketplace, downMyFish } from '@/api/market/market'
|
import {
|
||||||
|
getAllMarketplace,
|
||||||
|
getMyMarketplace,
|
||||||
|
getMarketplaceHistory,
|
||||||
|
buyFromMarketplace,
|
||||||
|
downMyFish,
|
||||||
|
} from "@/api/market/market";
|
||||||
|
|
||||||
const activeTab = ref('all')
|
const activeTab = ref("all");
|
||||||
|
const searchQuery = ref("");
|
||||||
|
const sortBy = ref("priceAsc");
|
||||||
|
|
||||||
const marketList = ref([])
|
const marketList = ref([]);
|
||||||
const myMarketList = ref([])
|
const myMarketList = ref([]);
|
||||||
const historyList = ref([])
|
const historyList = ref([]);
|
||||||
|
|
||||||
|
// 过滤和排序市场列表
|
||||||
|
const filteredMarketList = computed(() => {
|
||||||
|
let filtered = marketList.value.filter((item) =>
|
||||||
|
item.fishName.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
return filtered.sort((a, b) => {
|
||||||
|
if (sortBy.value === "priceAsc") {
|
||||||
|
return a.points - b.points;
|
||||||
|
} else {
|
||||||
|
return b.points - a.points;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 过滤我的市场列表
|
||||||
|
const filteredMyMarketList = computed(() => {
|
||||||
|
return myMarketList.value.filter((item) =>
|
||||||
|
item.fishName.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// 切换标签
|
// 切换标签
|
||||||
const handleTabChange = () => {
|
const handleTabChange = () => {
|
||||||
if (activeTab.value === 'all') {
|
searchQuery.value = "";
|
||||||
fetchAllMarketplace()
|
if (activeTab.value === "all") {
|
||||||
} else if (activeTab.value === 'mine') {
|
fetchAllMarketplace();
|
||||||
fetchMyMarketplace()
|
} else if (activeTab.value === "mine") {
|
||||||
} else if (activeTab.value === 'history') {
|
fetchMyMarketplace();
|
||||||
fetchHistory()
|
} else if (activeTab.value === "history") {
|
||||||
}
|
fetchHistory();
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取鱼市数据
|
// 获取鱼市数据
|
||||||
const fetchAllMarketplace = async () => {
|
const fetchAllMarketplace = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getAllMarketplace()
|
const res = await getAllMarketplace();
|
||||||
marketList.value = res
|
marketList.value = res;
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('获取鱼市数据失败')
|
ElMessage.error("获取鱼市数据失败");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 获取我的市场数据
|
// 获取我的市场数据
|
||||||
const fetchMyMarketplace = async () => {
|
const fetchMyMarketplace = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getMyMarketplace()
|
const res = await getMyMarketplace();
|
||||||
myMarketList.value = res
|
myMarketList.value = res;
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('获取我的市场数据失败')
|
ElMessage.error("获取我的市场数据失败");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 获取交易记录
|
// 获取交易记录
|
||||||
const fetchHistory = async () => {
|
const fetchHistory = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getMarketplaceHistory()
|
const res = await getMarketplaceHistory();
|
||||||
historyList.value = res
|
historyList.value = res;
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('获取历史记录失败')
|
ElMessage.error("获取历史记录失败");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 购买鱼
|
// 购买鱼
|
||||||
const buyFish = async (item) => {
|
const buyFish = async (item) => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await ElMessageBox.confirm(
|
||||||
`是否确认花费 ${item.points} 点数购买 ${item.fishName}?`,
|
`是否确认花费 ${item.points} 点数购买 ${item.fishName}?`,
|
||||||
'确认购买',
|
"确认购买",
|
||||||
{
|
{
|
||||||
confirmButtonText: '购买',
|
confirmButtonText: "购买",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning',
|
type: "warning",
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
const res = await buyFromMarketplace(item.id)
|
const res = await buyFromMarketplace(item.id);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success('购买成功')
|
ElMessage.success("购买成功");
|
||||||
fetchAllMarketplace()
|
fetchAllMarketplace();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '购买失败')
|
ElMessage.error(res.message || "购买失败");
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err !== 'cancel') {
|
|
||||||
ElMessage.error('购买过程中出错')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
if (err !== "cancel") {
|
||||||
|
ElMessage.error("购买过程中出错");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 下架自己挂售的鱼
|
// 下架自己挂售的鱼
|
||||||
const removeMyListing = async (item) => {
|
const removeMyListing = async (item) => {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定下架 ${item.fishName} 吗?`, '确认操作', {
|
await ElMessageBox.confirm(`确定下架 ${item.fishName} 吗?`, "确认操作", {
|
||||||
confirmButtonText: '下架',
|
confirmButtonText: "下架",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning',
|
type: "warning",
|
||||||
})
|
});
|
||||||
const res = await downMyFish(item.id)
|
const res = await downMyFish(item.id);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success('下架成功')
|
ElMessage.success("下架成功");
|
||||||
fetchMyMarketplace()
|
fetchMyMarketplace();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(res.message || '操作失败')
|
ElMessage.error(res.message || "操作失败");
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err !== 'cancel') {
|
|
||||||
ElMessage.error('操作失败')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
|
if (err !== "cancel") {
|
||||||
|
ElMessage.error("操作失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchAllMarketplace()
|
fetchAllMarketplace();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.market-page {
|
.market-page {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--bg-gradient-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-header {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: var(--glow-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-controls {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-card {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fish-name {
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.seller-info {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-tag {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-value {
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-action {
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-box {
|
.history-box {
|
||||||
margin-top: 20px;
|
background: rgba(255, 255, 255, 0.1);
|
||||||
line-height: 1.8;
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-filters {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.market-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+277
-72
@@ -1,96 +1,301 @@
|
|||||||
<!--
|
|
||||||
* @Descripttion:
|
|
||||||
* @version: 1.0.0
|
|
||||||
* @Author: LyMy
|
|
||||||
* @Date: 2025-04-11 16:51:41
|
|
||||||
* @LastEditors: LyMy
|
|
||||||
* @LastEditTime: 2025-04-15 17:26:25
|
|
||||||
* @FilePath: \go_fish_web\src\pages\shop\Shop.vue
|
|
||||||
-->
|
|
||||||
<template>
|
<template>
|
||||||
<div class="shop-page">
|
<div class="shop-page">
|
||||||
<h1>商店 - 您当前拥有 {{ points }} 积分</h1>
|
<div class="shop-header">
|
||||||
|
<h1 class="title">🏪 商店</h1>
|
||||||
<el-table :data="items" style="width: 100%;" height="calc(100vh - 110px)" border>
|
<div class="points-display">
|
||||||
<el-table-column label="ID" prop="id" width="100" align="center" />
|
<span class="points-icon">💰</span>
|
||||||
<el-table-column label="名称" prop="name" width="200" align="center" />
|
<span class="points-value">{{ points }}</span>
|
||||||
<el-table-column label="描述" prop="description" />
|
<span class="points-label">积分</span>
|
||||||
<el-table-column label="价格(积分)" prop="points" width="150" align="right" />
|
</div>
|
||||||
<el-table-column label="您已拥有" prop="myQuantity" width="150" align="right" />
|
|
||||||
<el-table-column label="操作" width="180" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-button v-if="row.points <= points" type="primary" @click="buyItem(row)">
|
|
||||||
购买
|
|
||||||
</el-button>
|
|
||||||
<el-button v-else type="warning" disabled>
|
|
||||||
积分不足
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="shop-controls">
|
||||||
|
<div class="search-box">
|
||||||
|
<el-input
|
||||||
|
v-model="searchQuery"
|
||||||
|
placeholder="搜索商品..."
|
||||||
|
prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="category-tags">
|
||||||
|
<el-tag
|
||||||
|
:class="{ active: selectedCategory === 'all' }"
|
||||||
|
@click="selectedCategory = 'all'"
|
||||||
|
>全部</el-tag
|
||||||
|
>
|
||||||
|
<el-tag
|
||||||
|
v-for="category in categories"
|
||||||
|
:key="category"
|
||||||
|
:class="{ active: selectedCategory === category }"
|
||||||
|
@click="selectedCategory = category"
|
||||||
|
>{{ category }}</el-tag
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="items-grid">
|
||||||
|
<el-empty v-if="filteredItems.length === 0" description="暂无商品" />
|
||||||
|
<el-card
|
||||||
|
v-for="item in filteredItems"
|
||||||
|
:key="item.id"
|
||||||
|
class="item-card"
|
||||||
|
:class="{ 'insufficient-points': item.points > points }"
|
||||||
|
>
|
||||||
|
<div class="item-icon">
|
||||||
|
<span v-if="item.name.indexOf('竿') != -1" class="emoji-icon"
|
||||||
|
>🎣</span
|
||||||
|
>
|
||||||
|
<span v-else-if="item.name === '蚯蚓'" class="emoji-icon">🪱</span>
|
||||||
|
<span v-else-if="item.name === '积分'" class="emoji-icon">🏳</span>
|
||||||
|
<span v-else-if="item.name === '渔具箱'" class="emoji-icon">🧰</span>
|
||||||
|
<span v-else class="emoji-icon">📦</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="item-name">{{ item.name }}</h3>
|
||||||
|
<p class="item-description">{{ item.description }}</p>
|
||||||
|
<div class="item-details">
|
||||||
|
<div class="points-cost">
|
||||||
|
<span class="points-icon">💰</span>
|
||||||
|
{{ item.points }}
|
||||||
|
</div>
|
||||||
|
<div class="owned-quantity">已拥有: {{ item.myQuantity }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-action">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="item.points > points"
|
||||||
|
@click="buyItem(item)"
|
||||||
|
>
|
||||||
|
{{ item.points <= points ? "购买" : "积分不足" }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted } from "vue";
|
||||||
import { whatCanIBuy, buy } from '@/api/shop/shop'
|
import { whatCanIBuy, buy } from "@/api/shop/shop";
|
||||||
import { ElButton, ElTable, ElTableColumn, ElMessage, ElInput, ElMessageBox } from 'element-plus'
|
import {
|
||||||
|
ElButton,
|
||||||
|
ElCard,
|
||||||
|
ElTag,
|
||||||
|
ElInput,
|
||||||
|
ElMessage,
|
||||||
|
ElMessageBox,
|
||||||
|
ElEmpty,
|
||||||
|
} from "element-plus";
|
||||||
|
|
||||||
const points = ref(0) // 当前用户的积分
|
const points = ref(0);
|
||||||
const items = ref([]) // 商店物品列表
|
const items = ref([]);
|
||||||
|
const searchQuery = ref("");
|
||||||
|
const selectedCategory = ref("all");
|
||||||
|
|
||||||
// 获取当前用户的积分和商店商品信息
|
// 模拟分类数据,实际应该从后端获取
|
||||||
|
const categories = ["装备", "道具", "特殊物品"];
|
||||||
|
|
||||||
|
// 过滤商品列表
|
||||||
|
const filteredItems = computed(() => {
|
||||||
|
return items.value.filter((item) => {
|
||||||
|
const matchesSearch =
|
||||||
|
item.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
||||||
|
item.description.toLowerCase().includes(searchQuery.value.toLowerCase());
|
||||||
|
const matchesCategory =
|
||||||
|
selectedCategory.value === "all" ||
|
||||||
|
item.category === selectedCategory.value;
|
||||||
|
return matchesSearch && matchesCategory;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取商店数据
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const pointsRes = await whatCanIBuy()
|
const pointsRes = await whatCanIBuy();
|
||||||
points.value = pointsRes.points
|
points.value = pointsRes.points;
|
||||||
items.value = pointsRes.items
|
items.value = pointsRes.items.map((item) => ({
|
||||||
} catch (err) {
|
...item,
|
||||||
ElMessage.error('加载数据失败,请重试 🛍️')
|
category: categories[Math.floor(Math.random() * categories.length)], // 模拟分类数据
|
||||||
}
|
}));
|
||||||
}
|
} catch (err) {
|
||||||
|
ElMessage.error("加载数据失败,请重试 🛍️");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 购买操作
|
// 购买操作
|
||||||
const buyItem = async (good) => {
|
const buyItem = async (good) => {
|
||||||
const { value: count } = await ElMessageBox.prompt('请输入购买数量', `物品名称 (${good.name})`, {
|
const { value: count } = await ElMessageBox.prompt(
|
||||||
confirmButtonText: '确认',
|
"请输入购买数量",
|
||||||
cancelButtonText: '取消',
|
`购买 ${good.name}`,
|
||||||
inputPattern: /^(?:[1-9]\d{0,3}|10000)$/,
|
{
|
||||||
inputErrorMessage: '请输入 1 到 10000 之间的整数'
|
confirmButtonText: "确认",
|
||||||
})
|
cancelButtonText: "取消",
|
||||||
|
inputPattern: /^(?:[1-9]\d{0,3}|10000)$/,
|
||||||
if (count) {
|
inputErrorMessage: "请输入 1 到 10000 之间的整数",
|
||||||
try {
|
|
||||||
const res = await await buy({ EquipmentId: good.id, Quantity: count })
|
|
||||||
if (res) {
|
|
||||||
good.myQuantity += Number(count)
|
|
||||||
points.value -= good.points * count
|
|
||||||
ElMessage.success("购买成功 😎")
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.message + ' 😞')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
ElMessage.error('购买失败,请稍后再试 👿')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
|
||||||
|
if (count) {
|
||||||
|
try {
|
||||||
|
const res = await buy({ EquipmentId: good.id, Quantity: count });
|
||||||
|
if (res) {
|
||||||
|
good.myQuantity += Number(count);
|
||||||
|
points.value -= good.points * count;
|
||||||
|
ElMessage.success("购买成功 🎉");
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.message + " 😞");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ElMessage.error("购买失败,请稍后再试 👿");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 页面加载时获取数据
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData()
|
fetchData();
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.shop-page {
|
.shop-page {
|
||||||
margin: 20px;
|
padding: 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--bg-gradient-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-table {
|
.shop-header {
|
||||||
margin-top: 20px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button {
|
.title {
|
||||||
margin-top: 10px;
|
font-size: 2.5rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
text-shadow: var(--glow-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-display {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-label {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shop-controls {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
&.active {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-card {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.insufficient-points {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.points-cost {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
color: var(--primary-color);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-action {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.shop-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+2
-3
@@ -24,10 +24,9 @@ a:hover {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
min-height: 100vh;
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
:root {
|
||||||
|
/* 主题色 */
|
||||||
|
--primary-color: #00d9ff;
|
||||||
|
--primary-light: #66b1ff;
|
||||||
|
--primary-dark: #0088cc;
|
||||||
|
|
||||||
|
/* 背景色 */
|
||||||
|
--bg-gradient-primary: linear-gradient(135deg, #001f3f 0%, #003366 100%);
|
||||||
|
--bg-gradient-secondary: linear-gradient(45deg, #0d47a1, #1976d2);
|
||||||
|
--bg-white: #ffffff;
|
||||||
|
--bg-light: #f5f7fa;
|
||||||
|
--bg-card: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
/* 文字颜色 */
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: rgba(255, 255, 255, 0.8);
|
||||||
|
--text-muted: rgba(255, 255, 255, 0.7);
|
||||||
|
--text-dark: #606266;
|
||||||
|
|
||||||
|
/* 边框和阴影 */
|
||||||
|
--border-color: rgba(255, 255, 255, 0.2);
|
||||||
|
--shadow-sm: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
|
/* 状态颜色 */
|
||||||
|
--success-color: #67c23a;
|
||||||
|
--warning-color: #e6a23c;
|
||||||
|
--danger-color: #f56c6c;
|
||||||
|
--info-color: #909399;
|
||||||
|
|
||||||
|
/* 特效 */
|
||||||
|
--glow-primary: 0 0 10px rgba(0, 217, 255, 0.5);
|
||||||
|
--glow-secondary: 0 0 5px rgba(0, 217, 255, 0.3);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user