设备端物模型管理界面

This commit is contained in:
ChenYi 2025-12-17 17:04:20 +08:00
parent 288b153e43
commit 031364ba7e
5 changed files with 438 additions and 746 deletions

View File

@ -2544,6 +2544,9 @@ export const DeviceThingModelCreateInputSchema = {
type: 'string', type: 'string',
description: '脚本函数体', description: '脚本函数体',
nullable: true nullable: true
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
} }
}, },
additionalProperties: false additionalProperties: false
@ -3105,6 +3108,9 @@ export const DeviceThingModelUpdateInputSchema = {
description: '脚本函数体', description: '脚本函数体',
nullable: true nullable: true
}, },
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
id: { id: {
type: 'string', type: 'string',
format: 'uuid' format: 'uuid'

View File

@ -1433,6 +1433,7 @@ export type DeviceThingModelCreateInput = {
* *
*/ */
functionScript?: (string) | null; functionScript?: (string) | null;
parsingSequence?: ParsingSequenceTypeEnum;
}; };
/** /**
@ -1796,6 +1797,7 @@ export type DeviceThingModelUpdateInput = {
* *
*/ */
functionScript?: (string) | null; functionScript?: (string) | null;
parsingSequence?: ParsingSequenceTypeEnum;
id?: string; id?: string;
}; };

View File

@ -1,188 +0,0 @@
<script setup lang="ts">
import { ref, watch, computed, h } from 'vue';
import { Select, Divider, Row } from 'ant-design-vue';
import { ChevronLeft, ChevronRight } from '@vben/icons';
import { postDataDictionarySelectDetail } from '#/api-client';
import { $t } from '#/locales';
import { useDebounceFn } from '@vueuse/core';
interface Props {
value?: string;
typeCode?: string | number | null;
placeholder?: string;
disabled?: boolean;
allowClear?: boolean;
onResolve?: (item: any | null) => void;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: $t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
disabled: false,
allowClear: true,
});
const emit = defineEmits<{
'update:value': [string | undefined];
change: [string | undefined];
'item-change': [any | null];
}>();
const VNodes = (props: any) => {
return props.vnodes;
};
const options = ref<any[]>([]);
const total = ref(0);
const loading = ref<boolean>(false);
const query = ref({
pageIndex: 1,
pageSize: 10,
filter: '',
});
const maxPage = computed(() => Math.ceil((total.value || 0) / query.value.pageSize) || 1);
const fetchData = async () => {
if (!props.typeCode) {
options.value = [];
total.value = 0;
return;
}
loading.value = true;
try {
const { data } = await postDataDictionarySelectDetail({
body: {
filter: query.value.filter,
typeCode: String(props.typeCode),
pageIndex: query.value.pageIndex,
pageSize: query.value.pageSize,
} as any,
});
const items = Array.isArray(data?.items) ? data!.items : [];
const mappedItems = items.map((item: any) => ({
label: `${item.displayText}`,
value: item.code,
...item,
}));
//
if (props.value && props.value.trim() !== '' && !mappedItems.find(item => item.value === props.value)) {
//
mappedItems.unshift({
label: `当前值: ${props.value}`,
value: props.value,
displayText: props.value,
code: props.value,
});
}
options.value = mappedItems;
total.value = (data as any)?.totalCount || 0;
} catch (err) {
console.error('获取标准物模型编码失败:', err);
} finally {
loading.value = false;
}
};
const changePage = (next: number) => {
if (next === 1) {
if (query.value.pageIndex >= maxPage.value) return;
query.value.pageIndex += 1;
} else {
if (query.value.pageIndex <= 1) return;
query.value.pageIndex -= 1;
}
fetchData();
};
const onSearch = useDebounceFn((kw: string) => {
query.value.pageIndex = 1;
query.value.filter = kw;
fetchData();
}, 400);
const onChange = (val?: string) => {
emit('update:value', val);
emit('change', val);
if (val) {
const found = options.value.find((o) => o.value === val) || null;
emit('item-change', found);
props.onResolve && props.onResolve(found);
} else {
emit('item-change', null);
props.onResolve && props.onResolve(null);
}
};
watch(
() => props.typeCode,
() => {
// reset when type changes, do not auto-fetch until user searches
query.value.pageIndex = 1;
query.value.filter = '';
options.value = [];
total.value = 0;
if (props.typeCode) {
fetchData();
}
},
{ immediate: true },
);
//
watch(
() => props.value,
(newValue) => {
if (newValue && newValue.trim() !== '' && props.typeCode && !options.value.find(item => item.value === newValue)) {
//
fetchData();
}
},
{ immediate: true },
);
</script>
<template>
<Select
:value="value"
:showSearch="true"
:filter-option="false"
:options="options"
:placeholder="placeholder"
:disabled="disabled"
:allowClear="allowClear"
:loading="loading"
@search="onSearch"
@change="onChange"
>
<template #dropdownRender="{ menuNode: menu }">
<v-nodes :vnodes="menu" />
<Divider style="margin: 4px 0" />
<div @mousedown="(e) => e.preventDefault()">
<Row type="flex" justify="space-around" align="middle">
<ChevronLeft
@click="changePage(0)"
:class="{ 'text-gray-400': query.pageIndex <= 1 }"
style="cursor: pointer; width: 16px; height: 16px;"
/>
<div>{{ `${query.pageIndex}/${maxPage}` }}</div>
<ChevronRight
@click="changePage(1)"
:class="{ 'text-gray-400': query.pageIndex >= maxPage }"
style="cursor: pointer; width: 16px; height: 16px;"
/>
</Row>
</div>
</template>
</Select>
</template>
<style lang="less" scoped>
.text-gray-400 {
color: #9ca3af;
cursor: not-allowed !important;
}
</style>

View File

@ -2,66 +2,91 @@
import type { VbenFormProps } from '#/adapter/form'; import type { VbenFormProps } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table'; import type { VxeGridProps } from '#/adapter/vxe-table';
import { h, nextTick, ref, watch } from 'vue'; import { h, nextTick, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { message as Message, Modal, Tag } from 'ant-design-vue'; import { message as Message, Tag } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
postIoTplatformThingModelInfoCopyAnotherThingModelAsync, postDeviceThingModelManagementCreateAsync,
postIoTplatformThingModelInfoCopyStandardThingModel, postDeviceThingModelManagementDeleteAsync,
postIoTplatformThingModelInfoCreateAsync, postDeviceThingModelManagementPageAsync,
postIoTplatformThingModelInfoDeleteAsync, postDeviceThingModelManagementUpdateAsync,
postIoTplatformThingModelInfoPageAsync,
postIoTplatformThingModelInfoUpdateAsync,
} from '#/api-client'; } from '#/api-client';
import { TableAction } from '#/components/table-action'; import { TableAction } from '#/components/table-action';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { import {
addThingModelFormSchema, addDeviceThingModelFormSchema,
copyThingModelFormSchema, editDeviceThingModelFormSchema,
editThingModelFormSchema,
querySchema, querySchema,
tableSchema, tableSchema,
} from './schema'; } from './schema';
defineOptions({ defineOptions({
name: 'ThingModelInfoModal', name: 'DeviceThingModelManagement',
}); });
const props = withDefaults(defineProps<Props>(), { const route = useRoute();
visible: false,
productId: '',
productName: '',
ioTPlatform: '2',
});
// emits //
const emit = defineEmits<{ const productId = ref<string>((route.query.productId as string) || '');
close: []; const ioTPlatform = ref<string>((route.query.ioTPlatform as string) || '');
'update:visible': [value: boolean];
}>();
// props
interface Props {
visible?: boolean;
productId?: string;
productName?: string;
ioTPlatform?: string;
}
const formOptions: VbenFormProps = { const formOptions: VbenFormProps = {
schema: querySchema.value, schema: querySchema.value,
initialValues: {
ioTPlatform: route.query.ioTPlatform
? String(route.query.ioTPlatform)
: undefined,
ioTPlatformProductId: route.query.productId
? String(route.query.productId)
: undefined,
},
submitOnChange: false,
handleValuesChange: async (values, changedFields) => {
//
if (changedFields.includes('ioTPlatform')) {
if (values.ioTPlatform) {
ioTPlatform.value = String(values.ioTPlatform);
if (gridApi?.formApi) {
await gridApi.formApi.setValues({
ioTPlatformProductId: undefined,
});
}
productId.value = '';
}
}
// productId
if (changedFields.includes('ioTPlatformProductId')) {
if (values.ioTPlatformProductId) {
productId.value = String(values.ioTPlatformProductId);
} else {
productId.value = '';
}
setTimeout(async () => {
if (gridApi && gridApi.reload) {
try {
await gridApi.reload();
} catch (error) {
console.error('重新加载设备端物模型列表时出错:', error);
}
}
}, 100);
}
},
}; };
const gridOptions: VxeGridProps<any> = { const gridOptions: VxeGridProps<any> = {
checkboxConfig: { checkboxConfig: {
highlight: true, highlight: true,
labelField: 'thingModelName', labelField: 'deviceModelName',
}, },
columns: tableSchema.value, columns: tableSchema.value,
height: 'auto', height: 'auto',
@ -76,20 +101,40 @@ const gridOptions: VxeGridProps<any> = {
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ page }, formValues) => { query: async ({ page }, formValues) => {
const { data } = await postIoTplatformThingModelInfoPageAsync({ // 使使
query: { const currentPlatform = formValues?.ioTPlatform || ioTPlatform.value;
input: { const currentProductId =
formValues?.ioTPlatformProductId || productId.value;
if (!currentPlatform || !currentProductId) {
return {
items: [],
totalCount: 0,
};
}
try {
const { data } = await postDeviceThingModelManagementPageAsync({
body: {
pageIndex: page.currentPage, pageIndex: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
ioTPlatform: Number.parseInt(props.ioTPlatform) as 1 | 2, ioTPlatform:
ioTPlatformProductId: props.productId, typeof currentPlatform === 'string'
...(formValues?.SearchKeyWords && { searchKeyWords: formValues.SearchKeyWords }), ? Number.parseInt(currentPlatform)
: currentPlatform,
ioTPlatformProductId: String(currentProductId),
searchKeyWords: formValues?.SearchKeyWords,
}, },
}, });
});
// return data || { items: [], totalCount: 0 };
hasData.value = data?.items && data.items.length > 0; } catch (error) {
return data; console.error('查询设备端物模型列表失败:', error);
return {
items: [],
totalCount: 0,
};
}
}, },
}, },
}, },
@ -99,9 +144,6 @@ const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });
const editRow: Record<string, any> = ref({}); const editRow: Record<string, any> = ref({});
//
const hasData = ref(true);
const [ThingModelModal, thingModelModalApi] = useVbenModal({ const [ThingModelModal, thingModelModalApi] = useVbenModal({
draggable: true, draggable: true,
footer: true, footer: true,
@ -124,20 +166,6 @@ const [ThingModelModal, thingModelModalApi] = useVbenModal({
}, },
}); });
const [CopyModal, copyModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopy,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyModalApi.close();
},
});
const [AddForm, addFormApi] = useVbenForm({ const [AddForm, addFormApi] = useVbenForm({
collapsed: false, collapsed: false,
commonConfig: { commonConfig: {
@ -147,7 +175,7 @@ const [AddForm, addFormApi] = useVbenForm({
}, },
}, },
layout: 'horizontal', layout: 'horizontal',
schema: addThingModelFormSchema.value, schema: addDeviceThingModelFormSchema.value,
showCollapseButton: false, showCollapseButton: false,
showDefaultActions: false, showDefaultActions: false,
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
@ -162,98 +190,49 @@ const [EditForm, editFormApi] = useVbenForm({
}, },
}, },
layout: 'horizontal', layout: 'horizontal',
schema: editThingModelFormSchema.value, schema: editDeviceThingModelFormSchema.value,
showCollapseButton: false, showCollapseButton: false,
showDefaultActions: false, showDefaultActions: false,
wrapperClass: 'grid-cols-2', wrapperClass: 'grid-cols-2',
}); });
const [CopyForm, copyFormApi] = useVbenForm({ // /
collapsed: false,
commonConfig: {
labelWidth: 110,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: copyThingModelFormSchema.value,
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
// props
watch(
() => [props.visible, props.productId, props.ioTPlatform],
async ([visible, productId, ioTPlatform]) => {
console.log('物模型模态框props变化:', { visible, productId, ioTPlatform });
console.log('所有props:', props);
if (visible && productId) {
//
setTimeout(async () => {
try {
//
const filterValues: any = {};
// ioTPlatformProductId API props.productId
// SearchKeyWords
console.log('设置筛选条件:', filterValues);
//
if (Object.keys(filterValues).length > 0) {
await gridApi.formApi.setValues(filterValues);
}
// 使 productId
await gridApi.reload();
} catch (error) {
console.error('设置筛选条件时出错:', error);
}
}, 100);
}
},
{ immediate: true },
);
//
async function submit() { async function submit() {
const isEdit = !!editRow.value.id; const isEdit = !!editRow.value.id;
const formApi = isEdit ? editFormApi : addFormApi; const formApi = isEdit ? editFormApi : addFormApi;
const api = isEdit const api = isEdit
? postIoTplatformThingModelInfoUpdateAsync ? postDeviceThingModelManagementUpdateAsync
: postIoTplatformThingModelInfoCreateAsync; : postDeviceThingModelManagementCreateAsync;
const { valid } = await formApi.validate(); const { valid } = await formApi.validate();
if (!valid) return; if (!valid) return;
const formValues = await formApi.getValues(); const formValues = await formApi.getValues();
// ioTPlatform / ioTPlatformProductId
const fetchParams: any = { const fetchParams: any = {
...formValues, ...formValues,
//
ioTPlatform: Number.parseInt(props.ioTPlatform) as 1 | 2,
ioTPlatformProductId: props.productId,
// ID
...(isEdit && { id: editRow.value.id }), ...(isEdit && { id: editRow.value.id }),
}; };
try { try {
const resp = await api({ query: { input: fetchParams } }); const resp = await api({ body: fetchParams });
if (resp.data) { if (resp.data) {
Message.success( Message.success(
editRow.value.id ? $t('common.editSuccess') : $t('common.addSuccess'), isEdit ? $t('common.editSuccess') : $t('common.addSuccess'),
); );
thingModelModalApi.close(); thingModelModalApi.close();
editRow.value = {}; editRow.value = {};
gridApi.reload(); gridApi.reload();
} else { } else {
Message.error( Message.error(
editRow.value.id ? $t('common.editFail') : $t('common.addFail'), isEdit ? $t('common.editFail') : $t('common.addFail'),
); );
} }
} catch (error) { } catch (error) {
console.error('提交失败:', error); console.error('提交设备端物模型失败:', error);
Message.error( Message.error(
editRow.value.id ? $t('common.editFail') : $t('common.addFail'), isEdit ? $t('common.editFail') : $t('common.addFail'),
); );
} }
} }
@ -268,63 +247,11 @@ const openAddModal = async () => {
thingModelModalApi.open(); thingModelModalApi.open();
}; };
// //
const openCopyAnotherThingModelModal = async () => {
console.log('打开复制模态框当前props:', {
productId: props.productId,
productName: props.productName,
ioTPlatform: props.ioTPlatform,
});
copyModalApi.open();
};
//
async function submitCopy() {
const { valid } = await copyFormApi.validate();
if (!valid) return;
const formValues = await copyFormApi.getValues();
console.log('复制提交参数:', {
formValues,
props: {
productId: props.productId,
productName: props.productName,
ioTPlatform: props.ioTPlatform,
},
});
try {
const resp = await postIoTplatformThingModelInfoCopyAnotherThingModelAsync({
query: {
input: {
ioTPlatform: Number.parseInt(props.ioTPlatform) as 1 | 2,
ioTPlatformProductId: props.productId,
sourceProductId: formValues.ioTPlatformProductId,
},
},
});
if (resp.data) {
Message.success('复制模型成功');
copyModalApi.close();
gridApi.reload();
} else {
Message.error('复制模型失败');
}
} catch (error) {
console.error('复制模型失败:', error);
Message.error('复制模型失败');
}
}
//
async function onDel(record: any) { async function onDel(record: any) {
try { try {
const resp = await postIoTplatformThingModelInfoDeleteAsync({ const resp = await postDeviceThingModelManagementDeleteAsync({
query: { body: { id: record.id },
input: { id: record.id },
},
}); });
if (resp.data) { if (resp.data) {
Message.success($t('common.deleteSuccess')); Message.success($t('common.deleteSuccess'));
@ -333,49 +260,18 @@ async function onDel(record: any) {
Message.error($t('common.deleteFail')); Message.error($t('common.deleteFail'));
} }
} catch (error) { } catch (error) {
console.error('删除失败:', error); console.error('删除设备端物模型失败:', error);
Message.error($t('common.deleteFail')); Message.error($t('common.deleteFail'));
} }
} }
//
async function copyStandardThingModel() {
try {
const resp = await postIoTplatformThingModelInfoCopyStandardThingModel({
query: {
input: {
ioTPlatform: Number.parseInt(props.ioTPlatform) as 1 | 2,
ioTPlatformProductId: props.productId,
},
},
});
if (resp.data) {
Message.success('复制标准模型成功');
gridApi.reload();
} else {
Message.error('复制标准模型失败');
}
} catch (error) {
console.error('复制标准模型失败:', error);
Message.error('复制标准模型失败');
}
}
//
function closeModal() {
emit('update:visible', false);
emit('close');
}
</script> </script>
<template> <template>
<Modal :open="visible" :title="`${props.ioTPlatform === 1 ? 'CTWing' : 'OneNET'}物模型管理 - ${productName || '产品'}`" <Page auto-content-height>
width="90%" :footer="null" @cancel="closeModal" @ok="closeModal" <Grid>
:body-style="{ height: '70vh', overflow: 'hidden' }"> <template #toolbar-actions>
<div style="display: flex; flex-direction: column; height: 100%"> <TableAction
<Grid style="flex: 1; overflow: hidden"> :actions="[
<template #toolbar-actions>
<TableAction :actions="[
{ {
label: $t('common.add'), label: $t('common.add'),
type: 'primary', type: 'primary',
@ -383,35 +279,26 @@ function closeModal() {
onClick: openAddModal.bind(null), onClick: openAddModal.bind(null),
auth: ['AbpIdentity.Users.Create'], auth: ['AbpIdentity.Users.Create'],
}, },
{ ]"
label: $t('abp.thingModelInfos.copyStandardThingModel'), />
type: 'default', </template>
icon: 'ant-design:copy-outlined',
onClick: copyStandardThingModel,
auth: ['AbpIdentity.Users.Create'],
},
{
label: $t('abp.thingModelInfos.copyAnotherThingModelModal'),
type: 'default',
icon: 'ant-design:copy-outlined',
onClick: openCopyAnotherThingModelModal,
auth: ['AbpIdentity.Users.Create'],
ifShow: !hasData,
},
]" />
</template>
<template #isValueNeedConvert="{ row }"> <!-- 解析启用列 -->
<component :is="h( <template #functionAnalysisFlag="{ row }">
Tag, <component
{ color: row.isValueNeedConvert ? 'blue' : 'default' }, :is="
() => (row.isValueNeedConvert ? '是' : '否'), h(
) Tag,
" /> { color: row.functionAnalysisFlag ? 'green' : 'red' },
</template> () => (row.functionAnalysisFlag ? '是' : '否'),
)
"
/>
</template>
<template #action="{ row }"> <template #action="{ row }">
<TableAction :actions="[ <TableAction
:actions="[
{ {
label: $t('common.edit'), label: $t('common.edit'),
type: 'link', type: 'link',
@ -430,17 +317,18 @@ function closeModal() {
confirm: onDel.bind(null, row), confirm: onDel.bind(null, row),
}, },
}, },
]" /> ]"
</template> />
</Grid> </template>
</div> </Grid>
<ThingModelModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
<ThingModelModal
:title="editRow.id ? $t('common.edit') : $t('common.add')"
class="w-[800px]"
>
<component :is="editRow.id ? EditForm : AddForm" /> <component :is="editRow.id ? EditForm : AddForm" />
</ThingModelModal> </ThingModelModal>
</Page>
<!-- 复制已有模型模态框 -->
<CopyModal title="复制已有模型" class="w-[600px]">
<CopyForm />
</CopyModal>
</Modal>
</template> </template>

View File

@ -6,11 +6,95 @@ import dayjs from 'dayjs';
import { import {
getCommonGetSelectList, getCommonGetSelectList,
postOneNetProductListAsync, postAggregationIoTplatformGetIoTplatformProductInfoAsync,
} from '#/api-client'; } from '#/api-client';
import { $t } from '#/locales'; import { $t } from '#/locales';
// 查询表单配置
export const querySchema = computed(() => [ export const querySchema = computed(() => [
{
component: 'ApiSelect',
fieldName: 'ioTPlatform',
label: $t('abp.deviceInfos.ioTPlatform'),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'IoTPlatformTypeEnum',
},
},
labelField: 'value',
valueField: 'key',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder:
$t('common.pleaseSelect') + $t('abp.deviceInfos.ioTPlatform'),
afterFetch: (res: any) => {
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
return [];
},
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('common.BelongingProductName'),
dependencies: {
show(values: any) {
return !!values.ioTPlatform;
},
triggerFields: ['ioTPlatform'],
},
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
return {
api: platform
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
// 聚合服务要求 JSON Body 传参
params: {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
},
},
labelField: 'productName',
valueField: 'ioTPlatformProductId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder:
$t('common.pleaseSelect') + $t('common.BelongingProductName'),
afterFetch: (res: any) => {
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
if (res && res.data && Array.isArray(res.data.items)) {
return res.data.items;
}
return [];
},
};
},
},
{ {
component: 'Input', component: 'Input',
fieldName: 'SearchKeyWords', fieldName: 'SearchKeyWords',
@ -18,64 +102,55 @@ export const querySchema = computed(() => [
}, },
]); ]);
// 列表列配置
export const tableSchema = computed(() => [ export const tableSchema = computed(() => [
{ {
field: 'filedType', field: 'deviceModelName',
title: $t('abp.thingModelInfos.FiledType'), title: '设备端物模型名称',
minWidth: 180,
showOverflow: 'tooltip',
},
{
field: 'ioTPlatform',
title: $t('abp.deviceInfos.ioTPlatform'),
minWidth: 120, minWidth: 120,
showOverflow: 'tooltip', showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => { formatter: ({ cellValue }: { cellValue: any }) => {
const typeMap: Record<string, string> = { const map: Record<string | number, string> = {
Property: '属性', 1: 'CTWing',
Service: '服务', 2: 'OneNET',
Event: '事件',
}; };
return typeMap[cellValue] || cellValue; return map[cellValue] || cellValue || '-';
}, },
}, },
{ {
field: 'ioTPlatformRawFieldName', field: 'ioTPlatformProductId',
title: $t('abp.thingModelInfos.IoTPlatformRawFieldName'), title: '平台产品ID',
minWidth: 150, minWidth: 160,
showOverflow: 'tooltip', showOverflow: 'tooltip',
}, },
{ {
field: 'standardFieldName', field: 'scriptName',
title: $t('abp.thingModelInfos.StandardFieldName'), title: '脚本函数名称',
minWidth: 150, minWidth: 160,
showOverflow: 'tooltip', showOverflow: 'tooltip',
}, },
{ {
field: 'standardFieldDisplayName', field: 'parsingSequence',
title: $t('abp.thingModelInfos.StandardFieldDisplayName'), title: '解析顺序',
minWidth: 150,
showOverflow: 'tooltip',
},
{
field: 'standardFieldValueType',
title: $t('abp.thingModelInfos.StandardFieldValueType'),
minWidth: 120, minWidth: 120,
showOverflow: 'tooltip', showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => { formatter: ({ cellValue }: { cellValue: any }) => {
const typeMap: Record<string, string> = { if (cellValue === 1) return '正序';
String: '字符串', if (cellValue === 2) return '高低位反转';
Int32: '整数', return cellValue ?? '-';
Int64: '长整数',
Float: '浮点数',
Double: '双精度',
Boolean: '布尔值',
DateTime: '日期时间',
Object: 'JSON对象',
Array: '数组',
};
return typeMap[cellValue] || cellValue;
}, },
}, },
{ {
field: 'isValueNeedConvert', field: 'functionAnalysisFlag',
title: $t('abp.thingModelInfos.IsValueNeedConvert'), title: '解析启用',
minWidth: 120, minWidth: 100,
slots: { default: 'isValueNeedConvert' }, slots: { default: 'functionAnalysisFlag' },
}, },
{ {
field: 'creationTime', field: 'creationTime',
@ -94,21 +169,29 @@ export const tableSchema = computed(() => [
}, },
]); ]);
// 添加物模型表单schema // 新增设备端物模型表单
export const addThingModelFormSchema = computed(() => [ export const addDeviceThingModelFormSchema = computed(() => [
{
component: 'Input',
fieldName: 'deviceModelName',
label: '设备端物模型名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
placeholder: $t('common.pleaseInput') + '设备端物模型名称',
},
},
{ {
component: 'ApiSelect', component: 'ApiSelect',
fieldName: 'filedType', fieldName: 'ioTPlatform',
label: $t('abp.thingModelInfos.FiledType'), label: $t('abp.deviceInfos.ioTPlatform'),
rules: z.preprocess( rules: z
(v) => (v == null ? '' : v), .preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
z.string().min(1, $t('common.required')),
),
componentProps: { componentProps: {
api: getCommonGetSelectList, api: getCommonGetSelectList,
params: { params: {
query: { query: {
typeName: 'DataDictionaryTypeConst', typeName: 'IoTPlatformTypeEnum',
}, },
}, },
labelField: 'value', labelField: 'value',
@ -117,9 +200,8 @@ export const addThingModelFormSchema = computed(() => [
immediate: true, immediate: true,
allowClear: true, allowClear: true,
placeholder: placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.FiledType'), $t('common.pleaseSelect') + $t('abp.deviceInfos.ioTPlatform'),
afterFetch: (res: any) => { afterFetch: (res: any) => {
// 确保返回的是数组格式
if (Array.isArray(res)) { if (Array.isArray(res)) {
return res; return res;
} }
@ -133,270 +215,107 @@ export const addThingModelFormSchema = computed(() => [
}, },
}, },
}, },
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
// 传入联动的类型编码(运行时具体值)
typeCode: formValues?.filedType ?? null,
onResolve: (item: any | null) => {
// 选择后自动回填:名称=displayText值类型=extendedAttribute(转大写)
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldName = item?.code ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
label: $t('abp.thingModelInfos.StandardFieldValueType'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
labelField: 'value',
valueField: 'secondValue',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder:
$t('common.pleaseSelect') +
$t('abp.thingModelInfos.StandardFieldValueType'),
afterFetch: (res: any) => {
// 确保返回的是数组格式
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
return [];
},
},
},
{
component: 'Switch',
fieldName: 'isValueNeedConvert',
label: $t('abp.thingModelInfos.IsValueNeedConvert'),
defaultValue: false,
componentProps: {
style: { width: 'auto' },
},
},
]);
// 编辑物模型表单schema
export const editThingModelFormSchema = computed(() => [
{
component: 'ApiSelect',
fieldName: 'filedType',
label: $t('abp.thingModelInfos.FiledType'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'DataDictionaryTypeConst',
},
},
labelField: 'value',
valueField: 'key',
optionsPropName: 'options',
immediate: true,
disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.FiledType'),
afterFetch: (res: any) => {
// 确保返回的是数组格式
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
return [];
},
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
// disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
typeCode: formValues?.filedType ?? null,
disabled: true, // 编辑时禁用
onResolve: (item: any | null) => {
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.string().min(1, $t('common.required')),
componentProps: {
disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
label: $t('abp.thingModelInfos.StandardFieldValueType'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
optionsPropName: 'options',
immediate: true,
allowClear: true,
disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseSelect') +
$t('abp.thingModelInfos.StandardFieldValueType'),
afterFetch: (res: any) => {
let items = [];
if (Array.isArray(res)) {
items = res;
} else if (res && Array.isArray(res.items)) {
items = res.items;
} else if (res && Array.isArray(res.data)) {
items = res.data;
}
// 转换选项值以匹配列表中的小写值
return items.map((item: any) => ({
...item,
// 使用secondValue的小写版本作为value保持label为原始value
value: item.secondValue || item.value,
label: item.value, // 显示文本
}));
},
},
},
{
component: 'Switch',
fieldName: 'isValueNeedConvert',
label: $t('abp.thingModelInfos.IsValueNeedConvert'),
componentProps: {
style: { width: 'auto' },
},
},
]);
// 复制已有模型表单schema
export const copyThingModelFormSchema = computed(() => [
{ {
component: 'ApiSelect', component: 'ApiSelect',
fieldName: 'ioTPlatformProductId', fieldName: 'ioTPlatformProductId',
label: '选择要复制的产品', label: $t('common.BelongingProductName'),
rules: z.preprocess( rules: z
(v) => (v == null ? '' : v), .preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
z.string().min(1, '请选择要复制的产品'), componentProps: (formValues: any) => {
), const platform = formValues?.ioTPlatform;
return {
api: platform
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
},
},
labelField: 'productName',
valueField: 'ioTPlatformProductId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder:
$t('common.pleaseSelect') + $t('common.BelongingProductName'),
afterFetch: (res: any) => {
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
if (res && res.data && Array.isArray(res.data.items)) {
return res.data.items;
}
return [];
},
};
},
},
{
component: 'Input',
fieldName: 'scriptName',
label: '脚本函数名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: { componentProps: {
api: postOneNetProductListAsync, placeholder: $t('common.pleaseInput') + '脚本函数名称',
},
},
{
component: 'Textarea',
fieldName: 'functionScript',
label: '脚本函数体',
componentProps: {
rows: 6,
placeholder: '请输入脚本函数体用于解析MODBUS设备数据',
},
},
]);
// 编辑设备端物模型表单
export const editDeviceThingModelFormSchema = computed(() => [
{
component: 'Input',
fieldName: 'deviceModelName',
label: '设备端物模型名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
placeholder: $t('common.pleaseInput') + '设备端物模型名称',
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatform',
label: $t('abp.deviceInfos.ioTPlatform'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
api: getCommonGetSelectList,
params: { params: {
query: { query: {
pageIndex: 1, typeName: 'IoTPlatformTypeEnum',
pageSize: 1000,
}, },
}, },
labelField: 'productName', labelField: 'value',
valueField: 'ioTPlatformProductId', valueField: 'key',
optionsPropName: 'options', optionsPropName: 'options',
immediate: true, immediate: true,
allowClear: true, allowClear: true,
placeholder: '请选择产品', disabled: true, // 编辑时不允许修改平台
placeholder:
$t('common.pleaseSelect') + $t('abp.deviceInfos.ioTPlatform'),
afterFetch: (res: any) => { afterFetch: (res: any) => {
// 确保返回的是数组格式
if (Array.isArray(res)) { if (Array.isArray(res)) {
return res; return res;
} }
@ -406,11 +325,76 @@ export const copyThingModelFormSchema = computed(() => [
if (res && Array.isArray(res.data)) { if (res && Array.isArray(res.data)) {
return res.data; return res.data;
} }
if (res && Array.isArray(res.data.items)) {
return res.data.items;
}
return []; return [];
}, },
}, },
}, },
{
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('common.BelongingProductName'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
return {
api: platform
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
},
},
labelField: 'productName',
valueField: 'ioTPlatformProductId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
disabled: true, // 编辑时不允许修改平台产品
placeholder:
$t('common.pleaseSelect') + $t('common.BelongingProductName'),
afterFetch: (res: any) => {
if (Array.isArray(res)) {
return res;
}
if (res && Array.isArray(res.items)) {
return res.items;
}
if (res && Array.isArray(res.data)) {
return res.data;
}
if (res && res.data && Array.isArray(res.data.items)) {
return res.data.items;
}
return [];
},
};
},
},
{
component: 'Input',
fieldName: 'scriptName',
label: '脚本函数名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
placeholder: $t('common.pleaseInput') + '脚本函数名称',
},
},
{
component: 'Textarea',
fieldName: 'functionScript',
label: '脚本函数体',
componentProps: {
rows: 6,
placeholder: '请输入脚本函数体用于解析MODBUS设备数据',
},
},
]); ]);