fix: iot warn
This commit is contained in:
@@ -101,6 +101,7 @@ export interface Action {
|
||||
identifier?: string;
|
||||
value?: any;
|
||||
alertConfigId?: number;
|
||||
params?: string;
|
||||
}
|
||||
|
||||
/** 查询场景联动规则分页 */
|
||||
|
||||
@@ -63,7 +63,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '关联场景联动规则',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleRuleSceneList,
|
||||
api: () => getSimpleRuleSceneList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -76,7 +76,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '接收的用户',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleUserList,
|
||||
api: () => getSimpleUserList(),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '告警配置',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleAlertConfigList,
|
||||
api: () => getSimpleAlertConfigList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择告警配置',
|
||||
@@ -40,7 +40,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -53,7 +53,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '设备',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceList,
|
||||
api: () => getSimpleDeviceList(),
|
||||
labelField: 'deviceName',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择设备',
|
||||
|
||||
@@ -101,7 +101,7 @@ async function handleProcess(row: AlertRecord) {
|
||||
try {
|
||||
await processAlertRecord(row.id as number, processRemark);
|
||||
message.success('处理成功');
|
||||
onRefresh();
|
||||
handleRefresh();
|
||||
} catch (error) {
|
||||
console.error('处理失败:', error);
|
||||
throw error;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getSimpleDeviceList } from '#/api/iot/device/device';
|
||||
import { getSimpleDeviceGroupList } from '#/api/iot/device/group';
|
||||
import {
|
||||
DeviceTypeEnum,
|
||||
@@ -27,7 +28,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -55,12 +56,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '网关设备',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: async () => {
|
||||
const { getSimpleDeviceList } = await import(
|
||||
'#/api/iot/device/device'
|
||||
);
|
||||
return getSimpleDeviceList(DeviceTypeEnum.GATEWAY);
|
||||
},
|
||||
api: () => getSimpleDeviceList(DeviceTypeEnum.GATEWAY),
|
||||
labelField: 'nickname',
|
||||
valueField: 'id',
|
||||
placeholder: '子设备可选择父设备',
|
||||
@@ -93,7 +89,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -160,7 +156,7 @@ export function useGroupFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
mode: 'multiple',
|
||||
@@ -203,7 +199,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -253,7 +249,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '设备分组',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择设备分组',
|
||||
|
||||
@@ -32,7 +32,6 @@ import { getSimpleProductList } from '#/api/iot/product/product';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns } from './data';
|
||||
// @ts-ignore
|
||||
import DeviceCardView from './modules/DeviceCardView.vue';
|
||||
import DeviceForm from './modules/DeviceForm.vue';
|
||||
import DeviceGroupForm from './modules/DeviceGroupForm.vue';
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<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 {
|
||||
createDevice,
|
||||
getDevice,
|
||||
updateDevice,
|
||||
} from '#/api/iot/device/device';
|
||||
import type { IotDeviceApi } from '#/api/iot/device/device';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { createDevice, getDevice, updateDevice } from '#/api/iot/device/device';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
@@ -44,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({
|
||||
// 关闭并提示
|
||||
await modalApi.close();
|
||||
emit('success');
|
||||
message.success($t('common.updateSuccess'));
|
||||
message.success($t('ui.actionMessage.operationSuccess'));
|
||||
} finally {
|
||||
modalApi.unlock();
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ async function saveConfig() {
|
||||
try {
|
||||
config.value = JSON.parse(configString.value);
|
||||
} catch (error) {
|
||||
console.error('JSON格式错误:', error);
|
||||
message.error({ content: 'JSON格式错误,请修正后再提交!' });
|
||||
return;
|
||||
}
|
||||
@@ -176,33 +177,31 @@ async function updateDeviceConfig() {
|
||||
placeholder="请输入 JSON 格式的配置信息"
|
||||
class="json-editor"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
max-height: 600px;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
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-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
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 {
|
||||
Button,
|
||||
Form,
|
||||
Pagination,
|
||||
Select,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { getDeviceMessagePage } from '#/api/iot/device/device';
|
||||
import { DictTag } from '#/components/dict-tag';
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { ContentWrap } from '@vben/common-ui';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -19,16 +20,14 @@ import {
|
||||
Tabs,
|
||||
Textarea,
|
||||
} from 'ant-design-vue';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
|
||||
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
|
||||
import {
|
||||
IotDeviceMessageMethodEnum,
|
||||
IoTThingModelTypeEnum,
|
||||
} from '#/views/iot/utils/constants';
|
||||
|
||||
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
|
||||
|
||||
import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -340,7 +339,7 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- 上方:指令调试区域 -->
|
||||
<Card class="mb-4 simulator-tabs">
|
||||
<Card class="simulator-tabs mb-4">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>指令调试</span>
|
||||
@@ -349,233 +348,233 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
size="small"
|
||||
@click="debugCollapsed = !debugCollapsed"
|
||||
>
|
||||
<UpOutlined v-if="!debugCollapsed" />
|
||||
<DownOutlined v-if="debugCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-up" v-if="!debugCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-down" 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.TabPane key="upstream" tab="上行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'upstream'"
|
||||
v-model:active-key="upstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
<!-- 属性上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
|
||||
tab="属性上报"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="eventList"
|
||||
align="center"
|
||||
:columns="eventColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.event?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入事件参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEventPost(record)"
|
||||
>
|
||||
上报事件
|
||||
</Button>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性上报」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertyPost">
|
||||
发送属性上报
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
<ContentWrap>
|
||||
<div class="flex gap-4">
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleDeviceState(DeviceStateEnum.ONLINE)"
|
||||
>
|
||||
设备上线
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
@click="handleDeviceState(DeviceStateEnum.OFFLINE)"
|
||||
>
|
||||
设备下线
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.TabPane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-model:active-key="downstreamTab"
|
||||
size="small"
|
||||
<!-- 事件上报 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.EVENT_POST.method"
|
||||
tab="事件上报"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="eventList"
|
||||
align="center"
|
||||
:columns="eventColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.event?.dataType ?? '-' }}
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入事件参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleEventPost(record)"
|
||||
>
|
||||
上报事件
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="serviceList"
|
||||
align="center"
|
||||
:columns="serviceColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
<!-- 状态变更 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
|
||||
tab="状态变更"
|
||||
>
|
||||
<ContentWrap>
|
||||
<div class="flex gap-4">
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleDeviceState(DeviceStateEnum.ONLINE)"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入服务参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleServiceInvoke(record)"
|
||||
>
|
||||
服务调用
|
||||
</Button>
|
||||
</template>
|
||||
设备上线
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
@click="handleDeviceState(DeviceStateEnum.OFFLINE)"
|
||||
>
|
||||
设备下线
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 下行指令调试 -->
|
||||
<Tabs.TabPane key="downstream" tab="下行指令调试">
|
||||
<Tabs
|
||||
v-if="activeTab === 'downstream'"
|
||||
v-model:active-key="downstreamTab"
|
||||
size="small"
|
||||
>
|
||||
<!-- 属性调试 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method"
|
||||
tab="属性设置"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="propertyList"
|
||||
align="center"
|
||||
:columns="propertyColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataType'">
|
||||
{{ record.property?.dataType ?? '-' }}
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
<template v-else-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Input
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
placeholder="输入值"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">
|
||||
设置属性值后,点击「发送属性设置」按钮
|
||||
</span>
|
||||
<Button type="primary" @click="handlePropertySet">
|
||||
发送属性设置
|
||||
</Button>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<!-- 服务调用 -->
|
||||
<Tabs.TabPane
|
||||
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method"
|
||||
tab="设备服务调用"
|
||||
>
|
||||
<ContentWrap>
|
||||
<Table
|
||||
:data-source="serviceList"
|
||||
align="center"
|
||||
:columns="serviceColumns"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 300 }"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dataDefinition'">
|
||||
<DataDefinition :data="record" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'value'">
|
||||
<Textarea
|
||||
:value="getFormValue(record.identifier)"
|
||||
@update:value="
|
||||
setFormValue(record.identifier, $event)
|
||||
"
|
||||
:rows="3"
|
||||
placeholder="输入服务参数(JSON格式)"
|
||||
size="small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleServiceInvoke(record)"
|
||||
>
|
||||
服务调用
|
||||
</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 下方:设备消息区域 -->
|
||||
<Card>
|
||||
@@ -587,8 +586,8 @@ async function handleServiceInvoke(row: ThingModelData) {
|
||||
size="small"
|
||||
@click="messageCollapsed = !messageCollapsed"
|
||||
>
|
||||
<UpOutlined v-if="!messageCollapsed" />
|
||||
<DownOutlined v-if="messageCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-down" v-if="!messageCollapsed" />
|
||||
<IconifyIcon icon="lucide:chevron-down" v-if="messageCollapsed" />
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -41,4 +41,3 @@ onMounted(() => {
|
||||
<!-- TODO: 实现子设备列表展示和管理功能 -->
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -137,7 +137,9 @@ async function getList() {
|
||||
try {
|
||||
const data = await getHistoryDevicePropertyList(queryParams);
|
||||
// 后端直接返回数组,不是 { list: [] } 格式
|
||||
list.value = (Array.isArray(data) ? data : (data?.list || [])) as IotDeviceApi.DevicePropertyDetail[];
|
||||
list.value = (
|
||||
Array.isArray(data) ? data : data?.list || []
|
||||
) as IotDeviceApi.DevicePropertyDetail[];
|
||||
total.value = list.value.length;
|
||||
|
||||
// 如果是图表模式且不是复杂数据类型,渲染图表
|
||||
@@ -176,29 +178,29 @@ function renderChart() {
|
||||
}
|
||||
|
||||
renderEcharts({
|
||||
title: {
|
||||
text: '属性值趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
title: {
|
||||
text: '属性值趋势',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: 60,
|
||||
right: 60,
|
||||
bottom: 100,
|
||||
top: 80,
|
||||
containLabel: true,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
grid: {
|
||||
left: 60,
|
||||
right: 60,
|
||||
bottom: 100,
|
||||
top: 80,
|
||||
containLabel: true,
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross',
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
const param = params[0];
|
||||
return `
|
||||
<div style="padding: 8px;">
|
||||
<div style="margin-bottom: 4px; font-weight: bold;">
|
||||
${formatDate(new Date(param.value[0]), 'YYYY-MM-DD HH:mm:ss')}
|
||||
@@ -209,76 +211,76 @@ function renderChart() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
name: '时间',
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
return String(formatDate(new Date(value), 'MM-DD HH:mm') || '');
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '属性值',
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 10, 0],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '属性值',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#1890FF',
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
name: '时间',
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890FF',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(24, 144, 255, 0.3)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(24, 144, 255, 0.05)',
|
||||
},
|
||||
],
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
return String(formatDate(new Date(value), 'MM-DD HH:mm') || '');
|
||||
},
|
||||
},
|
||||
data: chartData,
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
name: '属性值',
|
||||
nameTextStyle: {
|
||||
padding: [0, 0, 10, 0],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
height: 30,
|
||||
bottom: 20,
|
||||
},
|
||||
],
|
||||
});
|
||||
series: [
|
||||
{
|
||||
name: '属性值',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: '#1890FF',
|
||||
},
|
||||
itemStyle: {
|
||||
color: '#1890FF',
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(24, 144, 255, 0.3)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(24, 144, 255, 0.05)',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
data: chartData,
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
type: 'slider',
|
||||
height: 30,
|
||||
bottom: 20,
|
||||
},
|
||||
],
|
||||
});
|
||||
}, 300); // 延迟300ms渲染,确保 DOM 完全准备好
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '父级分组',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: getSimpleDeviceGroupList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleDeviceGroupList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择父级分组',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '所属产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -85,7 +85,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '所属产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
@@ -86,7 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||
label: '父级分类',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
fieldNames: {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
},
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择父级分类',
|
||||
allowClear: true,
|
||||
},
|
||||
|
||||
@@ -93,7 +93,7 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
|
||||
label: '产品分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品分类',
|
||||
@@ -246,7 +246,7 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
|
||||
label: '产品分类',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductCategoryList,
|
||||
api: () => getSimpleProductCategoryList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品分类',
|
||||
|
||||
@@ -11,16 +11,15 @@ import { downloadFileFromBlobPart } from '@vben/utils';
|
||||
import { Button, Card, Image, Input, message, Space } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import {
|
||||
deleteProduct,
|
||||
exportProduct,
|
||||
getProductPage,
|
||||
} from '#/api/iot/product/product';
|
||||
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useGridColumns, useImagePreview } from './data';
|
||||
// @ts-ignore
|
||||
import ProductCardView from './modules/ProductCardView.vue';
|
||||
import ProductForm from './modules/ProductForm.vue';
|
||||
|
||||
|
||||
@@ -14,13 +14,18 @@ import {
|
||||
} from '#/api/iot/product/product';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { generateProductKey, useBasicFormSchema, useAdvancedFormSchema } from '../data';
|
||||
import {
|
||||
generateProductKey,
|
||||
useAdvancedFormSchema,
|
||||
useBasicFormSchema,
|
||||
} from '../data';
|
||||
|
||||
defineOptions({ name: 'IoTProductForm' });
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const CollapsePanel = Collapse.Panel;
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
const formData = ref<any>();
|
||||
const getTitle = computed(() => {
|
||||
return formData.value?.id ? '编辑产品' : '新增产品';
|
||||
@@ -84,7 +89,10 @@ const [Modal, modalApi] = useVbenModal({
|
||||
};
|
||||
}
|
||||
|
||||
const values = { ...basicValues, ...advancedValues } as IotProductApi.Product;
|
||||
const values = {
|
||||
...basicValues,
|
||||
...advancedValues,
|
||||
} as IotProductApi.Product;
|
||||
const data = formData.value?.id
|
||||
? { ...values, id: formData.value.id }
|
||||
: values;
|
||||
@@ -132,7 +140,11 @@ const [Modal, modalApi] = useVbenModal({
|
||||
});
|
||||
|
||||
// 如果有图标、图片或描述,自动展开折叠面板以便显示
|
||||
if (formData.value.icon || formData.value.picUrl || formData.value.description) {
|
||||
if (
|
||||
formData.value.icon ||
|
||||
formData.value.picUrl ||
|
||||
formData.value.description
|
||||
) {
|
||||
activeKey.value = ['advanced'];
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -15,10 +15,10 @@ interface Props {
|
||||
defineProps<Props>();
|
||||
|
||||
/** 格式化日期 */
|
||||
const formatDate = (date?: Date | string) => {
|
||||
function formatDate(date?: Date | string) {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleString('zh-CN');
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
label: '产品',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: getSimpleProductList,
|
||||
api: () => getSimpleProductList(),
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
placeholder: '请选择产品',
|
||||
|
||||
@@ -210,7 +210,8 @@ function handleOperatorChange() {
|
||||
<!-- 设备状态条件配置 -->
|
||||
<div
|
||||
v-if="
|
||||
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
|
||||
condition.type ===
|
||||
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS.toString()
|
||||
"
|
||||
class="gap-16px flex flex-col"
|
||||
>
|
||||
@@ -222,7 +223,7 @@ function handleOperatorChange() {
|
||||
<Select
|
||||
:model-value="condition.operator"
|
||||
@update:model-value="
|
||||
(value) => updateConditionField('operator', value)
|
||||
(value: any) => updateConditionField('operator', value)
|
||||
"
|
||||
placeholder="请选择操作符"
|
||||
class="w-full"
|
||||
|
||||
@@ -120,7 +120,7 @@ const timeValue2 = computed(() => {
|
||||
* @param value 字段值
|
||||
*/
|
||||
function updateConditionField(field: any, value: any) {
|
||||
condition.value[field] = value;
|
||||
(condition.value as any)[field] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'),
|
||||
placeholder: '请选择功能类型',
|
||||
allowClear: true,
|
||||
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import { onMounted, provide, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getProduct } from '#/api/iot/product/product';
|
||||
import { deleteThingModel, getThingModelPage } from '#/api/iot/thingmodel';
|
||||
|
||||
import { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
import { DataDefinition } from './modules/components';
|
||||
import ThingModelForm from './modules/ThingModelForm.vue';
|
||||
import ThingModelTSL from './modules/ThingModelTSL.vue';
|
||||
import { DataDefinition } from './modules/components';
|
||||
|
||||
defineOptions({ name: 'IoTThingModel' });
|
||||
|
||||
@@ -119,7 +121,7 @@ onMounted(async () => {
|
||||
description="管理产品的物模型定义,包括属性、服务和事件"
|
||||
title="物模型管理"
|
||||
>
|
||||
<Grid ref="xGrid">
|
||||
<Grid>
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
|
||||
@@ -21,11 +21,7 @@ defineOptions({ name: 'ThingModelEvent' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const thingModelEvent = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<any>;
|
||||
const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
// 默认选中,INFO 信息
|
||||
watch(
|
||||
|
||||
@@ -64,7 +64,8 @@ const formRef = ref(); // 表单 Ref
|
||||
async function open(type: string, id?: number) {
|
||||
dialogVisible.value = true;
|
||||
// 设置标题:create -> 新增,update -> 编辑
|
||||
dialogTitle.value = type === 'create' ? $t('page.action.add') : $t('page.action.edit');
|
||||
dialogTitle.value =
|
||||
type === 'create' ? $t('page.action.add') : $t('page.action.edit');
|
||||
formType.value = type;
|
||||
resetForm();
|
||||
if (id) {
|
||||
|
||||
@@ -42,8 +42,13 @@ const getDataTypeOptions2 = computed(() => {
|
||||
if (!props.isStructDataSpecs) {
|
||||
return getDataTypeOptions();
|
||||
}
|
||||
const excludedTypes = [IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.ARRAY];
|
||||
return getDataTypeOptions().filter((item: any) => !excludedTypes.includes(item.value));
|
||||
const excludedTypes = new Set([
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
]);
|
||||
return getDataTypeOptions().filter(
|
||||
(item: any) => !excludedTypes.has(item.value),
|
||||
);
|
||||
}); // 获得数据类型列表
|
||||
|
||||
/** 属性值的数据类型切换时初始化相关数据 */
|
||||
@@ -52,19 +57,11 @@ function handleChange(dataType: any) {
|
||||
property.value.dataSpecsList = [];
|
||||
// 不是列表型数据才设置 dataSpecs.dataType
|
||||
![
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.BOOL,
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
].includes(dataType) && (property.value.dataSpecs.dataType = dataType);
|
||||
switch (dataType) {
|
||||
case IoTDataSpecsDataTypeEnum.ENUM: {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
break;
|
||||
}
|
||||
case IoTDataSpecsDataTypeEnum.BOOL: {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
property.value.dataSpecsList.push({
|
||||
@@ -75,6 +72,14 @@ function handleChange(dataType: any) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IoTDataSpecsDataTypeEnum.ENUM: {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// useVModel 会自动同步数据到父组件,不需要手动 emit
|
||||
}
|
||||
@@ -95,9 +100,7 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
label="数据类型"
|
||||
>
|
||||
<Form.Item label="数据类型">
|
||||
<Select
|
||||
v-model:value="property.dataType"
|
||||
placeholder="请选择数据类型"
|
||||
@@ -135,7 +138,7 @@ watch(
|
||||
label="布尔值"
|
||||
>
|
||||
<template v-for="item in property.dataSpecsList" :key="item.value">
|
||||
<div class="flex items-center justify-start w-1/1 mb-5px">
|
||||
<div class="w-1/1 mb-5px flex items-center justify-start">
|
||||
<span>{{ item.value }}</span>
|
||||
<span class="mx-2">-</span>
|
||||
<div class="flex-1">
|
||||
|
||||
@@ -92,26 +92,26 @@ watch(tslString, (newValue) => {
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
max-height: 600px;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
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-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,22 +19,30 @@ const props = defineProps<{ data: ThingModelData }>();
|
||||
|
||||
// 格式化布尔值和枚举值列表为字符串
|
||||
const formattedDataSpecsList = computed(() => {
|
||||
if (!props.data.property?.dataSpecsList || props.data.property.dataSpecsList.length === 0) {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return props.data.property.dataSpecsList
|
||||
.map(item => `${item.value}-${item.name}`)
|
||||
.map((item) => `${item.value}-${item.name}`)
|
||||
.join('、');
|
||||
});
|
||||
|
||||
// 显示的简短文本(第一个值)
|
||||
const shortText = computed(() => {
|
||||
if (!props.data.property?.dataSpecsList || props.data.property.dataSpecsList.length === 0) {
|
||||
if (
|
||||
!props.data.property?.dataSpecsList ||
|
||||
props.data.property.dataSpecsList.length === 0
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
const first = props.data.property.dataSpecsList[0];
|
||||
const count = props.data.property.dataSpecsList.length;
|
||||
return count > 1 ? `${first.value}-${first.name} 等${count}项` : `${first.value}-${first.name}`;
|
||||
return count > 1
|
||||
? `${first.value}-${first.name} 等${count}项`
|
||||
: `${first.value}-${first.name}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -108,8 +116,8 @@ const shortText = computed(() => {
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: #1890ff;
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -51,7 +51,10 @@ function handleChange(val: any) {
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="元素个数" name="property.dataSpecs.size">
|
||||
<Input v-model:value="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
||||
<Input
|
||||
v-model:value="dataSpecs.size"
|
||||
placeholder="请输入数组中的元素个数"
|
||||
/>
|
||||
</Form.Item>
|
||||
<!-- Struct 型配置-->
|
||||
<ThingModelStructDataSpecs
|
||||
|
||||
@@ -28,7 +28,6 @@ function deleteEnum(index: number) {
|
||||
}
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -41,7 +40,7 @@ function deleteEnum(index: number) {
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="flex items-center justify-between mb-5px"
|
||||
class="mb-5px flex items-center justify-between"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
|
||||
@@ -1,4 +1,35 @@
|
||||
<!-- dataType:number 数组类型 -->
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsNumberData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
/** 数值型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelNumberDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecs = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位发生变化时触发 */
|
||||
const unitChange = (UnitSpecs: any) => {
|
||||
if (!UnitSpecs) return;
|
||||
const [unitName, unit] = String(UnitSpecs).split('-');
|
||||
dataSpecs.value.unitName = unitName;
|
||||
dataSpecs.value.unit = unit;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -38,38 +69,6 @@
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsNumberData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
/** 数值型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelNumberDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecs = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位发生变化时触发 */
|
||||
const unitChange = (UnitSpecs: any) => {
|
||||
if (!UnitSpecs) return;
|
||||
const [unitName, unit] = String(UnitSpecs).split('-');
|
||||
dataSpecs.value.unitName = unitName;
|
||||
dataSpecs.value.unit = unit;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
|
||||
@@ -115,7 +115,6 @@ function resetForm() {
|
||||
structFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
|
||||
/** 组件初始化 */
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
@@ -134,18 +133,14 @@ onMounted(async () => {
|
||||
>
|
||||
<span>参数:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button type="link" @click="openStructForm(item)">
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" @click="openStructForm(item)"> 编辑 </Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteStructItem(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="link" @click="openStructForm(null)">
|
||||
+新增参数
|
||||
</Button>
|
||||
<Button type="link" @click="openStructForm(null)"> +新增参数 </Button>
|
||||
</Form.Item>
|
||||
|
||||
<!-- struct 表单 -->
|
||||
|
||||
Reference in New Issue
Block a user