数据结构优化

This commit is contained in:
李琦
2026-01-20 14:31:39 +08:00
parent 05b315013c
commit 946855bff2
16 changed files with 104045 additions and 157 deletions

View File

@@ -347,7 +347,7 @@ const runCode = async () => {
doc.write(loadingTemplate)
try {
const response = await fetch('http://localhost:8081/api/run', {
const response = await fetch('/api/run', {
method: 'POST',
headers: {
'Content-Type': 'application/json'

View File

@@ -115,7 +115,7 @@ const uploadFile = async (file: File) => {
formData.append('storageType', 'local')
const token = localStorage.getItem('token')
const response = await fetch('http://localhost:8081/api/admin/attachments/upload', {
const response = await fetch('/api/admin/attachments/upload', {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {})

View File

@@ -95,12 +95,16 @@ const drawStars = () => {
}
}
ctx.globalAlpha = star.alpha
ctx.beginPath()
ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2)
ctx.fill()
if (ctx) {
ctx.globalAlpha = star.alpha
ctx.beginPath()
ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2)
ctx.fill()
}
})
ctx.globalAlpha = 1
if (ctx) {
ctx.globalAlpha = 1
}
}
// 生成流星

File diff suppressed because it is too large Load Diff

View File

@@ -191,7 +191,7 @@ const uploadCategoryOptions = computed(() => {
})
const API_BASE = 'http://localhost:8081/api'
const API_BASE = '/api'
const getAuthHeaders = () => {
const token = localStorage.getItem('token')
return {

View File

@@ -344,7 +344,7 @@ const handlePaste = async (e: ClipboardEvent) => {
formData.append('storageType', 'local')
const token = localStorage.getItem('token')
const response = await fetch('http://localhost:8081/api/admin/attachments/upload', {
const response = await fetch('/api/admin/attachments/upload', {
method: 'POST',
headers: {
...(token ? { Authorization: `Bearer ${token}` } : {})

View File

@@ -64,12 +64,12 @@
</div>
<!-- Charts Row 2 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- User Regions -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Operation Trend -->
<div class="chart-card relative">
<h3 class="chart-title">用户地域分布</h3>
<div class="h-80" v-if="!loading && hasData(stats.userRegions)">
<v-chart class="chart" :option="regionOption" autoresize />
<h3 class="chart-title">管理员操作统计</h3>
<div class="h-80" v-if="!loading && hasData(stats.operationTrend)">
<v-chart class="chart" :option="operationTrendOption" autoresize />
</div>
<div v-else-if="loading" class="h-80 flex items-center justify-center text-white/20">
加载中...
@@ -93,6 +93,26 @@
</div>
</div>
</div>
<!-- User Regions - Full Width -->
<div class="chart-card relative">
<h3 class="chart-title">用户地域分布</h3>
<div class="h-[800px]" v-if="!loading && hasData(stats.userRegions) && mapDataLoaded">
<v-chart class="chart" :option="regionOption" autoresize />
</div>
<div v-else-if="loading" class="h-[800px] flex items-center justify-center text-white/20">
加载中...
</div>
<div v-else-if="!mapDataLoaded" class="h-[800px] flex items-center justify-center text-white/40">
<div class="text-center">
<p class="mb-2">地图数据加载失败</p>
<p class="text-sm text-white/60">请检查网络连接或稍后重试</p>
</div>
</div>
<div v-else class="h-[800px] flex items-center justify-center text-white/20">
暂无数据
</div>
</div>
</div>
</template>
@@ -113,6 +133,7 @@ import * as echarts from 'echarts/core'
import VChart from 'vue-echarts'
import { getDashboardStats } from '../../../services/api'
import { useToast } from '../../../composables/useToast'
import chinaMapData from '../../../composables/full.json'
use([
CanvasRenderer,
@@ -278,44 +299,6 @@ const uvTrendOption = computed(() => ({
}]
}))
// 省份名称映射(确保与地图数据一致)
const provinceNameMap: Record<string, string> = {
'北京': '北京',
'天津': '天津',
'河北': '河北',
'山西': '山西',
'内蒙古': '内蒙古',
'辽宁': '辽宁',
'吉林': '吉林',
'黑龙江': '黑龙江',
'上海': '上海',
'江苏': '江苏',
'浙江': '浙江',
'安徽': '安徽',
'福建': '福建',
'江西': '江西',
'山东': '山东',
'河南': '河南',
'湖北': '湖北',
'湖南': '湖南',
'广东': '广东',
'广西': '广西',
'海南': '海南',
'重庆': '重庆',
'四川': '四川',
'贵州': '贵州',
'云南': '云南',
'西藏': '西藏',
'陕西': '陕西',
'甘肃': '甘肃',
'青海': '青海',
'宁夏': '宁夏',
'新疆': '新疆',
'台湾': '台湾',
'香港': '香港',
'澳门': '澳门'
}
// 所有省份列表(用于设置默认值)
const allProvinces = [
'北京', '天津', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江',
@@ -354,6 +337,20 @@ const getMapData = () => {
}
const regionOption = computed(() => {
// 如果地图数据未加载,返回空配置
if (!mapDataLoaded.value) {
return {
title: {
text: '地图数据加载中...',
left: 'center',
top: 'middle',
textStyle: {
color: '#fff'
}
}
}
}
const mapData = getMapData()
const maxValue = mapData.length > 0 ? Math.max(...mapData.map((d: any) => d[1] || 0)) : 1
@@ -372,7 +369,7 @@ const regionOption = computed(() => {
min: 0,
max: maxValue || 100,
left: 'left',
top: 'bottom',
top: 'middle',
text: ['高', '低'],
inRange: {
color: ['#1a1a1a', '#d4b383']
@@ -380,11 +377,15 @@ const regionOption = computed(() => {
textStyle: {
color: '#fff'
},
calculable: true
calculable: true,
itemWidth: 12,
itemHeight: 200
},
geo: {
map: 'china',
roam: false,
layoutCenter: ['55%', '50%'],
layoutSize: '95%',
itemStyle: {
areaColor: 'rgba(255, 255, 255, 0.05)',
borderColor: 'rgba(255, 255, 255, 0.2)',
@@ -398,7 +399,7 @@ const regionOption = computed(() => {
label: {
show: true,
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 10
fontSize: 14
}
},
series: [
@@ -421,7 +422,7 @@ const regionOption = computed(() => {
},
label: {
color: '#fff',
fontSize: 12
fontSize: 16
}
}
}
@@ -429,6 +430,33 @@ const regionOption = computed(() => {
}
})
const operationTrendOption = computed(() => ({
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0,0,0,0.8)',
borderColor: '#333',
textStyle: { color: '#fff' }
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'category',
data: stats.value.operationTrend?.map((i: any) => i.date) || [],
axisLabel: { color: '#ccc' }
},
yAxis: {
type: 'value',
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [{
name: '操作次数',
data: stats.value.operationTrend?.map((i: any) => i.value) || [],
type: 'bar',
itemStyle: { color: '#f59e0b' },
barWidth: '40%'
}]
}))
const topPostsOption = computed(() => ({
tooltip: {
trigger: 'axis',
@@ -475,21 +503,69 @@ const loadData = async () => {
}
}
// 地图数据加载状态
const mapDataLoaded = ref(false)
// 加载中国地图数据
const loadChinaMap = async () => {
try {
// 使用 echarts 官方的中国地图数据(从 CDN 加载)
const response = await fetch('https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json')
if (response.ok) {
const geoJson = await response.json()
// 注册地图数据
echarts.registerMap('china', geoJson)
} else {
console.warn('Failed to load China map data, using fallback')
// 优先使用本地地图数据
if (chinaMapData && chinaMapData.type === 'FeatureCollection') {
echarts.registerMap('china', chinaMapData as any)
mapDataLoaded.value = true
console.log('China map data loaded successfully from local file')
return
}
} catch (error) {
console.error('Error loading China map:', error)
console.warn('Failed to load local map data:', error)
}
// 如果本地数据加载失败,尝试备用数据源
const mapDataSources = [
'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json',
'https://cdn.jsdelivr.net/npm/echarts@5/map/json/china.json',
'https://unpkg.com/echarts@5/map/json/china.json'
]
for (const url of mapDataSources) {
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 10000) // 10秒超时
const response = await fetch(url, {
mode: 'cors',
credentials: 'omit',
signal: controller.signal
})
clearTimeout(timeoutId)
if (response.ok) {
const geoJson = await response.json()
// 验证数据格式
if (geoJson && geoJson.type === 'FeatureCollection') {
// 注册地图数据
echarts.registerMap('china', geoJson)
mapDataLoaded.value = true
console.log('China map data loaded successfully from:', url)
return
} else {
console.warn(`Invalid map data format from ${url}`)
}
}
} catch (error: any) {
if (error.name === 'AbortError') {
console.warn(`Timeout loading map data from ${url}`)
} else {
console.warn(`Failed to load map data from ${url}:`, error)
}
continue
}
}
// 所有数据源都失败
console.error('All map data sources failed, map will not be displayed')
mapDataLoaded.value = false
}
onMounted(async () => {

View File

@@ -1,4 +1,3 @@
// export const API_BASE = 'http://localhost:8081/api'
export const API_BASE = '/api'
// 通用请求头配置
@@ -145,6 +144,7 @@ export interface OperationLog {
userId: number
username: string
ip: string
region?: string
path: string
method: string
params: string