refactor(device): 重构设备详情页,按面板拆分信息并接入新接口

- 新增设备基本信息、硬件信息、网络信息、安全信息、其他信息独立接口和类型定义
- 将设备详情页从单接口加载改为分面板(el-collapse)独立请求和展示
- 优化数据展示,使用 formatText/formatBool 处理空值和布尔值
- 移除旧的 deviceDeveloper 接口
- 更新 getPage 和 getFormData 接口路径
This commit is contained in:
TongTongStudio
2026-08-05 02:11:04 +08:00
parent d698dd7395
commit 6e700851c4
3 changed files with 791 additions and 385 deletions

View File

@@ -10,6 +10,11 @@ import type {
DevicePasswordVerifyForm, DevicePasswordVerifyForm,
DeviceMobileUpdateForm, DeviceMobileUpdateForm,
DeviceEmailUpdateForm, DeviceEmailUpdateForm,
DeviceBasicInfo,
DeviceHardwareInfo,
DeviceNetworkInfo,
DeviceOtherInfo,
DeviceSecurityInfo,
} from "./types"; } from "./types";
import type { ExcelResult, PageResult, OptionItem } from "@/api/common"; import type { ExcelResult, PageResult, OptionItem } from "@/api/common";
@@ -36,21 +41,22 @@ const DeviceAPI = {
*/ */
getPage(queryParams: DeviceQueryParams) { getPage(queryParams: DeviceQueryParams) {
return request<any, PageResult<DeviceItem>>({ return request<any, PageResult<DeviceItem>>({
url: `${DEVICE_BASE_URL}`, url: `${DEVICE_BASE_URL}/page`,
method: "get", method: "get",
params: queryParams, params: queryParams,
}); });
}, },
/** /**
* 获取设备表详情 * 获取设备头部信息(设备主表详情
* 对应后端 GET /{sn}/detail (getSnBindInfo)
* *
* @param sn 设备SN * @param sn 设备SN
* @returns 设备表单详情 * @returns 设备头部信息含在线状态、IMEI、所有者、部门等
*/ */
getFormData(sn: string) { getFormData(sn: string) {
return request<any, DeviceForm>({ return request<any, DeviceForm>({
url: `${DEVICE_BASE_URL}/${sn}/info`, url: `${DEVICE_BASE_URL}/${sn}/detail`,
method: "get", method: "get",
}); });
}, },
@@ -308,15 +314,45 @@ const DeviceAPI = {
}); });
}, },
/** // ===================== 设备系统信息(分面板独立接口) =====================
* 推送开发者模式指令到设备
* @param sn 设备SN号 /** 设备基本信息 */
*/ getBasicInfo(serialno: string) {
deviceDeveloper(sn: string) { return request<any, DeviceBasicInfo>({
return request({ url: `${DEVICE_BASE_URL}/${serialno}/basic_info`,
url: `${DEVICE_BASE_URL}/developer`, method: "get",
method: "post", });
params: { sn }, },
/** 设备硬件信息 */
getHardwareInfo(serialno: string) {
return request<any, DeviceHardwareInfo>({
url: `${DEVICE_BASE_URL}/${serialno}/hardware_info`,
method: "get",
});
},
/** 设备网络信息 */
getNetworkInfo(serialno: string) {
return request<any, DeviceNetworkInfo>({
url: `${DEVICE_BASE_URL}/${serialno}/network_info`,
method: "get",
});
},
/** 设备安全信息 */
getSecurityInfo(serialno: string) {
return request<any, DeviceSecurityInfo>({
url: `${DEVICE_BASE_URL}/${serialno}/security_info`,
method: "get",
});
},
/** 设备其他信息 */
getOtherInfo(serialno: string) {
return request<any, DeviceOtherInfo>({
url: `${DEVICE_BASE_URL}/${serialno}/other_info`,
method: "get",
}); });
}, },
}; };

View File

@@ -161,3 +161,171 @@ export interface DeviceEmailUpdateForm {
/** 当前密码 */ /** 当前密码 */
password?: string; password?: string;
} }
// ===================== 设备系统信息(分面板) =====================
/** 设备基本信息 */
export interface DeviceBasicInfo {
id?: number;
serialno?: string;
deviceName?: string;
deviceBrand?: string;
deviceModel?: string;
deviceBoard?: string;
deviceManufacturer?: string;
hardware?: string;
host?: string;
androidVersion?: string;
androidApi?: number;
androidId?: string;
buildId?: string;
buildDisplayId?: string;
buildFingerprint?: string;
buildType?: string;
buildUser?: string;
buildHost?: string;
buildTags?: string;
buildTime?: string;
language?: string;
timezone?: string;
bootTime?: string;
screenResolution?: string;
screenDensity?: number;
kernelVersion?: string;
romVersion?: string;
// ===================== 补充基本信息字段 =====================
productName?: string;
productModel?: string;
productDevice?: string;
boardPlatform?: string;
deviceType?: string;
isTablet?: number;
releaseVersion?: string;
displayVersion?: string;
bootloader?: string;
basebandVersion?: string;
radioVersion?: string;
screenSize?: string;
screenInch?: number;
refreshRate?: number;
osType?: string;
osVersion?: string;
firmwareVersion?: string;
}
/** 设备硬件信息 */
export interface DeviceHardwareInfo {
id?: number;
serialno?: string;
cpuAbi?: string;
cpuAbi2?: string;
cpuMin?: string;
cpuModel?: string;
cpuCores?: number;
cpuMaxFreq?: string;
cpuMinFreq?: string;
totalRam?: string;
availableRam?: string;
internalStorage?: string;
availableStorage?: string;
externalStorage?: string;
batteryCapacity?: string;
batteryLevel?: number;
batteryStatus?: string;
batteryHealth?: string;
batteryTemp?: string;
batteryVoltage?: string;
batteryTechnology?: string;
gpuRenderer?: string;
gpuVendor?: string;
gpuVersion?: string;
sensors?: string;
supportAbi?: string;
supportAbi64?: string;
}
/** 设备网络信息 */
export interface DeviceNetworkInfo {
id?: number;
serialno?: string;
networkType?: string;
networkSubtype?: string;
isConnected?: number;
wifiSsid?: string;
wifiBssid?: string;
wifiIp?: string;
wifiMac?: string;
wifiGateway?: string;
wifiNetmask?: string;
wifiDns?: string;
wifiRssi?: number;
wifiSpeed?: string;
mobileIp?: string;
mobileMac?: string;
mobileImei?: string;
mobileImsi?: string;
mobileOperator?: string;
mobileSimSerial?: string;
mobileNetworkType?: string;
bluetoothMac?: string;
bluetoothName?: string;
isVpn?: number;
isProxy?: number;
proxyHost?: string;
proxyPort?: string;
}
/** 设备安全信息 */
export interface DeviceSecurityInfo {
id?: number;
serialno?: string;
isRoot?: number;
isJailbreak?: number;
rootStatus?: number;
hasSuBinary?: number;
hasBusybox?: number;
isEmulator?: number;
isDebuggable?: number;
isUsbDebug?: number;
isUnknownSources?: number;
isVerifyApps?: number;
isEncrypted?: number;
lockScreenType?: string;
hasPassword?: number;
passwordType?: string;
securityPatch?: string;
googlePlayProtect?: number;
adbStatus?: number;
isVpnActive?: number;
isDeviceOwner?: number;
androidVersion?: string;
kernelVersion?: string;
}
/** 设备其他信息 */
export interface DeviceOtherInfo {
id?: number;
serialno?: string;
appVersion?: string;
appVersionCode?: number;
installTime?: string;
updateTimeApp?: string;
packageName?: string;
channel?: string;
deviceId?: string;
gaid?: string;
oaid?: string;
phoneNumber?: string;
iccid?: string;
imsi?: string;
cameraInfo?: string;
sensorCount?: number;
appListMd5?: string;
isCharging?: number;
chargingType?: string;
temperature?: string;
freeStorage?: string;
totalStorage?: string;
freeRam?: string;
totalRam?: string;
}

View File

@@ -18,39 +18,39 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">IMEI</span> <span class="label">IMEI</span>
<span class="value">{{ deviceData.imei }}</span> <span class="value">{{ formatText(deviceData.imei) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">设备所有者</span> <span class="label">设备所有者</span>
<span class="value">{{ deviceData.owner }}</span> <span class="value">{{ formatText(deviceData.owner) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">所属部门</span> <span class="label">所属部门</span>
<span class="value">{{ deviceData.department }}</span> <span class="value">{{ formatText(deviceData.department) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">分组</span> <span class="label">分组</span>
<span class="value">{{ deviceData.group }}</span> <span class="value">{{ formatText(deviceData.group) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">设备类型</span> <span class="label">设备类型</span>
<span class="value">{{ deviceData.deviceType }}</span> <span class="value">{{ formatText(deviceData.deviceType) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">设备来源</span> <span class="label">设备来源</span>
<span class="value">{{ deviceData.source }}</span> <span class="value">{{ formatText(deviceData.source) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">激活时间</span> <span class="label">激活时间</span>
<span class="value">{{ deviceData.activationTime }}</span> <span class="value">{{ formatText(deviceData.activationTime) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">最后上线</span> <span class="label">最后上线</span>
<span class="value">{{ deviceData.lastOnline }}</span> <span class="value">{{ formatText(deviceData.lastOnline) }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">设备编号</span> <span class="label">设备编号</span>
<span class="value">{{ deviceData.deviceSn }}</span> <span class="value">{{ formatText(deviceData.deviceSn) }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -109,220 +109,349 @@
</div> </div>
</div> </div>
<!-- 标签页内容 --> <!-- 设备信息标签页第一个 tab内含基本信息等分面板独立加载 -->
<div class="tabs-container"> <el-tabs v-model="activeTab" class="tabs-container device-tabs">
<el-tabs v-model="activeTab" class="device-tabs">
<el-tab-pane label="设备信息" name="deviceInfo"> <el-tab-pane label="设备信息" name="deviceInfo">
<div class="tab-content"> <el-collapse v-model="activeNames" class="device-sn-detail">
<!-- 基本信息 --> <!-- 基本信息 -->
<div class="info-section"> <el-collapse-item title="基本信息" name="basic">
<h3 class="section-title">基本信息</h3> <template #title>
<el-descriptions :column="2" border> <div class="panel-title">
基本信息
<el-tag v-if="loading.basic" size="small" type="info" effect="plain">
加载中
</el-tag>
</div>
</template>
<el-descriptions v-loading="loading.basic" :column="2" border label-width="130px">
<el-descriptions-item label="设备名称"> <el-descriptions-item label="设备名称">
{{ basicInfo.deviceName }} {{ formatText(basicInfo.deviceName) }}
</el-descriptions-item>
<el-descriptions-item label="设备品牌">
{{ formatText(basicInfo.deviceBrand) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备型号"> <el-descriptions-item label="设备型号">
{{ basicInfo.deviceModel }} {{ formatText(basicInfo.deviceModel) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="操作系统">{{ basicInfo.os }}</el-descriptions-item> <el-descriptions-item label="主板名称">
<el-descriptions-item label="操作系统版本号"> {{ formatText(basicInfo.deviceBoard) }}
{{ basicInfo.osVersion }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备语言"> <el-descriptions-item label="制造商">
{{ basicInfo.language }} {{ formatText(basicInfo.deviceManufacturer) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="硬件信息">
<el-descriptions-item label="时区">{{ basicInfo.timezone }}</el-descriptions-item> {{ formatText(basicInfo.hardware) }}
<el-descriptions-item label="设备标识(IMEI)">
{{ basicInfo.imei }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="蓝牙地址"> <el-descriptions-item label="主机名">
{{ basicInfo.bluetoothMac }} {{ formatText(basicInfo.host) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Android 版本">
<el-descriptions-item label="Wi-Fi MAC地址"> {{ formatText(basicInfo.androidVersion) }}
{{ basicInfo.wifiMac }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备颜色">{{ basicInfo.color }}</el-descriptions-item> <el-descriptions-item label="Android API">
<el-descriptions-item label="设备容量"> {{ formatText(basicInfo.androidApi) }}
{{ basicInfo.capacity }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="可用容量"> <el-descriptions-item label="Android ID">
{{ basicInfo.availableCapacity }} {{ formatText(basicInfo.androidId) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Build ID">
<el-descriptions-item label="内存容量"> {{ formatText(basicInfo.buildId) }}
{{ basicInfo.memoryCapacity }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Build Display ID">
<el-descriptions-item label="可用内存"> {{ formatText(basicInfo.buildDisplayId) }}
{{ basicInfo.availableMemory }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Build 类型">
<el-descriptions-item label="电池电量"> {{ formatText(basicInfo.buildType) }}
{{ basicInfo.batteryLevel }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="系统语言">
<el-descriptions-item label="电池健康度"> {{ formatText(basicInfo.language) }}
{{ basicInfo.batteryHealth }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备重量">{{ basicInfo.weight }}</el-descriptions-item> <el-descriptions-item label="时区">
<el-descriptions-item label="设备尺寸"> {{ formatText(basicInfo.timezone) }}
{{ basicInfo.dimensions }}
</el-descriptions-item>
<el-descriptions-item
label="SIM卡状态
"
>
{{ basicInfo.simStatus }}
</el-descriptions-item>
<el-descriptions-item label="电话号码">
{{ basicInfo.phoneNumber }}
</el-descriptions-item>
<el-descriptions-item label="ICCID">
{{ basicInfo.iccid }}
</el-descriptions-item>
<el-descriptions-item label="MEID">
{{ basicInfo.meid }}
</el-descriptions-item>
</el-descriptions>
</div>
<!-- 硬件信息 -->
<div class="info-section">
<h3 class="section-title">硬件信息</h3>
<el-descriptions :column="2" border>
<el-descriptions-item label="处理器">
{{ hardwareInfo.processor }}
</el-descriptions-item>
<el-descriptions-item label="处理器核心数">
{{ hardwareInfo.processorCores }}
</el-descriptions-item>
<el-descriptions-item label="GPU">
{{ hardwareInfo.gpu }}
</el-descriptions-item>
<el-descriptions-item label="屏幕尺寸">
{{ hardwareInfo.screenSize }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="屏幕分辨率"> <el-descriptions-item label="屏幕分辨率">
{{ hardwareInfo.screenResolution }} {{ formatText(basicInfo.screenResolution) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="屏幕像素密度"> <el-descriptions-item label="屏幕密度">
{{ hardwareInfo.screenDensity }} {{ formatText(basicInfo.screenDensity) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="摄像头"> <el-descriptions-item label="内核版本">
{{ hardwareInfo.camera }} {{ formatText(basicInfo.kernelVersion) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备传感器"> <el-descriptions-item label="ROM 版本">
{{ hardwareInfo.sensors }} {{ formatText(basicInfo.romVersion) }}
</el-descriptions-item>
<el-descriptions-item label="NFC">
{{ hardwareInfo.nfc }}
</el-descriptions-item>
<el-descriptions-item label="Face ID">
{{ hardwareInfo.faceId }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item>
<!-- 硬件信息 -->
<el-collapse-item title="硬件信息" name="hardware">
<template #title>
<div class="panel-title">
硬件信息
<el-tag v-if="loading.hardware" size="small" type="info" effect="plain">
加载中
</el-tag>
</div> </div>
</template>
<el-descriptions v-loading="loading.hardware" :column="2" border label-width="130px">
<el-descriptions-item label="CPU ABI">
{{ formatText(hardwareInfo.cpuAbi) }}
</el-descriptions-item>
<el-descriptions-item label="CPU 型号">
{{ formatText(hardwareInfo.cpuModel) }}
</el-descriptions-item>
<el-descriptions-item label="CPU 核心数">
{{ formatText(hardwareInfo.cpuCores) }}
</el-descriptions-item>
<el-descriptions-item label="CPU 最大频率">
{{ formatText(hardwareInfo.cpuMaxFreq) }}
</el-descriptions-item>
<el-descriptions-item label="CPU 最小频率">
{{ formatText(hardwareInfo.cpuMinFreq) }}
</el-descriptions-item>
<el-descriptions-item label="总内存">
{{ formatText(hardwareInfo.totalRam) }}
</el-descriptions-item>
<el-descriptions-item label="可用内存">
{{ formatText(hardwareInfo.availableRam) }}
</el-descriptions-item>
<el-descriptions-item label="内部存储">
{{ formatText(hardwareInfo.internalStorage) }}
</el-descriptions-item>
<el-descriptions-item label="可用存储">
{{ formatText(hardwareInfo.availableStorage) }}
</el-descriptions-item>
<el-descriptions-item label="外部存储">
{{ formatText(hardwareInfo.externalStorage) }}
</el-descriptions-item>
<el-descriptions-item label="电池容量">
{{ formatText(hardwareInfo.batteryCapacity) }}
</el-descriptions-item>
<el-descriptions-item label="电池电量">
{{ formatText(hardwareInfo.batteryLevel) }}
</el-descriptions-item>
<el-descriptions-item label="电池状态">
{{ formatText(hardwareInfo.batteryStatus) }}
</el-descriptions-item>
<el-descriptions-item label="电池健康度">
{{ formatText(hardwareInfo.batteryHealth) }}
</el-descriptions-item>
<el-descriptions-item label="电池温度">
{{ formatText(hardwareInfo.batteryTemp) }}
</el-descriptions-item>
<el-descriptions-item label="GPU 渲染器">
{{ formatText(hardwareInfo.gpuRenderer) }}
</el-descriptions-item>
<el-descriptions-item label="GPU 厂商">
{{ formatText(hardwareInfo.gpuVendor) }}
</el-descriptions-item>
<el-descriptions-item label="GPU 版本">
{{ formatText(hardwareInfo.gpuVersion) }}
</el-descriptions-item>
<el-descriptions-item label="传感器">
{{ formatText(hardwareInfo.sensors) }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<!-- 网络信息 --> <!-- 网络信息 -->
<div class="info-section"> <el-collapse-item title="网络信息" name="network">
<h3 class="section-title">网络信息</h3> <template #title>
<el-descriptions :column="2" border> <div class="panel-title">
<el-descriptions-item label="当前网络"> 网络信息
{{ networkInfo.currentNetwork }} <el-tag v-if="loading.network" size="small" type="info" effect="plain">
加载中
</el-tag>
</div>
</template>
<el-descriptions v-loading="loading.network" :column="2" border label-width="130px">
<el-descriptions-item label="网络类型">
{{ formatText(networkInfo.networkType) }}
</el-descriptions-item>
<el-descriptions-item label="是否联网">
{{ formatBool(networkInfo.isConnected) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi SSID">
{{ formatText(networkInfo.wifiSsid) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi BSSID">
{{ formatText(networkInfo.wifiBssid) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi IP">
{{ formatText(networkInfo.wifiIp) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi MAC">
{{ formatText(networkInfo.wifiMac) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi 网关">
{{ formatText(networkInfo.wifiGateway) }}
</el-descriptions-item>
<el-descriptions-item label="WiFi 信号">
{{ formatText(networkInfo.wifiRssi) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="运营商"> <el-descriptions-item label="运营商">
{{ networkInfo.carrier }} {{ formatText(networkInfo.mobileOperator) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="信号强度"> <el-descriptions-item label="IMEI">
{{ networkInfo.signalStrength }} {{ formatText(networkInfo.mobileImei) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="IP地址"> <el-descriptions-item label="IMSI">
{{ networkInfo.ipAddress }} {{ formatText(networkInfo.mobileImsi) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Wi-Fi名称"> <el-descriptions-item label="蓝牙 MAC">
{{ networkInfo.wifiName }} {{ formatText(networkInfo.bluetoothMac) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="Wi-Fi MAC地址"> <el-descriptions-item label="蓝牙名称">
{{ networkInfo.wifiMac }} {{ formatText(networkInfo.bluetoothName) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="蜂窝网络类型"> <el-descriptions-item label="VPN">
{{ networkInfo.cellularType }} {{ formatBool(networkInfo.isVpn) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="漫游状态"> <el-descriptions-item label="代理">
{{ networkInfo.roamingStatus }} {{ formatBool(networkInfo.isProxy) }}
</el-descriptions-item>
<el-descriptions-item label="代理主机">
{{ formatText(networkInfo.proxyHost) }}
</el-descriptions-item>
<el-descriptions-item label="代理端口">
{{ formatText(networkInfo.proxyPort) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </el-collapse-item>
<!-- 安全信息 --> <!-- 安全信息 -->
<div class="info-section"> <el-collapse-item title="安全信息" name="security">
<h3 class="section-title">安全信息</h3> <template #title>
<el-descriptions :column="2" border> <div class="panel-title">
<el-descriptions-item label="锁屏密码"> 安全信息
{{ securityInfo.lockScreenPassword }} <el-tag v-if="loading.security" size="small" type="info" effect="plain">
加载中
</el-tag>
</div>
</template>
<el-descriptions v-loading="loading.security" :column="2" border label-width="130px">
<el-descriptions-item label="Root 状态">
{{ formatBool(securityInfo.rootStatus) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="密码复杂度"> <el-descriptions-item label="已 Root">
{{ securityInfo.passwordComplexity }} {{ formatBool(securityInfo.isRoot) }}
</el-descriptions-item>
<el-descriptions-item label="数据保护">
{{ securityInfo.dataProtection }}
</el-descriptions-item>
<el-descriptions-item label="查找我的iPhone">
{{ securityInfo.findMyIPhone }}
</el-descriptions-item>
<el-descriptions-item label="设备加密">
{{ securityInfo.deviceEncryption }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="越狱状态"> <el-descriptions-item label="越狱状态">
{{ securityInfo.jailbreakStatus }} {{ formatBool(securityInfo.isJailbreak) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="受监管状态"> <el-descriptions-item label="su 二进制">
{{ securityInfo.supervisedStatus }} {{ formatBool(securityInfo.hasSuBinary) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="最后安全扫描时间"> <el-descriptions-item label="BusyBox">
{{ securityInfo.lastSecurityScan }} {{ formatBool(securityInfo.hasBusybox) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="安全补丁版本"> <el-descriptions-item label="模拟器">
{{ securityInfo.securityPatchVersion }} {{ formatBool(securityInfo.isEmulator) }}
</el-descriptions-item>
<el-descriptions-item label="可调试">
{{ formatBool(securityInfo.isDebuggable) }}
</el-descriptions-item>
<el-descriptions-item label="USB 调试">
{{ formatBool(securityInfo.isUsbDebug) }}
</el-descriptions-item>
<el-descriptions-item label="未知来源">
{{ formatBool(securityInfo.isUnknownSources) }}
</el-descriptions-item>
<el-descriptions-item label="加密">
{{ formatBool(securityInfo.isEncrypted) }}
</el-descriptions-item>
<el-descriptions-item label="密码保护">
{{ formatBool(securityInfo.hasPassword) }}
</el-descriptions-item>
<el-descriptions-item label="锁屏类型">
{{ formatText(securityInfo.lockScreenType) }}
</el-descriptions-item>
<el-descriptions-item label="安全补丁">
{{ formatText(securityInfo.securityPatch) }}
</el-descriptions-item>
<el-descriptions-item label="ADB 状态">
{{ formatBool(securityInfo.adbStatus) }}
</el-descriptions-item>
<el-descriptions-item label="VPN 激活">
{{ formatBool(securityInfo.isVpnActive) }}
</el-descriptions-item>
<el-descriptions-item label="设备所有者">
{{ formatBool(securityInfo.isDeviceOwner) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </el-collapse-item>
<!-- 其他信息 --> <!-- 其他信息 -->
<div class="info-section"> <el-collapse-item title="其他信息" name="other">
<h3 class="section-title">其他信息</h3> <template #title>
<el-descriptions :column="2" border> <div class="panel-title">
<el-descriptions-item label="购买日期"> 其他信息
{{ otherInfo.purchaseDate }} <el-tag v-if="loading.other" size="small" type="info" effect="plain">
加载中
</el-tag>
</div>
</template>
<el-descriptions v-loading="loading.other" :column="2" border label-width="130px">
<el-descriptions-item label="App 版本">
{{ formatText(otherInfo.appVersion) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="保修到期日"> <el-descriptions-item label="App 版本号">
{{ otherInfo.warrantyExpiry }} {{ formatText(otherInfo.appVersionCode) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="设备来源">{{ otherInfo.source }}</el-descriptions-item> <el-descriptions-item label="包名">
<el-descriptions-item label="分配日期"> {{ formatText(otherInfo.packageName) }}
{{ otherInfo.assignDate }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="备注">{{ otherInfo.remark }}</el-descriptions-item> <el-descriptions-item label="渠道">
<el-descriptions-item label="标签">{{ otherInfo.tags }}</el-descriptions-item> {{ formatText(otherInfo.channel) }}
<el-descriptions-item label="自定义字段1">
{{ otherInfo.customField1 }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="自定义字段2"> <el-descriptions-item label="设备 ID">
{{ otherInfo.customField2 }} {{ formatText(otherInfo.deviceId) }}
</el-descriptions-item>
<el-descriptions-item label="GAID">
{{ formatText(otherInfo.gaid) }}
</el-descriptions-item>
<el-descriptions-item label="OAID">
{{ formatText(otherInfo.oaid) }}
</el-descriptions-item>
<el-descriptions-item label="手机号">
{{ formatText(otherInfo.phoneNumber) }}
</el-descriptions-item>
<el-descriptions-item label="ICCID">
{{ formatText(otherInfo.iccid) }}
</el-descriptions-item>
<el-descriptions-item label="IMSI">
{{ formatText(otherInfo.imsi) }}
</el-descriptions-item>
<el-descriptions-item label="充电中">
{{ formatBool(otherInfo.isCharging) }}
</el-descriptions-item>
<el-descriptions-item label="充电类型">
{{ formatText(otherInfo.chargingType) }}
</el-descriptions-item>
<el-descriptions-item label="设备温度">
{{ formatText(otherInfo.temperature) }}
</el-descriptions-item>
<el-descriptions-item label="剩余存储">
{{ formatText(otherInfo.freeStorage) }}
</el-descriptions-item>
<el-descriptions-item label="剩余内存">
{{ formatText(otherInfo.freeRam) }}
</el-descriptions-item>
<el-descriptions-item label="传感器数量">
{{ formatText(otherInfo.sensorCount) }}
</el-descriptions-item>
<el-descriptions-item label="应用列表 MD5" :span="2">
{{ formatText(otherInfo.appListMd5) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </el-collapse-item>
</div> </el-collapse>
</el-tab-pane> </el-tab-pane>
<!-- 扩展标签页应用信息 / 策略信息 / 合规状态 / 操作日志 / 定位信息 -->
<el-tab-pane label="应用信息" name="appInfo"> <el-tab-pane label="应用信息" name="appInfo">
<div class="ta,b-content"> <div class="tab-content">
<el-empty description="应用信息加载中..." /> <el-empty description="应用信息加载中..." />
</div> </div>
</el-tab-pane> </el-tab-pane>
@@ -349,17 +478,21 @@
<div class="tab-content"> <div class="tab-content">
<el-empty description="定位信息加载中..." /> <el-empty description="定位信息加载中..." />
</div> </div>
,
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import DeviceAPI from "@/api/system/device"; import DeviceAPI from "@/api/system/device";
import type {
import { ref, reactive, watch, onMounted } from "vue"; DeviceBasicInfo,
DeviceHardwareInfo,
DeviceNetworkInfo,
DeviceOtherInfo,
DeviceSecurityInfo,
} from "@/api/system/device/types";
import { reactive, ref, watch } from "vue";
import { import {
Back, Back,
Edit, Edit,
@@ -372,12 +505,6 @@ import {
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
onMounted(() => {
if (props.serialno) {
loadDeviceDetail(props.serialno);
}
});
// 接收传入的 props // 接收传入的 props
interface Props { interface Props {
modelValue?: boolean; // 控制对话框显示隐藏 modelValue?: boolean; // 控制对话框显示隐藏
@@ -398,20 +525,77 @@ const emit = defineEmits<{
// 监听 serialno 变化,加载对应的数据 // 监听 serialno 变化,加载对应的数据
watch( watch(
() => props.serialno, () => props.serialno,
async (newSerialno) => { (newSerialno) => {
if (newSerialno) { if (newSerialno) {
await loadDeviceDetail(newSerialno); loadAll(newSerialno);
} }
}, },
{ immediate: true } { immediate: true }
); );
// 加载设备详情的方法 const activeNames = ref(["basic", "hardware", "network", "security", "other"]);
const loadDeviceDetail = async (serialno: string) => {
/** 标签页当前选中项(设备信息为第一个 tab内含基本/硬件/网络/安全/其他分面板) */
const activeTab = ref("deviceInfo");
/** 设备头部数据 */
const deviceData = reactive({
snName: "",
onlineStatus: 0,
owner: "",
department: "",
group: "",
deviceType: "",
source: "",
activationTime: "",
lastOnline: "",
deviceSn: "",
imei: "",
serialNumber: "",
});
/** 设备图片 */
const deviceImage = ref("./src/assets/images/iphone-17-pro.webp");
/** 分面板数据 */
const basicInfo = reactive<Partial<DeviceBasicInfo>>({});
const hardwareInfo = reactive<Partial<DeviceHardwareInfo>>({});
const networkInfo = reactive<Partial<DeviceNetworkInfo>>({});
const securityInfo = reactive<Partial<DeviceSecurityInfo>>({});
const otherInfo = reactive<Partial<DeviceOtherInfo>>({});
const loading = reactive({
basic: false,
hardware: false,
network: false,
security: false,
other: false,
});
/** 空值统一显示占位符 */
function formatText(value?: string | number | null): string {
return value === undefined || value === null || value === "" ? "-" : String(value);
}
/** 0/1 枚举转文案 */
function formatBool(value?: number | null): string {
if (value === undefined || value === null) return "-";
return value === 1 ? "是" : "否";
}
function assignTo<T extends object>(reactiveObj: T, data?: object) {
Object.keys(reactiveObj as object).forEach(
(key) => delete (reactiveObj as Record<string, unknown>)[key]
);
if (data) {
Object.assign(reactiveObj, data);
}
}
/** 加载设备头部信息 */
async function loadDeviceDetail(serialno: string) {
try { try {
// 调用 API 获取设备详细信息
const response = await DeviceAPI.getFormData(serialno); const response = await DeviceAPI.getFormData(serialno);
// 更新 deviceData 等响应式数据
if (response) { if (response) {
Object.assign(deviceData, { Object.assign(deviceData, {
snName: response.snName, snName: response.snName,
@@ -430,118 +614,97 @@ const loadDeviceDetail = async (serialno: string) => {
ElMessage.error("加载设备详情失败"); ElMessage.error("加载设备详情失败");
console.error("Error loading device detail:", error); console.error("Error loading device detail:", error);
} }
}; }
// 设备数据 /** 基本信息 */
const deviceData = reactive({ async function loadBasicInfo(serialno: string) {
snName: "iPhone 15 Pro Max", loading.basic = true;
onlineStatus: 1, try {
owner: "张三", const data = await DeviceAPI.getBasicInfo(serialno);
department: "销售部", assignTo(basicInfo, data);
group: "华东区域 / 上海分公司", } catch (e) {
deviceType: "iOS", console.error("加载基本信息失败", e);
source: "企业注册", } finally {
activationTime: "2024-02-18 09:32:21", loading.basic = false;
lastOnline: "10:15:42", }
deviceSn: "550e8400-e29b-41d4-a716-446655440000", }
imei: "35455016276432",
serialNumber: "H0FJXOY7XO",
});
// 设备图片 /** 硬件信息 */
const deviceImage = ref("./src/assets/images/iphone-17-pro.webp"); async function loadHardwareInfo(serialno: string) {
loading.hardware = true;
try {
const data = await DeviceAPI.getHardwareInfo(serialno);
assignTo(hardwareInfo, data);
} catch (e) {
console.error("加载硬件信息失败", e);
} finally {
loading.hardware = false;
}
}
// 标签页 /** 网络信息 */
const activeTab = ref("deviceInfo"); async function loadNetworkInfo(serialno: string) {
loading.network = true;
try {
const data = await DeviceAPI.getNetworkInfo(serialno);
assignTo(networkInfo, data);
} catch (e) {
console.error("加载网络信息失败", e);
} finally {
loading.network = false;
}
}
// 基本信息 /** 安全信息 */
const basicInfo = reactive({ async function loadSecurityInfo(serialno: string) {
deviceName: "iPhone 15 Pro Max", loading.security = true;
deviceModel: "iPhone16,2", try {
os: "iOS 17.4.1", const data = await DeviceAPI.getSecurityInfo(serialno);
osVersion: "21E236", assignTo(securityInfo, data);
language: "zh-Hans-CN", } catch (e) {
timezone: "Asia/Shanghai", console.error("加载安全信息失败", e);
imei: "00008110-001C195E3EA1802E", } finally {
bluetoothMac: "90:81:5B:1B:4A:7F", loading.security = false;
wifiMac: "90:81:5B:1B:4A:7F", }
color: "原色钛金属", }
capacity: "256 GB",
availableCapacity: "128.35 GB (50.1%)",
memoryCapacity: "8 GB",
availableMemory: "3.12 GB (39.0%)",
batteryLevel: "87%",
batteryHealth: "98%",
weight: "221 g",
dimensions: "159.9 x 76.7 x 8.25 mm",
simStatus: "单卡",
phoneNumber: "+86 138 0000 0000",
iccid: "8986 0112 3456 7890 1234",
meid: "35455016276543",
});
// 硬件信息 /** 其他信息 */
const hardwareInfo = reactive({ async function loadOtherInfo(serialno: string) {
processor: "A17 Pro", loading.other = true;
processorCores: "6", try {
gpu: "Apple GPU", const data = await DeviceAPI.getOtherInfo(serialno);
screenSize: "6.7 英寸", assignTo(otherInfo, data);
screenResolution: "2796 x 1290", } catch (e) {
screenDensity: "4,60 ppi", console.error("加载其他信息失败", e);
camera: "后置三摄 48MP + 12MP + 12MP前置 12MP", } finally {
sensors: "加速度计,陀螺仪,指南针,气压计,距离传感器,环境光传感器,面容 ID", loading.other = false;
nfc: "支持", }
faceId: "支持", }
});
// 网络信息 /** 进入界面时分开加载各面板(含头部) */
const networkInfo = reactive({ async function loadAll(serialno?: string) {
currentNetwork: "Wi-Fi", if (!serialno) return;
carrier: "中国移动", loadDeviceDetail(serialno);
signalStrength: "强", loadBasicInfo(serialno);
ipAddress: "192.168.1.102", loadHardwareInfo(serialno);
wifiName: "Office-WiFi", loadNetworkInfo(serialno);
wifiMac: "90:81:5B:1B:4A:7F", loadSecurityInfo(serialno);
cellularType: "5G", loadOtherInfo(serialno);
roamingStatus: "否", }
});
// 安全信息 defineExpose({ loadAll });
const securityInfo = reactive({
lockScreenPassword: "已设置",
passwordComplexity: "高",
dataProtection: "已开启",
findMyIPhone: "已开启",
deviceEncryption: "已开启",
jailbreakStatus: "否",
supervisedStatus: "是",
lastSecurityScan: "2024-05-23 09:50:11",
securityPatchVersion: "iOS 17.4.1 (21E236)",
});
// 其他信息 /** 操作按钮事件 */
const otherInfo = reactive({
purchaseDate: "2024-02-10",
warrantyExpiry: "2025-02-09",
source: "企业注册",
assignDate: "2024-02-18",
remark: "销售部员工配发设备",
tags: "重要设备,外勤设备",
customField1: "-",
customField2: "-",
});
// 更新返回按钮事件
const handleReturn = () => { const handleReturn = () => {
emit("update:modelValue", false); // 隐藏对话框 emit("update:modelValue", false);
emit("returnClick"); // 触发返回事件 emit("returnClick");
}; };
const handleEdit = () => { const handleEdit = () => {
ElMessage.success("编辑功能"); ElMessage.success("编辑功能");
}; };
const handleRefresh = () => { const handleRefresh = () => {
loadDeviceDetail(props.serialno).then(() => { loadAll(props.serialno).then(() => {
ElMessage.success("刷新数据成功"); ElMessage.success("刷新数据成功");
}); });
}; };
@@ -695,6 +858,61 @@ const handleMoreActions = (command: string) => {
} }
} }
.device-sn-detail {
padding: 20px;
margin-bottom: 20px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
// 各分面板的描述列表统一对齐:
// 采用固定表格布局,让「标签列 / 内容列」在所有面板中列宽完全一致。
// 这样即使某个面板的条目数为奇数(最后一行只有一项)或存在 :span="2" 的跨列项,
// 该单元格也不会拉伸占满整行,从而避免各面板之间竖向分割线参差不齐。
:deep(.el-descriptions) {
--el-descriptions-item-bordered-label-width: 130px;
.el-descriptions__body .el-descriptions__table {
width: 100%;
table-layout: fixed;
}
// 2 列布局共 4 个单元格:标签 130px / 内容自适应 / 标签 130px / 内容自适应
.el-descriptions__body .el-descriptions__table .el-descriptions__cell {
width: calc((100% - 260px) / 2);
}
.el-descriptions__label {
width: 130px;
min-width: 130px;
font-weight: 500;
vertical-align: middle;
color: #606266;
text-align: left;
background-color: #fafafa;
}
.el-descriptions__content {
vertical-align: middle;
word-break: break-all;
}
}
// 分面板之间保留统一间距,避免参差不齐
:deep(.el-collapse-item) {
& + .el-collapse-item {
margin-top: 16px;
}
}
.panel-title {
display: flex;
gap: 8px;
align-items: center;
font-weight: 600;
}
}
.tabs-container { .tabs-container {
padding: 20px; padding: 20px;
background: #fff; background: #fff;
@@ -712,23 +930,7 @@ const handleMoreActions = (command: string) => {
} }
.tab-content { .tab-content {
.info-section { min-height: 120px;
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: inline-block;
padding-bottom: 8px;
margin-bottom: 16px;
font-size: 16px;
font-weight: 600;
color: #303133;
border-bottom: 2px solid #409eff;
}
}
} }
} }
} }