批量设备升级弹窗实现

This commit is contained in:
ChenYi 2026-01-04 17:29:53 +08:00
parent 292277b635
commit 3a2301efce
5 changed files with 708 additions and 6 deletions

View File

@ -464,6 +464,26 @@ export const BatchCreateDeviceAggregationInputSchema = {
description: '批量创建设备信息'
} as const;
export const BatchUpdateStatusInputSchema = {
required: ['ids', 'upgradeStatus'],
type: 'object',
properties: {
ids: {
type: 'array',
items: {
type: 'string',
format: 'uuid'
},
description: '设备升级记录Id集合'
},
upgradeStatus: {
'$ref': '#/components/schemas/DeviceUpgradeStatusTypeEnum'
}
},
additionalProperties: false,
description: '批量更新设备升级记录状态信息输入'
} as const;
export const BindingDeviceThingModelInputSchema = {
type: 'object',
properties: {
@ -2013,6 +2033,40 @@ export const DeleteTextTemplateInputSchema = {
description: '删除模板'
} as const;
export const DeviceBatchUpgradeForApiInputSchema = {
required: ['addressList', 'ioTPlatform', 'ioTPlatformProductId', 'nowFirmwareVersionDataId'],
type: 'object',
properties: {
addressList: {
type: 'array',
items: {
type: 'string'
},
description: '设备地址列表'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台中对应的产品Id'
},
nowFirmwareVersionDataId: {
type: 'string',
description: '固件版本信息',
format: 'uuid'
},
upgradeDescription: {
type: 'string',
description: '升级描述',
nullable: true
}
},
additionalProperties: false,
description: '设备批量升级信息'
} as const;
export const DeviceCommandForApiInputSchema = {
required: ['commandContent', 'id'],
type: 'object',
@ -11190,6 +11244,23 @@ export const UpdateSettingInputSchema = {
additionalProperties: false
} as const;
export const UpdateStatusInputSchema = {
required: ['id', 'upgradeStatus'],
type: 'object',
properties: {
id: {
type: 'string',
description: '设备升级记录Id',
format: 'uuid'
},
upgradeStatus: {
'$ref': '#/components/schemas/DeviceUpgradeStatusTypeEnum'
}
},
additionalProperties: false,
description: '更新升级状态'
} as const;
export const UpdateTenantInputSchema = {
type: 'object',
properties: {

File diff suppressed because one or more lines are too long

View File

@ -172,6 +172,17 @@ export type BatchCreateDeviceAggregationInput = {
deviceSourceTypeEnum?: DeviceSourceTypeEnum;
};
/**
*
*/
export type BatchUpdateStatusInput = {
/**
* Id集合
*/
ids: Array<(string)>;
upgradeStatus: DeviceUpgradeStatusTypeEnum;
};
/**
*
*/
@ -1106,6 +1117,29 @@ export type DeleteTextTemplateInput = {
id?: string;
};
/**
*
*/
export type DeviceBatchUpgradeForApiInput = {
/**
*
*/
addressList: Array<(string)>;
ioTPlatform: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId: string;
/**
*
*/
nowFirmwareVersionDataId: string;
/**
*
*/
upgradeDescription?: (string) | null;
};
/**
*
*/
@ -6086,6 +6120,17 @@ export type UpdateSettingInput = {
} | null;
};
/**
*
*/
export type UpdateStatusInput = {
/**
* Id
*/
id: string;
upgradeStatus: DeviceUpgradeStatusTypeEnum;
};
export type UpdateTenantInput = {
id?: string;
name?: (string) | null;
@ -6537,6 +6582,16 @@ export type PostAggregationDeviceDeviceUpgradeForApiAsyncResponse = (boolean);
export type PostAggregationDeviceDeviceUpgradeForApiAsyncError = unknown;
export type PostAggregationDeviceDeviceBatchUpgradeForApiAsyncData = {
query?: {
input?: DeviceBatchUpgradeForApiInput;
};
};
export type PostAggregationDeviceDeviceBatchUpgradeForApiAsyncResponse = (boolean);
export type PostAggregationDeviceDeviceBatchUpgradeForApiAsyncError = unknown;
export type PostAggregationDeviceDownloadFirmwareData = {
query?: {
input?: IdInput;
@ -6907,6 +6962,26 @@ export type PostDeviceThingModelManagementCacheAllDeviceThingModelToRedisAsyncRe
export type PostDeviceThingModelManagementCacheAllDeviceThingModelToRedisAsyncError = unknown;
export type PostUpgradeRecordUpdateStatusAsyncData = {
query?: {
input?: UpdateStatusInput;
};
};
export type PostUpgradeRecordUpdateStatusAsyncResponse = (DeviceUpgradeRecordDto);
export type PostUpgradeRecordUpdateStatusAsyncError = unknown;
export type PostUpgradeRecordBatchUpdateStatusAsyncData = {
query?: {
input?: BatchUpdateStatusInput;
};
};
export type PostUpgradeRecordBatchUpdateStatusAsyncResponse = (DeviceUpgradeRecordDto);
export type PostUpgradeRecordBatchUpdateStatusAsyncError = unknown;
export type PostUpgradeRecordDeleteAsyncData = {
query?: {
input?: IdInput;

View File

@ -24,6 +24,7 @@ import {
postAggregationDeviceBatchCreateAsync,
postAggregationDeviceCreateAsync,
postAggregationDeviceDeleteAsync,
postAggregationDeviceDeviceBatchUpgradeForApiAsync,
postAggregationDeviceDeviceCommandForApiAsync,
postAggregationDeviceDeviceUpgradeForApiAsync,
postAggregationDeviceGetDevicePropertyValueForApiAsync,
@ -31,7 +32,6 @@ import {
postDeviceInfoBindingDeviceThingModel,
postDeviceInfoCacheDeviceDataToRedis,
postDeviceInfoPage,
postFirmwareInfoFindByDeviceProductIdAsync,
postIoTplatformThingModelInfoPageAsync,
} from '#/api-client';
import { Icon } from '#/components/icon';
@ -42,6 +42,7 @@ import { $t } from '#/locales';
import {
addDeviceFormSchema,
batchAddDeviceFormSchema,
batchUpgradeDeviceFormSchema,
bindDeviceThingModelFormSchema,
commandFormSchema,
deviceUpgradeFormSchema,
@ -70,11 +71,9 @@ const formOptions: VbenFormProps = {
}
// ID
if (changedFields.includes('ioTPlatform')) {
if (gridApi?.formApi) {
if (changedFields.includes('ioTPlatform') && gridApi?.formApi) {
await gridApi.formApi.setFieldValue('ioTPlatformProductId', undefined);
}
}
//
if (changedFields.includes('ioTPlatformProductId')) {
@ -765,6 +764,259 @@ const [BatchAddModal, batchAddModalApi] = useVbenModal({
},
});
//
const [BatchUpgradeForm, batchUpgradeFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 120,
componentProps: {
class: 'w-full',
},
},
layout: 'horizontal',
schema: batchUpgradeDeviceFormSchema.value,
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
handleValuesChange: async (values, changedFields) => {
//
if (changedFields.includes('ioTPlatformAccountId')) {
await batchUpgradeFormApi.setFieldValue(
'ioTPlatformProductId',
undefined,
);
await batchUpgradeFormApi.setFieldValue('targetVersionId', undefined);
}
// ID
if (changedFields.includes('ioTPlatformProductId')) {
await nextTick();
await batchUpgradeFormApi.setFieldValue('targetVersionId', undefined);
}
},
});
//
const submitBatchUpgrade = async () => {
const { valid } = await batchUpgradeFormApi.validate();
if (!valid) return;
const formValues = await batchUpgradeFormApi.getValues();
//
const addressList = formValues.addressList
.split('\n')
.map((address: string) => address.trim())
.filter((address: string) => address.length > 0);
if (addressList.length === 0) {
Message.error('请输入至少一个设备地址');
return;
}
//
if (!formValues.ioTPlatform) {
Message.error('请选择平台类型');
return;
}
if (!formValues.ioTPlatformProductId) {
Message.error('请选择产品');
return;
}
//
if (!formValues.targetVersionId) {
Message.error('请选择目标版本');
return;
}
try {
batchUpgradeModalApi.setState({ loading: true, confirmLoading: true });
//
const result = await postAggregationDeviceDeviceBatchUpgradeForApiAsync({
body: {
addressList,
ioTPlatform:
typeof formValues.ioTPlatform === 'string'
? Number.parseInt(formValues.ioTPlatform)
: formValues.ioTPlatform,
ioTPlatformProductId: String(formValues.ioTPlatformProductId),
nowFirmwareVersionDataId: formValues.targetVersionId,
upgradeDescription: formValues.upgradeDescription || null,
},
});
if (result.data) {
Message.success(`批量升级任务已提交,共 ${addressList.length} 个设备`);
batchUpgradeModalApi.close();
batchUpgradeFormApi.resetForm();
gridApi.reload();
} else {
Message.error('批量升级失败');
}
} catch (error) {
console.error('批量升级失败:', error);
Message.error('批量升级失败');
} finally {
batchUpgradeModalApi.setState({ loading: false, confirmLoading: false });
}
};
//
const batchUpgradeAddressLines = ref(0);
//
watch(
() => batchUpgradeFormApi.getValues()?.addressList,
(newValue) => {
try {
if (!newValue || typeof newValue !== 'string') {
batchUpgradeAddressLines.value = 0;
return;
}
const lines = newValue.split('\n').filter((line: string) => line.trim());
batchUpgradeAddressLines.value = lines.length;
} catch {
batchUpgradeAddressLines.value = 0;
}
},
{ immediate: true },
);
//
watch(
() => batchUpgradeFormApi.getValues(),
(formValues) => {
try {
const addressList = formValues?.addressList;
if (!addressList || typeof addressList !== 'string') {
batchUpgradeAddressLines.value = 0;
return;
}
const lines = addressList
.split('\n')
.filter((line: string) => line.trim());
batchUpgradeAddressLines.value = lines.length;
} catch {
batchUpgradeAddressLines.value = 0;
}
},
{ deep: true, immediate: true },
);
// 使
let batchUpgradeLineCheckInterval: NodeJS.Timeout | null = null;
//
const startBatchUpgradeLineCheck = () => {
if (batchUpgradeLineCheckInterval) {
clearInterval(batchUpgradeLineCheckInterval);
}
batchUpgradeLineCheckInterval = setInterval(() => {
try {
//
let addressList = '';
// 1
const formValues = batchUpgradeFormApi.getValues();
addressList = formValues?.addressList || '';
// 21
if (!addressList) {
try {
const rawValues = batchUpgradeFormApi.getValues();
addressList = rawValues?.addressList || '';
} catch (error) {
console.log('方式2失败:', error);
}
}
// 3DOM
if (!addressList) {
const textarea = document.querySelector(
'textarea[name="addressList"]',
) as HTMLTextAreaElement;
if (textarea) {
addressList = textarea.value;
}
}
if (addressList && typeof addressList === 'string') {
const lines = addressList
.split('\n')
.filter((line: string) => line.trim());
const newLineCount = lines.length;
console.log(
'批量升级计算出的行数:',
newLineCount,
'当前行数:',
batchUpgradeAddressLines.value,
);
//
if (newLineCount !== batchUpgradeAddressLines.value) {
console.log('批量升级更新行数:', newLineCount);
batchUpgradeAddressLines.value = newLineCount;
// 使 nextTick DOM
nextTick(() => {
console.log(
'批量升级行数已更新到:',
batchUpgradeAddressLines.value,
);
});
}
} else {
// 0
if (batchUpgradeAddressLines.value !== 0) {
batchUpgradeAddressLines.value = 0;
}
}
} catch (error) {
console.error('定时器检查批量升级行数失败:', error);
}
}, 300);
};
//
const stopBatchUpgradeLineCheck = () => {
if (batchUpgradeLineCheckInterval) {
clearInterval(batchUpgradeLineCheckInterval);
batchUpgradeLineCheckInterval = null;
}
};
//
const openBatchUpgradeModal = () => {
batchUpgradeFormApi.resetForm();
batchUpgradeAddressLines.value = 0; //
batchUpgradeModalApi.open();
};
const [BatchUpgradeModal, batchUpgradeModalApi] = useVbenModal({
draggable: true,
onConfirm: submitBatchUpgrade,
onBeforeClose: () => {
batchUpgradeFormApi.resetForm();
batchUpgradeAddressLines.value = 0; //
stopBatchUpgradeLineCheck();
return true;
},
onOpenChange: (isOpen) => {
if (isOpen) {
//
setTimeout(() => {
startBatchUpgradeLineCheck();
}, 100);
} else {
//
stopBatchUpgradeLineCheck();
}
},
});
//
const batchUpgradeModalState = batchUpgradeModalApi.useStore();
//
const [UpgradeForm, upgradeFormApi] = useVbenForm({
collapsed: false,
@ -1640,6 +1892,13 @@ const toolbarActions = computed(() => [
onClick: openBatchBindModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: '批量升级',
type: 'default',
icon: 'ant-design:link-outlined',
onClick: openBatchUpgradeModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: cacheRefreshLoading.value
? $t('common.loading')
@ -1820,5 +2079,26 @@ const toolbarActions = computed(() => [
</div>
</template>
</BatchAddModal>
<BatchUpgradeModal title="批量升级设备" class="w-[800px]">
<BatchUpgradeForm />
<template #footer>
<div class="flex w-full items-center justify-between">
<div class="text-sm text-gray-500">
<span v-if="batchUpgradeAddressLines > 0">
{{ batchUpgradeAddressLines }} 个设备地址
</span>
<span v-else> 当前设备地址数: {{ batchUpgradeAddressLines }} </span>
</div>
<div class="flex gap-2">
<Button @click="batchUpgradeModalApi.close()">
{{ $t('common.cancel') }}
</Button>
<Button type="primary" :loading="batchUpgradeModalState?.confirmLoading" @click="submitBatchUpgrade">
{{ $t('common.confirm') }}
</Button>
</div>
</div>
</template>
</BatchUpgradeModal>
</Page>
</template>

View File

@ -941,3 +941,249 @@ export const batchAddDeviceFormSchema: any = computed(() => [
rules: z.string().optional(),
},
]);
// 批量设备升级表单
export const batchUpgradeDeviceFormSchema: any = computed(() => [
{
component: 'Textarea',
fieldName: 'addressList',
label: '设备地址列表',
componentProps: {
rows: 4,
placeholder: '请输入设备地址,每行一个设备地址',
showCount: false,
maxLength: 10_000,
style: {
resize: 'vertical',
minHeight: '32px',
maxHeight: '200px',
},
},
rules: z.string().min(1, {
message: '请输入设备地址',
}),
},
{
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 [];
},
},
rules: z.string().min(1, {
message: `${$t('common.pleaseSelect')}${$t('abp.deviceInfos.ioTPlatform')}`,
}),
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformAccountId',
label: $t('abp.deviceInfos.ioTPlatformAccountName'),
dependencies: {
show(values: any) {
return !!values.ioTPlatform;
},
rules(values: any) {
if (values.ioTPlatform) {
return 'required';
}
return null;
},
triggerFields: ['ioTPlatform'],
},
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
return {
api: platform
? postAggregationIoTplatformGetIoTplatformAccountInfoAsync
: null,
params: platform
? {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
},
}
: {},
labelField: 'ioTPlatformPhoneNumber',
valueField: 'ioTPlatformAccountId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder:
$t('common.pleaseSelect') +
$t('abp.deviceInfos.ioTPlatformAccountName'),
afterFetch: (res: any) => {
let items: any[] = [];
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;
} else if (res && res.data && Array.isArray(res.data.items)) {
items = res.data.items;
}
return items.map((item: any) => ({
...item,
ioTPlatformAccountId: item.ioTPlatformAccount,
label: item.ioTPlatformPhoneNumber || item.ioTPlatformAccount || '',
}));
},
};
},
rules: z.string().optional(),
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('abp.deviceInfos.ioTPlatformProductName'),
dependencies: {
show(values: any) {
return !!values.ioTPlatform && !!values.ioTPlatformAccountId;
},
rules(values: any) {
if (values.ioTPlatform && values.ioTPlatformAccountId) {
return 'required';
}
return null;
},
triggerFields: ['ioTPlatform', 'ioTPlatformAccountId'],
},
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
const accountId = formValues?.ioTPlatformAccountId;
return {
api:
platform && accountId
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params:
platform && accountId
? {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
ioTPlatformAccount: accountId,
},
}
: {},
labelField: 'productName',
valueField: 'ioTPlatformProductId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder:
$t('common.pleaseSelect') +
$t('abp.deviceInfos.ioTPlatformProductName'),
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 [];
},
};
},
rules: z.string().optional(),
},
{
component: 'ApiSelect',
fieldName: 'targetVersionId',
label: '目标版本',
dependencies: {
show(values: any) {
return !!values.ioTPlatformProductId;
},
rules(values: any) {
if (values.ioTPlatformProductId) {
return 'required';
}
return null;
},
triggerFields: ['ioTPlatformProductId'],
},
componentProps: (formValues: any) => {
const productId = formValues?.ioTPlatformProductId;
return {
api: productId ? postFirmwareInfoFindByDeviceProductIdAsync : null,
params: productId
? {
query: {
id: String(productId),
},
}
: {},
labelField: 'firmwareVersion',
valueField: 'id',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder: '请选择目标版本',
afterFetch: (res: any) => {
let firmwareList: any[] = [];
if (Array.isArray(res)) {
firmwareList = res;
} else if (res && Array.isArray(res.items)) {
firmwareList = res.items;
} else if (res && Array.isArray(res.data)) {
firmwareList = res.data;
} else if (res && res.data && Array.isArray(res.data.items)) {
firmwareList = res.data.items;
}
return firmwareList.filter((item: any) => item.isEnable === true);
},
};
},
rules: z.string().optional(),
},
{
component: 'Textarea',
fieldName: 'upgradeDescription',
label: '升级说明',
componentProps: {
rows: 4,
placeholder: '请输入升级说明(可选)',
maxLength: 500,
showCount: true,
},
},
]);