feature/login-screen #2

Merged
zyb merged 6 commits from feature/login-screen into master 2025-06-27 17:18:19 +08:00
3 changed files with 384 additions and 0 deletions
Showing only changes of commit 8f30b943a7 - Show all commits

View File

@ -76,6 +76,11 @@ const routes = [
path: 'editors-maps', path: 'editors-maps',
name: 'EditorsMaps', name: 'EditorsMaps',
component: () => import('@/views/index/EditorsMaps.vue') component: () => import('@/views/index/EditorsMaps.vue')
},
{
path:'terrain',
name: 'Terrain',
component: () => import('@/views/index/TerrainList.vue')
} }
] ]
}, },

View File

@ -82,6 +82,7 @@ onUnmounted(() => {
<router-link to="/maps" class="nav-link">最近上传地图</router-link> <router-link to="/maps" class="nav-link">最近上传地图</router-link>
<router-link to="/weekly" class="nav-link">热门下载地图</router-link> <router-link to="/weekly" class="nav-link">热门下载地图</router-link>
<router-link to="/author" class="nav-link">活跃作者推荐</router-link> <router-link to="/author" class="nav-link">活跃作者推荐</router-link>
<router-link to="/terrain" class="nav-link">地形图列表</router-link>
<router-link v-if="isLoggedIn" to="/weapon-match" class="nav-link">Weapon 匹配</router-link> <router-link v-if="isLoggedIn" to="/weapon-match" class="nav-link">Weapon 匹配</router-link>
<router-link v-if="isLoggedIn" to="/competition" class="nav-link">赛程信息</router-link> <router-link v-if="isLoggedIn" to="/competition" class="nav-link">赛程信息</router-link>
<router-link v-if="isLoggedIn" to="/demands" class="nav-link">办事大厅</router-link> <router-link v-if="isLoggedIn" to="/demands" class="nav-link">办事大厅</router-link>

View File

@ -0,0 +1,378 @@
<template>
<div class="map-detail">
<div class="map-header">
<h1>地形图列表</h1>
<div class="filter-controls">
<label for="category-select">分类</label>
<select v-model="selectedCategory" id="category-select" @change="currentPage = 1">
<option v-for="category in categoryList" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
</div>
<div class="pagination-controls">
<button class="pagination-btn" @click="prevPage" :disabled="currentPage === 1">
&lt; 上一页
</button>
<span class="page-info"> {{ currentPage }} / {{ totalPages }} </span>
<button class="pagination-btn" @click="nextPage" :disabled="currentPage === totalPages">
下一页 &gt;
</button>
</div>
<div v-if="loading" class="loading">加载中...</div>
<div v-if="error" class="error">
加载失败: {{ error }}
<button @click="fetchTerrainList" class="back-btn">重试</button>
</div>
<div class="map-content">
<div v-if="filteredTerrainsByCategory.length > 0" class="terrain-grid">
<div v-for="terrain in paginatedTerrains" :key="terrain.key" class="terrain-item">
<div class="map-image">
<img :src="getImageUrl(terrain.key)" :alt="'地形图 ' + terrain.key" />
<div class="image-overlay">
<span class="image-name">{{ terrain.key }}</span>
<a :href="getImageUrl(terrain.key)" class="download-link" download title="下载">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="white" d="M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z" />
</svg>
</a>
</div>
</div>
</div>
</div>
<div v-else-if="!loading" class="no-data">
当前分类下没有可用的地形图数据
</div>
</div>
<div class="pagination-controls bottom">
<button class="pagination-btn" @click="prevPage" :disabled="currentPage === 1">
&lt; 上一页
</button>
<span class="page-info"> {{ currentPage }} / {{ totalPages }} </span>
<button class="pagination-btn" @click="nextPage" :disabled="currentPage === totalPages">
下一页 &gt;
</button>
</div>
</div>
</template>
<script>
export default {
name: 'TerrainList',
data() {
return {
terrains: [],
filteredTerrains: [],
loading: false,
error: null,
currentPage: 1,
itemsPerPage: 100,
apiBaseUrl: 'http://zybdatasupport.online:8000',
categoryList: [],
selectedCategory: '全部',
};
},
computed: {
filteredTerrainsByCategory() {
if (this.selectedCategory === '全部') {
return this.filteredTerrains;
}
return this.filteredTerrains.filter(item =>
item.key.toLowerCase().startsWith(this.selectedCategory + '_')
);
},
totalPages() {
return Math.ceil(this.filteredTerrainsByCategory.length / this.itemsPerPage);
},
paginatedTerrains() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return this.filteredTerrainsByCategory.slice(start, end);
},
},
created() {
this.fetchTerrainList();
},
methods: {
async fetchTerrainList() {
this.loading = true;
this.error = null;
try {
const response = await fetch(`${this.apiBaseUrl}/terrain`);
if (!response.ok) {
throw new Error('获取地形图列表失败');
}
const data = await response.json();
this.terrains = data;
this.filteredTerrains = data.filter(item => this.isImageFile(item.key));
this.extractCategories();
this.currentPage = 1;
} catch (err) {
this.error = err.message;
console.error('Error fetching terrain list:', err);
} finally {
this.loading = false;
}
},
extractCategories() {
const categories = new Set();
this.filteredTerrains.forEach(item => {
const prefix = item.key.split('_')[0].toLowerCase();
categories.add(prefix);
});
this.categoryList = ['全部', ...Array.from(categories).sort()];
},
getImageUrl(key) {
return `http://dataimg-1307694021.cos.ap-beijing.myqcloud.com/Terrain/jpg/${key}`;
},
isImageFile(key) {
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
const lowerKey = key.toLowerCase();
return imageExtensions.some(ext => lowerKey.endsWith(ext));
},
nextPage() {
if (this.currentPage < this.totalPages) {
this.currentPage++;
}
},
prevPage() {
if (this.currentPage > 1) {
this.currentPage--;
}
}
}
};
</script>
<style scoped>
.map-detail {
padding: 15px;
max-width: 1800px;
margin: 0 auto;
}
.map-header {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.map-header h1 {
margin: 0;
color: #333;
font-size: 1.5rem;
}
.filter-controls {
display: flex;
align-items: center;
gap: 8px;
}
select {
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
}
.loading, .error, .no-data {
padding: 15px;
text-align: center;
margin: 20px 0;
}
.error {
color: #d32f2f;
background-color: #ffebee;
}
.no-data {
color: #757575;
}
.terrain-grid {
display: grid;
grid-template-columns: repeat(10, 1fr);
gap: 10px;
}
.terrain-item {
position: relative;
border-radius: 4px;
overflow: hidden;
aspect-ratio: 1;
transition: transform 0.2s;
}
.terrain-item:hover {
transform: scale(1.03);
z-index: 1;
}
.map-image {
position: relative;
width: 100%;
height: 100%;
}
.map-image img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.image-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
padding: 8px;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
opacity: 0;
transition: opacity 0.2s;
}
.terrain-item:hover .image-overlay {
opacity: 1;
}
.image-name {
font-size: 0.7rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 80%;
}
.download-link {
color: white;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
}
.download-link:hover {
color: #4fc3f7;
}
.pagination-controls {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin: 20px 0;
}
.pagination-controls.bottom {
margin-top: 30px;
}
.pagination-btn {
padding: 8px 16px;
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
}
.pagination-btn:hover:not(:disabled) {
background: #e9ecef;
border-color: #d0d0d0;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-info {
font-size: 0.9rem;
color: #666;
}
.back-btn {
display: inline-flex;
align-items: center;
padding: 8px 12px;
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 6px;
color: #333;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
margin-left: 10px;
}
.back-btn:hover {
background: #e9ecef;
border-color: #d0d0d0;
}
@media (max-width: 1800px) {
.terrain-grid {
grid-template-columns: repeat(8, 1fr);
}
}
@media (max-width: 1400px) {
.terrain-grid {
grid-template-columns: repeat(6, 1fr);
}
}
@media (max-width: 1000px) {
.terrain-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 700px) {
.terrain-grid {
grid-template-columns: repeat(3, 1fr);
}
.map-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}
@media (max-width: 500px) {
.terrain-grid {
grid-template-columns: repeat(2, 1fr);
}
.pagination-controls {
flex-direction: column;
gap: 10px;
}
}
</style>