feat: 增加策略信息页面和推送
This commit is contained in:
8775
pnpm-lock.yaml
generated
8775
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,8 @@ import type {
|
||||
ApkInstallInfo,
|
||||
DeviceLocationInfo,
|
||||
DeviceScreenshot,
|
||||
DevicePolicyInfo,
|
||||
DevicePolicyToggleForm,
|
||||
} from "./types";
|
||||
|
||||
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号
|
||||
@@ -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;
|
||||
|
||||
@@ -450,3 +450,59 @@ export interface DeviceScreenshot {
|
||||
/** 上传时间 */
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -735,7 +735,43 @@
|
||||
|
||||
<el-tab-pane label="策略信息" name="policyInfo">
|
||||
<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>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -766,6 +802,7 @@ import type {
|
||||
DeviceSecurityInfo,
|
||||
DeviceLocationInfo,
|
||||
DeviceScreenshot,
|
||||
DevicePolicyInfo,
|
||||
} from "@/api/system/device/types";
|
||||
import { nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
|
||||
import { useSse } from "@/composables";
|
||||
@@ -782,6 +819,7 @@ import {
|
||||
VideoPlay,
|
||||
Brush,
|
||||
SwitchButton,
|
||||
Select,
|
||||
} from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
|
||||
@@ -889,6 +927,36 @@ const apkList = ref<ApkInstallInfo[]>([]);
|
||||
/** 应用信息是否已加载(懒加载,仅首次进入 tab 时拉取) */
|
||||
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);
|
||||
loadLocationInfo(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) => {
|
||||
if (tab === "appInfo" && props.serialno && !apkLoaded.value) {
|
||||
loadApkInfo(props.serialno);
|
||||
@@ -1303,6 +1411,9 @@ watch(activeTab, (tab) => {
|
||||
if (tab === "locationInfo" && props.serialno) {
|
||||
loadLocationInfo(props.serialno);
|
||||
}
|
||||
if (tab === "policyInfo" && props.serialno) {
|
||||
loadPolicyInfo(props.serialno);
|
||||
}
|
||||
});
|
||||
|
||||
/** 打开应用 */
|
||||
@@ -1820,6 +1931,45 @@ const handleMoreActions = (command: string) => {
|
||||
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 {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { DeviceQueryParams, DeviceItem } from "@/api/system/device/types";
|
||||
import type { IContentConfig } from "@/components/DeviceSn/types";
|
||||
|
||||
const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
|
||||
permPrefix: "sys:user", // 不写不进行按钮权限校验
|
||||
permPrefix: "sys:device", // 不写不进行按钮权限校验
|
||||
table: {
|
||||
border: true,
|
||||
highlightCurrentRow: true,
|
||||
@@ -160,6 +160,11 @@ const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
|
||||
text: "截图",
|
||||
attrs: { icon: "FullScreen" },
|
||||
},
|
||||
{
|
||||
name: "mirror",
|
||||
text: "屏幕镜像",
|
||||
attrs: { icon: "VideoCamera" },
|
||||
},
|
||||
{
|
||||
name: "locate",
|
||||
text: "定位",
|
||||
@@ -180,6 +185,11 @@ const contentConfig: IContentConfig<DeviceQueryParams, DeviceItem> = {
|
||||
text: "重置",
|
||||
attrs: { icon: "RefreshLeft" },
|
||||
},
|
||||
{
|
||||
name: "unbind",
|
||||
text: "解除绑定",
|
||||
attrs: { icon: "Unlock" },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ISearchConfig } from "@/components/DeviceSn/types";
|
||||
import { deptArr, stateArr } from "./options";
|
||||
|
||||
const searchConfig: ISearchConfig = {
|
||||
permPrefix: "sys:user",
|
||||
permPrefix: "sys:device",
|
||||
formItems: [
|
||||
{
|
||||
tips: "支持模糊搜索",
|
||||
|
||||
@@ -183,6 +183,13 @@ const handleOperateClick = (data: IObject) => {
|
||||
DeviceAPI.deviceScreenshot(data.row.serialno as string).then(() => {
|
||||
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") {
|
||||
ElMessageBox.confirm("确定要重启该设备吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
@@ -215,6 +222,27 @@ const handleOperateClick = (data: IObject) => {
|
||||
});
|
||||
})
|
||||
.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(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user