fix: 产品、设备功能配置bug修复

This commit is contained in:
lzh
2025-12-26 15:03:40 +08:00
parent 2b0af6ea95
commit 7df833446d
13 changed files with 128 additions and 76 deletions

View File

@@ -11,7 +11,7 @@ export namespace IotDeviceApi {
productId: number; // 产品编号
productKey?: string; // 产品标识
deviceType?: number; // 设备类型
nickname?: string; // 设备备名称
nickname?: string; // 设备备名称
gatewayId?: number; // 网关设备 ID
state?: number; // 设备状态
status?: number; // 设备状态(兼容字段)

View File

@@ -37,15 +37,15 @@ export function useFormSchema(): VbenFormSchema[] {
},
{
fieldName: 'deviceName',
label: 'DeviceName',
label: '设备标识',
component: 'Input',
componentProps: {
placeholder: '请输入 DeviceName',
placeholder: '请输入设备标识',
},
rules: z
.string()
.min(4, 'DeviceName 长度不能少于 4 个字符')
.max(32, 'DeviceName 长度不能超过 32 个字符')
.min(4, '设备标识长度不能少于 4 个字符')
.max(32, '设备标识长度不能超过 32 个字符')
.regex(
/^[\w.\-:@]{4,32}$/,
'支持英文字母、数字、下划线_、中划线-)、点号(.)、半角冒号(:)和特殊字符@',
@@ -68,18 +68,18 @@ export function useFormSchema(): VbenFormSchema[] {
},
{
fieldName: 'nickname',
label: '备名称',
label: '备名称',
component: 'Input',
componentProps: {
placeholder: '请输入备名称',
placeholder: '请输入备名称',
},
rules: z
.string()
.min(4, '备名称长度限制为 4~64 个字符')
.max(64, '备名称长度限制为 4~64 个字符')
.min(4, '备名称长度限制为 4~64 个字符')
.max(64, '备名称长度限制为 4~64 个字符')
.regex(
/^[\u4E00-\u9FA5\u3040-\u30FF\w]+$/,
'备名称只能包含中文、英文字母、日文、数字和下划线_',
'备名称只能包含中文、英文字母、日文、数字和下划线_',
)
.optional()
.or(z.literal('')),
@@ -208,19 +208,19 @@ export function useGridFormSchema(): VbenFormSchema[] {
},
{
fieldName: 'deviceName',
label: 'DeviceName',
label: '设备名称',
component: 'Input',
componentProps: {
placeholder: '请输入 DeviceName',
placeholder: '请输入设备名称',
allowClear: true,
},
},
{
fieldName: 'nickname',
label: '备名称',
label: '备名称',
component: 'Input',
componentProps: {
placeholder: '请输入备名称',
placeholder: '请输入备名称',
allowClear: true,
},
},
@@ -265,12 +265,12 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
{ type: 'checkbox', width: 40 },
{
field: 'deviceName',
title: 'DeviceName',
title: '设备名称',
minWidth: 150,
},
{
field: 'nickname',
title: '备名称',
title: '备名称',
minWidth: 120,
},
{

View File

@@ -275,14 +275,14 @@ onMounted(async () => {
</Select>
<Input
v-model:value="searchParams.deviceName"
placeholder="请输入 DeviceName"
placeholder="请输入设备标识"
allow-clear
style="width: 200px"
@press-enter="handleSearch"
/>
<Input
v-model:value="searchParams.nickname"
placeholder="请输入备名称"
placeholder="请输入备名称"
allow-clear
style="width: 200px"
@press-enter="handleSearch"

View File

@@ -84,12 +84,12 @@ const queryFormRef = ref(); // 搜索的表单
const columns = computed(() => {
const baseColumns = [
{
title: 'DeviceName',
title: '设备标识',
dataIndex: 'deviceName',
key: 'deviceName',
},
{
title: '备名称',
title: '备名称',
dataIndex: 'nickname',
key: 'nickname',
},
@@ -250,19 +250,19 @@ onMounted(async () => {
</Select.Option>
</Select>
</Form.Item>
<Form.Item label="DeviceName" name="deviceName">
<Form.Item label="设备标识" name="deviceName">
<Input
v-model:value="queryParams.deviceName"
placeholder="请输入 DeviceName"
placeholder="请输入设备标识"
allow-clear
@press-enter="handleQuery"
style="width: 240px"
/>
</Form.Item>
<Form.Item label="备名称" name="nickname">
<Form.Item label="备名称" name="nickname">
<Input
v-model:value="queryParams.nickname"
placeholder="请输入备名称"
placeholder="请输入备名称"
allow-clear
@press-enter="handleQuery"
style="width: 240px"

View File

@@ -98,10 +98,10 @@ function handleAuthInfoDialogClose() {
:value="product.deviceType"
/>
</Descriptions.Item>
<Descriptions.Item label="DeviceName">
<Descriptions.Item label="设备标识">
{{ device.deviceName }}
</Descriptions.Item>
<Descriptions.Item label="备名称">
<Descriptions.Item label="备名称">
{{ device.nickname || '--' }}
</Descriptions.Item>
<Descriptions.Item label="当前状态">
@@ -122,7 +122,7 @@ function handleAuthInfoDialogClose() {
<Descriptions.Item label="最后离线时间">
{{ formatDate(device.offlineTime) }}
</Descriptions.Item>
<Descriptions.Item label="MQTT 连接参数">
<Descriptions.Item label="连接参数">
<Button
type="link"
@click="handleAuthInfoDialogOpen"
@@ -168,7 +168,7 @@ function handleAuthInfoDialogClose() {
<!-- 认证信息弹框 -->
<Modal
v-model:open="authDialogVisible"
title="MQTT 连接参数"
title="连接参数"
width="640px"
:footer="null"
>

View File

@@ -11,7 +11,7 @@ import {
import { ContentWrap } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { formatDate } from '@vben/utils';
import { formatDateTime } from '@vben/utils';
import {
Button,
@@ -229,7 +229,7 @@ defineExpose({
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'ts'">
{{ formatDate(record.ts) }}
{{ formatDateTime(record.ts) }}
</template>
<template v-else-if="column.key === 'upstream'">
<Tag :color="record.upstream ? 'blue' : 'green'">

View File

@@ -266,12 +266,18 @@ defineExpose({
</div>
<!-- 设备名称 -->
<div class="device-name" :title="item.deviceName">
{{ item.deviceName }}
<div class="device-name" :title="item.nickname">
{{ item.nickname }}
</div>
<!-- 信息区域 -->
<div class="info-section">
<div class="info-item">
<span class="label">设备标识</span>
<span class="value code" :title="item.deviceName">
{{ item.deviceName }}
</span>
</div>
<div class="info-item">
<span class="label">所属产品</span>
<a
@@ -286,6 +292,7 @@ defineExpose({
{{ getProductName(item.productId) }}
</a>
</div>
<div class="info-item">
<span class="label">设备类型</span>
<Tag

View File

@@ -129,17 +129,17 @@ export function useFormSchema(formApi?: any): VbenFormSchema[] {
},
rules: 'required',
},
{
fieldName: 'status',
label: '产品状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: 'required',
},
// {
// fieldName: 'status',
// label: '产品状态',
// component: 'RadioGroup',
// componentProps: {
// options: getDictOptions(DICT_TYPE.IOT_PRODUCT_STATUS, 'number'),
// buttonStyle: 'solid',
// optionType: 'button',
// },
// rules: 'required',
// },
{
fieldName: 'icon',
label: '产品图标',
@@ -278,17 +278,17 @@ export function useBasicFormSchema(formApi?: any): VbenFormSchema[] {
},
rules: 'required',
},
{
fieldName: 'status',
label: '产品状态',
component: 'RadioGroup',
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
buttonStyle: 'solid',
optionType: 'button',
},
rules: 'required',
},
// {
// fieldName: 'status',
// label: '产品状态',
// component: 'RadioGroup',
// componentProps: {
// options: getDictOptions(DICT_TYPE.IOT_PRODUCT_STATUS, 'number'),
// buttonStyle: 'solid',
// optionType: 'button',
// },
// rules: 'required',
// },
{
fieldName: 'locationType',
label: '定位类型',

View File

@@ -48,7 +48,7 @@ function formatDate(date?: Date | string) {
{{ product.codecType || '-' }}
</Descriptions.Item>
<Descriptions.Item label="产品状态">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="product.status" />
<DictTag :type="DICT_TYPE.IOT_PRODUCT_STATUS" :value="product.status" />
</Descriptions.Item>
<Descriptions.Item
v-if="

View File

@@ -18,6 +18,7 @@ import {
} from 'ant-design-vue';
import { getProductPage } from '#/api/iot/product/product';
import { DictTag } from '#/components/dict-tag';
// TODO @haohao应该是 card-view.vue
@@ -88,6 +89,15 @@ function getDeviceTypeColor(deviceType: number) {
return colors[deviceType] || 'default';
}
// 获取产品状态颜色
function getProductStatusColor(status: number) {
const colors: Record<number, string> = {
0: 'gray',
1: 'green',
};
return colors[status] || 'default';
}
defineExpose({
reload: getList,
search: () => {
@@ -134,6 +144,15 @@ onMounted(() => {
<!-- 内容区域 -->
<div class="mb-4 flex items-start">
<div class="info-list flex-1">
<div class="info-item">
<span class="info-label">产品标识</span>
<!-- TODO @haohao展示 有点奇怪要不小手 -->
<Tooltip :title="item.productKey || item.id" placement="top">
<span class="info-value product-key">
{{ item.productKey || item.id }}
</span>
</Tooltip>
</div>
<div class="info-item">
<span class="info-label">产品分类</span>
<span class="info-value text-primary">
@@ -154,14 +173,13 @@ onMounted(() => {
}}
</Tag>
</div>
<div class="info-item">
<span class="info-label">产品标识</span>
<!-- TODO @haohao展示 有点奇怪要不小手 -->
<Tooltip :title="item.productKey || item.id" placement="top">
<span class="info-value product-key">
{{ item.productKey || item.id }}
</span>
</Tooltip>
<span class="info-label">产品状态</span>
<DictTag
:type="DICT_TYPE.IOT_PRODUCT_STATUS"
:value="item.status"
/>
</div>
</div>
<!-- TODO @haohao这里是不是有 image然后默认 icon -->

View File

@@ -23,13 +23,20 @@ const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
const emits = defineEmits(['update:modelValue']);
const thingModelEvent = useVModel(props, 'modelValue', emits) as Ref<any>;
/** 默认选中INFO 信息 */
/** 确保 event 对象存在并初始化 type */
watch(
() => thingModelEvent.value.type,
(val: string | undefined) =>
isEmpty(val) &&
(thingModelEvent.value.type = IoTThingModelEventTypeEnum.INFO.value),
{ immediate: true },
() => thingModelEvent.value,
(val) => {
if (!val) {
thingModelEvent.value = {
type: IoTThingModelEventTypeEnum.INFO.value,
outputParams: [],
};
} else if (isEmpty(val.type)) {
val.type = IoTThingModelEventTypeEnum.INFO.value;
}
},
{ immediate: true, deep: true },
);
</script>
@@ -37,7 +44,7 @@ watch(
<Form.Item
:rules="[{ required: true, message: '请选择事件类型', trigger: 'change' }]"
label="事件类型"
name="event.type"
:name="['event', 'type']"
>
<Radio.Group v-model:value="thingModelEvent.type">
<Radio

View File

@@ -51,10 +51,12 @@ const formData = ref<any>({
},
},
service: {
callType: 'async', // 默认异步调用
inputParams: [],
outputParams: [],
},
event: {
type: 'info', // 默认信息类型
outputParams: [],
},
});
@@ -110,10 +112,15 @@ async function open(type: string, id?: number) {
Object.keys(formData.value.service).length === 0
) {
formData.value.service = {
callType: 'async', // 默认异步调用
inputParams: [],
outputParams: [],
};
} else {
// 确保 callType 存在
if (!formData.value.service.callType) {
formData.value.service.callType = 'async';
}
// 确保参数数组存在
if (!formData.value.service.inputParams) {
formData.value.service.inputParams = [];
@@ -128,9 +135,14 @@ async function open(type: string, id?: number) {
Object.keys(formData.value.event).length === 0
) {
formData.value.event = {
type: 'info', // 默认信息类型
outputParams: [],
};
} else {
// 确保 type 存在
if (!formData.value.event.type) {
formData.value.event.type = 'info';
}
// 确保参数数组存在
if (!formData.value.event.outputParams) {
formData.value.event.outputParams = [];

View File

@@ -23,13 +23,21 @@ const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
const emits = defineEmits(['update:modelValue']);
const service = useVModel(props, 'modelValue', emits) as Ref<any>;
/** 默认选中ASYNC 异步 */
/** 确保 service 对象存在并初始化 callType */
watch(
() => service.value.callType,
(val: string | undefined) =>
isEmpty(val) &&
(service.value.callType = IoTThingModelServiceCallTypeEnum.ASYNC.value),
{ immediate: true },
() => service.value,
(val) => {
if (!val) {
service.value = {
callType: IoTThingModelServiceCallTypeEnum.ASYNC.value,
inputParams: [],
outputParams: [],
};
} else if (isEmpty(val.callType)) {
val.callType = IoTThingModelServiceCallTypeEnum.ASYNC.value;
}
},
{ immediate: true, deep: true },
);
</script>
@@ -37,7 +45,7 @@ watch(
<Form.Item
:rules="[{ required: true, message: '请选择调用方式', trigger: 'change' }]"
label="调用方式"
name="service.callType"
:name="['service', 'callType']"
>
<Radio.Group v-model:value="service.callType">
<Radio