提交
This commit is contained in:
parent
56d8d7a345
commit
062550d126
13
index.html
Normal file
13
index.html
Normal 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
8
jsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
2887
package-lock.json
generated
Normal file
2887
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal 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
9231
public/Weapon.xml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
128
src/App.vue
Normal file
128
src/App.vue
Normal 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
65
src/api/maps.js
Normal 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
176
src/assets/styles/Maps.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
13
src/assets/styles/WeeklyRecommend.css
Normal file
13
src/assets/styles/WeeklyRecommend.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.weekly-recommend {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a237e;
|
||||||
|
}
|
84
src/assets/styles/common.css
Normal file
84
src/assets/styles/common.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
52
src/components/FileUploader.vue
Normal file
52
src/components/FileUploader.vue
Normal 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>
|
||||||
|
|
26
src/components/MatchResultTable.vue
Normal file
26
src/components/MatchResultTable.vue
Normal 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
11
src/components/index.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
8
src/main.js
Normal file
8
src/main.js
Normal 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
31
src/router/index.js
Normal 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
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>
|
16
vite.config.js
Normal file
16
vite.config.js
Normal 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
|
||||||
|
}
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user