This commit is contained in:
徐星 2026-03-31 18:19:21 +08:00
parent 9fcd3cc350
commit e93000b0c2
8 changed files with 71 additions and 103 deletions

View File

@ -15,7 +15,7 @@
<template v-if="!collapsedGroups.has(group.title)">
<router-link v-for="item in group.items" :key="item.path" :to="item.path"
class="block px-8 py-2 text-sm transition-colors"
:style="{ color: route.path === item.path ? '#fff' : 'rgba(255,255,255,0.65)', backgroundColor: route.path === item.path ? '#1890FF' : 'transparent' }">
:style="{ color: route.path === item.path || (item.path !== '/' && route.path.startsWith(item.path)) ? '#fff' : 'rgba(255,255,255,0.65)', backgroundColor: route.path === item.path || (item.path !== '/' && route.path.startsWith(item.path)) ? '#1890FF' : 'transparent' }">
{{ item.label }}
</router-link>
</template>
@ -39,9 +39,9 @@ const toggleGroup = (title: string) => {
const menuGroups = [
{ title: '设备', items: [{ path: '/devices', label: '设备列表' }, { path: '/models', label: '型号管理' }] },
{ title: '授权', items: [{ path: '/licenses', label: '授权管理' }, { path: '/activation', label: '激活管理' }] },
{ title: '固件', items: [{ path: '/firmware', label: '固件库' }, { path: '/firmware-upgrade', label: '升级任务' }] },
{ title: '固件', items: [{ path: '/firmware', label: '固件库' }] },
{ title: '配置', items: [{ path: '/config-files', label: '配置管理' }] },
{ title: '校准', items: [{ path: '/calibration', label: '校准管理' }] },
{ title: '校准', items: [{ path: '/calibration', label: '校准记录' }] },
{ title: '维修', items: [{ path: '/repair', label: '维修工单' }, { path: '/repair/stats', label: '维修统计' }, { path: '/scrap', label: '报废回收' }] },
]
</script>

View File

@ -1,112 +1,74 @@
<template>
<div class="p-6">
<!-- Page Header -->
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-1">首页</h2>
<p class="text-sm" style="color: rgba(0, 0, 0, 0.45)">设备管理数据总览</p>
</div>
<!-- Metric Cards -->
<!-- Metric Cards (clickable) -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="(metric, index) in metrics"
:key="index"
class="bg-white p-6 rounded-lg"
<router-link
v-for="(metric, index) in metrics" :key="index"
:to="metric.link"
class="bg-white p-6 rounded-lg block hover:shadow-md transition-shadow"
style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)"
>
<div class="flex items-start justify-between">
<div class="flex-1">
<div class="text-sm mb-2" style="color: rgba(0, 0, 0, 0.65)">{{ metric.label }}</div>
<div class="text-3xl font-semibold mb-2">{{ metric.value }}</div>
<div
v-if="metric.trend && metric.trendValue"
class="flex items-center gap-1"
:style="{ color: metric.trend === 'up' ? '#52C41A' : '#FF4D4F' }"
>
<div v-if="metric.trend && metric.trendValue" class="flex items-center gap-1"
:style="{ color: metric.trend === 'up' ? '#52C41A' : '#FF4D4F' }">
<TrendingUp v-if="metric.trend === 'up'" :size="14" />
<TrendingDown v-else :size="14" />
<span class="text-sm">{{ metric.trendValue }}</span>
</div>
</div>
<div
class="w-12 h-12 rounded-lg flex items-center justify-center"
:style="{ backgroundColor: metric.color + '15' }"
>
<div class="w-12 h-12 rounded-lg flex items-center justify-center" :style="{ backgroundColor: metric.color + '15' }">
<component :is="metric.icon" :size="24" :style="{ color: metric.color }" />
</div>
</div>
</div>
</router-link>
</div>
<!-- Device Status Chart (CSS-based horizontal bars replacing recharts) -->
<!-- Device Status Chart -->
<div class="bg-white p-6 rounded-lg mb-6" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">设备状态分布</h3>
<div style="display: flex; flex-direction: column; gap: 16px;">
<div
v-for="item in deviceStatusData"
:key="item.name"
style="display: flex; align-items: center; gap: 12px;"
>
<div style="width: 60px; text-align: right; font-size: 14px; color: rgba(0,0,0,0.65); flex-shrink: 0;">
{{ item.name }}
</div>
<div v-for="item in deviceStatusData" :key="item.name" style="display: flex; align-items: center; gap: 12px;">
<div style="width: 60px; text-align: right; font-size: 14px; color: rgba(0,0,0,0.65); flex-shrink: 0;">{{ item.name }}</div>
<div style="flex: 1; background-color: #F5F5F5; border-radius: 4px; height: 24px; overflow: hidden;">
<div
:style="{
width: (item.value / maxStatusValue * 100) + '%',
height: '100%',
backgroundColor: item.color,
borderRadius: '0 4px 4px 0',
transition: 'width 0.3s ease',
minWidth: item.value > 0 ? '2px' : '0'
}"
></div>
</div>
<div style="width: 40px; font-size: 14px; color: rgba(0,0,0,0.85);">
{{ item.value }}
<div :style="{ width: (item.value / maxStatusValue * 100) + '%', height: '100%', backgroundColor: item.color, borderRadius: '0 4px 4px 0', transition: 'width 0.3s ease', minWidth: item.value > 0 ? '2px' : '0' }"></div>
</div>
<div style="width: 40px; font-size: 14px; color: rgba(0,0,0,0.85);">{{ item.value }}</div>
</div>
</div>
</div>
<!-- Pending Tasks -->
<!-- Pending Tasks with navigation -->
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<h3 class="text-lg font-semibold mb-6">通知</h3>
<h3 class="text-lg font-semibold mb-6">待处理任务</h3>
<div class="grid grid-cols-2 gap-6">
<div v-for="(group, groupIndex) in taskGroups" :key="groupIndex">
<div v-for="(group, gi) in taskGroups" :key="gi">
<div class="flex items-center justify-between mb-4">
<h4 class="text-base font-medium">{{ group.title }}</h4>
<span
class="px-2 py-1 rounded text-xs"
style="background-color: #F0F2F5; color: rgba(0, 0, 0, 0.65)"
>
{{ group.count }}
</span>
<span class="px-2 py-1 rounded text-xs" style="background-color: #F0F2F5; color: rgba(0, 0, 0, 0.65)">{{ group.count }}</span>
</div>
<div>
<div
v-for="(task, taskIndex) in group.tasks"
:key="taskIndex"
class="flex items-start justify-between py-3"
style="border-bottom: 1px solid #F0F0F0"
>
<div v-for="(task, ti) in group.tasks" :key="ti" class="flex items-start justify-between py-3" style="border-bottom: 1px solid #F0F0F0">
<div class="flex-1">
<div class="text-sm mb-1">{{ task.deviceSN }}</div>
<div class="text-sm" style="color: rgba(0, 0, 0, 0.45)">{{ task.description }}</div>
</div>
<div class="flex items-center gap-3">
<span v-if="task.time" class="text-xs" style="color: rgba(0, 0, 0, 0.45)">{{ task.time }}</span>
<button class="text-sm" style="color: #1890FF">处理</button>
<router-link :to="task.link" class="text-sm" style="color: #1890FF">处理</router-link>
</div>
</div>
</div>
<button
v-if="group.tasks.length < group.count"
class="w-full mt-3 text-center text-sm"
style="color: #1890FF"
>
<router-link v-if="group.tasks.length < group.count" :to="group.link" class="block w-full mt-3 text-center text-sm" style="color: #1890FF">
查看全部 {{ group.count }}
</button>
</router-link>
</div>
</div>
</div>
@ -115,28 +77,17 @@
<script setup lang="ts">
import { computed } from 'vue'
import {
TrendingUp,
TrendingDown,
Server,
Wifi,
CheckCircle,
PackageCheck,
Wrench,
Target,
Clock,
Upload,
} from 'lucide-vue-next'
import { TrendingUp, TrendingDown, Server, Wifi, CheckCircle, PackageCheck, Wrench, Target, Clock, Upload } from 'lucide-vue-next'
const metrics = [
{ label: '设备总数', value: '5,234', trend: 'up' as const, trendValue: '+5.2%', color: '#1890FF', icon: Server },
{ label: '装配中', value: '4,856', trend: 'up' as const, trendValue: '+2.8%', color: '#52C41A', icon: Wifi },
{ label: '已激活', value: '4,912', trend: 'up' as const, trendValue: '+1.5%', color: '#1890FF', icon: CheckCircle },
{ label: '有新版本', value: '156', color: '#722ED1', icon: PackageCheck },
{ label: '维修中', value: '23', trend: 'down' as const, trendValue: '-12.3%', color: '#FF4D4F', icon: Wrench },
{ label: '报废', value: '56', color: '#FA8C16', icon: Target },
{ label: '授权即将到期', value: '45', color: '#FAAD14', icon: Clock },
{ label: '升级中', value: '8', color: '#13C2C2', icon: Upload },
{ label: '设备总数', value: '5,234', trend: 'up' as const, trendValue: '+5.2%', color: '#1890FF', icon: Server, link: '/devices' },
{ label: '装配中', value: '4,856', trend: 'up' as const, trendValue: '+2.8%', color: '#52C41A', icon: Wifi, link: '/devices' },
{ label: '已激活', value: '4,912', trend: 'up' as const, trendValue: '+1.5%', color: '#1890FF', icon: CheckCircle, link: '/activation' },
{ label: '有新版本', value: '156', color: '#722ED1', icon: PackageCheck, link: '/firmware' },
{ label: '维修中', value: '23', trend: 'down' as const, trendValue: '-12.3%', color: '#FF4D4F', icon: Wrench, link: '/repair' },
{ label: '报废', value: '56', color: '#FA8C16', icon: Target, link: '/scrap' },
{ label: '授权即将到期', value: '45', color: '#FAAD14', icon: Clock, link: '/licenses' },
{ label: '升级中', value: '8', color: '#13C2C2', icon: Upload, link: '/firmware' },
]
const deviceStatusData = [
@ -145,24 +96,35 @@ const deviceStatusData = [
{ name: '已激活', value: 286, color: '#FAAD14' },
{ name: '报废', value: 7, color: '#8C8C8C' },
]
const maxStatusValue = computed(() => Math.max(...deviceStatusData.map((d) => d.value)))
const taskGroups = [
{
title: '固件升级通知',
count: 8,
title: '待校准设备', count: 23, link: '/calibration',
tasks: [
{ deviceSN: 'SN2024030710', description: '固件版本v2.3.5可用', time: '1天前' },
{ deviceSN: 'SN2024030711', description: '固件版本v2.3.5可用', time: '1天前' },
{ deviceSN: 'AC20240308005', description: '采集板校准即将到期', time: '3天后到期', link: '/calibration' },
{ deviceSN: 'AC20240308006', description: '新采集板待校准', time: '今天', link: '/calibration' },
],
},
{
title: '维修工单',
count: 5,
title: '维修工单', count: 5, link: '/repair',
tasks: [
{ deviceSN: 'SN2024030530', description: '设备故障报修', time: '4小时前' },
{ deviceSN: 'SN2024030531', description: '电池故障', time: '6小时前' },
{ deviceSN: 'GD30-2024-000056', description: '板卡故障,待处理', time: '4小时前', link: '/repair/WO-2024-0001' },
{ deviceSN: 'GD30-2024-000078', description: '固件异常', time: '6小时前', link: '/repair/WO-2024-0002' },
],
},
{
title: '固件升级通知', count: 8, link: '/firmware',
tasks: [
{ deviceSN: 'SN2024030710', description: '固件版本v2.3.5可用', time: '1天前', link: '/firmware' },
{ deviceSN: 'SN2024030711', description: '固件版本v2.3.5可用', time: '1天前', link: '/firmware' },
],
},
{
title: '授权即将到期', count: 45, link: '/licenses',
tasks: [
{ deviceSN: 'GD30-2025-000001', description: '授权将于30天后到期', time: '30天', link: '/licenses' },
{ deviceSN: 'GT20-2025-000045', description: '授权将于15天后到期', time: '15天', link: '/licenses' },
],
},
]

View File

@ -165,7 +165,7 @@
<div class="bg-white p-6 rounded-lg" style="box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05)">
<div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-semibold">维修历史</h3>
<button class="text-sm" style="color: #1890FF">查看全部</button>
<router-link to="/repair" class="text-sm" style="color: #1890FF">查看全部</router-link>
</div>
<div class="relative">
<!-- Timeline line -->

View File

@ -240,13 +240,13 @@ const boardVersionData = [
<td class="px-6 py-4">{{ model.name }}</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ model.code }}</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.authFile }}</button>
<router-link to="/licenses" class="text-sm" style="color: #1890FF">{{ model.authFile }}</router-link>
</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.configFile }}</button>
<router-link to="/config-files" class="text-sm" style="color: #1890FF">{{ model.configFile }}</router-link>
</td>
<td class="px-6 py-4">
<button class="text-sm" :style="{ color: '#1890FF' }">{{ model.firmwareVersion }}</button>
<router-link to="/firmware" class="text-sm" style="color: #1890FF">{{ model.firmwareVersion }}</router-link>
</td>
<td class="px-6 py-4">{{ model.deviceCount.toLocaleString() }}</td>
<td class="px-6 py-4">
@ -264,9 +264,9 @@ const boardVersionData = [
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" :style="{ color: '#1890FF' }">编辑</button>
<button class="text-sm" :style="{ color: '#1890FF' }">授权</button>
<button class="text-sm" :style="{ color: '#1890FF' }">配置</button>
<button class="text-sm" :style="{ color: '#1890FF' }">固件</button>
<router-link to="/licenses" class="text-sm" style="color: #1890FF">授权</router-link>
<router-link to="/config-files" class="text-sm" style="color: #1890FF">配置</router-link>
<router-link to="/firmware" class="text-sm" style="color: #1890FF">固件</router-link>
</div>
</td>
</tr>

View File

@ -123,7 +123,7 @@ const firmwares = ref<Firmware[]>([
<button class="text-sm" style="color: #1890FF">下载</button>
<button class="text-sm" style="color: #1890FF">编辑</button>
<button class="text-sm" style="color: #1890FF">撤回发布</button>
<button class="text-sm" style="color: #1890FF">查看升级任务</button>
<router-link to="/firmware-upgrade" class="text-sm" style="color: #1890FF">查看升级任务</router-link>
</div>
</template>

View File

@ -112,6 +112,7 @@ const processRecords = [
<div class="flex items-center justify-center gap-4 p-4 bg-white rounded-lg sticky bottom-0" style="box-shadow: 0 -2px 8px rgba(0,0,0,0.05)">
<button class="px-6 py-2 rounded text-sm" style="border: 1px solid #D9D9D9; color: rgba(0,0,0,0.85)" @click="router.go(-1)">取消</button>
<button class="px-6 py-2 rounded text-white text-sm" style="background-color: #1890FF">关闭工单</button>
<button class="px-6 py-2 rounded text-sm" style="border: 1px solid #FF4D4F; color: #FF4D4F" @click="router.push('/scrap')">申请报废</button>
</div>
</div>
</template>

View File

@ -1,5 +1,8 @@
<script setup lang="ts">
import { Info, Download, Upload, Link as LinkIcon } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
const router = useRouter()
interface ScrapDevice {
sn: string
@ -196,16 +199,16 @@ const getStatusStyle = (status: ScrapDevice['status']) => {
</span>
</td>
<td class="px-6 py-4">
<button class="text-sm flex items-center gap-1" style="color: #1890FF">
<router-link :to="'/repair/' + device.sourceOrder" class="text-sm flex items-center gap-1" style="color: #1890FF">
<LinkIcon :size="14" />
{{ device.sourceOrder }}
</button>
</router-link>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<button class="text-sm" style="color: #1890FF">查看详情</button>
<button class="text-sm" style="color: #1890FF">物料检测</button>
<button v-if="device.status === '已审批'" class="text-sm" style="color: #52C41A">回收入库</button>
<button v-if="device.status === '已审批'" class="text-sm" style="color: #52C41A" @click="router.push('/registration')">回收入库</button>
</div>
</td>
</tr>

View File

@ -22,6 +22,8 @@ export const router = createRouter({
{ path: 'repair/:orderId', component: () => import('./pages/RepairOrderDetail.vue') },
{ path: 'scrap', component: () => import('./pages/ScrapManagement.vue') },
{ path: 'firmware', component: () => import('./pages/FirmwareLibrary.vue') },
{ path: 'firmware-upgrade', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '升级任务' } },
{ path: 'calibration-reports', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '校准报告' } },
{ path: 'config-files', component: () => import('./pages/ConfigFileManagement.vue') },
{ path: 'config-files/:configId', component: () => import('./pages/ParameterConfiguration.vue') },
{ path: 'reports', component: () => import('./pages/PlaceholderPage.vue'), props: { title: '数据报表' } },