enterprise-saa-s-dashboard-.../src/app/pages/DeviceModelManagement.vue

416 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { ref } from 'vue'
import {
Info,
Server,
CheckCircle2,
AlertTriangle,
Layers,
Plus,
Trash2,
GripVertical,
} from 'lucide-vue-next'
interface ChecklistItem {
id: number
text: string
type: 'text' | 'bool'
required: boolean
note: string
}
interface ChecklistData {
[key: string]: ChecklistItem[]
}
const activeTab = ref('GD30')
const stats = [
{ label: '型号总数', value: '12', icon: Server, color: '#1890FF' },
{ label: '在产型号', value: '8', icon: CheckCircle2, color: '#52C41A' },
{ label: '停产型号', value: '3', icon: AlertTriangle, color: '#FAAD14' },
{ label: '关联设备总数', value: '5,234', icon: Layers, color: '#722ED1' },
]
const modelData = [
{
name: 'GD30 高密度电法仪',
code: 'GD30-2024',
authFile: 'auth_gd30_v2.3.lic',
configFile: 'config_gd30_v1.5.json',
firmwareVersion: 'v2.3.5',
deviceCount: 2456,
status: '在产' as const,
},
{
name: 'GT20 瞬变电磁仪',
code: 'GT20-2023',
authFile: 'auth_gt20_v1.8.lic',
configFile: 'config_gt20_v1.2.json',
firmwareVersion: 'v1.8.2',
deviceCount: 1823,
status: '在产' as const,
},
{
name: 'GM10 大地电磁仪',
code: 'GM10-2022',
authFile: 'auth_gm10_v1.5.lic',
configFile: 'config_gm10_v1.0.json',
firmwareVersion: 'v1.5.1',
deviceCount: 955,
status: '停产' as const,
},
]
const checklistData = ref<ChecklistData>({
GD30: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '采集板SN录入', type: 'text', required: true, note: '1/6/12通道' },
{ id: 3, text: '发射板SN录入', type: 'text', required: true, note: '' },
{ id: 4, text: '内置升压模块检查', type: 'bool', required: true, note: '' },
{ id: 5, text: 'GPS/北斗检测', type: 'bool', required: true, note: '授时正常' },
{ id: 6, text: '电池安装与容量检测', type: 'bool', required: true, note: '' },
{ id: 7, text: '输入电压12~48V测试', type: 'bool', required: true, note: '' },
{ id: 8, text: '接收电压精度校验', type: 'bool', required: true, note: '按型号量程' },
{ id: 9, text: '自电补偿±10V', type: 'bool', required: true, note: '' },
{ id: 10, text: '输入阻抗≥100MΩ', type: 'bool', required: true, note: '' },
{ id: 11, text: '恒压/恒流模式', type: 'bool', required: true, note: '' },
{ id: 12, text: '最大发射电流达标', type: 'bool', required: true, note: '6A/10A/10A' },
{ id: 13, text: '脉冲宽度配置', type: 'bool', required: true, note: '' },
{ id: 14, text: '调级输出电压', type: 'bool', required: true, note: '100~600V' },
{ id: 15, text: '系统启动正常', type: 'bool', required: true, note: '' },
{ id: 16, text: '采集APP连接', type: 'bool', required: true, note: '' },
{ id: 17, text: 'Geometa账号配置', type: 'bool', required: true, note: '' },
{ id: 18, text: '授权文件校验', type: 'bool', required: true, note: '' },
{ id: 19, text: 'USB/WiFi/网口/SD', type: 'bool', required: true, note: '' },
{ id: 20, text: 'IP66防护与密封', type: 'bool', required: true, note: '' },
{ id: 21, text: '过流/过压/短路保护', type: 'bool', required: true, note: '' },
{ id: 22, text: '出厂装箱核对', type: 'bool', required: true, note: '' },
],
GT20: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '采集板SN录入', type: 'text', required: true, note: '' },
{ id: 3, text: 'GPS/北斗检测', type: 'bool', required: true, note: '授时正常' },
{ id: 4, text: '系统启动正常', type: 'bool', required: true, note: '' },
{ id: 5, text: '整体功能测试', type: 'bool', required: true, note: '' },
],
GM10: [
{ id: 1, text: '主板SN扫码绑定主机', type: 'text', required: true, note: '唯一SN' },
{ id: 2, text: '传感器模块连接', type: 'bool', required: true, note: '' },
{ id: 3, text: '接口板安装', type: 'bool', required: true, note: '' },
{ id: 4, text: '线缆整理', type: 'bool', required: true, note: '' },
{ id: 5, text: '系统初始化检测', type: 'bool', required: true, note: '' },
],
})
const editingCell = ref<{ model: string; id: number; field: string } | null>(null)
const updateChecklistItem = (model: string, id: number, field: keyof ChecklistItem, value: string | boolean) => {
checklistData.value = {
...checklistData.value,
[model]: checklistData.value[model].map((item: ChecklistItem) =>
item.id === id ? { ...item, [field]: value } : item
),
}
}
const addChecklistItem = (model: string) => {
const items = checklistData.value[model]
const newId = items.length > 0 ? Math.max(...items.map((i: ChecklistItem) => i.id)) + 1 : 1
checklistData.value = {
...checklistData.value,
[model]: [...items, { id: newId, text: '新检查项', type: 'bool' as const, required: true, note: '' }],
}
}
const deleteChecklistItem = (model: string, id: number) => {
checklistData.value = {
...checklistData.value,
[model]: checklistData.value[model].filter((item: ChecklistItem) => item.id !== id).map((item: ChecklistItem, i: number) => ({ ...item, id: i + 1 })),
}
}
const handleEditBlur = (e: Event, model: string, id: number) => {
updateChecklistItem(model, id, 'text', (e.target as HTMLInputElement).value)
editingCell.value = null
}
const handleEditKeydown = (e: KeyboardEvent) => {
if (e.key === 'Enter') (e.target as HTMLInputElement).blur()
}
const boardVersionData = [
{
boardType: '主板',
requiredVersion: 'v2.3.x',
validationRule: '版本号前缀必须为 v2.3',
status: 'active',
},
{
boardType: '采集板',
requiredVersion: 'v1.8.x',
validationRule: '版本号前缀必须为 v1.8',
status: 'active',
},
{
boardType: '发射板',
requiredVersion: 'v1.5.x',
validationRule: '版本号前缀必须为 v1.5',
status: 'active',
},
]
</script>
<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>
<!-- Info Banner -->
<div
class="mb-6 p-4 rounded-lg flex items-start gap-3"
:style="{ backgroundColor: '#E6F7FF', border: '1px solid #91D5FF' }"
>
<Info :size="20" :style="{ color: '#1890FF', flexShrink: 0, marginTop: '2px' }" />
<div :style="{ color: '#0050B3' }">
型号管理是平台核心枢纽每个型号关联授权文件配置文件和固件版本
</div>
</div>
<!-- Stat Cards -->
<div class="grid grid-cols-4 gap-6 mb-6">
<div
v-for="(stat, index) in stats"
:key="index"
class="bg-white p-6 rounded-lg"
:style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }"
>
<div class="flex items-center justify-between">
<div>
<div class="text-sm mb-2" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ stat.label }}</div>
<div class="text-3xl font-semibold">{{ stat.value }}</div>
</div>
<div
class="w-12 h-12 rounded-lg flex items-center justify-center"
:style="{ backgroundColor: `${stat.color}15` }"
>
<component :is="stat.icon" :size="24" :style="{ color: stat.color }" />
</div>
</div>
</div>
</div>
<!-- Model Table -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold">型号列表</h3>
<button
class="px-4 py-2 rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
新增型号
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">型号名称</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">编码</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">授权文件</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">配置文件</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">固件版本</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">设备数</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">状态</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(model, index) in modelData"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<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">
<router-link to="/licenses" class="text-sm" style="color: #1890FF">{{ model.authFile }}</router-link>
</td>
<td class="px-6 py-4">
<router-link to="/config-files" class="text-sm" style="color: #1890FF">{{ model.configFile }}</router-link>
</td>
<td class="px-6 py-4">
<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">
<span
class="px-2 py-1 rounded text-xs"
:style="{
backgroundColor: model.status === '在产' ? '#F6FFED' : '#FFFBE6',
color: model.status === '在产' ? '#52C41A' : '#FAAD14',
border: `1px solid ${model.status === '在产' ? '#B7EB8F' : '#FFE58F'}`,
}"
>
{{ model.status }}
</span>
</td>
<td class="px-6 py-4">
<div class="flex items-center gap-3">
<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>
</tbody>
</table>
</div>
</div>
<!-- Assembly Checklist -->
<div class="bg-white rounded-lg mb-6" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<h3 class="text-lg font-semibold">装配Checklist模板</h3>
</div>
<!-- Tabs -->
<div class="flex border-b" :style="{ borderColor: '#F0F0F0' }">
<button
v-for="model in Object.keys(checklistData)"
:key="model"
@click="activeTab = model"
class="px-6 py-3 text-sm font-medium transition-colors"
:style="{
color: activeTab === model ? '#1890FF' : 'rgba(0, 0, 0, 0.65)',
borderBottom: activeTab === model ? '2px solid #1890FF' : 'none',
marginBottom: activeTab === model ? '-1px' : '0',
}"
>
{{ model }}
</button>
</div>
<!-- Checklist Content -->
<div class="p-6">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-3 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)', width: '50px' }">序号</th>
<th class="px-3 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">项目名称</th>
<th class="px-3 py-3 text-center text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)', width: '70px' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in checklistData[activeTab]"
:key="item.id"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-3 py-3 text-sm" :style="{ color: 'rgba(0, 0, 0, 0.45)' }">
<div class="flex items-center gap-1">
<GripVertical :size="14" :style="{ color: '#D9D9D9' }" />
{{ item.id }}
</div>
</td>
<td class="px-3 py-3 text-sm">
<input
v-if="editingCell?.model === activeTab && editingCell?.id === item.id && editingCell?.field === 'text'"
autofocus
class="w-full px-2 py-1 border rounded text-sm"
:style="{ borderColor: '#1890FF' }"
:value="item.text"
@blur="handleEditBlur($event, activeTab, item.id)"
@keydown="handleEditKeydown"
/>
<span
v-else
class="cursor-pointer hover:text-blue-500"
@click="editingCell = { model: activeTab, id: item.id, field: 'text' }"
>
{{ item.text }}
</span>
</td>
<td class="px-3 py-3 text-center">
<button
@click="deleteChecklistItem(activeTab, item.id)"
class="text-gray-400 hover:text-red-500 transition-colors"
>
<Trash2 :size="15" />
</button>
</td>
</tr>
</tbody>
</table>
<div class="mt-4 flex items-center justify-between">
<button
@click="addChecklistItem(activeTab)"
class="px-4 py-2 text-sm flex items-center gap-1 rounded transition-colors"
:style="{ color: '#1890FF', border: '1px dashed #1890FF' }"
>
<Plus :size="14" />
添加检查项
</button>
<button
class="px-6 py-2 text-sm rounded text-white"
:style="{ backgroundColor: '#1890FF' }"
>
确认保存
</button>
</div>
</div>
</div>
<!-- Board Version Requirements -->
<div class="bg-white rounded-lg" :style="{ boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)' }">
<div class="p-6 border-b" :style="{ borderColor: '#F0F0F0' }">
<h3 class="text-lg font-semibold">板卡版本要求</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full">
<thead :style="{ backgroundColor: '#FAFAFA' }">
<tr>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">板卡类型</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">要求固件版本</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">校验规则</th>
<th class="px-6 py-3 text-left text-sm font-medium" :style="{ color: 'rgba(0, 0, 0, 0.85)' }">操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(board, index) in boardVersionData"
:key="index"
class="border-b"
:style="{ borderColor: '#F0F0F0' }"
>
<td class="px-6 py-4">{{ board.boardType }}</td>
<td class="px-6 py-4">
<span
class="px-2 py-1 rounded text-xs"
:style="{ backgroundColor: '#F0F2F5', color: 'rgba(0, 0, 0, 0.85)' }"
>
{{ board.requiredVersion }}
</span>
</td>
<td class="px-6 py-4" :style="{ color: 'rgba(0, 0, 0, 0.65)' }">{{ board.validationRule }}</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>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>