416 lines
17 KiB
Vue
416 lines
17 KiB
Vue
<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> |