fix: 修复前端登录体验和API调用问题

- 修复路由守卫:未登录时直接跳转,不显示提示信息
- 修复API拦截器:401错误直接跳转,无需确认
- 移除不必要的ElMessageBox确认框
- 优化Token过期处理逻辑
- 修复文件管理API引入路径和URL前缀
- 修复调拨/回收管理API端点不匹配问题
- 修复通知管理API方法不匹配问题
- 统一系统配置API路径为单数形式

影响文件:
- src/router/index.ts
- src/api/request.ts
- src/api/file.ts
- src/api/index.ts

测试状态:
- 前端构建通过
- 所有API路径已验证
- 登录流程测试通过

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-01-25 00:26:33 +08:00
commit e48975f9d5
151 changed files with 39477 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
<!--
资产分布图组件
展示按机构类型的资产分布
-->
<template>
<div class="asset-distribution-chart">
<BarChart
:data="chartData"
title="资产分布统计"
type="vertical"
:x-axis-label="xLabel"
y-axis-label="数量"
:show-data-zoom="chartData.length > 10"
height="400px"
@click="handleClick"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import BarChart from '../BarChart.vue'
import type { AssetDistributionStatistics, AssetTypeStatistics } from '@/types/charts'
/** Props */
interface Props {
data: Array<AssetDistributionStatistics | AssetTypeStatistics>
type?: 'organization' | 'deviceType'
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
data: () => [],
type: 'organization',
loading: false,
})
/** Emits */
interface Emits {
(e: 'click', item: any): void
}
const emit = defineEmits<Emits>()
/** X轴标签 */
const xLabel = computed(() => {
return props.type === 'organization' ? '机构' : '设备类型'
})
/** 图表数据 */
const chartData = computed(() => {
return props.data.map(item => {
const name = props.type === 'organization'
? (item as AssetDistributionStatistics).organizationName
: (item as AssetTypeStatistics).typeName
return {
name: name || '未知',
value: item.count,
original: item,
}
}).sort((a, b) => b.value - a.value)
})
/** 处理点击 */
const handleClick = (item: any) => {
emit('click', item.original)
}
</script>
<style scoped lang="scss">
.asset-distribution-chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,70 @@
<!--
资产状态图组件
展示8种资产状态分布
-->
<template>
<div class="asset-status-chart">
<PieChart
:data="chartData"
title="资产状态分布"
type="doughnut"
:show-legend="true"
:show-label="true"
height="400px"
:custom-color="true"
@click="handleClick"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import PieChart from '../PieChart.vue'
import { assetStatusNames, assetStatusColors, formatPercentage } from '@/utils/echarts'
import type { AssetStatusStatistics } from '@/types/charts'
/** Props */
interface Props {
data: AssetStatusStatistics[]
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
data: () => [],
loading: false,
})
/** Emits */
interface Emits {
(e: 'click', item: AssetStatusStatistics): void
}
const emit = defineEmits<Emits>()
/** 图表数据 */
const chartData = computed(() => {
return props.data.map(item => ({
name: item.statusName || assetStatusNames[item.status],
value: item.count,
status: item.status,
percentage: item.percentage,
color: item.color || assetStatusColors[item.status],
}))
})
/** 处理点击 */
const handleClick = (item: any) => {
const statusItem = props.data.find(d => d.status === item.status)
if (statusItem) {
emit('click', statusItem)
}
}
</script>
<style scoped lang="scss">
.asset-status-chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,62 @@
<!--
资产利用率图表组件
使用仪表盘展示利用率
-->
<template>
<div class="asset-utilization-chart">
<GaugeChart
:value="utilizationRate"
:min="0"
:max="100"
title="资产利用率"
unit="%"
height="300px"
:color="gaugeColors"
:show-detail="true"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import GaugeChart from '../GaugeChart.vue'
/** Props */
interface Props {
totalAssets: number
usedAssets: number
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
totalAssets: 0,
usedAssets: 0,
loading: false,
})
/** 利用率 */
const utilizationRate = computed(() => {
if (props.totalAssets === 0) return 0
return (props.usedAssets / props.totalAssets) * 100
})
/** 仪表盘颜色 */
const gaugeColors = computed(() => {
const rate = utilizationRate.value
if (rate < 50) {
return ['#ef4444', '#f59e0b', '#10b981'] // 低:红橙绿
} else if (rate < 80) {
return ['#f59e0b', '#10b981', '#3b82f6'] // 中:橙绿蓝
} else {
return ['#10b981', '#3b82f6', '#6366f1'] // 高:绿蓝紫
}
})
</script>
<style scoped lang="scss">
.asset-utilization-chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,93 @@
<!--
资产价值趋势图组件
展示资产价值折旧净值趋势
-->
<template>
<div class="asset-value-trend-chart">
<LineChart
:data="dateData"
:series="seriesData"
title="资产价值趋势"
:area="true"
:smooth="true"
x-axis-label="日期"
y-axis-label="金额(万元)"
:show-data-zoom="true"
height="400px"
@click="handleClick"
/>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import LineChart from '../LineChart.vue'
import type { AssetTrendData } from '@/types/charts'
/** Props */
interface Props {
data: AssetTrendData[]
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
data: () => [],
loading: false,
})
/** Emits */
interface Emits {
(e: 'click', item: AssetTrendData): void
}
const emit = defineEmits<Emits>()
/** 日期数据 */
const dateData = computed(() => {
return props.data.map(item => ({
name: item.date,
value: item.value / 10000, // 转换为万元
}))
})
/** 系列数据 */
const seriesData = computed(() => {
const valueData = props.data.map(item => item.value / 10000)
const depreciationData = props.data.map(item => (item.depreciation || 0) / 10000)
const netValueData = props.data.map(item => (item.netValue || item.value) / 10000)
return [
{
name: '总价值',
data: valueData,
color: '#475569',
},
{
name: '累计折旧',
data: depreciationData,
color: '#ef4444',
},
{
name: '净值',
data: netValueData,
color: '#10b981',
},
]
})
/** 处理点击 */
const handleClick = (item: any) => {
const original = props.data.find(d => d.date === item.name)
if (original) {
emit('click', original)
}
}
</script>
<style scoped lang="scss">
.asset-value-trend-chart {
width: 100%;
height: 100%;
}
</style>