fix: iot warn

This commit is contained in:
xingyu4j
2025-10-20 10:37:23 +08:00
parent 948cb916c4
commit c6ef77694e
35 changed files with 476 additions and 455 deletions

View File

@@ -101,6 +101,7 @@ export interface Action {
identifier?: string; identifier?: string;
value?: any; value?: any;
alertConfigId?: number; alertConfigId?: number;
params?: string;
} }
/** 查询场景联动规则分页 */ /** 查询场景联动规则分页 */

View File

@@ -63,7 +63,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '关联场景联动规则', label: '关联场景联动规则',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleRuleSceneList, api: () => getSimpleRuleSceneList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
mode: 'multiple', mode: 'multiple',
@@ -76,7 +76,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '接收的用户', label: '接收的用户',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleUserList, api: () => getSimpleUserList(),
labelField: 'nickname', labelField: 'nickname',
valueField: 'id', valueField: 'id',
mode: 'multiple', mode: 'multiple',

View File

@@ -17,7 +17,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '告警配置', label: '告警配置',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleAlertConfigList, api: () => getSimpleAlertConfigList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择告警配置', placeholder: '请选择告警配置',
@@ -40,7 +40,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',
@@ -53,7 +53,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '设备', label: '设备',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleDeviceList, api: () => getSimpleDeviceList(),
labelField: 'deviceName', labelField: 'deviceName',
valueField: 'id', valueField: 'id',
placeholder: '请选择设备', placeholder: '请选择设备',

View File

@@ -101,7 +101,7 @@ async function handleProcess(row: AlertRecord) {
try { try {
await processAlertRecord(row.id as number, processRemark); await processAlertRecord(row.id as number, processRemark);
message.success('处理成功'); message.success('处理成功');
onRefresh(); handleRefresh();
} catch (error) { } catch (error) {
console.error('处理失败:', error); console.error('处理失败:', error);
throw error; throw error;

View File

@@ -5,6 +5,7 @@ import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks'; import { getDictOptions } from '@vben/hooks';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { getSimpleDeviceList } from '#/api/iot/device/device';
import { getSimpleDeviceGroupList } from '#/api/iot/device/group'; import { getSimpleDeviceGroupList } from '#/api/iot/device/group';
import { import {
DeviceTypeEnum, DeviceTypeEnum,
@@ -27,7 +28,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',
@@ -55,12 +56,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '网关设备', label: '网关设备',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: async () => { api: () => getSimpleDeviceList(DeviceTypeEnum.GATEWAY),
const { getSimpleDeviceList } = await import(
'#/api/iot/device/device'
);
return getSimpleDeviceList(DeviceTypeEnum.GATEWAY);
},
labelField: 'nickname', labelField: 'nickname',
valueField: 'id', valueField: 'id',
placeholder: '子设备可选择父设备', placeholder: '子设备可选择父设备',
@@ -93,7 +89,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '设备分组', label: '设备分组',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleDeviceGroupList, api: () => getSimpleDeviceGroupList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
mode: 'multiple', mode: 'multiple',
@@ -160,7 +156,7 @@ export function useGroupFormSchema(): VbenFormSchema[] {
label: '设备分组', label: '设备分组',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleDeviceGroupList, api: () => getSimpleDeviceGroupList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
mode: 'multiple', mode: 'multiple',
@@ -203,7 +199,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',
@@ -253,7 +249,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '设备分组', label: '设备分组',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleDeviceGroupList, api: () => getSimpleDeviceGroupList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择设备分组', placeholder: '请选择设备分组',

View File

@@ -32,7 +32,6 @@ import { getSimpleProductList } from '#/api/iot/product/product';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useGridColumns } from './data'; import { useGridColumns } from './data';
// @ts-ignore
import DeviceCardView from './modules/DeviceCardView.vue'; import DeviceCardView from './modules/DeviceCardView.vue';
import DeviceForm from './modules/DeviceForm.vue'; import DeviceForm from './modules/DeviceForm.vue';
import DeviceGroupForm from './modules/DeviceGroupForm.vue'; import DeviceGroupForm from './modules/DeviceGroupForm.vue';

View File

@@ -1,15 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { IotDeviceApi } from '#/api/iot/device/device';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { message } from 'ant-design-vue';
import { useVbenForm, useVbenModal } from '@vben/common-ui'; import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { import { message } from 'ant-design-vue';
createDevice,
getDevice, import { createDevice, getDevice, updateDevice } from '#/api/iot/device/device';
updateDevice,
} from '#/api/iot/device/device';
import type { IotDeviceApi } from '#/api/iot/device/device';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useFormSchema } from '../data'; import { useFormSchema } from '../data';

View File

@@ -44,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({
// 关闭并提示 // 关闭并提示
await modalApi.close(); await modalApi.close();
emit('success'); emit('success');
message.success($t('common.updateSuccess')); message.success($t('ui.actionMessage.operationSuccess'));
} finally { } finally {
modalApi.unlock(); modalApi.unlock();
} }

View File

@@ -80,6 +80,7 @@ async function saveConfig() {
try { try {
config.value = JSON.parse(configString.value); config.value = JSON.parse(configString.value);
} catch (error) { } catch (error) {
console.error('JSON格式错误:', error);
message.error({ content: 'JSON格式错误请修正后再提交' }); message.error({ content: 'JSON格式错误请修正后再提交' });
return; return;
} }
@@ -176,33 +177,31 @@ async function updateDeviceConfig() {
placeholder="请输入 JSON 格式的配置信息" placeholder="请输入 JSON 格式的配置信息"
class="json-editor" class="json-editor"
/> />
</div> </div>
</template> </template>
<style scoped> <style scoped>
.json-viewer-container { .json-viewer-container {
max-height: 600px;
padding: 12px;
overflow-y: auto;
background-color: #f5f5f5; background-color: #f5f5f5;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 4px;
padding: 12px;
max-height: 600px;
overflow-y: auto;
} }
.json-code { .json-code {
margin: 0; margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px; font-size: 13px;
line-height: 1.5; line-height: 1.5;
color: #333; color: #333;
white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
white-space: pre-wrap;
} }
.json-editor { .json-editor {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px; font-size: 13px;
} }
</style> </style>

View File

@@ -1,12 +1,27 @@
<script setup lang="ts"> <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 { ContentWrap } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants'; import { DICT_TYPE } from '@vben/constants';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { formatDate } from '@vben/utils'; 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 { getDeviceMessagePage } from '#/api/iot/device/device';
import { DictTag } from '#/components/dict-tag'; import { DictTag } from '#/components/dict-tag';

View File

@@ -9,6 +9,7 @@ import type { ThingModelData } from '#/api/iot/thingmodel';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { ContentWrap } from '@vben/common-ui'; import { ContentWrap } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { import {
Button, Button,
@@ -19,16 +20,14 @@ import {
Tabs, Tabs,
Textarea, Textarea,
} from 'ant-design-vue'; } from 'ant-design-vue';
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue';
import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device'; import { DeviceStateEnum, sendDeviceMessage } from '#/api/iot/device/device';
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
import { import {
IotDeviceMessageMethodEnum, IotDeviceMessageMethodEnum,
IoTThingModelTypeEnum, IoTThingModelTypeEnum,
} from '#/views/iot/utils/constants'; } from '#/views/iot/utils/constants';
import DataDefinition from '#/views/iot/thingmodel/modules/components/DataDefinition.vue';
import DeviceDetailsMessage from './DeviceDetailsMessage.vue'; import DeviceDetailsMessage from './DeviceDetailsMessage.vue';
const props = defineProps<{ const props = defineProps<{
@@ -340,7 +339,7 @@ async function handleServiceInvoke(row: ThingModelData) {
<template> <template>
<ContentWrap> <ContentWrap>
<!-- 上方指令调试区域 --> <!-- 上方指令调试区域 -->
<Card class="mb-4 simulator-tabs"> <Card class="simulator-tabs mb-4">
<template #title> <template #title>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span>指令调试</span> <span>指令调试</span>
@@ -349,233 +348,233 @@ async function handleServiceInvoke(row: ThingModelData) {
size="small" size="small"
@click="debugCollapsed = !debugCollapsed" @click="debugCollapsed = !debugCollapsed"
> >
<UpOutlined v-if="!debugCollapsed" /> <IconifyIcon icon="lucide:chevron-up" v-if="!debugCollapsed" />
<DownOutlined v-if="debugCollapsed" /> <IconifyIcon icon="lucide:chevron-down" v-if="debugCollapsed" />
</Button> </Button>
</div> </div>
</template> </template>
<div v-show="!debugCollapsed"> <div v-show="!debugCollapsed">
<Tabs v-model:active-key="activeTab" size="small"> <Tabs v-model:active-key="activeTab" size="small">
<!-- 上行指令调试 --> <!-- 上行指令调试 -->
<Tabs.TabPane key="upstream" tab="上行指令调试"> <Tabs.TabPane key="upstream" tab="上行指令调试">
<Tabs <Tabs
v-if="activeTab === 'upstream'" v-if="activeTab === 'upstream'"
v-model:active-key="upstreamTab" v-model:active-key="upstreamTab"
size="small" size="small"
>
<!-- 属性上报 -->
<Tabs.TabPane
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method"
tab="属性上报"
> >
<!-- 属性上报 --> <ContentWrap>
<Tabs.TabPane <Table
:key="IotDeviceMessageMethodEnum.PROPERTY_POST.method" :data-source="propertyList"
tab="属性上报" align="center"
> :columns="propertyColumns"
<ContentWrap> :pagination="false"
<Table :scroll="{ y: 300 }"
:data-source="propertyList" size="small"
align="center" >
:columns="propertyColumns" <template #bodyCell="{ column, record }">
:pagination="false" <template v-if="column.key === 'dataType'">
:scroll="{ y: 300 }" {{ record.property?.dataType ?? '-' }}
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>
</template> </template>
</Table> <template v-else-if="column.key === 'dataDefinition'">
<div class="mt-4 flex items-center justify-between"> <DataDefinition :data="record" />
<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> </template>
</Table> <template v-else-if="column.key === 'value'">
</ContentWrap> <Input
</Tabs.TabPane> :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 <Tabs.TabPane
:key="IotDeviceMessageMethodEnum.STATE_UPDATE.method" :key="IotDeviceMessageMethodEnum.EVENT_POST.method"
tab="状态变更" 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"
> >
<!-- 属性调试 --> <ContentWrap>
<Tabs.TabPane <Table
:key="IotDeviceMessageMethodEnum.PROPERTY_SET.method" :data-source="eventList"
tab="属性设置" align="center"
> :columns="eventColumns"
<ContentWrap> :pagination="false"
<Table :scroll="{ y: 300 }"
:data-source="propertyList" size="small"
align="center" >
:columns="propertyColumns" <template #bodyCell="{ column, record }">
:pagination="false" <template v-if="column.key === 'dataType'">
:scroll="{ y: 300 }" {{ record.event?.dataType ?? '-' }}
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>
</template> </template>
</Table> <template v-else-if="column.key === 'dataDefinition'">
<div class="mt-4 flex items-center justify-between"> <DataDefinition :data="record" />
<span class="text-sm text-gray-600"> </template>
设置属性值后点击发送属性设置按钮 <template v-else-if="column.key === 'value'">
</span> <Textarea
<Button type="primary" @click="handlePropertySet"> :value="getFormValue(record.identifier)"
发送属性设置 @update:value="
</Button> setFormValue(record.identifier, $event)
</div> "
</ContentWrap> :rows="3"
</Tabs.TabPane> 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 <Tabs.TabPane
:key="IotDeviceMessageMethodEnum.SERVICE_INVOKE.method" :key="IotDeviceMessageMethodEnum.STATE_UPDATE.method"
tab="设备服务调用" tab="状态变更"
> >
<ContentWrap> <ContentWrap>
<Table <div class="flex gap-4">
:data-source="serviceList" <Button
align="center" type="primary"
:columns="serviceColumns" @click="handleDeviceState(DeviceStateEnum.ONLINE)"
:pagination="false"
:scroll="{ y: 300 }"
size="small"
> >
<template #bodyCell="{ column, record }"> 设备上线
<template v-if="column.key === 'dataDefinition'"> </Button>
<DataDefinition :data="record" /> <Button
</template> danger
<template v-else-if="column.key === 'value'"> @click="handleDeviceState(DeviceStateEnum.OFFLINE)"
<Textarea >
:value="getFormValue(record.identifier)" 设备下线
@update:value=" </Button>
setFormValue(record.identifier, $event) </div>
" </ContentWrap>
:rows="3" </Tabs.TabPane>
placeholder="输入服务参数JSON格式" </Tabs>
size="small" </Tabs.TabPane>
/>
</template> <!-- 下行指令调试 -->
<template v-else-if="column.key === 'action'"> <Tabs.TabPane key="downstream" tab="下行指令调试">
<Button <Tabs
type="primary" v-if="activeTab === 'downstream'"
size="small" v-model:active-key="downstreamTab"
@click="handleServiceInvoke(record)" size="small"
> >
服务调用 <!-- 属性调试 -->
</Button> <Tabs.TabPane
</template> :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>
</Table> <template v-else-if="column.key === 'dataDefinition'">
</ContentWrap> <DataDefinition :data="record" />
</Tabs.TabPane> </template>
</Tabs> <template v-else-if="column.key === 'value'">
</Tabs.TabPane> <Input
</Tabs> :value="getFormValue(record.identifier)"
</div> @update:value="
</Card> 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> <Card>
@@ -587,8 +586,8 @@ async function handleServiceInvoke(row: ThingModelData) {
size="small" size="small"
@click="messageCollapsed = !messageCollapsed" @click="messageCollapsed = !messageCollapsed"
> >
<UpOutlined v-if="!messageCollapsed" /> <IconifyIcon icon="lucide:chevron-down" v-if="!messageCollapsed" />
<DownOutlined v-if="messageCollapsed" /> <IconifyIcon icon="lucide:chevron-down" v-if="messageCollapsed" />
</Button> </Button>
</div> </div>
</template> </template>

View File

@@ -41,4 +41,3 @@ onMounted(() => {
<!-- TODO: 实现子设备列表展示和管理功能 --> <!-- TODO: 实现子设备列表展示和管理功能 -->
</Card> </Card>
</template> </template>

View File

@@ -137,7 +137,9 @@ async function getList() {
try { try {
const data = await getHistoryDevicePropertyList(queryParams); const data = await getHistoryDevicePropertyList(queryParams);
// 后端直接返回数组,不是 { list: [] } 格式 // 后端直接返回数组,不是 { 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; total.value = list.value.length;
// 如果是图表模式且不是复杂数据类型,渲染图表 // 如果是图表模式且不是复杂数据类型,渲染图表
@@ -176,29 +178,29 @@ function renderChart() {
} }
renderEcharts({ renderEcharts({
title: { title: {
text: '属性值趋势', text: '属性值趋势',
left: 'center', left: 'center',
textStyle: { textStyle: {
fontSize: 16, fontSize: 16,
fontWeight: 'normal', fontWeight: 'normal',
},
}, },
}, grid: {
grid: { left: 60,
left: 60, right: 60,
right: 60, bottom: 100,
bottom: 100, top: 80,
top: 80, containLabel: true,
containLabel: true,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
}, },
formatter: (params: any) => { tooltip: {
const param = params[0]; trigger: 'axis',
return ` axisPointer: {
type: 'cross',
},
formatter: (params: any) => {
const param = params[0];
return `
<div style="padding: 8px;"> <div style="padding: 8px;">
<div style="margin-bottom: 4px; font-weight: bold;"> <div style="margin-bottom: 4px; font-weight: bold;">
${formatDate(new Date(param.value[0]), 'YYYY-MM-DD HH:mm:ss')} ${formatDate(new Date(param.value[0]), 'YYYY-MM-DD HH:mm:ss')}
@@ -209,76 +211,76 @@ function renderChart() {
</div> </div>
</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') || '');
}, },
}, },
}, xAxis: {
yAxis: { type: 'time',
type: 'value', name: '时间',
name: '属性值', nameTextStyle: {
nameTextStyle: { padding: [10, 0, 0, 0],
padding: [0, 0, 10, 0],
},
},
series: [
{
name: '属性值',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 2,
color: '#1890FF',
}, },
itemStyle: { axisLabel: {
color: '#1890FF', formatter: (value: number) => {
}, return String(formatDate(new Date(value), 'MM-DD HH:mm') || '');
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,
}, },
], yAxis: {
dataZoom: [ type: 'value',
{ name: '属性值',
type: 'inside', nameTextStyle: {
start: 0, padding: [0, 0, 10, 0],
end: 100, },
}, },
{ series: [
type: 'slider', {
height: 30, name: '属性值',
bottom: 20, 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 完全准备好 }, 300); // 延迟300ms渲染确保 DOM 完全准备好
} }

View File

@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
label: '父级分组', label: '父级分组',
component: 'ApiTreeSelect', component: 'ApiTreeSelect',
componentProps: { componentProps: {
api: getSimpleDeviceGroupList, api: () => getSimpleDeviceGroupList(),
fieldNames: { labelField: 'name',
label: 'name', valueField: 'id',
value: 'id',
},
placeholder: '请选择父级分组', placeholder: '请选择父级分组',
allowClear: true, allowClear: true,
}, },

View File

@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '所属产品', label: '所属产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',
@@ -85,7 +85,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',

View File

@@ -29,7 +29,7 @@ export function useFormSchema(): VbenFormSchema[] {
label: '所属产品', label: '所属产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',
@@ -86,7 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',

View File

@@ -34,11 +34,9 @@ export function useFormSchema(): VbenFormSchema[] {
label: '父级分类', label: '父级分类',
component: 'ApiTreeSelect', component: 'ApiTreeSelect',
componentProps: { componentProps: {
api: getSimpleProductCategoryList, api: () => getSimpleProductCategoryList(),
fieldNames: { labelField: 'name',
label: 'name', valueField: 'id',
value: 'id',
},
placeholder: '请选择父级分类', placeholder: '请选择父级分类',
allowClear: true, allowClear: true,
}, },

View File

@@ -93,7 +93,7 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
label: '产品分类', label: '产品分类',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductCategoryList, api: () => getSimpleProductCategoryList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品分类', placeholder: '请选择产品分类',
@@ -246,7 +246,7 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
label: '产品分类', label: '产品分类',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductCategoryList, api: () => getSimpleProductCategoryList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品分类', placeholder: '请选择产品分类',

View File

@@ -11,16 +11,15 @@ import { downloadFileFromBlobPart } from '@vben/utils';
import { Button, Card, Image, Input, message, Space } from 'ant-design-vue'; import { Button, Card, Image, Input, message, Space } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
import { import {
deleteProduct, deleteProduct,
exportProduct, exportProduct,
getProductPage, getProductPage,
} from '#/api/iot/product/product'; } from '#/api/iot/product/product';
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useGridColumns, useImagePreview } from './data'; import { useGridColumns, useImagePreview } from './data';
// @ts-ignore
import ProductCardView from './modules/ProductCardView.vue'; import ProductCardView from './modules/ProductCardView.vue';
import ProductForm from './modules/ProductForm.vue'; import ProductForm from './modules/ProductForm.vue';

View File

@@ -14,13 +14,18 @@ import {
} from '#/api/iot/product/product'; } from '#/api/iot/product/product';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { generateProductKey, useBasicFormSchema, useAdvancedFormSchema } from '../data'; import {
generateProductKey,
useAdvancedFormSchema,
useBasicFormSchema,
} from '../data';
defineOptions({ name: 'IoTProductForm' }); defineOptions({ name: 'IoTProductForm' });
const emit = defineEmits(['success']);
const CollapsePanel = Collapse.Panel; const CollapsePanel = Collapse.Panel;
const emit = defineEmits(['success']);
const formData = ref<any>(); const formData = ref<any>();
const getTitle = computed(() => { const getTitle = computed(() => {
return formData.value?.id ? '编辑产品' : '新增产品'; 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 const data = formData.value?.id
? { ...values, id: formData.value.id } ? { ...values, id: formData.value.id }
: values; : 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']; activeKey.value = ['advanced'];
} }
} catch (error) { } catch (error) {

View File

@@ -15,10 +15,10 @@ interface Props {
defineProps<Props>(); defineProps<Props>();
/** 格式化日期 */ /** 格式化日期 */
const formatDate = (date?: Date | string) => { function formatDate(date?: Date | string) {
if (!date) return '-'; if (!date) return '-';
return new Date(date).toLocaleString('zh-CN'); return new Date(date).toLocaleString('zh-CN');
}; }
</script> </script>
<template> <template>

View File

@@ -24,7 +24,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
label: '产品', label: '产品',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: getSimpleProductList, api: () => getSimpleProductList(),
labelField: 'name', labelField: 'name',
valueField: 'id', valueField: 'id',
placeholder: '请选择产品', placeholder: '请选择产品',

View File

@@ -210,7 +210,8 @@ function handleOperatorChange() {
<!-- 设备状态条件配置 --> <!-- 设备状态条件配置 -->
<div <div
v-if=" v-if="
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS condition.type ===
IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS.toString()
" "
class="gap-16px flex flex-col" class="gap-16px flex flex-col"
> >
@@ -222,7 +223,7 @@ function handleOperatorChange() {
<Select <Select
:model-value="condition.operator" :model-value="condition.operator"
@update:model-value=" @update:model-value="
(value) => updateConditionField('operator', value) (value: any) => updateConditionField('operator', value)
" "
placeholder="请选择操作符" placeholder="请选择操作符"
class="w-full" class="w-full"

View File

@@ -120,7 +120,7 @@ const timeValue2 = computed(() => {
* @param value 字段值 * @param value 字段值
*/ */
function updateConditionField(field: any, value: any) { function updateConditionField(field: any, value: any) {
condition.value[field] = value; (condition.value as any)[field] = value;
} }
/** /**

View File

@@ -15,7 +15,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'), options: getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE, 'number'),
placeholder: '请选择功能类型', placeholder: '请选择功能类型',
allowClear: true, allowClear: true,
}, },
}, },
]; ];

View File

@@ -1,19 +1,21 @@
<script setup lang="ts"> <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 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 { getDataTypeOptionsLabel, IOT_PROVIDE_KEY } from '../utils/constants';
import { useGridColumns, useGridFormSchema } from './data';
import { DataDefinition } from './modules/components';
import ThingModelForm from './modules/ThingModelForm.vue'; import ThingModelForm from './modules/ThingModelForm.vue';
import ThingModelTSL from './modules/ThingModelTSL.vue'; import ThingModelTSL from './modules/ThingModelTSL.vue';
import { DataDefinition } from './modules/components';
defineOptions({ name: 'IoTThingModel' }); defineOptions({ name: 'IoTThingModel' });
@@ -119,7 +121,7 @@ onMounted(async () => {
description="管理产品的物模型定义,包括属性、服务和事件" description="管理产品的物模型定义,包括属性、服务和事件"
title="物模型管理" title="物模型管理"
> >
<Grid ref="xGrid"> <Grid>
<template #toolbar-tools> <template #toolbar-tools>
<TableAction <TableAction
:actions="[ :actions="[

View File

@@ -21,11 +21,7 @@ defineOptions({ name: 'ThingModelEvent' });
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>(); const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
const emits = defineEmits(['update:modelValue']); const emits = defineEmits(['update:modelValue']);
const thingModelEvent = useVModel( const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
props,
'modelValue',
emits,
) as Ref<any>;
// 默认选中INFO 信息 // 默认选中INFO 信息
watch( watch(

View File

@@ -64,7 +64,8 @@ const formRef = ref(); // 表单 Ref
async function open(type: string, id?: number) { async function open(type: string, id?: number) {
dialogVisible.value = true; dialogVisible.value = true;
// 设置标题create -> 新增update -> 编辑 // 设置标题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; formType.value = type;
resetForm(); resetForm();
if (id) { if (id) {

View File

@@ -42,8 +42,13 @@ const getDataTypeOptions2 = computed(() => {
if (!props.isStructDataSpecs) { if (!props.isStructDataSpecs) {
return getDataTypeOptions(); return getDataTypeOptions();
} }
const excludedTypes = [IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.ARRAY]; const excludedTypes = new Set([
return getDataTypeOptions().filter((item: any) => !excludedTypes.includes(item.value)); IoTDataSpecsDataTypeEnum.ARRAY,
IoTDataSpecsDataTypeEnum.STRUCT,
]);
return getDataTypeOptions().filter(
(item: any) => !excludedTypes.has(item.value),
);
}); // 获得数据类型列表 }); // 获得数据类型列表
/** 属性值的数据类型切换时初始化相关数据 */ /** 属性值的数据类型切换时初始化相关数据 */
@@ -52,19 +57,11 @@ function handleChange(dataType: any) {
property.value.dataSpecsList = []; property.value.dataSpecsList = [];
// 不是列表型数据才设置 dataSpecs.dataType // 不是列表型数据才设置 dataSpecs.dataType
![ ![
IoTDataSpecsDataTypeEnum.ENUM,
IoTDataSpecsDataTypeEnum.BOOL, IoTDataSpecsDataTypeEnum.BOOL,
IoTDataSpecsDataTypeEnum.ENUM,
IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.STRUCT,
].includes(dataType) && (property.value.dataSpecs.dataType = dataType); ].includes(dataType) && (property.value.dataSpecs.dataType = dataType);
switch (dataType) { switch (dataType) {
case IoTDataSpecsDataTypeEnum.ENUM: {
property.value.dataSpecsList.push({
dataType: IoTDataSpecsDataTypeEnum.ENUM,
name: '', // 枚举项的名称
value: undefined, // 枚举值
});
break;
}
case IoTDataSpecsDataTypeEnum.BOOL: { case IoTDataSpecsDataTypeEnum.BOOL: {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
property.value.dataSpecsList.push({ property.value.dataSpecsList.push({
@@ -75,6 +72,14 @@ function handleChange(dataType: any) {
} }
break; break;
} }
case IoTDataSpecsDataTypeEnum.ENUM: {
property.value.dataSpecsList.push({
dataType: IoTDataSpecsDataTypeEnum.ENUM,
name: '', // 枚举项的名称
value: undefined, // 枚举值
});
break;
}
} }
// useVModel 会自动同步数据到父组件,不需要手动 emit // useVModel 会自动同步数据到父组件,不需要手动 emit
} }
@@ -95,9 +100,7 @@ watch(
</script> </script>
<template> <template>
<Form.Item <Form.Item label="数据类型">
label="数据类型"
>
<Select <Select
v-model:value="property.dataType" v-model:value="property.dataType"
placeholder="请选择数据类型" placeholder="请选择数据类型"
@@ -135,7 +138,7 @@ watch(
label="布尔值" label="布尔值"
> >
<template v-for="item in property.dataSpecsList" :key="item.value"> <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>{{ item.value }}</span>
<span class="mx-2">-</span> <span class="mx-2">-</span>
<div class="flex-1"> <div class="flex-1">

View File

@@ -92,26 +92,26 @@ watch(tslString, (newValue) => {
<style scoped> <style scoped>
.json-viewer-container { .json-viewer-container {
max-height: 600px;
padding: 12px;
overflow-y: auto;
background-color: #f5f5f5; background-color: #f5f5f5;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 4px;
padding: 12px;
max-height: 600px;
overflow-y: auto;
} }
.json-code { .json-code {
margin: 0; margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px; font-size: 13px;
line-height: 1.5; line-height: 1.5;
color: #333; color: #333;
white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
white-space: pre-wrap;
} }
.json-editor { .json-editor {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace; font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, monospace;
font-size: 13px; font-size: 13px;
} }
</style> </style>

View File

@@ -19,22 +19,30 @@ const props = defineProps<{ data: ThingModelData }>();
// 格式化布尔值和枚举值列表为字符串 // 格式化布尔值和枚举值列表为字符串
const formattedDataSpecsList = computed(() => { 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 '';
} }
return props.data.property.dataSpecsList return props.data.property.dataSpecsList
.map(item => `${item.value}-${item.name}`) .map((item) => `${item.value}-${item.name}`)
.join('、'); .join('、');
}); });
// 显示的简短文本(第一个值) // 显示的简短文本(第一个值)
const shortText = computed(() => { 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 '-'; return '-';
} }
const first = props.data.property.dataSpecsList[0]; const first = props.data.property.dataSpecsList[0];
const count = props.data.property.dataSpecsList.length; 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> </script>
@@ -108,8 +116,8 @@ const shortText = computed(() => {
border-bottom: 1px dashed #d9d9d9; border-bottom: 1px dashed #d9d9d9;
&:hover { &:hover {
border-bottom-color: #1890ff;
color: #1890ff; color: #1890ff;
border-bottom-color: #1890ff;
} }
} }
</style> </style>

View File

@@ -51,7 +51,10 @@ function handleChange(val: any) {
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label="元素个数" name="property.dataSpecs.size"> <Form.Item label="元素个数" name="property.dataSpecs.size">
<Input v-model:value="dataSpecs.size" placeholder="请输入数组中的元素个数" /> <Input
v-model:value="dataSpecs.size"
placeholder="请输入数组中的元素个数"
/>
</Form.Item> </Form.Item>
<!-- Struct 型配置--> <!-- Struct 型配置-->
<ThingModelStructDataSpecs <ThingModelStructDataSpecs

View File

@@ -28,7 +28,6 @@ function deleteEnum(index: number) {
} }
dataSpecsList.value.splice(index, 1); dataSpecsList.value.splice(index, 1);
} }
</script> </script>
<template> <template>
@@ -41,7 +40,7 @@ function deleteEnum(index: number) {
<div <div
v-for="(item, index) in dataSpecsList" v-for="(item, index) in dataSpecsList"
:key="index" :key="index"
class="flex items-center justify-between mb-5px" class="mb-5px flex items-center justify-between"
> >
<div class="flex-1"> <div class="flex-1">
<Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" /> <Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" />

View File

@@ -1,4 +1,35 @@
<!-- dataTypenumber 数组类型 --> <!-- dataTypenumber 数组类型 -->
<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> <template>
<Form.Item label="取值范围"> <Form.Item label="取值范围">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -38,38 +69,6 @@
</Form.Item> </Form.Item>
</template> </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> <style lang="scss" scoped>
:deep(.ant-form-item) { :deep(.ant-form-item) {
.ant-form-item { .ant-form-item {

View File

@@ -115,7 +115,6 @@ function resetForm() {
structFormRef.value?.resetFields(); structFormRef.value?.resetFields();
} }
/** 组件初始化 */ /** 组件初始化 */
onMounted(async () => { onMounted(async () => {
await nextTick(); await nextTick();
@@ -134,18 +133,14 @@ onMounted(async () => {
> >
<span>参数{{ item.name }}</span> <span>参数{{ item.name }}</span>
<div class="btn"> <div class="btn">
<Button type="link" @click="openStructForm(item)"> <Button type="link" @click="openStructForm(item)"> 编辑 </Button>
编辑
</Button>
<Divider type="vertical" /> <Divider type="vertical" />
<Button type="link" danger @click="deleteStructItem(index)"> <Button type="link" danger @click="deleteStructItem(index)">
删除 删除
</Button> </Button>
</div> </div>
</div> </div>
<Button type="link" @click="openStructForm(null)"> <Button type="link" @click="openStructForm(null)"> +新增参数 </Button>
+新增参数
</Button>
</Form.Item> </Form.Item>
<!-- struct 表单 --> <!-- struct 表单 -->