feat(device): 新增开发者模式指令及下拉菜单样式优化
新增推送开发者模式指令到设备的 API。 优化设备页面表格工具栏更多按钮的下拉菜单: - 点击触发并支持点击菜单项后自动隐藏 - 移除触发按钮 hover 时的背景色 - 调整下拉菜单项的布局、间距与圆角样式
This commit is contained in:
62
src/api/system/developer/index.ts
Normal file
62
src/api/system/developer/index.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import request from "@/utils/request";
|
||||
import type { DeveloperQueryParams, DeveloperForm, DeveloperItem } from "./types";
|
||||
import type { PageResult } from "@/api/common";
|
||||
|
||||
const DEVELOPER_BASE_URL = "/api/v1/developers";
|
||||
|
||||
const DeveloperAPI = {
|
||||
/** 获取开发者选项分页数据 */
|
||||
getPage(queryParams?: DeveloperQueryParams) {
|
||||
return request<unknown, PageResult<DeveloperItem>>({
|
||||
url: `${DEVELOPER_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
},
|
||||
/** 获取开发者选项表单数据 */
|
||||
getFormData(id: string) {
|
||||
return request<unknown, DeveloperForm>({
|
||||
url: `${DEVELOPER_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
/** 新增开发者选项配置 */
|
||||
create(data: DeveloperForm) {
|
||||
return request({ url: `${DEVELOPER_BASE_URL}`, method: "post", data });
|
||||
},
|
||||
/** 修改开发者选项配置 */
|
||||
update(id: string, data: DeveloperForm) {
|
||||
return request({ url: `${DEVELOPER_BASE_URL}/${id}`, method: "put", data });
|
||||
},
|
||||
/** 批量删除开发者选项配置,多个以英文逗号(,)分隔 */
|
||||
deleteByIds(ids: string) {
|
||||
return request({ url: `${DEVELOPER_BASE_URL}/${ids}`, method: "delete" });
|
||||
},
|
||||
/** 根据设备序列号获取开发者选项开关 */
|
||||
getBySn(sn: string) {
|
||||
return request<unknown, number>({
|
||||
url: `${DEVELOPER_BASE_URL}/sn/${sn}`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
/** 根据设备序列号保存或更新开发者选项开关 */
|
||||
saveOrUpdateBySn(sn: string, developerOptions: number) {
|
||||
return request({
|
||||
url: `${DEVELOPER_BASE_URL}/sn/${sn}`,
|
||||
method: "put",
|
||||
params: { developerOptions },
|
||||
});
|
||||
},
|
||||
/** 推送开发者模式指令到设备 */
|
||||
pushBySn(sn: string) {
|
||||
return request({
|
||||
url: `${DEVELOPER_BASE_URL}/sn/${sn}/push`,
|
||||
method: "post",
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default DeveloperAPI;
|
||||
|
||||
// 重导出类型
|
||||
export * from "./types";
|
||||
37
src/api/system/developer/types.ts
Normal file
37
src/api/system/developer/types.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Developer 设备开发者选项类型定义
|
||||
*/
|
||||
|
||||
import type { BaseQueryParams } from "@/api/common";
|
||||
|
||||
/** 开发者选项分页查询参数 */
|
||||
export interface DeveloperQueryParams extends BaseQueryParams {
|
||||
/** 搜索关键字(设备序列号) */
|
||||
keywords?: string;
|
||||
/** 开发者选项开关(0关闭,1开启) */
|
||||
developerOptions?: number;
|
||||
}
|
||||
|
||||
/** 开发者选项表单对象 */
|
||||
export interface DeveloperForm {
|
||||
/** 配置ID */
|
||||
id?: string;
|
||||
/** 设备序列号 */
|
||||
sn?: string;
|
||||
/** 开发者选项开关(0关闭,1开启) */
|
||||
developerOptions?: number;
|
||||
}
|
||||
|
||||
/** 开发者选项分页对象 */
|
||||
export interface DeveloperItem {
|
||||
/** 配置ID */
|
||||
id: string;
|
||||
/** 设备序列号 */
|
||||
sn: string;
|
||||
/** 开发者选项开关(0关闭,1开启) */
|
||||
developerOptions: number;
|
||||
/** 创建时间 */
|
||||
createTime?: string;
|
||||
/** 更新时间 */
|
||||
updateTime?: string;
|
||||
}
|
||||
@@ -307,6 +307,18 @@ const DeviceAPI = {
|
||||
params: { sn },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 推送开发者模式指令到设备
|
||||
* @param sn 设备SN号
|
||||
*/
|
||||
deviceDeveloper(sn: string) {
|
||||
return request({
|
||||
url: `${DEVICE_BASE_URL}/developer`,
|
||||
method: "post",
|
||||
params: { sn },
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default DeviceAPI;
|
||||
|
||||
@@ -160,11 +160,17 @@
|
||||
<template v-else-if="col.templet === 'tool'">
|
||||
<div class="flex items-center justify-center gap-x-2">
|
||||
<template v-for="(btn, index) in tableToolbarBtn" :key="index">
|
||||
<el-dropdown v-if="btn.children !== undefined && btn.children.length > 0">
|
||||
<el-dropdown
|
||||
v-if="btn.children !== undefined && btn.children.length > 0"
|
||||
trigger="click"
|
||||
:hide-on-click="true"
|
||||
popper-class="more-dropdown-menu"
|
||||
>
|
||||
<el-button
|
||||
v-if="btn.render === undefined || btn.render(scope.row)"
|
||||
v-hasPerm="btn.perm ?? '*:*:*'"
|
||||
v-bind="btn.attrs"
|
||||
class="more-trigger-btn"
|
||||
>
|
||||
{{ btn.text }}
|
||||
<el-icon class="el-icon--right"><arrow-down /></el-icon>
|
||||
@@ -976,4 +982,40 @@ defineExpose({ fetchPageData, exportPageData, getFilterParams, getSelectionData,
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 更多按钮:去掉 link 类型 hover 时的浅蓝背景层 */
|
||||
.more-trigger-btn {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 非 scoped:下拉菜单通过 teleport 渲染到 body,需用全局样式覆盖 -->
|
||||
<style lang="scss">
|
||||
.more-dropdown-menu {
|
||||
.el-dropdown-menu {
|
||||
gap: 2px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
line-height: 1.4;
|
||||
border-radius: 6px;
|
||||
|
||||
& + .el-dropdown-menu__item {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
340
src/views/devices/developer/index.vue
Normal file
340
src/views/devices/developer/index.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<el-card class="page-search" shadow="never">
|
||||
<el-form ref="queryFormRef" :model="params" :inline="true">
|
||||
<el-form-item label="设备序列号" prop="keywords">
|
||||
<el-input
|
||||
v-model="params.keywords"
|
||||
placeholder="请输入设备序列号"
|
||||
clearable
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开关状态" prop="developerOptions">
|
||||
<el-select
|
||||
v-model="params.developerOptions"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option label="已开启" :value="1" />
|
||||
<el-option label="已关闭" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery">搜索</el-button>
|
||||
<el-button @click="handleResetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card ref="tableWrapperRef" class="page-content" shadow="never">
|
||||
<div class="page-toolbar">
|
||||
<div class="page-toolbar__left">
|
||||
<el-button v-hasPerm="['device:developer:create']" type="primary" @click="openDialog()">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['device:developer:delete']"
|
||||
type="danger"
|
||||
:disabled="selectIds.length === 0"
|
||||
@click="handleDelete()"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="page-toolbar__right">
|
||||
<el-tooltip content="刷新" placement="top">
|
||||
<el-button class="page-icon-btn" @click="handleQuery">
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="全屏" placement="top">
|
||||
<el-button class="page-icon-btn" @click="toggleFullscreen">
|
||||
<el-icon><FullScreen /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-table-wrapper">
|
||||
<el-table
|
||||
ref="dataTableRef"
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
class="page-table"
|
||||
height="100%"
|
||||
highlight-current-row
|
||||
border
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="center" />
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
<el-table-column key="sn" label="设备序列号" prop="sn" min-width="180" />
|
||||
<el-table-column key="developerOptions" label="开发者选项" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
:model-value="scope.row.developerOptions === 1"
|
||||
:loading="switchLoadingSn === scope.row.sn"
|
||||
:disabled="!hasSwitchPerm"
|
||||
inline-prompt
|
||||
active-text="开"
|
||||
inactive-text="关"
|
||||
@change="
|
||||
(val: string | number | boolean) =>
|
||||
handleSwitchChange(scope.row as DeveloperItem, Boolean(val))
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column key="createTime" label="创建时间" prop="createTime" min-width="160" />
|
||||
<el-table-column key="updateTime" label="更新时间" prop="updateTime" min-width="160" />
|
||||
<el-table-column fixed="right" label="操作" width="240">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-hasPerm="['device:developer:push']"
|
||||
type="success"
|
||||
size="small"
|
||||
link
|
||||
@click="handlePush(scope.row.sn)"
|
||||
>
|
||||
推送设备
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['device:developer:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
@click="openDialog(scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['device:developer:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
@click="handleDelete(scope.row.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<pagination
|
||||
v-if="total > 0"
|
||||
v-model:total="total"
|
||||
v-model:page="params.pageNum"
|
||||
v-model:limit="params.pageSize"
|
||||
@pagination="fetchData"
|
||||
/>
|
||||
</el-card>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogState.visible"
|
||||
:title="dialogState.title"
|
||||
width="500px"
|
||||
@close="closeDialog"
|
||||
>
|
||||
<el-form
|
||||
ref="dataFormRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
label-suffix=":"
|
||||
label-width="120px"
|
||||
>
|
||||
<el-form-item label="设备序列号" prop="sn">
|
||||
<el-input
|
||||
v-model="formData.sn"
|
||||
placeholder="请输入设备序列号"
|
||||
:maxlength="64"
|
||||
:disabled="!!formData.id"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="开发者选项" prop="developerOptions">
|
||||
<el-radio-group v-model="formData.developerOptions">
|
||||
<el-radio :value="1">开启</el-radio>
|
||||
<el-radio :value="0">关闭</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
<el-button @click="closeDialog">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
|
||||
import { Refresh, FullScreen } from "@element-plus/icons-vue";
|
||||
|
||||
import DeveloperAPI from "@/api/system/developer";
|
||||
import type { DeveloperItem, DeveloperForm, DeveloperQueryParams } from "@/api/system/developer";
|
||||
import { usePageTable } from "@/composables";
|
||||
import { hasPerm } from "@/utils/auth";
|
||||
|
||||
defineOptions({
|
||||
name: "Developer",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
const tableWrapperRef = ref<HTMLElement | null>(null);
|
||||
const { toggle: toggleFullscreen } = useFullscreen(tableWrapperRef);
|
||||
|
||||
const queryFormRef = ref<FormInstance>();
|
||||
const dataFormRef = ref<FormInstance>();
|
||||
|
||||
/** 分页表格数据管理 */
|
||||
const { loading, list, total, params, fetchData, handleQuery, handleResetQuery } = usePageTable<
|
||||
DeveloperItem,
|
||||
DeveloperQueryParams
|
||||
>({
|
||||
initialParams: { pageNum: 1, pageSize: 10, keywords: "", developerOptions: undefined },
|
||||
request: DeveloperAPI.getPage,
|
||||
onBeforeReset: () => queryFormRef.value?.resetFields(),
|
||||
});
|
||||
|
||||
const selectIds = ref<string[]>([]);
|
||||
const switchLoadingSn = ref<string>("");
|
||||
|
||||
/** 是否拥有开关快捷切换权限 */
|
||||
const hasSwitchPerm = computed(() => hasPerm("device:developer:update"));
|
||||
|
||||
const dialogState = reactive({ title: "", visible: false });
|
||||
|
||||
const formData = reactive<DeveloperForm>({
|
||||
id: undefined,
|
||||
sn: "",
|
||||
developerOptions: 0,
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
sn: [{ required: true, message: "请输入设备序列号", trigger: "blur" }],
|
||||
developerOptions: [{ required: true, message: "请选择开发者选项开关", trigger: "change" }],
|
||||
};
|
||||
|
||||
function handleSelectionChange(selection: DeveloperItem[]): void {
|
||||
selectIds.value = selection.map((item) => item.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开新增/编辑开发者选项弹窗
|
||||
*/
|
||||
async function openDialog(id?: string): Promise<void> {
|
||||
dialogState.visible = true;
|
||||
if (id) {
|
||||
dialogState.title = "修改开发者选项";
|
||||
const data = await DeveloperAPI.getFormData(id);
|
||||
Object.assign(formData, data);
|
||||
} else {
|
||||
dialogState.title = "新增开发者选项";
|
||||
formData.id = undefined;
|
||||
formData.sn = "";
|
||||
formData.developerOptions = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格内快捷切换开发者选项开关
|
||||
*/
|
||||
async function handleSwitchChange(row: DeveloperItem, checked: boolean): Promise<void> {
|
||||
switchLoadingSn.value = row.sn;
|
||||
try {
|
||||
await DeveloperAPI.saveOrUpdateBySn(row.sn, checked ? 1 : 0);
|
||||
row.developerOptions = checked ? 1 : 0;
|
||||
ElMessage.success(checked ? "已开启开发者选项" : "已关闭开发者选项");
|
||||
} finally {
|
||||
switchLoadingSn.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送开发者模式指令到设备
|
||||
*/
|
||||
async function handlePush(sn: string): Promise<void> {
|
||||
await ElMessageBox.confirm(`确认向设备【${sn}】推送开发者模式指令?`, "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
});
|
||||
loading.value = true;
|
||||
try {
|
||||
await DeveloperAPI.pushBySn(sn);
|
||||
ElMessage.success("推送成功");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并提交开发者选项表单
|
||||
*/
|
||||
async function handleSubmit(): Promise<void> {
|
||||
const valid = await dataFormRef.value?.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const id = formData.id;
|
||||
if (id) {
|
||||
await DeveloperAPI.update(id, formData);
|
||||
ElMessage.success("修改成功");
|
||||
} else {
|
||||
await DeveloperAPI.create(formData);
|
||||
ElMessage.success("新增成功");
|
||||
}
|
||||
closeDialog();
|
||||
handleResetQuery();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗并重置表单
|
||||
*/
|
||||
function closeDialog(): void {
|
||||
dialogState.visible = false;
|
||||
dataFormRef.value?.resetFields();
|
||||
dataFormRef.value?.clearValidate();
|
||||
formData.id = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除开发者选项配置
|
||||
*/
|
||||
async function handleDelete(id?: string): Promise<void> {
|
||||
const ids = id ? [id] : selectIds.value;
|
||||
if (ids.length === 0) {
|
||||
ElMessage.warning("请勾选要删除的数据");
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.confirm("确认删除选中的开发者选项配置?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
});
|
||||
loading.value = true;
|
||||
try {
|
||||
await DeveloperAPI.deleteByIds(ids.join(","));
|
||||
ElMessage.success("删除成功");
|
||||
selectIds.value = [];
|
||||
handleResetQuery();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery();
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user