Compare commits

...

3 Commits

Author SHA1 Message Date
ChenYi
4d7ee72d24 产品管理实现物模型文件上传和下载 2025-07-29 13:45:02 +08:00
ChenYi
15dbbc6825 OneNET产品完善 2025-07-29 11:17:27 +08:00
ChenYi
ee179e1f6d 文件管理优化,OneNET产品管理优化 2025-07-29 10:06:30 +08:00
9 changed files with 508 additions and 155 deletions

View File

@ -2071,6 +2071,39 @@ export const FileLoadBalancerOptionsSchema = {
additionalProperties: false additionalProperties: false
} as const; } as const;
export const FileObjectDtoSchema = {
type: 'object',
properties: {
id: {
type: 'string',
description: '主键Id',
format: 'uuid'
},
creationTime: {
type: 'string',
description: '创建时间',
format: 'date-time'
},
fileSize: {
type: 'integer',
description: '文件大小',
format: 'int64'
},
contentType: {
type: 'string',
description: '文件名称',
nullable: true
},
fileName: {
type: 'string',
description: '文件名称',
nullable: true
}
},
additionalProperties: false,
description: '文件'
} as const;
export const FileQoSOptionsSchema = { export const FileQoSOptionsSchema = {
type: 'object', type: 'object',
properties: { properties: {
@ -3362,11 +3395,11 @@ export const IoTDBDynamicObjectPagedResultDtoSchema = {
} as const; } as const;
export const IoTPlatformTypeEnumSchema = { export const IoTPlatformTypeEnumSchema = {
enum: [0, 1, 2], enum: [1, 2],
type: 'integer', type: 'integer',
description: '物联网平台类型枚举', description: '物联网平台类型枚举',
format: 'int32', format: 'int32',
'说明:': '无=0,电信CTWing=1,移动OneNET=2' '说明:': '电信CTWing=1,移动OneNET=2'
} as const; } as const;
export const LanguageInfoSchema = { export const LanguageInfoSchema = {
@ -4429,6 +4462,11 @@ export const OneNETProductInfoDtoSchema = {
description: 'OneNET账户Id', description: 'OneNET账户Id',
nullable: true nullable: true
}, },
oneNETAccountName: {
type: 'string',
description: 'OneNET账户',
nullable: true
},
ioTPlatformProductId: { ioTPlatformProductId: {
type: 'string', type: 'string',
description: '物联网平台对应的产品Id', description: '物联网平台对应的产品Id',
@ -4444,14 +4482,15 @@ export const OneNETProductInfoDtoSchema = {
description: '产品访问密钥', description: '产品访问密钥',
nullable: true nullable: true
}, },
deviceThingModelUrl: { deviceThingModelFileId: {
type: 'string', type: 'string',
description: '设备物模型文件管理地址', description: '设备物模型文件Id',
nullable: true nullable: true
}, },
isEncrypted: { deviceThingModelFileName: {
type: 'boolean', type: 'string',
description: '通信是否加密' description: '设备物模型文件名称',
nullable: true
}, },
isEnabled: { isEnabled: {
type: 'boolean', type: 'boolean',
@ -4655,32 +4694,38 @@ export const OneNetAccountModifyInputSchema = {
} as const; } as const;
export const OneNetProductInfoInsertInputSchema = { export const OneNetProductInfoInsertInputSchema = {
required: ['communicationAddress', 'ioTPlatformProductId', 'oneNETAccountId', 'productAccesskey'],
type: 'object', type: 'object',
properties: { properties: {
oneNETAccountId: { oneNETAccountId: {
minLength: 1,
type: 'string', type: 'string',
description: 'OneNET 账号ID', description: 'OneNET 账号ID'
nullable: true
}, },
ioTPlatformProductId: { ioTPlatformProductId: {
minLength: 1,
type: 'string', type: 'string',
description: '物联网平台对应的产品Id', description: '物联网平台对应的产品Id'
nullable: true
}, },
productAccesskey: { productAccesskey: {
minLength: 1,
type: 'string', type: 'string',
description: '产品访问密钥', description: '产品访问密钥'
nullable: true
}, },
communicationAddress: { communicationAddress: {
minLength: 1,
type: 'string', type: 'string',
description: '通讯服务地址', description: '通讯服务地址'
nullable: true
}, },
communicationAddressTLS: { communicationAddressTLS: {
type: 'string', type: 'string',
description: 'TLS通讯服务地址', description: 'TLS通讯服务地址',
nullable: true nullable: true
},
deviceThingModelUrl: {
type: 'string',
description: '物模型文件链接',
nullable: true
} }
}, },
additionalProperties: false additionalProperties: false
@ -4733,11 +4778,48 @@ export const OneNetProductInfoListInputSchema = {
} as const; } as const;
export const OneNetProductInfoModifyInputSchema = { export const OneNetProductInfoModifyInputSchema = {
required: ['communicationAddress', 'ioTPlatformProductId', 'oneNETAccountId', 'productAccesskey'],
type: 'object', type: 'object',
properties: { properties: {
id: { id: {
type: 'string', type: 'string',
description: '产品数据ID',
format: 'uuid' format: 'uuid'
},
oneNETAccountId: {
minLength: 1,
type: 'string',
description: 'OneNET 账号ID'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台对应的产品Id'
},
productAccesskey: {
minLength: 1,
type: 'string',
description: '产品访问密钥'
},
communicationAddress: {
minLength: 1,
type: 'string',
description: '通讯服务地址'
},
communicationAddressTLS: {
type: 'string',
description: 'TLS通讯服务地址',
nullable: true
},
deviceThingModelFileId: {
type: 'string',
description: '设备物模型文件Id',
nullable: true
},
deviceThingModelFileName: {
type: 'string',
description: '设备物模型文件名称',
nullable: true
} }
}, },
additionalProperties: false, additionalProperties: false,

View File

@ -917,6 +917,32 @@ export type FileLoadBalancerOptions = {
expiry?: number; expiry?: number;
}; };
/**
*
*/
export type FileObjectDto = {
/**
* Id
*/
id?: string;
/**
*
*/
creationTime?: string;
/**
*
*/
fileSize?: number;
/**
*
*/
contentType?: (string) | null;
/**
*
*/
fileName?: (string) | null;
};
export type FileQoSOptions = { export type FileQoSOptions = {
exceptionsAllowedBeforeBreaking?: number; exceptionsAllowedBeforeBreaking?: number;
durationOfBreak?: number; durationOfBreak?: number;
@ -1380,7 +1406,7 @@ export type IoTDBDynamicObjectPagedResultDto = {
/** /**
* *
*/ */
export type IoTPlatformTypeEnum = 0 | 1 | 2; export type IoTPlatformTypeEnum = 1 | 2;
export type IStringValueType = { export type IStringValueType = {
readonly name?: (string) | null; readonly name?: (string) | null;
@ -2107,6 +2133,10 @@ export type OneNETProductInfoDto = {
* OneNET账户Id * OneNET账户Id
*/ */
oneNETAccountId?: (string) | null; oneNETAccountId?: (string) | null;
/**
* OneNET账户
*/
oneNETAccountName?: (string) | null;
/** /**
* Id * Id
*/ */
@ -2120,13 +2150,13 @@ export type OneNETProductInfoDto = {
*/ */
productAccesskey?: (string) | null; productAccesskey?: (string) | null;
/** /**
* * Id
*/ */
deviceThingModelUrl?: (string) | null; deviceThingModelFileId?: (string) | null;
/** /**
* *
*/ */
isEncrypted?: boolean; deviceThingModelFileName?: (string) | null;
/** /**
* *
*/ */
@ -2200,23 +2230,27 @@ export type OneNetProductInfoInsertInput = {
/** /**
* OneNET ID * OneNET ID
*/ */
oneNETAccountId?: (string) | null; oneNETAccountId: string;
/** /**
* Id * Id
*/ */
ioTPlatformProductId?: (string) | null; ioTPlatformProductId: string;
/** /**
* 访 * 访
*/ */
productAccesskey?: (string) | null; productAccesskey: string;
/** /**
* *
*/ */
communicationAddress?: (string) | null; communicationAddress: string;
/** /**
* TLS通讯服务地址 * TLS通讯服务地址
*/ */
communicationAddressTLS?: (string) | null; communicationAddressTLS?: (string) | null;
/**
*
*/
deviceThingModelUrl?: (string) | null;
}; };
export type OneNetProductInfoListInput = { export type OneNetProductInfoListInput = {
@ -2257,7 +2291,38 @@ export type OneNetProductInfoListInput = {
* OneNET产品 * OneNET产品
*/ */
export type OneNetProductInfoModifyInput = { export type OneNetProductInfoModifyInput = {
/**
* ID
*/
id?: string; id?: string;
/**
* OneNET ID
*/
oneNETAccountId: string;
/**
* Id
*/
ioTPlatformProductId: string;
/**
* 访
*/
productAccesskey: string;
/**
*
*/
communicationAddress: string;
/**
* TLS通讯服务地址
*/
communicationAddressTLS?: (string) | null;
/**
* Id
*/
deviceThingModelFileId?: (string) | null;
/**
*
*/
deviceThingModelFileName?: (string) | null;
}; };
/** /**
@ -4529,7 +4594,7 @@ export type PostFilesUploadData = {
}; };
}; };
export type PostFilesUploadResponse = (unknown); export type PostFilesUploadResponse = (Array<FileObjectDto>);
export type PostFilesUploadError = (RemoteServiceErrorResponse); export type PostFilesUploadError = (RemoteServiceErrorResponse);

View File

@ -269,12 +269,13 @@
"ProductCount": "ProductCount", "ProductCount": "ProductCount",
"OneNETAccountId": "OneNETAccountId", "OneNETAccountId": "OneNETAccountId",
"OneNETProductId": "OneNETProductId", "OneNETProductId": "OneNETProductId",
"ProductKey": "ProductKey",
"ProductSecret": "ProductSecret",
"IoTPlatformProductId": "IoTPlatformProductId", "IoTPlatformProductId": "IoTPlatformProductId",
"ProductName": "ProductName", "ProductName": "ProductName",
"ProductAccesskey": "ProductAccesskey", "ProductAccesskey": "ProductAccesskey",
"IsEncrypted": "IsEncrypted", "IsEncrypted": "IsEncrypted",
"BelongingProductName": "BelongingProductName" "BelongingProductName": "Belonging ProductName",
"CommunicationAddress": "Communication Address",
"CommunicationAddressTLS": "TLS Communication Address",
"DeviceThingModelFileName": "DeviceThingModelFileName"
} }
} }

View File

@ -271,12 +271,13 @@
"ProductCount": "产品数量", "ProductCount": "产品数量",
"OneNETAccountId": "OneNET账户Id", "OneNETAccountId": "OneNET账户Id",
"OneNETProductId": "OneNET产品Id", "OneNETProductId": "OneNET产品Id",
"ProductKey": "产品密钥",
"ProductSecret": "产品密钥",
"IoTPlatformProductId": "物联网平台对应的产品Id", "IoTPlatformProductId": "物联网平台对应的产品Id",
"ProductName": "产品名称", "ProductName": "产品名称",
"ProductAccesskey": "产品访问密钥", "ProductAccesskey": "产品密钥",
"IsEncrypted": "是否加密", "IsEncrypted": "是否加密",
"BelongingProductName": "所属产品" "BelongingProductName": "所属产品",
"CommunicationAddress": "通讯地址",
"CommunicationAddressTLS": "TLS通讯地址",
"DeviceThingModelFileName": "物模型文件"
} }
} }

View File

@ -2,7 +2,7 @@
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, ref } from 'vue'; import { h, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
@ -16,6 +16,8 @@ import {
postOneNetProductDeleteAsync, postOneNetProductDeleteAsync,
postOneNetProductListAsync, postOneNetProductListAsync,
postOneNetProductModifyAsync, postOneNetProductModifyAsync,
postFilesUpload,
postFilesDownload,
} 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';
@ -25,6 +27,7 @@ import {
editProductFormSchemaEdit, editProductFormSchemaEdit,
querySchema, querySchema,
tableSchema, tableSchema,
setFileSelectedCallback,
} from './schema'; } from './schema';
defineOptions({ defineOptions({
@ -69,13 +72,41 @@ const gridOptions: VxeGridProps<any> = {
const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions }); const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });
const editRow: Record<string, any> = ref({}); const editRow: Record<string, any> = ref({});
//
let selectedFile: File | null = null;
//
setFileSelectedCallback((file) => {
selectedFile = file;
});
const [UserModal, userModalApi] = useVbenModal({ const [UserModal, userModalApi] = useVbenModal({
draggable: true, draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submit, onConfirm: submit,
onBeforeClose: () => { onBeforeClose: () => {
// //
return true; return true;
}, },
onOpen: () => {
//
selectedFile = null;
},
onOpenChange: (isOpen: boolean) => {
if (isOpen && editRow.value.id) {
//
nextTick(() => {
editFormApi.setValues({ ...editRow.value });
});
}
},
onCancel: () => {
//
selectedFile = null;
//
userModalApi.close();
},
}); });
const [AddForm, addFormApi] = useVbenForm({ const [AddForm, addFormApi] = useVbenForm({
@ -122,6 +153,45 @@ async function submit() {
if (!valid) return; if (!valid) return;
const formValues = await formApi.getValues(); const formValues = await formApi.getValues();
//
if (!formValues.deviceThingModelFileName) {
Message.error('请选择设备模型文件');
return;
}
//
if (selectedFile) {
try {
userModalApi.setState({ loading: true, confirmLoading: true });
const result = await postFilesUpload({ body: { files: [selectedFile] } });
if (result.status === 204 || result.status === 200) {
const fileInfo = result.data?.[0];
if (fileInfo && fileInfo.id) {
formValues.deviceThingModelFileId = fileInfo.id;
//
} else {
Message.error('文件上传成功但未获取到文件ID');
userModalApi.setState({ loading: false, confirmLoading: false });
return;
}
} else {
Message.error('文件上传失败');
userModalApi.setState({ loading: false, confirmLoading: false });
return;
}
} catch (error) {
Message.error('文件上传失败');
userModalApi.setState({ loading: false, confirmLoading: false });
return;
}
}
//
selectedFile = null;
//
const fetchParams: any = isEdit const fetchParams: any = isEdit
? { ? {
id: editRow.value.id, id: editRow.value.id,
@ -132,7 +202,6 @@ async function submit() {
}; };
try { try {
userModalApi.setState({ loading: true, confirmLoading: true });
const resp = await api({ body: fetchParams }); const resp = await api({ body: fetchParams });
if (resp.data) { if (resp.data) {
Message.success( Message.success(
@ -154,12 +223,15 @@ async function submit() {
async function onEdit(record: any) { async function onEdit(record: any) {
editRow.value = record; editRow.value = record;
userModalApi.open(); userModalApi.open();
editFormApi.setValues({ ...record }); //
selectedFile = null;
} }
const openAddModal = async () => { const openAddModal = async () => {
editRow.value = {}; editRow.value = {};
userModalApi.open(); userModalApi.open();
//
selectedFile = null;
}; };
// //
@ -168,6 +240,8 @@ async function onDel(record: any) {
const resp = await postOneNetProductDeleteAsync({ body: { id: record.id } }); const resp = await postOneNetProductDeleteAsync({ body: { id: record.id } });
if (resp.data) { if (resp.data) {
Message.success($t('common.deleteSuccess')); Message.success($t('common.deleteSuccess'));
//
selectedFile = null;
gridApi.reload(); gridApi.reload();
} else { } else {
Message.error($t('common.deleteFail')); Message.error($t('common.deleteFail'));
@ -176,6 +250,32 @@ async function onDel(record: any) {
Message.error($t('common.deleteFail')); Message.error($t('common.deleteFail'));
} }
} }
//
async function onDownloadFile(record: any) {
if (!record.deviceThingModelFileId) {
Message.error('文件ID不存在无法下载');
return;
}
try {
const { data } = await postFilesDownload({
body: { id: record.deviceThingModelFileId },
responseType: 'blob',
});
const url = window.URL.createObjectURL(new Blob([data as Blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', record.deviceThingModelFileName || 'device-model-file');
document.body.append(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
Message.success('文件下载成功');
} catch (error) {
Message.error('文件下载失败');
}
}
</script> </script>
<template> <template>
@ -205,6 +305,17 @@ async function onDel(record: any) {
/> />
</template> </template>
<template #deviceThingModelFileName="{ row }">
<a
v-if="row.deviceThingModelFileName && row.deviceThingModelFileId"
@click="onDownloadFile(row)"
style="color: #1890ff; cursor: pointer; text-decoration: underline;"
>
{{ row.deviceThingModelFileName }}
</a>
<span v-else>{{ row.deviceThingModelFileName || '-' }}</span>
</template>
<template #action="{ row }"> <template #action="{ row }">
<TableAction <TableAction
:actions="[ :actions="[

View File

@ -1,11 +1,11 @@
import type { VxeGridProps } from '#/adapter/vxe-table'; import type { VxeGridProps } from '#/adapter/vxe-table';
import { computed } from 'vue'; import { computed, h } from 'vue';
import { z } from '@vben/common-ui'; import { z } from '@vben/common-ui';
import { postOneNetAccountListAsync, postFilesUpload } from '#/api-client';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { postOneNetAccountListAsync } from '#/api-client';
export const querySchema = computed(() => [ export const querySchema = computed(() => [
{ {
@ -18,7 +18,12 @@ export const querySchema = computed(() => [
export const tableSchema: any = computed((): VxeGridProps['columns'] => [ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
{ title: $t('common.seq'), type: 'seq', width: 50 }, { title: $t('common.seq'), type: 'seq', width: 50 },
{ {
field: 'oneNETProductId', field: 'oneNETAccountName',
title: $t('abp.OneNETManagement.BelongingAccountName'),
minWidth: '150',
},
{
field: 'ioTPlatformProductId',
title: $t('abp.OneNETManagement.OneNETProductId'), title: $t('abp.OneNETManagement.OneNETProductId'),
minWidth: '150', minWidth: '150',
}, },
@ -28,22 +33,28 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
minWidth: '150', minWidth: '150',
}, },
{ {
field: 'productKey', field: 'productAccesskey',
title: $t('abp.OneNETManagement.ProductKey'), title: $t('abp.OneNETManagement.ProductAccesskey'),
minWidth: '150', minWidth: '150',
}, },
{ {
field: 'productSecret', field: 'communicationAddress',
title: $t('abp.OneNETManagement.ProductSecret'), title: $t('abp.OneNETManagement.CommunicationAddress'),
minWidth: '150', minWidth: '150',
}, },
{ {
field: 'accountName', field: 'communicationAddressTLS',
title: $t('abp.OneNETManagement.AccountName'), title: $t('abp.OneNETManagement.CommunicationAddressTLS'),
minWidth: '150', minWidth: '150',
}, },
{ {
field: 'enabled', field: 'deviceThingModelFileName',
title: $t('abp.OneNETManagement.DeviceThingModelFileName'),
minWidth: '150',
slots: { default: 'deviceThingModelFileName' },
},
{
field: 'isEnabled',
title: $t('common.isEnable'), title: $t('common.isEnable'),
minWidth: '150', minWidth: '150',
slots: { default: 'isEnable' }, slots: { default: 'isEnable' },
@ -57,6 +68,16 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
}, },
]); ]);
// 全局变量存储选择的文件
export let selectedFile: File | null = null;
// 文件选择回调函数
let _onFileSelected: ((file: File) => void) | null = null;
export function setFileSelectedCallback(callback: (file: File) => void) {
_onFileSelected = callback;
}
export const addProductFormSchema: any = computed(() => [ export const addProductFormSchema: any = computed(() => [
{ {
component: 'ApiSelect', component: 'ApiSelect',
@ -74,32 +95,25 @@ export const addProductFormSchema: any = computed(() => [
valueField: 'oneNETAccountId', valueField: 'oneNETAccountId',
immediate: true, immediate: true,
afterFetch: (res: any) => { afterFetch: (res: any) => {
console.log('ApiSelect afterFetch res:', res);
// 如果是 Axios 响应对象,提取 data // 如果是 Axios 响应对象,提取 data
if (res && res.data) { if (res && res.data) {
console.log('提取 res.data:', res.data);
const data = res.data; const data = res.data;
// 确保返回的是数组格式 // 确保返回的是数组格式
if (Array.isArray(data)) { if (Array.isArray(data)) {
console.log('返回数组,长度:', data.length);
return data; return data;
} }
// 如果是包装在 items 中的,提取出来 // 如果是包装在 items 中的,提取出来
if (data && Array.isArray(data.items)) { if (data && Array.isArray(data.items)) {
console.log('返回items数组长度:', data.items.length);
return data.items; return data.items;
} }
// 如果是包装在 data 中的,提取出来 // 如果是包装在 data 中的,提取出来
if (data && Array.isArray(data.data)) { if (data && Array.isArray(data.data)) {
console.log('返回data数组长度:', data.data.length);
return data.data; return data.data;
} }
} }
// 如果都不是,返回空数组 // 如果都不是,返回空数组
console.log('没有找到数组数据,返回空数组');
return []; return [];
}, },
placeholder: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.BelongingAccountName')}`, placeholder: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.BelongingAccountName')}`,
@ -110,7 +124,7 @@ export const addProductFormSchema: any = computed(() => [
}, },
{ {
component: 'Input', component: 'Input',
fieldName: 'oneNETProductId', fieldName: 'ioTPlatformProductId',
label: $t('abp.OneNETManagement.OneNETProductId'), label: $t('abp.OneNETManagement.OneNETProductId'),
rules: z.string().min(1, { rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.OneNETProductId')}`, message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.OneNETProductId')}`,
@ -118,73 +132,86 @@ export const addProductFormSchema: any = computed(() => [
}, },
{ {
component: 'Input', component: 'Input',
fieldName: 'productName', fieldName: 'productAccesskey',
label: $t('abp.OneNETManagement.ProductName'), label: $t('abp.OneNETManagement.ProductAccesskey'),
rules: z.string().min(1, { rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductName')}`, message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductAccesskey')}`,
}), }),
}, },
{ {
component: 'Input', component: 'Input',
fieldName: 'productKey', fieldName: 'communicationAddress',
label: $t('abp.OneNETManagement.ProductKey'), label: $t('abp.OneNETManagement.CommunicationAddress'),
rules: z.string().min(1, { rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductKey')}`, message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.CommunicationAddress')}`,
}), }),
}, },
{ {
component: 'Input', component: 'Input',
fieldName: 'productSecret', fieldName: 'communicationAddressTLS',
label: $t('abp.OneNETManagement.ProductSecret'), label: $t('abp.OneNETManagement.CommunicationAddressTLS'),
rules: z.string().min(1, { rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductSecret')}`, message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.CommunicationAddressTLS')}`,
}), }),
}, },
{ {
component: 'Switch', component: 'Input',
fieldName: 'deviceThingModelFileName',
label: $t('abp.OneNETManagement.DeviceThingModelFileName'),
componentProps: { componentProps: {
class: 'w-auto', placeholder: '请选择文件',
readonly: true,
addonAfter: h('button', {
type: 'button',
style: 'border: none; background: #1890ff; color: white; padding: 4px 8px; border-radius: 4px; cursor: pointer;',
onClick: () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,.xlsx,.xls';
input.onchange = (e: any) => {
const file = e.target.files[0];
if (file) {
// 只显示文件名,不上传
const currentInput = document.querySelector('input[placeholder="请选择文件"]') as HTMLInputElement;
if (currentInput) {
currentInput.value = file.name;
// 触发change事件
currentInput.dispatchEvent(new Event('input', { bubbles: true }));
currentInput.dispatchEvent(new Event('change', { bubbles: true }));
// 存储文件对象到全局变量,用于后续上传
selectedFile = file;
// 调用回调函数
if (_onFileSelected) {
_onFileSelected(file);
}
}
console.log('文件已选择:', file.name, '大小:', file.size, '字节');
}
};
input.click();
}
}, '选择文件'),
},
rules: z.string().min(1, {
message: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.DeviceThingModelFileName')}`,
}),
},
{
component: 'Input',
fieldName: 'deviceThingModelFileId',
label: '',
componentProps: {
type: 'hidden',
}, },
fieldName: 'enabled',
label: $t('common.isEnable'),
}, },
]); ]);
export const editProductFormSchemaEdit: any = computed(() => [ export const editProductFormSchemaEdit: any = computed(() => [
{
component: 'Input',
fieldName: 'oneNETProductId',
label: $t('abp.OneNETManagement.OneNETProductId'),
disabled: true,
},
{
component: 'Input',
fieldName: 'productName',
label: $t('abp.OneNETManagement.ProductName'),
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductName')}`,
}),
},
{
component: 'Input',
fieldName: 'productKey',
label: $t('abp.OneNETManagement.ProductKey'),
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductKey')}`,
}),
},
{
component: 'Input',
fieldName: 'productSecret',
label: $t('abp.OneNETManagement.ProductSecret'),
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductSecret')}`,
}),
},
{ {
component: 'ApiSelect', component: 'ApiSelect',
fieldName: 'accountName', fieldName: 'oneNETAccountId',
label: $t('abp.OneNETManagement.AccountName'), label: $t('abp.OneNETManagement.BelongingAccountName'),
componentProps: { componentProps: {
api: postOneNetAccountListAsync, api: postOneNetAccountListAsync,
params: { params: {
@ -196,47 +223,124 @@ export const editProductFormSchemaEdit: any = computed(() => [
labelField: 'accountName', labelField: 'accountName',
valueField: 'oneNETAccountId', valueField: 'oneNETAccountId',
immediate: true, immediate: true,
disabled: true, // 编辑时禁用
afterFetch: (res: any) => { afterFetch: (res: any) => {
console.log('ApiSelect afterFetch res:', res);
// 如果是 Axios 响应对象,提取 data // 如果是 Axios 响应对象,提取 data
if (res && res.data) { if (res && res.data) {
console.log('提取 res.data:', res.data);
const data = res.data; const data = res.data;
// 确保返回的是数组格式 // 确保返回的是数组格式
if (Array.isArray(data)) { if (Array.isArray(data)) {
console.log('返回数组,长度:', data.length);
return data; return data;
} }
// 如果是包装在 items 中的,提取出来 // 如果是包装在 items 中的,提取出来
if (data && Array.isArray(data.items)) { if (data && Array.isArray(data.items)) {
console.log('返回items数组长度:', data.items.length);
return data.items; return data.items;
} }
// 如果是包装在 data 中的,提取出来 // 如果是包装在 data 中的,提取出来
if (data && Array.isArray(data.data)) { if (data && Array.isArray(data.data)) {
console.log('返回data数组长度:', data.data.length);
return data.data; return data.data;
} }
} }
// 如果都不是,返回空数组 // 如果都不是,返回空数组
console.log('没有找到数组数据,返回空数组');
return []; return [];
}, },
placeholder: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.AccountName')}`, placeholder: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.BelongingAccountName')}`,
}, },
rules: z.string().min(1, { rules: z.string().min(1, {
message: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.AccountName')}`, message: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.BelongingAccountName')}`,
}), }),
}, },
{ {
component: 'Switch', component: 'Input',
fieldName: 'ioTPlatformProductId',
label: $t('abp.OneNETManagement.OneNETProductId'),
disabled: true,
componentProps: { componentProps: {
class: 'w-auto', readonly: true, // 编辑时只读
},
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.OneNETProductId')}`,
}),
},
{
component: 'Input',
fieldName: 'productAccesskey',
label: $t('abp.OneNETManagement.ProductAccesskey'),
disabled: true,
componentProps: {
readonly: true, // 编辑时只读
},
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.ProductAccesskey')}`,
}),
},
{
component: 'Input',
fieldName: 'communicationAddress',
label: $t('abp.OneNETManagement.CommunicationAddress'),
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.CommunicationAddress')}`,
}),
},
{
component: 'Input',
fieldName: 'communicationAddressTLS',
label: $t('abp.OneNETManagement.CommunicationAddressTLS'),
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('common.info')}${$t('abp.OneNETManagement.CommunicationAddressTLS')}`,
}),
},
{
component: 'Input',
fieldName: 'deviceThingModelFileName',
label: $t('abp.OneNETManagement.DeviceThingModelFileName'),
componentProps: {
placeholder: '请选择文件',
readonly: true,
addonAfter: h('button', {
type: 'button',
style: 'border: none; background: #1890ff; color: white; padding: 4px 8px; border-radius: 4px; cursor: pointer;',
onClick: () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,.xlsx,.xls';
input.onchange = (e: any) => {
const file = e.target.files[0];
if (file) {
// 只显示文件名,不上传
const currentInput = document.querySelector('input[placeholder="请选择文件"]') as HTMLInputElement;
if (currentInput) {
currentInput.value = file.name;
// 触发change事件
currentInput.dispatchEvent(new Event('input', { bubbles: true }));
currentInput.dispatchEvent(new Event('change', { bubbles: true }));
// 存储文件对象到全局变量,用于后续上传
selectedFile = file;
// 调用回调函数
if (_onFileSelected) {
_onFileSelected(file);
}
}
console.log('文件已选择:', file.name, '大小:', file.size, '字节');
}
};
input.click();
}
}, '选择文件'),
},
rules: z.string().min(1, {
message: `${$t('common.pleaseSelect')}${$t('abp.OneNETManagement.DeviceThingModelFileName')}`,
}),
},
{
component: 'Input',
fieldName: 'deviceThingModelFileId',
label: '',
componentProps: {
type: 'hidden',
}, },
fieldName: 'enabled',
label: $t('common.isEnable'),
}, },
]); ]);

View File

@ -9,9 +9,12 @@ import { $t } from '#/locales';
import { addFormSchema } from './schema'; import { addFormSchema } from './schema';
import { postFilesUpload } from '#/api-client/index'; import { postFilesUpload } from '#/api-client/index';
import { useUserStore } from '@vben/stores';
const emit = defineEmits(['reload']); const emit = defineEmits(['reload']);
const userStore = useUserStore();
// //
const UploadIcon = createIconifyIcon('mdi:upload'); const UploadIcon = createIconifyIcon('mdi:upload');
const FolderIcon = createIconifyIcon('mdi:folder'); const FolderIcon = createIconifyIcon('mdi:folder');
@ -144,51 +147,40 @@ const handleUpload = async () => {
uploading.value = true; uploading.value = true;
try { try {
//
const formValues = await formApi.getValues();
console.log('Form values:', formValues);
console.log('File list:', fileList.value); console.log('File list:', fileList.value);
// FormData //
const uploadFormData = new FormData(); const files = fileList.value
.map(fileInfo => fileInfo.originFileObj)
.filter(file => file !== null && file !== undefined);
// FormData console.log('Extracted files:', files);
fileList.value.forEach((fileInfo, index) => {
if (fileInfo.originFileObj) {
console.log(`Adding file ${index}:`, fileInfo.name, fileInfo.originFileObj);
uploadFormData.append('files', fileInfo.originFileObj);
}
});
// // - 使API
if (formValues) { console.log('Sending request to /Files/Upload...');
Object.keys(formValues).forEach(key => {
if (formValues[key] !== undefined && formValues[key] !== null) {
console.log(`Adding form field ${key}:`, formValues[key]);
uploadFormData.append(key, formValues[key]);
}
});
}
// FormData
for (let [key, value] of uploadFormData.entries()) {
console.log(`FormData entry: ${key} =`, value);
}
//
const result = await postFilesUpload({ const result = await postFilesUpload({
body: uploadFormData as any, body: {
files: files
},
headers: {
'Authorization': `Bearer ${userStore.userInfo?.token}`,
},
}); });
console.log('Upload result:', result); console.log('Upload result:', result);
console.log('Response status:', result.status);
console.log('Response data:', result.data);
console.log('Response headers:', result.headers);
// //
if (result.status === 204 || result.status === 200) { if (result.status === 204 || result.status === 200) {
message.success(`文件上传成功!共上传 ${fileList.value.length} 个文件`); message.success(`文件上传成功!共上传 ${fileList.value.length} 个文件`);
fileList.value = [];
console.log('Emitting reload event...');
emit('reload'); emit('reload');
modalApi.close(); modalApi.close();
} else { } else {
message.error('文件上传失败,请重试'); message.error(`文件上传失败,状态码: ${result.status}`);
} }
} catch (error) { } catch (error) {
console.error('Upload error:', error); console.error('Upload error:', error);

View File

@ -73,6 +73,11 @@ const [AddVbenModal, addModalApi] = useVbenModal({
connectedComponent: AddModal, connectedComponent: AddModal,
}); });
const handleReload = () => {
console.log('Reloading file list...');
gridApi.reload();
};
const handleAdd = () => { const handleAdd = () => {
addModalApi.open(); addModalApi.open();
}; };
@ -144,7 +149,7 @@ const handleDown = async (row: any) => {
/> />
</template> </template>
</Grid> </Grid>
<AddVbenModal @reload="gridApi.reload" /> <AddVbenModal @reload="handleReload" />
</Page> </Page>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -69,13 +69,5 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
]); ]);
export const addFormSchema = computed(() => [ export const addFormSchema = computed(() => [
// 文件上传现在在AddModal中处理 // 文件上传现在在AddModal中处理不需要表单字段
{
component: 'Input',
fieldName: 'note',
label: $t('common.note'),
componentProps: {
placeholder: '请输入备注信息',
},
},
]); ]);