diff --git a/src/api/system/device/index.ts b/src/api/system/device/index.ts index d4103e30..ee1e7763 100644 --- a/src/api/system/device/index.ts +++ b/src/api/system/device/index.ts @@ -17,6 +17,7 @@ import type { DeviceSecurityInfo, ApkInstallInfo, DeviceLocationInfo, + DeviceScreenshot, } from "./types"; import type { ExcelResult, PageResult, OptionItem } from "@/api/common"; @@ -379,6 +380,46 @@ const DeviceAPI = { }); }, + /** + * 获取设备截图列表(按上传时间倒序) + * 对应后端 GET /{sn}/screenshots (listScreenshots) + * + * @param sn 设备SN号 + */ + getScreenshotList(sn: string) { + return request({ + url: `${DEVICE_BASE_URL}/${sn}/screenshots`, + method: "get", + }); + }, + + /** + * 删除单张设备截图 + * 对应后端 DELETE /{sn}/screenshots/{id} (deleteScreenshot) + * + * @param sn 设备SN号 + * @param id 截图ID + */ + deleteScreenshot(sn: string, id: number) { + return request({ + url: `${DEVICE_BASE_URL}/${sn}/screenshots/${id}`, + method: "delete", + }); + }, + + /** + * 清空设备全部截图 + * 对应后端 DELETE /{sn}/screenshots (clearScreenshots) + * + * @param sn 设备SN号 + */ + clearScreenshots(sn: string) { + return request({ + url: `${DEVICE_BASE_URL}/${sn}/screenshots`, + method: "delete", + }); + }, + /** 打开应用 */ launchApp(sn: string, packageName: string) { return request({ diff --git a/src/api/system/device/types.ts b/src/api/system/device/types.ts index 11fe59b5..bab23a94 100644 --- a/src/api/system/device/types.ts +++ b/src/api/system/device/types.ts @@ -401,3 +401,19 @@ export interface DeviceLocationInfo { /** 上次定位成功时间 */ lastSuccessfulTime?: string; } + +/** 设备截图信息 */ +export interface DeviceScreenshot { + /** 截图ID */ + id?: number; + /** 设备序列号 */ + sn?: string; + /** 文件名称 */ + fileName?: string; + /** 文件大小(字节) */ + fileSize?: number; + /** 可访问的图片URL */ + url?: string; + /** 上传时间 */ + uploadTime?: string; +} diff --git a/src/components/DeviceSn/DeviceSnDetail.vue b/src/components/DeviceSn/DeviceSnDetail.vue index 3551df74..e359fe02 100644 --- a/src/components/DeviceSn/DeviceSnDetail.vue +++ b/src/components/DeviceSn/DeviceSnDetail.vue @@ -56,6 +56,88 @@ + +
+
+ 设备截图 + + {{ screenshots.length }} 张 + + + 刷新 + + + 截图 + + + 清空截图 + +
+ + + +
+ + + +
+ 上传时间:{{ item.uploadTime }} +
+ + + + +
+
+
+ + + +
+
@@ -668,6 +750,7 @@ import type { DeviceOtherInfo, DeviceSecurityInfo, DeviceLocationInfo, + DeviceScreenshot, } from "@/api/system/device/types"; import { nextTick, reactive, ref, watch } from "vue"; import { @@ -677,6 +760,7 @@ import { Lock, Warning, Delete, + Loading, RefreshRight, ArrowDown, VideoPlay, @@ -740,6 +824,22 @@ const deviceData = reactive({ /** 设备图片 */ const deviceImage = ref("./src/assets/images/iphone-17-pro.webp"); +/** 设备截图列表 */ +const screenshots = ref([]); +const screenshotLoading = ref(false); +/** 当前设备SN,用于刷新截图 */ +const currentSn = ref(""); +/** 全屏预览可见性 */ +const previewVisible = ref(false); +/** 全屏预览初始索引 */ +const previewIndex = ref(0); +/** 截图指令请求中 */ +const captureLoading = ref(false); +/** 清空截图请求中 */ +const clearLoading = ref(false); +/** 正在删除的截图ID(用于单张删除按钮 loading) */ +const deletingId = ref(null); + /** 分面板数据 */ const basicInfo = reactive>({}); const hardwareInfo = reactive>({}); @@ -1026,6 +1126,103 @@ async function loadAll(serialno?: string) { loadSecurityInfo(serialno); loadOtherInfo(serialno); loadLocationInfo(serialno); + loadScreenshots(serialno); +} + +/** 设备截图列表 */ +async function loadScreenshots(serialno: string) { + if (!serialno) return; + currentSn.value = serialno; + screenshotLoading.value = true; + try { + const data = await DeviceAPI.getScreenshotList(serialno); + screenshots.value = data || []; + } catch (e) { + console.error("加载设备截图失败", e); + screenshots.value = []; + } finally { + screenshotLoading.value = false; + } +} + +/** 刷新设备截图列表 */ +function refreshScreenshots() { + loadScreenshots(currentSn.value); +} + +/** 点击单张截图,全屏预览(保留当前轮播顺序) */ +function openPreview(index: number) { + previewIndex.value = index; + previewVisible.value = true; +} + +/** 请求设备截图,下发指令后延迟 5 秒自动刷新一次列表 */ +async function captureScreenshot() { + if (!currentSn.value) return; + captureLoading.value = true; + try { + await DeviceAPI.deviceScreenshot(currentSn.value); + ElMessage.success("已下发截图指令,5 秒后自动刷新"); + } catch (e) { + console.error("下发截图指令失败", e); + ElMessage.error("下发截图指令失败"); + } finally { + captureLoading.value = false; + } + // 延迟 5 秒自动刷新一次截图列表 + setTimeout(() => { + loadScreenshots(currentSn.value); + }, 5000); +} + +/** 删除单张截图 */ +async function deleteScreenshotItem(item: DeviceScreenshot) { + if (!item.id || !currentSn.value) return; + try { + await ElMessageBox.confirm("确认删除该截图?", "提示", { + type: "warning", + confirmButtonText: "删除", + cancelButtonText: "取消", + }); + } catch { + return; // 用户取消 + } + deletingId.value = item.id; + try { + await DeviceAPI.deleteScreenshot(currentSn.value, item.id); + ElMessage.success("删除成功"); + await loadScreenshots(currentSn.value); + } catch (e) { + console.error("删除截图失败", e); + ElMessage.error("删除截图失败"); + } finally { + deletingId.value = null; + } +} + +/** 清空设备全部截图 */ +async function clearScreenshotsFn() { + if (!currentSn.value || screenshots.value.length === 0) return; + try { + await ElMessageBox.confirm("确认清空该设备的全部截图?此操作不可恢复。", "提示", { + type: "warning", + confirmButtonText: "清空", + cancelButtonText: "取消", + }); + } catch { + return; // 用户取消 + } + clearLoading.value = true; + try { + const count = await DeviceAPI.clearScreenshots(currentSn.value); + ElMessage.success(`已清空 ${count} 张截图`); + await loadScreenshots(currentSn.value); + } catch (e) { + console.error("清空截图失败", e); + ElMessage.error("清空截图失败"); + } finally { + clearLoading.value = false; + } } /** 应用信息 */ @@ -1213,6 +1410,10 @@ const handleMoreActions = (command: string) => { background: #f5f7fa; .device-header { + display: flex; + flex-wrap: wrap; + gap: 24px; + align-items: stretch; padding: 20px; margin-bottom: 20px; background: #fff; @@ -1221,8 +1422,9 @@ const handleMoreActions = (command: string) => { .device-info { display: flex; + flex: 1; gap: 20px; - margin-bottom: 20px; + min-width: 360px; .device-image { width: 120px; @@ -1250,7 +1452,7 @@ const handleMoreActions = (command: string) => { .info-grid { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(2, 1fr); gap: 12px; .info-item { @@ -1274,6 +1476,7 @@ const handleMoreActions = (command: string) => { .action-buttons { display: flex; + flex-basis: 100%; flex-wrap: wrap; gap: 12px; align-items: center; @@ -1301,6 +1504,123 @@ const handleMoreActions = (command: string) => { } } } + + // 设备截图轮播(与 device-info 左右排列) + .device-screenshots { + display: flex; + flex: 0 0 480px; + flex-direction: column; + min-width: 320px; + max-width: 480px; + padding-left: 24px; + border-left: 1px solid #ebeef5; + + .screenshot-title { + display: flex; + gap: 10px; + align-items: center; + margin-bottom: 12px; + font-size: 15px; + font-weight: 600; + color: #303133; + + .refresh-btn { + margin-left: auto; + } + } + + .screenshot-carousel { + flex: 1; + overflow: hidden; + background: #f5f7fa; + border-radius: 8px; + } + + .screenshot-item { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + } + + .screenshot-image { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 320px; + } + + .screenshot-meta { + padding: 8px 0; + font-size: 12px; + color: #909399; + } + + .screenshot-error { + font-size: 14px; + color: #f56c6c; + } + + // 单张截图右下角删除按钮(正圆形,原生 span 不受 EP 样式干扰) + .delete-btn { + position: absolute; + right: 12px; + bottom: 12px; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + cursor: pointer; + background-color: rgba(245, 108, 108, 0.75); + border-radius: 50%; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + transition: background-color 0.2s; + + .delete-icon { + width: 14px; + height: 14px; + color: #fff; + } + + .delete-icon.is-loading { + animation: rotate 1s linear infinite; + } + + &:hover { + background-color: rgba(245, 108, 108, 0.9); + } + } + + @keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + } + + // 窄屏时上下排列,截图区改为顶部分隔样式 + @media (max-width: 900px) { + .device-info { + flex-basis: 100%; + } + + .device-screenshots { + flex: 1 1 100%; + max-width: 100%; + padding-top: 16px; + padding-left: 0; + border-top: 1px solid #ebeef5; + border-left: none; + } + } } .device-sn-detail {