feat: 增加策略信息页面和推送

This commit is contained in:
TongTongStudio
2026-08-27 11:44:41 +08:00
parent 640a3ecc57
commit bb2e687d0a
7 changed files with 5314 additions and 3785 deletions

8419
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ import type {
ApkInstallInfo, ApkInstallInfo,
DeviceLocationInfo, DeviceLocationInfo,
DeviceScreenshot, DeviceScreenshot,
DevicePolicyInfo,
DevicePolicyToggleForm,
} from "./types"; } from "./types";
import type { ExcelResult, PageResult, OptionItem } from "@/api/common"; import type { ExcelResult, PageResult, OptionItem } from "@/api/common";
@@ -272,6 +274,18 @@ const DeviceAPI = {
}); });
}, },
/**
* 屏幕镜像(通知设备开启屏幕采集,由家属端 client 进行 WebRTC 实时观看)
* @param sn 设备SN号
*/
deviceMirror(sn: string) {
return request({
url: `${DEVICE_BASE_URL}/mirror`,
method: "post",
params: { sn },
});
},
/** /**
* 设备重启 * 设备重启
* @param sn 设备SN号 * @param sn 设备SN号
@@ -319,6 +333,18 @@ const DeviceAPI = {
}); });
}, },
/**
* 解除设备手机号绑定(管理员端)
* 对应后端 POST /{sn}/unbind (unbindMobile)
* @param sn 设备SN号
*/
unbindDeviceMobile(sn: string) {
return request({
url: `${DEVICE_BASE_URL}/${sn}/unbind`,
method: "post",
});
},
// ===================== 设备系统信息(分面板独立接口) ===================== // ===================== 设备系统信息(分面板独立接口) =====================
/** 设备基本信息 */ /** 设备基本信息 */
@@ -523,6 +549,52 @@ const DeviceAPI = {
}, },
}); });
}, },
// ===================== 设备策略信息 =====================
/**
* 获取设备策略信息
* 对应后端 GET /{sn}/policy (getDevicePolicy)
* SN 合法且无记录时自动新建默认策略(默认全开)
*
* @param sn 设备SN号
*/
getPolicy(sn: string) {
return request<any, DevicePolicyInfo>({
url: `${DEVICE_BASE_URL}/${sn}/policy`,
method: "get",
});
},
/**
* 全量设置设备策略信息
* 对应后端 PUT /{sn}/policy (updateDevicePolicy)
*
* @param sn 设备SN号
* @param data 策略信息(空字段保持原值)
*/
updatePolicy(sn: string, data: DevicePolicyInfo) {
return request<any, boolean>({
url: `${DEVICE_BASE_URL}/${sn}/policy`,
method: "put",
data,
});
},
/**
* 单独设置设备策略开关
* 对应后端 PUT /{sn}/policy/toggle (toggleDevicePolicy)
*
* @param sn 设备SN号
* @param data 策略字段与开关值
*/
togglePolicy(sn: string, data: DevicePolicyToggleForm) {
return request<any, boolean>({
url: `${DEVICE_BASE_URL}/${sn}/policy/toggle`,
method: "put",
data,
});
},
}; };
export default DeviceAPI; export default DeviceAPI;

View File

@@ -450,3 +450,59 @@ export interface DeviceScreenshot {
/** 上传时间 */ /** 上传时间 */
uploadTime?: string; uploadTime?: string;
} }
// ===================== 设备策略信息 =====================
/** 设备策略信息 */
export interface DevicePolicyInfo {
/** 策略ID */
id?: number;
/** 设备序列号 */
serialno?: string;
/** USB偏好设置0关闭 1开启 */
usbData?: number;
/** 时间设置管控0关闭 1开启 */
timeSetting?: number;
/** 存储卡0关闭 1开启 */
storageCard?: number;
/** 恢复出厂0关闭 1开启 */
factoryReset?: number;
/** WiFi热点开关0关闭 1开启 */
wifiHotspot?: number;
/** 蓝牙开关0关闭 1开启 */
bluetoothSwitch?: number;
/** 壁纸功能0关闭 1开启 */
wallpaper?: number;
/** 通知栏显示0关闭 1开启 */
notificationBar?: number;
/** 状态栏下拉开关0关闭 1开启 */
statusBarPullDown?: number;
/** 系统导航条显示0关闭 1开启 */
systemNavBarShow?: number;
/** 系统导航栏设置0关闭 1开启 */
systemNavBarSetting?: number;
/** 导航条选项0关闭 1开启 */
navBarOptions?: number;
/** 蓝牙功能开关0关闭 1开启 */
bluetoothFunction?: number;
/** OTA升级管控0关闭 1开启 */
otaUpgrade?: number;
/** 应用安装开关0关闭 1开启 */
appInstall?: number;
/** 自动旋转0关闭 1开启 */
autoRotate?: number;
/** 自动亮度0关闭 1开启 */
autoBrightness?: number;
/** 护眼模式0关闭 1开启 */
eyeProtection?: number;
/** 深色模式0关闭 1开启 */
darkMode?: number;
}
/** 设备策略单独设置请求 */
export interface DevicePolicyToggleForm {
/** 策略字段名(如 usbData、appInstall */
field: string;
/** 开关值0关闭 1开启 */
value: number;
}

View File

@@ -735,7 +735,43 @@
<el-tab-pane label="策略信息" name="policyInfo"> <el-tab-pane label="策略信息" name="policyInfo">
<div class="tab-content"> <div class="tab-content">
<el-empty description="策略信息加载中..." /> <div class="policy-toolbar">
<el-button
type="primary"
plain
size="small"
:loading="policyLoading"
:icon="Refresh"
@click="loadPolicyInfo(props.serialno)"
>
刷新
</el-button>
<div class="policy-toolbar__right">
<el-button
type="success"
size="small"
:loading="policySaving"
:icon="Select"
@click="handleSavePolicy"
>
保存
</el-button>
</div>
</div>
<div v-loading="policyLoading" class="policy-grid">
<div v-for="item in policyItems" :key="item.field" class="policy-item">
<div class="policy-item__label">{{ item.label }}</div>
<el-switch
v-model="policyState[item.field]"
:active-value="1"
:inactive-value="0"
inline-prompt
active-text=""
inactive-text=""
size="large"
/>
</div>
</div>
</div> </div>
</el-tab-pane> </el-tab-pane>
@@ -766,6 +802,7 @@ import type {
DeviceSecurityInfo, DeviceSecurityInfo,
DeviceLocationInfo, DeviceLocationInfo,
DeviceScreenshot, DeviceScreenshot,
DevicePolicyInfo,
} from "@/api/system/device/types"; } from "@/api/system/device/types";
import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue"; import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import { useSse } from "@/composables"; import { useSse } from "@/composables";
@@ -782,6 +819,7 @@ import {
VideoPlay, VideoPlay,
Brush, Brush,
SwitchButton, SwitchButton,
Select,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
@@ -889,6 +927,36 @@ const apkList = ref<ApkInstallInfo[]>([]);
/** 应用信息是否已加载(懒加载,仅首次进入 tab 时拉取) */ /** 应用信息是否已加载(懒加载,仅首次进入 tab 时拉取) */
const apkLoaded = ref(false); const apkLoaded = ref(false);
/** 设备策略信息(开关值 0/1 */
const policyState = reactive<Record<string, number>>({});
/** 策略信息加载中 */
const policyLoading = ref(false);
/** 策略信息保存中 */
const policySaving = ref(false);
/** 策略开关项配置label 展示名field 对应后端字段名) */
const policyItems: { label: string; field: string }[] = [
{ label: "USB偏好设置", field: "usbData" },
{ label: "时间设置管控", field: "timeSetting" },
{ label: "存储卡", field: "storageCard" },
{ label: "恢复出厂", field: "factoryReset" },
{ label: "WiFi热点开关", field: "wifiHotspot" },
{ label: "蓝牙开关", field: "bluetoothSwitch" },
{ label: "壁纸功能", field: "wallpaper" },
{ label: "通知栏显示", field: "notificationBar" },
{ label: "状态栏下拉开关", field: "statusBarPullDown" },
{ label: "系统导航条显示", field: "systemNavBarShow" },
{ label: "系统导航栏设置", field: "systemNavBarSetting" },
{ label: "导航条选项", field: "navBarOptions" },
{ label: "蓝牙功能开关", field: "bluetoothFunction" },
{ label: "OTA升级管控", field: "otaUpgrade" },
{ label: "应用安装开关", field: "appInstall" },
{ label: "自动旋转", field: "autoRotate" },
{ label: "自动亮度", field: "autoBrightness" },
{ label: "护眼模式", field: "eyeProtection" },
{ label: "深色模式", field: "darkMode" },
];
/** /**
* 拼接应用图标完整访问地址 * 拼接应用图标完整访问地址
* *
@@ -1183,6 +1251,7 @@ async function loadAll(serialno?: string) {
loadOtherInfo(serialno); loadOtherInfo(serialno);
loadLocationInfo(serialno); loadLocationInfo(serialno);
loadScreenshots(serialno); loadScreenshots(serialno);
loadPolicyInfo(serialno);
} }
/** 设备截图列表 */ /** 设备截图列表 */
@@ -1295,7 +1364,46 @@ async function loadApkInfo(serialno: string) {
} }
} }
/** 切换标签页懒加载应用信息 / 定位信息(仅首次进入时拉取) */ /** 策略信息 */
async function loadPolicyInfo(serialno: string) {
if (!serialno) return;
policyLoading.value = true;
try {
const data = (await DeviceAPI.getPolicy(serialno)) as DevicePolicyInfo | null;
// 初始化/刷新所有开关值
policyItems.forEach((item) => {
const record = data as Record<string, number | undefined> | null;
policyState[item.field] = record?.[item.field] ?? 1;
});
} catch (e) {
console.error("加载策略信息失败", e);
ElMessage.error("加载策略信息失败");
} finally {
policyLoading.value = false;
}
}
/** 全量保存策略信息 */
async function handleSavePolicy() {
if (!props.serialno) return;
policySaving.value = true;
try {
const data: DevicePolicyInfo = { serialno: props.serialno };
const record = data as Record<string, number | undefined>;
policyItems.forEach((item) => {
record[item.field] = policyState[item.field];
});
await DeviceAPI.updatePolicy(props.serialno, data);
ElMessage.success("策略信息保存成功");
} catch (e) {
console.error("保存策略信息失败", e);
ElMessage.error("保存策略信息失败");
} finally {
policySaving.value = false;
}
}
/** 切换标签页懒加载应用信息 / 定位信息 / 策略信息 */
watch(activeTab, (tab) => { watch(activeTab, (tab) => {
if (tab === "appInfo" && props.serialno && !apkLoaded.value) { if (tab === "appInfo" && props.serialno && !apkLoaded.value) {
loadApkInfo(props.serialno); loadApkInfo(props.serialno);
@@ -1303,6 +1411,9 @@ watch(activeTab, (tab) => {
if (tab === "locationInfo" && props.serialno) { if (tab === "locationInfo" && props.serialno) {
loadLocationInfo(props.serialno); loadLocationInfo(props.serialno);
} }
if (tab === "policyInfo" && props.serialno) {
loadPolicyInfo(props.serialno);
}
}); });
/** 打开应用 */ /** 打开应用 */
@@ -1820,6 +1931,45 @@ const handleMoreActions = (command: string) => {
margin-bottom: 12px; margin-bottom: 12px;
} }
.policy-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
&__right {
display: flex;
gap: 8px;
}
}
.policy-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
.policy-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 18px;
background: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 8px;
transition: border-color 0.2s;
&:hover {
border-color: #c0c4cc;
}
&__label {
font-size: 15px;
font-weight: 500;
color: #303133;
}
}
}
.apk-actions { .apk-actions {
display: flex; display: flex;
gap: 6px; gap: 6px;

View File

@@ -4,7 +4,7 @@ import type { DeviceQueryParams, DeviceItem } from "@/api/system/device/types";
import type { IContentConfig } from "@/components/DeviceSn/types"; import type { IContentConfig } from "@/components/DeviceSn/types";
const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = { const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
permPrefix: "sys:user", // 不写不进行按钮权限校验 permPrefix: "sys:device", // 不写不进行按钮权限校验
table: { table: {
border: true, border: true,
highlightCurrentRow: true, highlightCurrentRow: true,
@@ -160,6 +160,11 @@ const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
text: "截图", text: "截图",
attrs: { icon: "FullScreen" }, attrs: { icon: "FullScreen" },
}, },
{
name: "mirror",
text: "屏幕镜像",
attrs: { icon: "VideoCamera" },
},
{ {
name: "locate", name: "locate",
text: "定位", text: "定位",
@@ -180,6 +185,11 @@ const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
text: "重置", text: "重置",
attrs: { icon: "RefreshLeft" }, attrs: { icon: "RefreshLeft" },
}, },
{
name: "unbind",
text: "解除绑定",
attrs: { icon: "Unlock" },
},
], ],
}, },
], ],

View File

@@ -2,7 +2,7 @@ import type { ISearchConfig } from "@/components/DeviceSn/types";
import { deptArr, stateArr } from "./options"; import { deptArr, stateArr } from "./options";
const searchConfig: ISearchConfig = { const searchConfig: ISearchConfig = {
permPrefix: "sys:user", permPrefix: "sys:device",
formItems: [ formItems: [
{ {
tips: "支持模糊搜索", tips: "支持模糊搜索",

View File

@@ -183,6 +183,13 @@ const handleOperateClick = (data: IObject) => {
DeviceAPI.deviceScreenshot(data.row.serialno as string).then(() => { DeviceAPI.deviceScreenshot(data.row.serialno as string).then(() => {
ElMessage.success(`截图设备设备SN: ${data.row.serialno as string}`); ElMessage.success(`截图设备设备SN: ${data.row.serialno as string}`);
}); });
} else if (data.name === "mirror") {
// 屏幕镜像:通知设备端启动屏幕采集,由家属端 client 进行 WebRTC 实时远程观看/控制
DeviceAPI.deviceMirror(data.row.serialno as string)
.then(() => {
ElMessage.success(`已发送屏幕镜像指令设备SN: ${data.row.serialno as string}`);
})
.catch(() => {});
} else if (data.name === "reboot") { } else if (data.name === "reboot") {
ElMessageBox.confirm("确定要重启该设备吗?", "提示", { ElMessageBox.confirm("确定要重启该设备吗?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",
@@ -215,6 +222,27 @@ const handleOperateClick = (data: IObject) => {
}); });
}) })
.catch(() => {}); .catch(() => {});
} else if (data.name === "unbind") {
// 解除设备手机号绑定(管理员端):确认后清除 snMobile 并推送通知设备端标记解绑
const sn = data.row.serialno as string;
const mobile = (data.row.snMobile as string) || "";
ElMessageBox.confirm(
mobile
? `设备「${sn}」当前绑定手机号:${mobile}\n确定要解除该绑定吗解除后需由家属端重新绑定。`
: `设备「${sn}」当前未绑定手机号,无需解除绑定。`,
"解除绑定",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
DeviceAPI.unbindDeviceMobile(data.row.serialno as string).then(() => {
ElMessage.success(`已解除设备绑定设备SN: ${data.row.serialno as string}`);
});
})
.catch(() => {});
} }
}; };