iot产品管理问题
1.修复物模型列表无限加载的问题 2.修复物模型管理页面添加,TSL,编辑,删除,功能类型选项功能不用问题 3.修复TSL按钮物模型接口没有的问题 4.修复物模型新增编辑页面的属性不能正常编辑修改问题美化显示 iot设备管理问题 1.修复新增编辑页面缺少字段相关组件 2.修复设备详情中子页面不显示问题 3.修复设备详情子页面物模型数据页面不显示问题 4.修复模拟设备右侧不显示问题 右侧溢出,改为上下分栏 Signed-off-by: Administrator <425053404@qq.com>
This commit is contained in:
@@ -113,16 +113,16 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
.optional()
|
||||
.or(z.literal('')),
|
||||
},
|
||||
// {
|
||||
// fieldName: 'locationType',
|
||||
// label: '定位类型',
|
||||
// component: 'RadioGroup',
|
||||
// componentProps: {
|
||||
// options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'),
|
||||
// buttonStyle: 'solid',
|
||||
// optionType: 'button',
|
||||
// },
|
||||
// },
|
||||
{
|
||||
fieldName: 'locationType',
|
||||
label: '定位类型',
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
options: getDictOptions(DICT_TYPE.IOT_LOCATION_TYPE, 'number'),
|
||||
buttonStyle: 'solid',
|
||||
optionType: 'button',
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'longitude',
|
||||
label: '设备经度',
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useVbenForm, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { createDevice, getDevice, updateDevice } from '#/api/iot/device/device';
|
||||
import {
|
||||
createDevice,
|
||||
getDevice,
|
||||
updateDevice,
|
||||
type IotDeviceApi
|
||||
} from '#/api/iot/device/device';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import { computed, ref, watchEffect } from 'vue';
|
||||
|
||||
import { Alert, Button, message } from 'ant-design-vue';
|
||||
import { Alert, Button, message, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { sendDeviceMessage, updateDevice } from '#/api/iot/device/device';
|
||||
import { IotDeviceMessageMethodEnum } from '#/views/iot/utils/constants';
|
||||
@@ -22,41 +22,68 @@ const emit = defineEmits<{
|
||||
const loading = ref(false); // 加载中
|
||||
const pushLoading = ref(false); // 推送加载中
|
||||
const config = ref<any>({}); // 只存储 config 字段
|
||||
const hasJsonError = ref(false); // 是否有 JSON 格式错误
|
||||
const configString = ref(''); // 用于编辑器的字符串格式
|
||||
|
||||
/** 监听 props.device 的变化,只更新 config 字段 */
|
||||
watchEffect(() => {
|
||||
try {
|
||||
config.value = props.device.config ? JSON.parse(props.device.config) : {};
|
||||
// 将对象转换为格式化的 JSON 字符串
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
config.value = {};
|
||||
configString.value = '{}';
|
||||
}
|
||||
});
|
||||
|
||||
const isEditing = ref(false); // 编辑状态
|
||||
|
||||
/** 格式化的配置用于只读展示 */
|
||||
const formattedConfig = computed(() => {
|
||||
try {
|
||||
if (typeof config.value === 'string') {
|
||||
return JSON.stringify(JSON.parse(config.value), null, 2);
|
||||
}
|
||||
return JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
return JSON.stringify(config.value, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
/** 判断配置是否有数据 */
|
||||
const hasConfigData = computed(() => {
|
||||
return config.value && Object.keys(config.value).length > 0;
|
||||
});
|
||||
|
||||
/** 启用编辑模式的函数 */
|
||||
function enableEdit() {
|
||||
isEditing.value = true;
|
||||
hasJsonError.value = false; // 重置错误状态
|
||||
// 重新同步编辑器内容
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
}
|
||||
|
||||
/** 取消编辑的函数 */
|
||||
function cancelEdit() {
|
||||
try {
|
||||
config.value = props.device.config ? JSON.parse(props.device.config) : {};
|
||||
configString.value = JSON.stringify(config.value, null, 2);
|
||||
} catch {
|
||||
config.value = {};
|
||||
configString.value = '{}';
|
||||
}
|
||||
isEditing.value = false;
|
||||
hasJsonError.value = false; // 重置错误状态
|
||||
}
|
||||
|
||||
/** 保存配置的函数 */
|
||||
async function saveConfig() {
|
||||
if (hasJsonError.value) {
|
||||
// 验证 JSON 格式
|
||||
try {
|
||||
config.value = JSON.parse(configString.value);
|
||||
} catch (error) {
|
||||
message.error({ content: 'JSON格式错误,请修正后再提交!' });
|
||||
return;
|
||||
}
|
||||
|
||||
await updateDeviceConfig();
|
||||
isEditing.value = false;
|
||||
}
|
||||
@@ -102,39 +129,26 @@ async function updateDeviceConfig() {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 处理 JSON 编辑器错误的函数 */
|
||||
function onError(errors: any) {
|
||||
if (!errors || (Array.isArray(errors) && errors.length === 0)) {
|
||||
hasJsonError.value = false;
|
||||
return;
|
||||
}
|
||||
hasJsonError.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 只在没有配置数据时显示提示 -->
|
||||
<Alert
|
||||
v-if="!hasConfigData"
|
||||
message="支持远程更新设备的配置文件(JSON 格式),可以在下方编辑配置模板,对设备的系统参数、网络参数等进行远程配置。配置完成后,需点击「下发」按钮,设备即可进行远程配置。"
|
||||
type="info"
|
||||
show-icon
|
||||
class="my-4"
|
||||
description="如需编辑文件,请点击下方编辑按钮"
|
||||
/>
|
||||
<JsonEditor
|
||||
v-model="config"
|
||||
:mode="isEditing ? 'code' : 'view'"
|
||||
height="600px"
|
||||
@error="onError"
|
||||
/>
|
||||
<div class="mt-5 text-center">
|
||||
<Button v-if="isEditing" @click="cancelEdit">取消</Button>
|
||||
<Button
|
||||
v-if="isEditing"
|
||||
type="primary"
|
||||
@click="saveConfig"
|
||||
:disabled="hasJsonError"
|
||||
:loading="loading"
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
@@ -148,5 +162,47 @@ function onError(errors: any) {
|
||||
配置推送
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 代码视图 - 只读展示 -->
|
||||
<div v-if="!isEditing" class="json-viewer-container">
|
||||
<pre class="json-code"><code>{{ formattedConfig }}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- 编辑器视图 - 可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="configString"
|
||||
:rows="20"
|
||||
placeholder="请输入 JSON 格式的配置信息"
|
||||
class="json-editor"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.json-code {
|
||||
margin: 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -85,7 +85,7 @@ function handleAuthInfoDialogClose() {
|
||||
<span>设备信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<Descriptions :column="1" bordered>
|
||||
<Descriptions :column="1" bordered size="small">
|
||||
<Descriptions.Item label="产品名称">
|
||||
{{ product.name }}
|
||||
</Descriptions.Item>
|
||||
|
||||
@@ -1,174 +1,4 @@
|
||||
<!-- 设备消息列表 -->
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { ContentWrap } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Pagination,
|
||||
Select,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getDeviceMessagePage } from '#/api/iot/device/device';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { IotDeviceMessageMethodEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
const props = defineProps<{
|
||||
deviceId: number;
|
||||
}>();
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
deviceId: props.deviceId,
|
||||
method: undefined,
|
||||
upstream: undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 列表数据
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<any[]>([]);
|
||||
const autoRefresh = ref(false); // 自动刷新开关
|
||||
let autoRefreshTimer: any = null; // 自动刷新定时器
|
||||
|
||||
// 消息方法选项
|
||||
const methodOptions = computed(() => {
|
||||
return Object.values(IotDeviceMessageMethodEnum).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.method,
|
||||
}));
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'ts',
|
||||
key: 'ts',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '上行/下行',
|
||||
dataIndex: 'upstream',
|
||||
key: 'upstream',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '是否回复',
|
||||
dataIndex: 'reply',
|
||||
key: 'reply',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求编号',
|
||||
dataIndex: 'requestId',
|
||||
key: 'requestId',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '请求方法',
|
||||
dataIndex: 'method',
|
||||
key: 'method',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求/响应数据',
|
||||
dataIndex: 'params',
|
||||
key: 'params',
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
||||
/** 查询消息列表 */
|
||||
async function getMessageList() {
|
||||
if (!props.deviceId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getDeviceMessagePage(queryParams);
|
||||
total.value = data.total;
|
||||
list.value = data.list;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索操作 */
|
||||
function handleQuery() {
|
||||
queryParams.pageNo = 1;
|
||||
getMessageList();
|
||||
}
|
||||
|
||||
/** 监听自动刷新 */
|
||||
watch(autoRefresh, (newValue) => {
|
||||
if (newValue) {
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
getMessageList();
|
||||
}, 5000);
|
||||
} else {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听设备标识变化 */
|
||||
watch(
|
||||
() => props.deviceId,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
handleQuery();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** 组件卸载时清除定时器 */
|
||||
onBeforeUnmount(() => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
if (props.deviceId) {
|
||||
getMessageList();
|
||||
}
|
||||
});
|
||||
|
||||
/** 刷新消息列表 */
|
||||
function refresh(delay = 0) {
|
||||
if (delay > 0) {
|
||||
setTimeout(() => {
|
||||
handleQuery();
|
||||
}, delay);
|
||||
} else {
|
||||
handleQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/** 暴露方法给父组件 */
|
||||
defineExpose({
|
||||
refresh,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 搜索区域 -->
|
||||
@@ -220,7 +50,6 @@ defineExpose({
|
||||
:data-source="list"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
align="center"
|
||||
class="whitespace-nowrap"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
@@ -265,3 +94,164 @@ defineExpose({
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { ContentWrap } from '@vben/common-ui';
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDate } from '@vben/utils';
|
||||
|
||||
import { Button, Form, Pagination, Select, Switch, Table, Tag } from 'ant-design-vue';
|
||||
|
||||
import { getDeviceMessagePage } from '#/api/iot/device/device';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
import { IotDeviceMessageMethodEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
const props = defineProps<{
|
||||
deviceId: number;
|
||||
}>();
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive({
|
||||
deviceId: props.deviceId,
|
||||
method: undefined,
|
||||
upstream: undefined,
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
// 列表数据
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<any[]>([]);
|
||||
const autoRefresh = ref(false); // 自动刷新开关
|
||||
let autoRefreshTimer: any = null; // 自动刷新定时器
|
||||
|
||||
// 消息方法选项
|
||||
const methodOptions = computed(() => {
|
||||
return Object.values(IotDeviceMessageMethodEnum).map((item) => ({
|
||||
label: item.name,
|
||||
value: item.method,
|
||||
}));
|
||||
});
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'ts',
|
||||
key: 'ts',
|
||||
align: 'center' as const,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '上行/下行',
|
||||
dataIndex: 'upstream',
|
||||
key: 'upstream',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '是否回复',
|
||||
dataIndex: 'reply',
|
||||
key: 'reply',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求编号',
|
||||
dataIndex: 'requestId',
|
||||
key: 'requestId',
|
||||
align: 'center' as const,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '请求方法',
|
||||
dataIndex: 'method',
|
||||
key: 'method',
|
||||
align: 'center' as const,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '请求/响应数据',
|
||||
dataIndex: 'params',
|
||||
key: 'params',
|
||||
align: 'center' as const,
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
||||
/** 查询消息列表 */
|
||||
const getMessageList = async () => {
|
||||
if (!props.deviceId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getDeviceMessagePage(queryParams);
|
||||
total.value = data.total;
|
||||
list.value = data.list;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1;
|
||||
getMessageList();
|
||||
};
|
||||
|
||||
/** 监听自动刷新 */
|
||||
watch(autoRefresh, (newValue) => {
|
||||
if (newValue) {
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
getMessageList();
|
||||
}, 5000);
|
||||
} else {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听设备标识变化 */
|
||||
watch(
|
||||
() => props.deviceId,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
handleQuery();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** 组件卸载时清除定时器 */
|
||||
onBeforeUnmount(() => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer);
|
||||
autoRefreshTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(() => {
|
||||
if (props.deviceId) {
|
||||
getMessageList();
|
||||
}
|
||||
});
|
||||
|
||||
/** 刷新消息列表 */
|
||||
const refresh = (delay = 0) => {
|
||||
if (delay > 0) {
|
||||
setTimeout(() => {
|
||||
handleQuery();
|
||||
}, delay);
|
||||
} else {
|
||||
handleQuery();
|
||||
}
|
||||
};
|
||||
|
||||
/** 暴露方法给父组件 */
|
||||
defineExpose({
|
||||
refresh,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -8,17 +8,18 @@ import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { ContentWrap } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Input,
|
||||
message,
|
||||
Row,
|
||||
Table,
|
||||
Tabs,
|
||||
Textarea,
|
||||
} from 'ant-design-vue';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
|
||||
import {
|
||||
@@ -26,6 +27,8 @@ import {
|
||||
IoTThingModelTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
|
||||
|
||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -41,6 +44,10 @@ const downstreamTab = ref(IotDeviceMessageMethodEnum.PROPERTY_SET.method); //
|
||||
const deviceMessageRef = ref(); // 设备消息组件引用
|
||||
const deviceMessageRefreshDelay = 2000; // 延迟 N 秒,保证模拟上行的消息被处理
|
||||
|
||||
// 折叠状态
|
||||
const debugCollapsed = ref(false); // 指令调试区域折叠状态
|
||||
const messageCollapsed = ref(false); // 设备消息区域折叠状态
|
||||
|
||||
// 表单数据:存储用户输入的模拟值
|
||||
const formData = ref<Record<string, string>>({});
|
||||
|
||||
@@ -90,12 +97,12 @@ const propertyColumns: TableColumnType[] = [
|
||||
{
|
||||
title: '数据定义',
|
||||
key: 'dataDefinition',
|
||||
minWidth: 200,
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
title: '值',
|
||||
key: 'value',
|
||||
width: 150,
|
||||
width: 300,
|
||||
fixed: 'right' as any,
|
||||
},
|
||||
];
|
||||
@@ -332,19 +339,32 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Row :gutter="20">
|
||||
<!-- 左侧指令调试区域 -->
|
||||
<Col :span="12">
|
||||
<Card>
|
||||
<Tabs v-model:active-key="activeTab">
|
||||
<!-- 上行指令调试 -->
|
||||
<Tabs.Pane key="upstream" tab="上行指令调试">
|
||||
<!-- 上方:指令调试区域 -->
|
||||
<Card class="mb-4 simulator-tabs">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>指令调试</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="debugCollapsed = !debugCollapsed"
|
||||
>
|
||||
<UpOutlined v-if="!debugCollapsed" />
|
||||
<DownOutlined v-if="debugCollapsed" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!debugCollapsed">
|
||||
<Tabs v-model:active-key="activeTab" size="small">
|
||||
<!-- 上行指令调试 -->
|
||||
<Tabs.TabPane key="upstream" tab="上行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'upstream'"
|
||||
v-model:active-key="upstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.Pane
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
@@ -354,6 +374,8 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
@@ -383,10 +405,10 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.Pane
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
@@ -396,6 +418,8 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
align="center"
|
||||
:columns="eventColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
@@ -427,10 +451,10 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.Pane
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
@@ -450,18 +474,19 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.Pane key="downstream" tab="下行指令调试">
|
||||
<Tabs.TabPane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-model:active-key="downstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.Pane
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
@@ -471,6 +496,8 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
@@ -500,10 +527,10 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.Pane
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
@@ -513,6 +540,8 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
align="center"
|
||||
:columns="serviceColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataDefinition'">
|
||||
@@ -541,23 +570,35 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Col>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 右侧设备日志区域 -->
|
||||
<Col :span="12">
|
||||
<ContentWrap title="设备消息">
|
||||
<DeviceDetailsMessage
|
||||
v-if="device.id"
|
||||
ref="deviceMessageRef"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</ContentWrap>
|
||||
</Col>
|
||||
</Row>
|
||||
<!-- 下方:设备消息区域 -->
|
||||
<Card>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>设备消息</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
@click="messageCollapsed = !messageCollapsed"
|
||||
>
|
||||
<UpOutlined v-if="!messageCollapsed" />
|
||||
<DownOutlined v-if="messageCollapsed" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!messageCollapsed">
|
||||
<DeviceDetailsMessage
|
||||
v-if="device.id"
|
||||
ref="deviceMessageRef"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
import { Card, Empty } from 'ant-design-vue';
|
||||
|
||||
interface Props {
|
||||
deviceId: number;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const loading = ref(false);
|
||||
const subDevices = ref<any[]>([]);
|
||||
|
||||
/** 获取子设备列表 */
|
||||
async function getSubDeviceList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
// TODO: 实现获取子设备列表的API调用
|
||||
// const data = await getSubDevicesByGatewayId(deviceId);
|
||||
// subDevices.value = data || [];
|
||||
subDevices.value = [];
|
||||
} catch (error) {
|
||||
console.error('获取子设备列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSubDeviceList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card :loading="loading" title="子设备管理">
|
||||
<Empty
|
||||
description="暂无子设备数据,此功能待实现"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
/>
|
||||
<!-- TODO: 实现子设备列表展示和管理功能 -->
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
@@ -22,21 +22,26 @@ const activeTab = ref('property'); // 默认选中设备属性
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<Tabs v-model:active-key="activeTab" class="!h-auto !p-0">
|
||||
<Tabs.Pane key="property" tab="设备属性(运行状态)">
|
||||
<DeviceDetailsThingModelProperty :device-id="deviceId" />
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="event" tab="设备事件上报">
|
||||
<Tabs.TabPane key="property" tab="设备属性(运行状态)">
|
||||
<DeviceDetailsThingModelProperty
|
||||
v-if="activeTab === 'property'"
|
||||
:device-id="deviceId"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="event" tab="设备事件上报">
|
||||
<DeviceDetailsThingModelEvent
|
||||
v-if="activeTab === 'event'"
|
||||
:device-id="props.deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="service" tab="设备服务调用">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="service" tab="设备服务调用">
|
||||
<DeviceDetailsThingModelService
|
||||
v-if="activeTab === 'service'"
|
||||
:device-id="deviceId"
|
||||
:thing-model-list="props.thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
@@ -148,6 +148,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.times"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<!-- 设备物模型 -> 运行状态 -> 查看数据(设备的属性值历史)-->
|
||||
// 重新关闭打开图表,图表不显示可能图例注销失败等大佬修复
|
||||
<script setup lang="ts">
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
@@ -10,7 +11,7 @@ import { computed, nextTick, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
|
||||
import { beginOfDay, endOfDay, formatDate } from '@vben/utils';
|
||||
import { beginOfDay, endOfDay, formatDate, formatDateTime } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
RangePicker,
|
||||
Space,
|
||||
Spin,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -49,8 +51,8 @@ const queryParams = reactive({
|
||||
deviceId: -1,
|
||||
identifier: '',
|
||||
times: [
|
||||
formatDate(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
|
||||
formatDate(endOfDay(new Date())),
|
||||
formatDateTime(beginOfDay(new Date(Date.now() - 3600 * 1000 * 24 * 7))),
|
||||
formatDateTime(endOfDay(new Date())),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -100,7 +102,7 @@ const tableColumns = computed(() => [
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
customRender: ({ index }: { index: number }) => index + 1,
|
||||
},
|
||||
{
|
||||
@@ -108,20 +110,20 @@ const tableColumns = computed(() => [
|
||||
key: 'updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
width: 200,
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
},
|
||||
{
|
||||
title: '属性值',
|
||||
key: 'value',
|
||||
dataIndex: 'value',
|
||||
align: 'center',
|
||||
align: 'center' as const,
|
||||
},
|
||||
]);
|
||||
|
||||
// 分页配置
|
||||
const paginationConfig = computed(() => ({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
pageSize: 10,
|
||||
total: total.value,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
@@ -134,7 +136,8 @@ async function getList() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await getHistoryDevicePropertyList(queryParams);
|
||||
list.value = (data?.list as IotDeviceApi.DevicePropertyDetail[]) || [];
|
||||
// 后端直接返回数组,不是 { list: [] } 格式
|
||||
list.value = (Array.isArray(data) ? data : (data?.list || [])) as IotDeviceApi.DevicePropertyDetail[];
|
||||
total.value = list.value.length;
|
||||
|
||||
// 如果是图表模式且不是复杂数据类型,渲染图表
|
||||
@@ -143,7 +146,9 @@ async function getList() {
|
||||
!isComplexDataType.value &&
|
||||
list.value.length > 0
|
||||
) {
|
||||
// 等待 DOM 更新完成后再渲染图表
|
||||
await nextTick();
|
||||
await nextTick(); // 双重 nextTick 确保 DOM 完全准备好
|
||||
renderChart();
|
||||
}
|
||||
} catch {
|
||||
@@ -157,11 +162,20 @@ async function getList() {
|
||||
|
||||
/** 渲染图表 */
|
||||
function renderChart() {
|
||||
if (!list.value || list.value.length === 0) return;
|
||||
if (!list.value || list.value.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chartData = list.value.map((item) => [item.updateTime, item.value]);
|
||||
|
||||
renderEcharts({
|
||||
// 使用 setTimeout 延迟渲染,避免 ECharts 主进程冲突
|
||||
setTimeout(() => {
|
||||
// 检查 chartRef 是否存在且已挂载
|
||||
if (!chartRef.value || !chartRef.value.$el) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderEcharts({
|
||||
title: {
|
||||
text: '属性值趋势',
|
||||
left: 'center',
|
||||
@@ -265,6 +279,7 @@ function renderChart() {
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 300); // 延迟300ms渲染,确保 DOM 完全准备好
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
@@ -275,12 +290,31 @@ async function open(deviceId: number, identifier: string, dataType: string) {
|
||||
propertyIdentifier.value = identifier;
|
||||
thingModelDataType.value = dataType;
|
||||
|
||||
// 重置时间范围为最近7天
|
||||
dateRange.value = [
|
||||
dayjs().subtract(7, 'day').startOf('day'),
|
||||
dayjs().endOf('day'),
|
||||
];
|
||||
|
||||
// 更新查询参数的时间
|
||||
queryParams.times = [
|
||||
formatDateTime(dateRange.value[0].toDate()),
|
||||
formatDateTime(dateRange.value[1].toDate()),
|
||||
];
|
||||
|
||||
// 如果物模型是 struct、array,需要默认使用 list 模式
|
||||
viewMode.value = isComplexDataType.value ? 'list' : 'chart';
|
||||
|
||||
// 等待弹窗完全渲染后再获取数据
|
||||
await nextTick();
|
||||
await getList();
|
||||
|
||||
// 如果是图表模式,延迟渲染图表
|
||||
if (viewMode.value === 'chart' && !isComplexDataType.value) {
|
||||
setTimeout(() => {
|
||||
renderChart();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/** 时间变化处理 */
|
||||
@@ -290,8 +324,8 @@ function handleTimeChange() {
|
||||
}
|
||||
|
||||
queryParams.times = [
|
||||
formatDate(dateRange.value[0].toDate()),
|
||||
formatDate(dateRange.value[1].toDate()),
|
||||
formatDateTime(dateRange.value[0].toDate()),
|
||||
formatDateTime(dateRange.value[1].toDate()),
|
||||
];
|
||||
|
||||
getList();
|
||||
@@ -372,8 +406,14 @@ watch(viewMode, async (newMode) => {
|
||||
!isComplexDataType.value &&
|
||||
list.value.length > 0
|
||||
) {
|
||||
// 等待 DOM 显示完成
|
||||
await nextTick();
|
||||
renderChart();
|
||||
await nextTick();
|
||||
|
||||
// 延迟渲染图表
|
||||
setTimeout(() => {
|
||||
renderChart();
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -398,7 +438,7 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="['开始时间', '结束时间']"
|
||||
class="!w-[400px]"
|
||||
@press-enter="handleTimeChange"
|
||||
@change="handleTimeChange"
|
||||
/>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
@@ -460,18 +500,20 @@ defineExpose({ open }); // 提供 open 方法,用于打开弹窗
|
||||
<!-- 数据展示区域 -->
|
||||
<Spin :spinning="loading" :delay="200">
|
||||
<!-- 图表模式 -->
|
||||
<div v-if="viewMode === 'chart'" class="chart-container">
|
||||
<div v-show="viewMode === 'chart'" class="chart-container">
|
||||
<Empty
|
||||
v-if="list.length === 0"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
description="暂无数据"
|
||||
class="py-20"
|
||||
/>
|
||||
<EchartsUI v-else ref="chartRef" height="500px" />
|
||||
<div v-else>
|
||||
<EchartsUI ref="chartRef" height="500px" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格模式 -->
|
||||
<div v-else class="table-container">
|
||||
<div v-show="viewMode === 'list'" class="table-container">
|
||||
<Table
|
||||
:data-source="list"
|
||||
:columns="tableColumns"
|
||||
|
||||
@@ -148,6 +148,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.times"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 360px"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
|
||||
import { message, Tabs } from 'ant-design-vue';
|
||||
|
||||
@@ -20,33 +19,42 @@ import DeviceDetailsHeader from './DeviceDetailsHeader.vue';
|
||||
import DeviceDetailsInfo from './DeviceDetailsInfo.vue';
|
||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
||||
import DeviceDetailsSimulator from './DeviceDetailsSimulator.vue';
|
||||
import DeviceDetailsSubDevice from './DeviceDetailsSubDevice.vue';
|
||||
import DeviceDetailsThingModel from './DeviceDetailsThingModel.vue';
|
||||
|
||||
defineOptions({ name: 'IoTDeviceDetail' });
|
||||
|
||||
const route = useRoute();
|
||||
const id = Number(route.params.id); // 将字符串转换为数字
|
||||
const loading = ref(true); // 加载中
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product); // 产品详情
|
||||
const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device); // 设备详情
|
||||
const activeTab = ref('info'); // 默认激活的标签页
|
||||
const thingModelList = ref<ThingModelData[]>([]); // 物模型列表数据
|
||||
const router = useRouter();
|
||||
|
||||
const id = Number(route.params.id);
|
||||
const loading = ref(true);
|
||||
const product = ref<IotProductApi.Product>({} as IotProductApi.Product);
|
||||
const device = ref<IotDeviceApi.Device>({} as IotDeviceApi.Device);
|
||||
const activeTab = ref('info');
|
||||
const thingModelList = ref<ThingModelData[]>([]);
|
||||
|
||||
/** 获取设备详情 */
|
||||
async function getDeviceData() {
|
||||
async function getDeviceData(deviceId: number) {
|
||||
loading.value = true;
|
||||
try {
|
||||
device.value = await getDevice(id);
|
||||
device.value = await getDevice(deviceId);
|
||||
await getProductData(device.value.productId);
|
||||
await getThingModelList(device.value.productId);
|
||||
} catch {
|
||||
message.error('获取设备详情失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取产品详情 */
|
||||
async function getProductData(id: number) {
|
||||
product.value = await getProduct(id);
|
||||
async function getProductData(productId: number) {
|
||||
try {
|
||||
product.value = await getProduct(productId);
|
||||
} catch (error) {
|
||||
console.error('获取产品详情失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/** 获取物模型列表 */
|
||||
@@ -61,17 +69,20 @@ async function getThingModelList(productId: number) {
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
const tabbarStore = useTabbarStore(); // 视图操作
|
||||
const router = useRouter(); // 路由
|
||||
const { currentRoute } = router;
|
||||
onMounted(async () => {
|
||||
if (!id) {
|
||||
message.warning({ content: '参数错误,产品不能为空!' });
|
||||
await tabbarStore.closeTab(unref(currentRoute), router);
|
||||
message.warning('参数错误,设备不能为空!');
|
||||
router.back();
|
||||
return;
|
||||
}
|
||||
await getDeviceData();
|
||||
activeTab.value = (route.query.tab as string) || 'info';
|
||||
|
||||
await getDeviceData(id);
|
||||
|
||||
// 处理 tab 参数
|
||||
const { tab } = route.query;
|
||||
if (tab) {
|
||||
activeTab.value = tab as string;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
@@ -80,50 +91,55 @@ onMounted(async () => {
|
||||
:loading="loading"
|
||||
:product="product"
|
||||
:device="device"
|
||||
@refresh="getDeviceData"
|
||||
@refresh="() => getDeviceData(id)"
|
||||
/>
|
||||
|
||||
<Tabs v-model:active-key="activeTab" class="device-detail-tabs mt-4">
|
||||
<Tabs.Pane key="info" tab="设备信息">
|
||||
<Tabs v-model:active-key="activeTab" class="mt-4">
|
||||
<Tabs.TabPane key="info" tab="设备信息">
|
||||
<DeviceDetailsInfo
|
||||
v-if="activeTab === 'info'"
|
||||
:product="product"
|
||||
:device="device"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="model" tab="物模型数据">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="model" tab="物模型数据">
|
||||
<DeviceDetailsThingModel
|
||||
v-if="activeTab === 'model' && device.id"
|
||||
:device-id="device.id"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
v-if="product.deviceType === DeviceTypeEnum.GATEWAY"
|
||||
key="sub-device"
|
||||
tab="子设备管理"
|
||||
/>
|
||||
<Tabs.Pane key="log" tab="设备消息">
|
||||
>
|
||||
<DeviceDetailsSubDevice
|
||||
v-if="activeTab === 'sub-device' && device.id"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="log" tab="设备消息">
|
||||
<DeviceDetailsMessage
|
||||
v-if="activeTab === 'log' && device.id"
|
||||
:device-id="device.id"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="simulator" tab="模拟设备">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="simulator" tab="模拟设备">
|
||||
<DeviceDetailsSimulator
|
||||
v-if="activeTab === 'simulator'"
|
||||
:product="product"
|
||||
:device="device"
|
||||
:thing-model-list="thingModelList"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
<Tabs.Pane key="config" tab="设备配置">
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="config" tab="设备配置">
|
||||
<DeviceDetailConfig
|
||||
v-if="activeTab === 'config'"
|
||||
:device="device"
|
||||
@success="getDeviceData"
|
||||
@success="() => getDeviceData(id)"
|
||||
/>
|
||||
</Tabs.Pane>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
@@ -22,7 +22,7 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
/** 刷新表格 */
|
||||
function handleRefresh() {
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ async function handleDelete(row: IotDeviceGroupApi.DeviceGroup) {
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
});
|
||||
handleRefresh();
|
||||
onRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
@@ -97,7 +97,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormModal @success="handleRefresh" />
|
||||
<FormModal @success="onRefresh" />
|
||||
<Grid table-title="设备分组列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
|
||||
Reference in New Issue
Block a user