提交
This commit is contained in:
178
src/views/MapDetail.vue
Normal file
178
src/views/MapDetail.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="map-detail" v-if="map">
|
||||
<div class="back-button">
|
||||
<button @click="goBack" class="back-btn">
|
||||
<span class="back-icon">←</span> 返回列表
|
||||
</button>
|
||||
</div>
|
||||
<div class="map-header">
|
||||
<h1>{{ map.chinese_name }}</h1>
|
||||
<p class="author">作者: {{ map.user }}</p>
|
||||
</div>
|
||||
|
||||
<div class="map-content">
|
||||
<div class="map-image">
|
||||
<img :src="map.img_file" :alt="map.chinese_name">
|
||||
</div>
|
||||
|
||||
<div class="map-info">
|
||||
<div class="info-item">
|
||||
<h3>基本信息</h3>
|
||||
<p>下载次数: {{ map.download_count }}</p>
|
||||
<p>收藏次数: {{ map.favourite_count }}</p>
|
||||
<p>玩家数量: {{ map.player_count }}</p>
|
||||
<p>创建时间: {{ formatDate(map.create_time) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
<h3>标签</h3>
|
||||
<div class="tag-list">
|
||||
<span v-for="tag in map.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<a :href="map.zip_file" class="download-btn" download>下载地图</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { getMapDetail } from '../api/maps'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const map = ref(null)
|
||||
|
||||
const fetchMapDetail = async () => {
|
||||
try {
|
||||
map.value = await getMapDetail(route.params.id)
|
||||
} catch (error) {
|
||||
console.error('获取地图详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchMapDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map-detail {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 6px;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background: #e9ecef;
|
||||
border-color: #d0d0d0;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
margin-right: 6px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.map-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.map-header h1 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.author {
|
||||
color: #666;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.map-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.map-image img {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.map-info {
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.info-item h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.tags {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: #e9ecef;
|
||||
padding: 5px 10px;
|
||||
border-radius: 15px;
|
||||
font-size: 14px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.download-btn:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
</style>
|
||||
257
src/views/Maps.vue
Normal file
257
src/views/Maps.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="maps">
|
||||
<div class="page-header">
|
||||
<h1>热门下载地图</h1>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<div class="search-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索地图..."
|
||||
v-model="searchValue"
|
||||
@input="handleSearchInput"
|
||||
@blur="handleSearchBlur"
|
||||
>
|
||||
</div>
|
||||
<select v-model="playerCountFilter" class="filter-select" @change="handleFilterChange">
|
||||
<option value="">玩家数</option>
|
||||
<option v-for="n in [2,3,4,5,6,7,8]" :key="n" :value="n">{{ n }}人</option>
|
||||
</select>
|
||||
<select v-model="tagFilter" class="filter-select" @change="handleFilterChange">
|
||||
<option value="">分类</option>
|
||||
<option v-for="tag in tagOptions" :key="tag" :value="tag">{{ tag }}</option>
|
||||
</select>
|
||||
<select v-model="selectedOrder" class="filter-select" @change="handleFilterChange">
|
||||
<option v-for="opt in orderOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="maps-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>预览图</th>
|
||||
<th>地图名称</th>
|
||||
<th>作者</th>
|
||||
<th>下载次数</th>
|
||||
<th>收藏次数</th>
|
||||
<th>玩家数量</th>
|
||||
<th>创建时间</th>
|
||||
<th>标签</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="map in maps" :key="map.id" @click="goToMapDetail(map.id)" class="table-row">
|
||||
<td class="preview-cell">
|
||||
<img :src="map.thumbnail" :alt="map.chinese_name">
|
||||
</td>
|
||||
<td class="map-name">{{ map.chinese_name }}</td>
|
||||
<td>{{ map.user }}</td>
|
||||
<td>{{ map.download_count }}</td>
|
||||
<td>{{ map.favourite_count }}</td>
|
||||
<td>{{ map.player_count }}</td>
|
||||
<td>{{ formatDate(map.create_time) }}</td>
|
||||
<td>
|
||||
<div class="tags">
|
||||
<span v-for="tag in map.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 分页控件 -->
|
||||
<div class="pagination">
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="currentPage === 1"
|
||||
@click="changePage(currentPage - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
|
||||
<div class="page-numbers">
|
||||
<template v-for="(page, index) in displayedPages" :key="index">
|
||||
<button
|
||||
v-if="typeof page === 'number'"
|
||||
class="page-number"
|
||||
:class="{ active: page === currentPage }"
|
||||
@click="changePage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<span v-else class="page-ellipsis">{{ page }}</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="page-btn"
|
||||
:disabled="!hasNextPage"
|
||||
@click="changePage(currentPage + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
|
||||
<div class="page-jump">
|
||||
<input
|
||||
type="number"
|
||||
v-model="jumpPage"
|
||||
:min="1"
|
||||
:max="totalPages"
|
||||
class="jump-input"
|
||||
@keyup.enter="handleJumpPage"
|
||||
>
|
||||
<button class="jump-btn" @click="handleJumpPage">跳转</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { getMaps, getAllTags } from '../api/maps'
|
||||
import '../assets/styles/common.css'
|
||||
import '../assets/styles/Maps.css'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const maps = ref([])
|
||||
const currentPage = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const hasNextPage = ref(false)
|
||||
const jumpPage = ref('')
|
||||
|
||||
// 排序和筛选相关
|
||||
const orderOptions = [
|
||||
{ label: '下载量降序', value: '-download_count' },
|
||||
{ label: '下载量升序', value: 'download_count' },
|
||||
{ label: '收藏数降序', value: '-favourite_count' },
|
||||
{ label: '收藏数升序', value: 'favourite_count' },
|
||||
{ label: '创建时间降序', value: '-create_time' },
|
||||
{ label: '创建时间升序', value: 'create_time' }
|
||||
]
|
||||
const selectedOrder = ref('-download_count')
|
||||
const searchValue = ref('')
|
||||
const playerCountFilter = ref('')
|
||||
const tagFilter = ref('')
|
||||
const tagOptions = ref([])
|
||||
|
||||
// 计算显示的页码
|
||||
const displayedPages = computed(() => {
|
||||
const pages = []
|
||||
const total = totalPages.value
|
||||
const current = currentPage.value
|
||||
|
||||
if (total <= 5) {
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push(i)
|
||||
}
|
||||
} else if (current <= 3) {
|
||||
pages.push(1, 2, 3, 4, '...', total)
|
||||
} else if (current >= total - 2) {
|
||||
pages.push(1, '...')
|
||||
for (let i = total - 2; i <= total; i++) {
|
||||
if (i > 1) pages.push(i)
|
||||
}
|
||||
} else {
|
||||
pages.push(1, '...', current - 1, current, current + 1, '...', total)
|
||||
}
|
||||
return pages
|
||||
})
|
||||
|
||||
// 获取地图数据
|
||||
const fetchMaps = async (page = 1) => {
|
||||
try {
|
||||
const params = {
|
||||
page,
|
||||
search: searchValue.value,
|
||||
player_count: playerCountFilter.value,
|
||||
tags: tagFilter.value,
|
||||
ordering: selectedOrder.value
|
||||
}
|
||||
console.log('正在获取地图数据,参数:', params)
|
||||
const data = await getMaps(params)
|
||||
maps.value = data.results
|
||||
hasNextPage.value = !!data.next
|
||||
totalPages.value = Math.ceil(data.count / 20)
|
||||
currentPage.value = page
|
||||
console.log('分页状态:', {
|
||||
currentPage: currentPage.value,
|
||||
totalPages: totalPages.value,
|
||||
hasNextPage: hasNextPage.value,
|
||||
resultsCount: data.results.length
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取地图列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理搜索输入
|
||||
const handleSearchInput = () => {
|
||||
if (!searchValue.value) {
|
||||
fetchMaps(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理搜索框失去焦点
|
||||
const handleSearchBlur = () => {
|
||||
if (searchValue.value) {
|
||||
fetchMaps(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理筛选条件变化
|
||||
const handleFilterChange = () => {
|
||||
console.log('筛选条件变化:', {
|
||||
playerCount: playerCountFilter.value,
|
||||
tag: tagFilter.value,
|
||||
order: selectedOrder.value
|
||||
})
|
||||
fetchMaps(1)
|
||||
}
|
||||
|
||||
// 处理页码跳转
|
||||
const handleJumpPage = () => {
|
||||
const page = parseInt(jumpPage.value)
|
||||
if (page && page >= 1 && page <= totalPages.value) {
|
||||
changePage(page)
|
||||
}
|
||||
jumpPage.value = ''
|
||||
}
|
||||
|
||||
// 切换页码
|
||||
const changePage = (page) => {
|
||||
console.log('尝试切换页码:', page, '当前页码:', currentPage.value, '总页数:', totalPages.value)
|
||||
if (page < 1 || page > totalPages.value) {
|
||||
console.log('页码无效:', page)
|
||||
return
|
||||
}
|
||||
currentPage.value = page
|
||||
fetchMaps(page)
|
||||
// 滚动到页面顶部
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
// 跳转到地图详情
|
||||
const goToMapDetail = (id) => {
|
||||
router.push(`/map/${id}`)
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('组件挂载')
|
||||
fetchMaps(1)
|
||||
getAllTags().then(tags => {
|
||||
tagOptions.value = tags
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
1170
src/views/WeaponMatch.vue
Normal file
1170
src/views/WeaponMatch.vue
Normal file
File diff suppressed because it is too large
Load Diff
96
src/views/WeeklyRecommend.vue
Normal file
96
src/views/WeeklyRecommend.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="weekly-recommend">
|
||||
<div class="page-header">
|
||||
<h1>每周地图推荐</h1>
|
||||
<div class="header-subtitle">
|
||||
<span class="date-range">{{ currentWeekRange }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="maps-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>预览图</th>
|
||||
<th>地图名称</th>
|
||||
<th>作者</th>
|
||||
<th>下载次数</th>
|
||||
<th>收藏次数</th>
|
||||
<th>玩家数量</th>
|
||||
<th>创建时间</th>
|
||||
<th>标签</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(map, index) in recommendedMaps" :key="map.id" @click="goToMapDetail(map.id)" class="table-row">
|
||||
<td class="rank-number">{{ index + 1 }}</td>
|
||||
<td class="preview-cell">
|
||||
<img :src="map.thumbnail" :alt="map.chinese_name">
|
||||
</td>
|
||||
<td class="map-name">{{ map.chinese_name }}</td>
|
||||
<td>{{ map.user }}</td>
|
||||
<td>{{ map.download_count }}</td>
|
||||
<td>{{ map.favourite_count }}</td>
|
||||
<td>{{ map.player_count }}</td>
|
||||
<td>{{ formatDate(map.create_time) }}</td>
|
||||
<td>
|
||||
<div class="tags">
|
||||
<span v-for="tag in map.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getWeeklyTopMaps } from '../api/maps'
|
||||
import '../assets/styles/common.css'
|
||||
import '../assets/styles/WeeklyRecommend.css'
|
||||
|
||||
const router = useRouter()
|
||||
const recommendedMaps = ref([])
|
||||
|
||||
// 获取当前周的范围
|
||||
const currentWeekRange = computed(() => {
|
||||
const now = new Date()
|
||||
const start = new Date(now.setDate(now.getDate() - now.getDay()))
|
||||
const end = new Date(now.setDate(now.getDate() - now.getDay() + 6))
|
||||
return `${start.toLocaleDateString('zh-CN')} - ${end.toLocaleDateString('zh-CN')}`
|
||||
})
|
||||
|
||||
const goToMapDetail = (id) => {
|
||||
router.push(`/map/${id}`)
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
const fetchRecommendedMaps = async () => {
|
||||
try {
|
||||
recommendedMaps.value = await getWeeklyTopMaps()
|
||||
} catch (error) {
|
||||
console.error('获取推荐地图失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchRecommendedMaps()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rank-number {
|
||||
font-weight: bold;
|
||||
color: #1a237e;
|
||||
text-align: center;
|
||||
width: 50px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user