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:
77
src/components/charts/business/AssetDistributionChart.vue
Normal file
77
src/components/charts/business/AssetDistributionChart.vue
Normal 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>
|
||||
70
src/components/charts/business/AssetStatusChart.vue
Normal file
70
src/components/charts/business/AssetStatusChart.vue
Normal 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>
|
||||
62
src/components/charts/business/AssetUtilizationChart.vue
Normal file
62
src/components/charts/business/AssetUtilizationChart.vue
Normal 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>
|
||||
93
src/components/charts/business/AssetValueTrendChart.vue
Normal file
93
src/components/charts/business/AssetValueTrendChart.vue
Normal 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>
|
||||
Reference in New Issue
Block a user