You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
7.9 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="market-page">
<div class="market-header">
<h1 class="title">🛒 京海鱼市交易中心</h1>
</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>
<script setup>
import { ref, computed, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
getAllMarketplace,
getMyMarketplace,
getMarketplaceHistory,
buyFromMarketplace,
downMyFish,
} from "@/api/market/market";
const activeTab = ref("all");
const searchQuery = ref("");
const sortBy = ref("priceAsc");
const marketList = ref([]);
const myMarketList = 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 = () => {
searchQuery.value = "";
if (activeTab.value === "all") {
fetchAllMarketplace();
} else if (activeTab.value === "mine") {
fetchMyMarketplace();
} else if (activeTab.value === "history") {
fetchHistory();
}
};
// 获取鱼市数据
const fetchAllMarketplace = async () => {
try {
const res = await getAllMarketplace();
marketList.value = res;
} catch {
ElMessage.error("获取鱼市数据失败");
}
};
// 获取我的市场数据
const fetchMyMarketplace = async () => {
try {
const res = await getMyMarketplace();
myMarketList.value = res;
} catch {
ElMessage.error("获取我的市场数据失败");
}
};
// 获取交易记录
const fetchHistory = async () => {
try {
const res = await getMarketplaceHistory();
historyList.value = res;
} catch {
ElMessage.error("获取历史记录失败");
}
};
// 购买鱼
const buyFish = async (item) => {
try {
await ElMessageBox.confirm(
`是否确认花费 ${item.points} 点数购买 ${item.fishName}`,
"确认购买",
{
confirmButtonText: "购买",
cancelButtonText: "取消",
type: "warning",
}
);
const res = await buyFromMarketplace(item.id);
if (res.success) {
ElMessage.success("购买成功");
fetchAllMarketplace();
} else {
ElMessage.error(res.message || "购买失败");
}
} catch (err) {
if (err !== "cancel") {
ElMessage.error("购买过程中出错");
}
}
};
// 下架自己挂售的鱼
const removeMyListing = async (item) => {
try {
await ElMessageBox.confirm(`确定下架 ${item.fishName} 吗?`, "确认操作", {
confirmButtonText: "下架",
cancelButtonText: "取消",
type: "warning",
});
const res = await downMyFish(item.id);
if (res.success) {
ElMessage.success("下架成功");
fetchMyMarketplace();
} else {
ElMessage.error(res.message || "操作失败");
}
} catch (err) {
if (err !== "cancel") {
ElMessage.error("操作失败");
}
}
};
onMounted(() => {
fetchAllMarketplace();
});
</script>
<style scoped>
.market-page {
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 {
background: rgba(255, 255, 255, 0.1);
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>