This commit is contained in:
zyb 2025-05-06 19:57:50 +08:00
parent 56d8d7a345
commit 062550d126
22 changed files with 14472 additions and 0 deletions

View File

@ -1 +1,2 @@
#DCF #DCF
first push

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

2887
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "untitled2",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.9.0",
"vue": "^3.5.13",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.3",
"vite": "^6.2.4",
"vite-plugin-vue-devtools": "^7.7.2"
}
}

9231
public/Weapon.xml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

128
src/App.vue Normal file
View File

@ -0,0 +1,128 @@
<script setup>
</script>
<template>
<div class="app">
<nav class="navbar">
<div class="nav-container">
<div class="nav-left">
<div class="nav-brand">红色警戒3数据分析中心</div>
<router-link to="/" class="nav-link">热门下载地图</router-link>
<router-link to="/weekly" class="nav-link">每周推荐</router-link>
<router-link to="/weapon-match" class="nav-link">Weapon 匹配</router-link>
</div>
</div>
</nav>
<main class="main-content">
<router-view></router-view>
</main>
</div>
</template>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f7fa;
color: #2c3e50;
line-height: 1.6;
}
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.navbar {
background: linear-gradient(135deg, #71eaeb 0%, #416bdf 100%);
padding: 0;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
height: 60px;
display: flex;
align-items: center;
}
.nav-left {
display: flex;
align-items: center;
gap: 30px;
}
.nav-brand {
color: white;
font-size: 1.5rem;
font-weight: 600;
text-decoration: none;
white-space: nowrap;
}
.nav-link {
color: rgba(255, 255, 255, 0.9);
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
transition: all 0.3s ease;
font-weight: 500;
}
.nav-link:hover {
background-color: rgba(255, 255, 255, 0.1);
color: white;
}
.nav-link.router-link-active {
background-color: rgba(255, 255, 255, 0.2);
color: white;
}
.main-content {
margin-top: 60px;
padding: 20px;
flex: 1;
max-width: 1200px;
width: 100%;
margin-left: auto;
margin-right: auto;
}
/* 响应式设计 */
@media (max-width: 768px) {
.nav-container {
padding: 0 15px;
}
.nav-left {
gap: 15px;
}
.nav-brand {
font-size: 1.2rem;
}
.nav-link {
padding: 6px 12px;
}
.main-content {
padding: 15px;
}
}
</style>

65
src/api/maps.js Normal file
View File

@ -0,0 +1,65 @@
import axios from 'axios'
const API_BASE_URL = 'https://ra3.z31.xyz/v1'
// 获取地图列表
export const getMaps = async (params = {}) => {
try {
const response = await axios.get(`${API_BASE_URL}/maps/`, {
params: {
p: params.page || 1,
search: params.search || '',
player_count: params.player_count || '',
tags: params.tags || '',
ordering: params.ordering || '-download_count'
}
})
return response.data
} catch (error) {
console.error('获取地图列表失败:', error)
throw error
}
}
// 获取每周推荐地图
export const getWeeklyTopMaps = async () => {
try {
const response = await axios.get('https://ra3.z31.xyz/v1/maps/', {
params: {
ordering: '-download_count',
format: 'json',
p: 1
}
})
return response.data.results.slice(0, 10)
} catch (error) {
console.error('获取每周推荐地图失败:', error)
throw error
}
}
// 获取地图详情
export const getMapDetail = async (id) => {
try {
const response = await axios.get(`${API_BASE_URL}/maps/${id}/`)
return response.data
} catch (error) {
console.error('获取地图详情失败:', error)
throw error
}
}
// 获取所有标签
export const getAllTags = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/maps/`)
const tags = new Set()
response.data.results.forEach(map => {
map.tags.forEach(tag => tags.add(tag))
})
return Array.from(tags)
} catch (error) {
console.error('获取标签列表失败:', error)
throw error
}
}

176
src/assets/styles/Maps.css Normal file
View File

@ -0,0 +1,176 @@
.maps {
padding: 20px;
}
.search-box {
display: flex;
align-items: center;
}
.search-box input {
padding: 6px 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
width: 200px;
transition: all 0.3s ease;
}
.search-box input:focus {
outline: none;
border-color: #1a237e;
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
.filters {
display: flex;
gap: 16px;
margin-bottom: 20px;
align-items: center;
}
.filter-select {
padding: 6px 12px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
background: #fff;
min-width: 100px;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin-top: 20px;
flex-wrap: wrap;
padding: 20px 0;
}
.page-numbers {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
justify-content: center;
}
.page-btn,
.page-number {
padding: 8px 16px;
border: 1px solid #e0e0e0;
border-radius: 6px;
background: white;
color: #1a237e;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
min-width: 40px;
text-align: center;
display: inline-flex;
align-items: center;
justify-content: center;
}
.page-btn:hover:not(:disabled),
.page-number:hover:not(.active) {
background: #f5f7fa;
border-color: #1a237e;
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.page-number.active {
background: #1a237e;
color: white;
border-color: #1a237e;
}
.page-ellipsis {
color: #666;
padding: 0 8px;
display: inline-flex;
align-items: center;
}
.page-jump {
display: flex;
align-items: center;
gap: 8px;
color: #666;
flex-wrap: wrap;
justify-content: center;
}
.page-jump input {
width: 50px;
padding: 4px 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
text-align: center;
font-size: 14px;
}
.page-jump input:focus {
outline: none;
border-color: #1a237e;
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
}
.jump-btn {
padding: 4px 12px;
border: 1px solid #1a237e;
border-radius: 4px;
background: #1a237e;
color: white;
cursor: pointer;
transition: all 0.2s ease;
font-size: 14px;
}
.jump-btn:hover:not(:disabled) {
background: #283593;
border-color: #283593;
}
.jump-btn:disabled {
background: #e0e0e0;
border-color: #e0e0e0;
color: #999;
cursor: not-allowed;
}
@media (max-width: 768px) {
.search-box input {
width: 100%;
}
.pagination {
gap: 12px;
}
.page-btn,
.page-number {
padding: 6px 12px;
font-size: 13px;
}
.page-jump {
font-size: 13px;
}
.page-jump input {
width: 40px;
padding: 3px 6px;
}
.jump-btn {
padding: 3px 10px;
font-size: 13px;
}
}

View File

@ -0,0 +1,13 @@
.weekly-recommend {
padding: 20px;
}
.header-subtitle {
color: #666;
font-size: 0.9rem;
}
.map-name {
font-weight: 500;
color: #1a237e;
}

View File

@ -0,0 +1,84 @@
/* 表格通用样式 */
.table-container {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.maps-table {
width: 100%;
border-collapse: collapse;
min-width: 1000px;
}
.maps-table th,
.maps-table td {
padding: 16px;
text-align: left;
border-bottom: 1px solid #f0f0f0;
}
.maps-table th {
background-color: #f8f9fa;
font-weight: 600;
color: #1a237e;
white-space: nowrap;
}
.table-row {
cursor: pointer;
transition: all 0.2s ease;
}
.table-row:hover {
background-color: #f8f9fa;
}
/* 预览图样式 */
.preview-cell {
width: 100px;
}
.preview-cell img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 标签样式 */
.tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.tag {
background: #e8eaf6;
color: #1a237e;
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
/* 页面标题样式 */
.page-header {
margin-bottom: 30px;
}
.page-header h1 {
font-size: 1.8rem;
color: #1a237e;
margin: 0 0 8px 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.table-container {
margin: 0 -20px;
border-radius: 0;
}
}

View File

@ -0,0 +1,52 @@
<template>
<div>
<label>
上传 Weapon.xml
<input type="file" @change="onWeaponFileChange" />
</label>
<br />
<label>
上传单位 XML AlliedAntiInfantryInfantry.xml
<input type="file" @change="onUnitFileChange" />
</label>
</div>
</template>
<script setup>
import { ref } from 'vue'
const emit = defineEmits(['updateWeaponTemplates', 'updateUnitDoc'])
const onWeaponFileChange = (e) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = (event) => {
const parser = new DOMParser()
const xml = parser.parseFromString(event.target.result, 'text/xml')
const templates = xml.querySelectorAll('WeaponTemplate')
const ids = new Set()
templates.forEach((t) => {
const id = t.getAttribute('id')
if (id) ids.add(id)
})
emit('updateWeaponTemplates', ids)
}
reader.readAsText(file)
}
const onUnitFileChange = (e) => {
const file = e.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = (event) => {
const parser = new DOMParser()
const xml = parser.parseFromString(event.target.result, 'text/xml')
emit('updateUnitDoc', xml)
}
reader.readAsText(file)
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<table>
<thead>
<tr>
<th>GameObject id</th>
<th>匹配到的 WeaponTemplate</th>
</tr>
</thead>
<tbody>
<tr v-for="(result, index) in matchResults" :key="index">
<td>{{ result.goId }}</td>
<td>{{ result.templates.length > 0 ? result.templates.join(', ') : 'None' }}</td>
</tr>
</tbody>
</table>
</template>
<script setup>
defineProps({
matchResults: {
type: Array,
required: true
}
})
</script>

11
src/components/index.vue Normal file
View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

8
src/main.js Normal file
View File

@ -0,0 +1,8 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')

31
src/router/index.js Normal file
View File

@ -0,0 +1,31 @@
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Maps',
component: () => import('../views/Maps.vue')
},
{
path: '/weekly',
name: 'WeeklyRecommend',
component: () => import('../views/WeeklyRecommend.vue')
},
{
path: '/map/:id',
name: 'MapDetail',
component: () => import('../views/MapDetail.vue')
},
{
path: '/weapon-match',
name: 'WeaponMatch',
component: () => import('../views/WeaponMatch.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router

178
src/views/MapDetail.vue Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

View 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>

16
vite.config.js Normal file
View File

@ -0,0 +1,16 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
server: {
port: 8082,
open: true
}
})