846 lines
24 KiB
Vue
Raw Normal View History

2025-12-18 22:05:55 +08:00
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue';
2025-12-18 22:05:55 +08:00
import { useVbenModal, z } from '@vben/common-ui';
2025-12-18 22:05:55 +08:00
import { message as Message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
2025-12-18 22:05:55 +08:00
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
getCommonGetSelectList,
postAggregationIoTplatformGetIoTplatformProductInfoAsync,
postDeviceThingModelManagementCopyAnotherDeviceThingModelAsync,
postDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsync,
postDeviceThingModelManagementPageAsync,
postDeviceThingModelManagementPropertyCreateAsync,
postDeviceThingModelManagementPropertyDeleteAsync,
postDeviceThingModelManagementPropertyFindByIdAsync,
postDeviceThingModelManagementPropertyPageAsync,
postDeviceThingModelManagementPropertyUpdateAsync,
} from '#/api-client';
import { TableAction } from '#/components/table-action';
2025-12-18 22:05:55 +08:00
import { $t } from '#/locales';
defineOptions({
name: 'DeviceThingModelPropertyModal',
});
const deviceThingModelId = ref<string>('');
const deviceModelName = ref<string>('');
const ioTPlatform = ref<number | string>('');
const ioTPlatformProductId = ref<string>('');
const editRow: Record<string, any> = ref({});
2025-12-18 22:05:55 +08:00
// 属性列表
2025-12-18 22:05:55 +08:00
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: [],
showDefaultActions: false,
},
gridOptions: {
columns: [
{
field: 'filedType',
title: '物模型类型',
minWidth: 160,
showOverflow: 'tooltip',
},
2025-12-18 22:05:55 +08:00
{
field: 'standardFieldDisplayName',
title: '标准属性名称',
2025-12-18 22:05:55 +08:00
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'standardFieldName',
title: '标准属性标识符',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'standardFieldValueType',
title: '标准属性值类型',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'ioTPlatformRawFieldName',
title: '平台属性标识符',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'ioTPlatformRawFieldDataType',
title: '平台属性值类型',
minWidth: 160,
2025-12-18 22:05:55 +08:00
showOverflow: 'tooltip',
},
{
field: 'isValueNeedConvert',
title: '值类型是否转换',
2025-12-18 22:05:55 +08:00
minWidth: 120,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
return cellValue ? '是' : '否';
},
2025-12-18 22:05:55 +08:00
},
{
field: 'parsingSequence',
title: '解析方式',
minWidth: 140,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
if (cellValue === 1) return '正序';
if (cellValue === 2) return '高低位反转';
return cellValue ?? '-';
},
},
{
field: 'nativeSkipNumber',
title: '正序跳过数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'nativeTakeNumber',
title: '正序获取数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'reversalSkipNumber',
title: '反转跳过数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'reversalTakeNumber',
title: '反转获取数量',
minWidth: 140,
showOverflow: 'tooltip',
},
2025-12-18 22:05:55 +08:00
{
field: 'action',
title: $t('common.action'),
width: 200,
fixed: 'right',
slots: { default: 'action' },
},
],
height: 400,
pagerConfig: {},
toolbarConfig: {
custom: true,
},
2025-12-18 22:05:55 +08:00
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!deviceThingModelId.value) {
return { items: [], totalCount: 0 };
}
const { data } =
await postDeviceThingModelManagementPropertyPageAsync({
body: {
pageIndex: page.currentPage,
pageSize: page.pageSize,
deviceThingModelId: deviceThingModelId.value,
},
});
2025-12-18 22:05:55 +08:00
return data || { items: [], totalCount: 0 };
},
},
},
},
});
// 属性新增/编辑弹窗
const [PropertyFormModal, propertyFormModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitProperty,
onBeforeClose: () => {
return true;
},
onOpenChange: (isOpen: boolean) => {
if (isOpen && editRow.value.id) {
// 编辑模式下,加载属性详情
nextTick(async () => {
try {
const { data } =
await postDeviceThingModelManagementPropertyFindByIdAsync({
body: { id: editRow.value.id },
});
if (data) {
const values: any = { ...data };
// 处理解析方式字段(从后端获取的可能是数字,需要转换成字符串)
if (
values.parsingSequence !== undefined &&
values.parsingSequence !== null
) {
// 如果后端返回的是数字,转换为字符串;如果已经是字符串,保持原样
values.parsingSequence = String(values.parsingSequence);
}
propertyFormApi.setValues(values);
}
} catch (error) {
console.error('加载属性详情失败:', error);
}
});
} else if (isOpen) {
// 新增模式下,清空表单
propertyFormApi.resetForm();
}
},
onCancel: () => {
propertyFormModalApi.close();
},
});
// 快速复制平台端物模型属性弹窗
const [CopyPropertyModal, copyPropertyModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopyProperty,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyPropertyModalApi.close();
},
});
// 快速复制设备端物模型弹窗
const [CopyDeviceThingModelModal, copyDeviceThingModelModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopyDeviceThingModel,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyDeviceThingModelModalApi.close();
},
});
// 属性表单 schema
const propertyFormSchema = computed(() => {
const isEdit = !!editRow.value.id;
return [
{
component: 'Input',
fieldName: 'standardFieldDisplayName',
label: '标准属性名称',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}标准属性名称`,
disabled: isEdit, // 编辑模式下禁用
},
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: '标准属性标识符',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}标准属性标识符`,
disabled: isEdit, // 编辑模式下禁用
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
label: '标准属性值类型',
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,
disabled: isEdit, // 编辑模式下禁用
placeholder: `${$t('common.pleaseSelect')}标准属性值类型`,
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: '平台属性标识符',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}平台属性标识符`,
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformRawFieldDataType',
label: '平台属性值类型',
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')}平台属性值类型`,
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: '值类型是否转换',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
style: { width: 'auto' }, // 优化宽度显示
},
},
{
component: 'ApiSelect',
fieldName: 'parsingSequence',
label: '解析方式',
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'ParsingSequenceTypeEnum',
},
},
labelField: 'value',
valueField: 'key',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder: `${$t('common.pleaseSelect')}解析方式`,
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;
}
// 将 key 转换为字符串,因为表单需要字符串类型
return items.map((item: any) => ({
...item,
key: String(item.key || item.value),
}));
},
},
},
{
component: 'InputNumber',
fieldName: 'nativeSkipNumber',
label: '正序跳过数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}正序跳过数量`,
},
},
{
component: 'InputNumber',
fieldName: 'nativeTakeNumber',
label: '正序获取数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}正序获取数量`,
},
},
{
component: 'InputNumber',
fieldName: 'reversalSkipNumber',
label: '反转跳过数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}反转跳过数量`,
},
},
{
component: 'InputNumber',
fieldName: 'reversalTakeNumber',
label: '反转获取数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}反转获取数量`,
},
},
];
});
// 属性表单
const [PropertyForm, propertyFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: propertyFormSchema.value,
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
// 监听 editRow 变化,动态更新 schema
watch(
() => editRow.value.id,
() => {
if (propertyFormApi && propertyFormApi.updateSchema) {
propertyFormApi.updateSchema(propertyFormSchema.value);
}
},
);
// 快速复制表单(选择平台和产品)
const [CopyPropertyForm, copyPropertyFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
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: {
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'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
dependencies: {
show(values: any) {
return !!values.ioTPlatform;
},
triggerFields: ['ioTPlatform'],
},
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 [];
},
};
},
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
// 快速复制设备端物模型表单
const [CopyDeviceThingModelForm, copyDeviceThingModelFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
component: 'ApiSelect',
fieldName: 'sourceDeviceThingModelId',
label: '选择设备端物模型',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: postDeviceThingModelManagementPageAsync,
params: {
body: {
pageIndex: 1,
pageSize: 1000,
isPage: false, // 不分页,获取全部数据
},
},
labelField: 'deviceModelName',
valueField: 'id',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder: '请选择要复制的设备端物模型',
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 [];
},
},
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
// 打开新增属性弹窗
function openAddPropertyModal() {
editRow.value = {};
propertyFormModalApi.open();
}
// 打开编辑属性弹窗
function openEditPropertyModal(record: any) {
editRow.value = record;
propertyFormModalApi.open();
}
// 打开快速复制平台端物模型弹窗
function openCopyPropertyModal() {
copyPropertyFormApi.resetForm();
copyPropertyModalApi.open();
}
// 打开快速复制设备端物模型弹窗
function openCopyDeviceThingModelModal() {
copyDeviceThingModelFormApi.resetForm();
copyDeviceThingModelModalApi.open();
}
// 提交属性(新增/编辑)
async function submitProperty() {
const isEdit = !!editRow.value.id;
const { valid } = await propertyFormApi.validate();
if (!valid) return;
const formValues = await propertyFormApi.getValues();
const fetchParams: any = {
...formValues,
deviceThingModelId: deviceThingModelId.value,
...(formValues.parsingSequence && {
// 解析方式从字符串转换为数字
parsingSequence: Number.parseInt(String(formValues.parsingSequence)),
}),
...(isEdit && { id: editRow.value.id }),
};
try {
const api = isEdit
? postDeviceThingModelManagementPropertyUpdateAsync
: postDeviceThingModelManagementPropertyCreateAsync;
const resp = await api({ body: fetchParams });
if (resp.data) {
Message.success(
isEdit ? $t('common.editSuccess') : $t('common.addSuccess'),
);
propertyFormModalApi.close();
editRow.value = {};
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
} catch (error) {
console.error('提交属性失败:', error);
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
}
// 提交快速复制设备端物模型
async function submitCopyDeviceThingModel() {
const { valid } = await copyDeviceThingModelFormApi.validate();
if (!valid) return;
const formValues = await copyDeviceThingModelFormApi.getValues();
if (!formValues.sourceDeviceThingModelId) {
Message.warning('请选择要复制的设备端物模型');
return;
}
try {
const resp =
await postDeviceThingModelManagementCopyAnotherDeviceThingModelAsync({
body: {
ioTPlatform: Number.parseInt(String(ioTPlatform.value)) as 1 | 2,
deviceThingModelId: deviceThingModelId.value,
sourceProductId: formValues.sourceDeviceThingModelId,
},
});
if (resp.data) {
Message.success('快速复制设备端物模型属性成功');
copyDeviceThingModelModalApi.close();
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error('快速复制设备端物模型属性失败');
}
} catch (error) {
console.error('快速复制设备端物模型属性失败:', error);
Message.error('快速复制设备端物模型属性失败');
}
}
// 提交快速复制平台端物模型
async function submitCopyProperty() {
const { valid } = await copyPropertyFormApi.validate();
if (!valid) return;
const formValues = await copyPropertyFormApi.getValues();
if (!formValues.ioTPlatform || !formValues.ioTPlatformProductId) {
Message.warning('请选择平台和产品');
return;
}
try {
const resp =
await postDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsync(
{
body: {
ioTPlatform: Number.parseInt(String(formValues.ioTPlatform)) as
| 1
| 2,
ioTPlatformProductId: String(formValues.ioTPlatformProductId),
deviceThingModelId: deviceThingModelId.value,
},
},
);
if (resp.data) {
Message.success('快速复制平台端物模型属性成功');
copyPropertyModalApi.close();
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error('快速复制平台端物模型属性失败');
}
} catch (error) {
console.error('快速复制平台端物模型属性失败:', error);
Message.error('快速复制平台端物模型属性失败');
}
}
// 删除属性
async function onDeleteProperty(record: any) {
try {
const resp = await postDeviceThingModelManagementPropertyDeleteAsync({
body: { id: record.id },
});
if (resp.data) {
Message.success($t('common.deleteSuccess'));
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error($t('common.deleteFail'));
}
} catch (error) {
console.error('删除属性失败:', error);
Message.error($t('common.deleteFail'));
}
}
2025-12-18 22:05:55 +08:00
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
const data = modalApi.getData<Record<string, any>>();
deviceThingModelId.value = data?.deviceThingModelId || '';
deviceModelName.value = data?.deviceModelName || '';
ioTPlatform.value = data?.ioTPlatform || '';
ioTPlatformProductId.value = data?.ioTPlatformProductId || '';
// 使用 nextTick 确保 Grid 组件已完全初始化
nextTick(() => {
if (gridApi && gridApi.reload) {
gridApi.reload();
}
});
2025-12-18 22:05:55 +08:00
}
},
});
</script>
<template>
<Modal :title="`属性管理 - ${deviceModelName || ''}`" class="w-[1200px]">
2025-12-18 22:05:55 +08:00
<Grid>
<template #toolbar-actions>
<TableAction :actions="[
{
label: $t('common.add'),
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: openAddPropertyModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: '快速复制平台端物模型',
type: 'default',
icon: 'ant-design:copy-outlined',
onClick: openCopyPropertyModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: '快速复制设备端物模型',
type: 'default',
icon: 'ant-design:copy-outlined',
onClick: openCopyDeviceThingModelModal,
auth: ['AbpIdentity.Users.Create'],
},
]" />
</template>
<template #action="{ row }">
<TableAction :actions="[
{
label: $t('common.edit'),
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openEditPropertyModal.bind(null, row),
},
{
label: $t('common.delete'),
icon: 'ant-design:delete-outlined',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Delete'],
popConfirm: {
title: $t('common.askConfirmDelete'),
confirm: onDeleteProperty.bind(null, row),
},
},
]" />
</template>
2025-12-18 22:05:55 +08:00
</Grid>
<!-- 属性新增/编辑弹窗 -->
<PropertyFormModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[900px]">
<PropertyForm />
</PropertyFormModal>
<!-- 快速复制平台端物模型属性弹窗 -->
<CopyPropertyModal title="快速复制平台端物模型属性" class="w-[600px]">
<CopyPropertyForm />
</CopyPropertyModal>
<!-- 快速复制设备端物模型属性弹窗 -->
<CopyDeviceThingModelModal
title="快速复制设备端物模型属性"
class="w-[600px]"
>
<CopyDeviceThingModelForm />
</CopyDeviceThingModelModal>
2025-12-18 22:05:55 +08:00
</Modal>
</template>