+
-
-
设备类型
+
+
+ 设备类型
+
-
-
Deviceid
-
-
- {{ item.id }}
+
+
+ 备注名称
+
+
+
+ {{ item.nickname || item.deviceName }}
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue b/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue
index a0839970c..a3a3c13fe 100644
--- a/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue
+++ b/apps/web-antdv-next/src/views/iot/device/device/modules/form.vue
@@ -1,4 +1,4 @@
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/data.ts b/apps/web-antdv-next/src/views/iot/ota/data.ts
deleted file mode 100644
index a873d4d85..000000000
--- a/apps/web-antdv-next/src/views/iot/ota/data.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import type { VbenFormSchema } from '#/adapter/form';
-import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-
-import { getSimpleProductList } from '#/api/iot/product/product';
-import { getRangePickerDefaultProps } from '#/utils';
-
-/** 新增/修改固件的表单 */
-export function useFormSchema(): VbenFormSchema[] {
- return [
- {
- component: 'Input',
- fieldName: 'id',
- dependencies: {
- triggerFields: [''],
- show: () => false,
- },
- },
- {
- fieldName: 'name',
- label: '固件名称',
- component: 'Input',
- componentProps: {
- placeholder: '请输入固件名称',
- },
- rules: 'required',
- },
- {
- fieldName: 'productId',
- label: '所属产品',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '请选择产品',
- },
- rules: 'required',
- },
- {
- fieldName: 'version',
- label: '版本号',
- component: 'Input',
- componentProps: {
- placeholder: '请输入版本号',
- },
- rules: 'required',
- },
- {
- fieldName: 'description',
- label: '固件描述',
- component: 'TextArea',
- componentProps: {
- placeholder: '请输入固件描述',
- rows: 3,
- },
- },
- {
- fieldName: 'fileUrl',
- label: '固件文件',
- component: 'Upload',
- componentProps: {
- maxCount: 1,
- accept: '.bin,.hex,.zip',
- },
- rules: 'required',
- help: '支持上传 .bin、.hex、.zip 格式的固件文件',
- },
- ];
-}
-
-/** 列表的搜索表单 */
-export function useGridFormSchema(): VbenFormSchema[] {
- return [
- {
- fieldName: 'name',
- label: '固件名称',
- component: 'Input',
- componentProps: {
- placeholder: '请输入固件名称',
- allowClear: true,
- },
- },
- {
- fieldName: 'productId',
- label: '产品',
- component: 'ApiSelect',
- componentProps: {
- api: getSimpleProductList,
- labelField: 'name',
- valueField: 'id',
- placeholder: '请选择产品',
- allowClear: true,
- },
- },
- {
- fieldName: 'createTime',
- label: '创建时间',
- component: 'RangePicker',
- componentProps: {
- ...getRangePickerDefaultProps(),
- allowClear: true,
- },
- },
- ];
-}
-
-/** 列表的字段 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
- return [
- {
- type: 'checkbox',
- width: 50,
- fixed: 'left',
- },
- {
- field: 'id',
- title: '固件编号',
- width: 100,
- },
- {
- field: 'name',
- title: '固件名称',
- minWidth: 150,
- },
- {
- field: 'version',
- title: '版本号',
- width: 120,
- },
- {
- field: 'productName',
- title: '所属产品',
- minWidth: 150,
- },
- {
- field: 'description',
- title: '固件描述',
- minWidth: 200,
- showOverflow: 'tooltip',
- },
- {
- field: 'fileSize',
- title: '文件大小',
- width: 120,
- formatter: ({ cellValue }) => {
- if (!cellValue) return '-';
- const kb = cellValue / 1024;
- if (kb < 1024) return `${kb.toFixed(2)} KB`;
- return `${(kb / 1024).toFixed(2)} MB`;
- },
- },
- {
- field: 'status',
- title: '状态',
- width: 100,
- formatter: ({ cellValue }) => {
- return cellValue === 1 ? '启用' : '禁用';
- },
- },
- {
- field: 'createTime',
- title: '创建时间',
- width: 180,
- formatter: 'formatDateTime',
- },
- {
- title: '操作',
- width: 160,
- fixed: 'right',
- slots: { default: 'actions' },
- },
- ];
-}
diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts b/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts
index 298cd340b..8174e6b2b 100644
--- a/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts
+++ b/apps/web-antdv-next/src/views/iot/ota/firmware/data.ts
@@ -1,9 +1,44 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { IotProductApi } from '#/api/iot/product/product';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { formatDateTime } from '@vben/utils';
import { getSimpleProductList } from '#/api/iot/product/product';
import { getRangePickerDefaultProps } from '#/utils';
+/** 关联数据 */
+let productList: IotProductApi.Product[] = [];
+getSimpleProductList().then((data) => (productList = data));
+
+/** 根据产品 ID 取产品名称 */
+export function getProductName(productId?: number): string {
+ if (!productId) {
+ return '-';
+ }
+ return productList.find((product) => product.id === productId)?.name || '-';
+}
+
+/** 固件详情的描述字段 */
+export function useDetailSchema(): DescriptionItemSchema[] {
+ return [
+ { field: 'name', label: '固件名称' },
+ {
+ field: 'productName',
+ label: '所属产品',
+ render: (val) => val || '-',
+ },
+ { field: 'version', label: '固件版本' },
+ {
+ field: 'createTime',
+ label: '创建时间',
+ render: (val) => (val ? (formatDateTime(val) as string) : '-'),
+ },
+ { field: 'description', label: '固件描述', span: 2 },
+ ];
+}
+
/** 新增/修改固件的表单 */
export function useFormSchema(): VbenFormSchema[] {
return [
@@ -34,7 +69,13 @@ export function useFormSchema(): VbenFormSchema[] {
valueField: 'id',
placeholder: '请选择产品',
},
- rules: 'required',
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({
+ disabled: !!values.id,
+ }),
+ rules: (values) => (values.id ? null : 'required'),
+ },
},
{
fieldName: 'version',
@@ -43,7 +84,13 @@ export function useFormSchema(): VbenFormSchema[] {
componentProps: {
placeholder: '请输入版本号',
},
- rules: 'required',
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({
+ disabled: !!values.id,
+ }),
+ rules: (values) => (values.id ? null : 'required'),
+ },
},
{
fieldName: 'description',
@@ -60,11 +107,17 @@ export function useFormSchema(): VbenFormSchema[] {
component: 'FileUpload',
componentProps: {
maxNumber: 1,
- accept: ['bin', 'hex', 'zip'],
+ accept: ['bin', 'zip', 'pdf'],
maxSize: 50,
- helpText: '支持上传 .bin、.hex、.zip 格式的固件文件,最大 50MB',
+ helpText: '支持上传 .bin、.zip、.pdf 格式的固件文件,最大 50MB',
+ },
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({
+ disabled: !!values.id,
+ }),
+ rules: (values) => (values.id ? null : 'required'),
},
- rules: 'required',
},
];
}
@@ -108,7 +161,6 @@ export function useGridFormSchema(): VbenFormSchema[] {
/** 列表的字段 */
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
- { type: 'checkbox', width: 40 },
{
field: 'id',
title: '固件编号',
@@ -133,7 +185,7 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
field: 'productId',
title: '所属产品',
minWidth: 150,
- slots: { default: 'product' },
+ slots: { default: 'productName' },
},
{
field: 'fileUrl',
diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue
new file mode 100644
index 000000000..7f9ff9a36
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/index.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue
new file mode 100644
index 000000000..6f40f3e45
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/firmware/detail/modules/info.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue b/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue
index f39d4ae67..ade6c08cf 100644
--- a/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue
+++ b/apps/web-antdv-next/src/views/iot/ota/firmware/index.vue
@@ -13,10 +13,8 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
import { $t } from '#/locales';
-import OtaFirmwareForm from '../modules/ota-firmware-form.vue';
-import { useGridColumns, useGridFormSchema } from './data';
-
-defineOptions({ name: 'IoTOtaFirmware' });
+import { getProductName, useGridColumns, useGridFormSchema } from './data';
+import OtaFirmwareForm from './modules/form.vue';
const { push } = useRouter();
@@ -47,7 +45,7 @@ async function handleDelete(row: IoTOtaFirmwareApi.Firmware) {
duration: 0,
});
try {
- await deleteOtaFirmware(row.id as number);
+ await deleteOtaFirmware(row.id!);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
});
@@ -62,6 +60,11 @@ function handleDetail(row: IoTOtaFirmwareApi.Firmware) {
push({ name: 'IoTOtaFirmwareDetail', params: { id: row.id } });
}
+/** 跳转到产品详情 */
+function handleOpenProductDetail(productId: number) {
+ push({ name: 'IoTProductDetail', params: { id: productId } });
+}
+
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -104,17 +107,23 @@ const [Grid, gridApi] = useVbenVxeGrid({
label: $t('ui.actionTitle.create', ['固件']),
type: 'primary',
icon: ACTION_ICON.ADD,
+ auth: ['iot:ota-firmware:create'],
onClick: handleCreate,
},
]"
/>
-
-
-
- {{ row.productName || '未知产品' }}
+
+
+
+ {{ getProductName(row.productId) }}
+
+ -
-
无文件
-
-
+
-
-
-
-
-
-
-
- {{ firmware?.name }}
-
-
- {{ firmware?.productName }}
-
-
- {{ firmware?.version }}
-
-
- {{
- firmware?.createTime
- ? formatDate(firmware.createTime, 'YYYY-MM-DD HH:mm:ss')
- : '-'
- }}
-
-
- {{ firmware?.description }}
-
-
-
-
-
-
-
-
-
-
- {{
- Object.values(firmwareStatistics).reduce(
- (sum: number, count) => sum + (count || 0),
- 0,
- ) || 0
- }}
-
-
升级设备总数
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.PENDING.value] ||
- 0
- }}
-
-
待推送
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0
- }}
-
-
已推送
-
-
-
-
-
- {{
- firmwareStatistics[
- IoTOtaTaskRecordStatusEnum.UPGRADING.value
- ] || 0
- }}
-
-
正在升级
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] ||
- 0
- }}
-
-
升级成功
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] ||
- 0
- }}
-
-
升级失败
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] ||
- 0
- }}
-
-
升级取消
-
-
-
-
-
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/modules/firmware-detail/index.vue b/apps/web-antdv-next/src/views/iot/ota/modules/firmware-detail/index.vue
deleted file mode 100644
index 1e4cc3adf..000000000
--- a/apps/web-antdv-next/src/views/iot/ota/modules/firmware-detail/index.vue
+++ /dev/null
@@ -1,196 +0,0 @@
-
-
-
-
-
-
-
-
- {{ firmware?.name }}
-
-
- {{ firmware?.productName }}
-
-
- {{ firmware?.version }}
-
-
- {{
- firmware?.createTime
- ? formatDate(firmware.createTime, 'YYYY-MM-DD HH:mm:ss')
- : '-'
- }}
-
-
- {{ firmware?.description }}
-
-
-
-
-
-
-
-
-
-
- {{
- Object.values(firmwareStatistics).reduce(
- (sum: number, count) => sum + (count || 0),
- 0,
- ) || 0
- }}
-
-
升级设备总数
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.PENDING.value] ||
- 0
- }}
-
-
待推送
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0
- }}
-
-
已推送
-
-
-
-
-
- {{
- firmwareStatistics[
- IoTOtaTaskRecordStatusEnum.UPGRADING.value
- ] || 0
- }}
-
-
正在升级
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] ||
- 0
- }}
-
-
升级成功
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] ||
- 0
- }}
-
-
升级失败
-
-
-
-
-
- {{
- firmwareStatistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] ||
- 0
- }}
-
-
升级取消
-
-
-
-
-
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-detail.vue b/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-detail.vue
deleted file mode 100644
index d45630df9..000000000
--- a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-detail.vue
+++ /dev/null
@@ -1,411 +0,0 @@
-
-
-
-
-
-
-
-
- {{ task.id }}
-
- {{ task.name }}
-
-
- 全部设备
- 指定设备
- {{ task.deviceScope }}
-
-
- 待执行
- 执行中
- 已完成
- 已取消
- {{ task.status }}
-
-
- {{
- task.createTime
- ? formatDate(task.createTime, 'YYYY-MM-DD HH:mm:ss')
- : '-'
- }}
-
-
- {{ task.description }}
-
-
-
-
-
-
-
-
-
-
- {{
- Object.values(taskStatistics).reduce(
- (sum, count) => sum + (count || 0),
- 0,
- ) || 0
- }}
-
-
升级设备总数
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.PENDING.value] || 0
- }}
-
-
待推送
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0
- }}
-
-
已推送
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.UPGRADING.value] ||
- 0
- }}
-
-
正在升级
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] || 0
- }}
-
-
升级成功
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] || 0
- }}
-
-
升级失败
-
-
-
-
-
- {{
- taskStatistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0
- }}
-
-
升级取消
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 待推送
- 已推送
-
- 升级中
-
- 成功
- 失败
- 已取消
- {{ record.status }}
-
-
-
-
- {{ record.progress }}%
-
-
-
-
-
- 取消
-
-
-
-
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-form.vue b/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-form.vue
deleted file mode 100644
index 0a153ca38..000000000
--- a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-form.vue
+++ /dev/null
@@ -1,173 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-list.vue b/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-list.vue
deleted file mode 100644
index f3a815bce..000000000
--- a/apps/web-antdv-next/src/views/iot/ota/modules/task/ota-task-list.vue
+++ /dev/null
@@ -1,257 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 全部设备
- 指定设备
- {{ record.deviceScope }}
-
-
-
-
- {{ record.deviceSuccessCount }}/{{ record.deviceTotalCount }}
-
-
-
-
- 待执行
- 执行中
- 已完成
- 已取消
- {{ record.status }}
-
-
-
-
-
- 详情
-
- 取消
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/data.ts b/apps/web-antdv-next/src/views/iot/ota/task/data.ts
new file mode 100644
index 000000000..5fc45a604
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/data.ts
@@ -0,0 +1,163 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { DescriptionItemSchema } from '#/components/description';
+
+import { DICT_TYPE, IoTOtaTaskDeviceScopeEnum } from '@vben/constants';
+import { getDictLabel, getDictOptions } from '@vben/hooks';
+import { formatDateTime } from '@vben/utils';
+
+/** 任务详情的描述字段 */
+export function useDetailSchema(): DescriptionItemSchema[] {
+ return [
+ { field: 'id', label: '任务编号' },
+ { field: 'name', label: '任务名称' },
+ {
+ field: 'deviceScope',
+ label: '升级范围',
+ render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, val),
+ },
+ {
+ field: 'status',
+ label: '任务状态',
+ render: (val) => getDictLabel(DICT_TYPE.IOT_OTA_TASK_STATUS, val),
+ },
+ {
+ field: 'createTime',
+ label: '创建时间',
+ render: (val) => (val ? (formatDateTime(val) as string) : '-'),
+ },
+ { field: 'description', label: '任务描述', span: 3 },
+ ];
+}
+
+/** 新增升级任务的表单 */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ component: 'Input',
+ fieldName: 'firmwareId',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '任务名称',
+ component: 'Input',
+ componentProps: {
+ placeholder: '请输入任务名称',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'description',
+ label: '任务描述',
+ component: 'TextArea',
+ componentProps: {
+ placeholder: '请输入任务描述',
+ rows: 3,
+ },
+ },
+ {
+ fieldName: 'deviceScope',
+ label: '升级范围',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, 'number'),
+ placeholder: '请选择升级范围',
+ },
+ defaultValue: IoTOtaTaskDeviceScopeEnum.ALL.value,
+ rules: 'required',
+ },
+ {
+ fieldName: 'deviceIds',
+ label: '选择设备',
+ component: 'Select',
+ componentProps: {
+ mode: 'multiple',
+ placeholder: '请选择设备',
+ showSearch: true,
+ filterOption: true,
+ optionFilterProp: 'label',
+ },
+ defaultValue: [],
+ dependencies: {
+ triggerFields: ['deviceScope'],
+ show: (values) =>
+ values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value,
+ rules: (values) =>
+ values.deviceScope === IoTOtaTaskDeviceScopeEnum.SELECT.value
+ ? 'required'
+ : null,
+ },
+ },
+ ];
+}
+
+/** 任务列表的字段 */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ {
+ field: 'id',
+ title: '任务编号',
+ width: 80,
+ align: 'center',
+ },
+ {
+ field: 'name',
+ title: '任务名称',
+ minWidth: 150,
+ align: 'center',
+ },
+ {
+ field: 'deviceScope',
+ title: '升级范围',
+ width: 110,
+ align: 'center',
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE },
+ },
+ },
+ {
+ field: 'progress',
+ title: '升级进度',
+ width: 110,
+ align: 'center',
+ formatter: ({ row }) =>
+ `${row.deviceSuccessCount || 0}/${row.deviceTotalCount || 0}`,
+ },
+ {
+ field: 'createTime',
+ title: '创建时间',
+ width: 180,
+ align: 'center',
+ formatter: 'formatDateTime',
+ },
+ {
+ field: 'description',
+ title: '任务描述',
+ minWidth: 150,
+ align: 'center',
+ showOverflow: 'tooltip',
+ },
+ {
+ field: 'status',
+ title: '任务状态',
+ width: 110,
+ align: 'center',
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.IOT_OTA_TASK_STATUS },
+ },
+ },
+ {
+ title: '操作',
+ width: 120,
+ fixed: 'right',
+ align: 'center',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/modules/detail.vue b/apps/web-antdv-next/src/views/iot/ota/task/modules/detail.vue
new file mode 100644
index 000000000..9e0492c1c
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/modules/detail.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/modules/form.vue b/apps/web-antdv-next/src/views/iot/ota/task/modules/form.vue
new file mode 100644
index 000000000..a1341ff54
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/modules/form.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/modules/info.vue b/apps/web-antdv-next/src/views/iot/ota/task/modules/info.vue
new file mode 100644
index 000000000..dcd365156
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/modules/info.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/modules/list.vue b/apps/web-antdv-next/src/views/iot/ota/task/modules/list.vue
new file mode 100644
index 000000000..d83bf0150
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/modules/list.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/modules/statistics.vue b/apps/web-antdv-next/src/views/iot/ota/task/modules/statistics.vue
new file mode 100644
index 000000000..0bc9c8528
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/modules/statistics.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+ {{ item.value }}
+
+
{{ item.label }}
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/record/data.ts b/apps/web-antdv-next/src/views/iot/ota/task/record/data.ts
new file mode 100644
index 000000000..a07093b09
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/record/data.ts
@@ -0,0 +1,59 @@
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+
+import { DICT_TYPE } from '@vben/constants';
+
+/** 升级记录的列表字段 */
+export function useGridColumns(): VxeTableGridOptions['columns'] {
+ return [
+ {
+ field: 'deviceName',
+ title: '设备名称',
+ minWidth: 150,
+ align: 'center',
+ },
+ {
+ field: 'fromFirmwareVersion',
+ title: '当前版本',
+ width: 120,
+ align: 'center',
+ },
+ {
+ field: 'status',
+ title: '升级状态',
+ width: 120,
+ align: 'center',
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS },
+ },
+ },
+ {
+ field: 'progress',
+ title: '升级进度',
+ width: 120,
+ align: 'center',
+ formatter: ({ row }) => `${row.progress || 0}%`,
+ },
+ {
+ field: 'description',
+ title: '状态描述',
+ minWidth: 150,
+ align: 'center',
+ showOverflow: 'tooltip',
+ },
+ {
+ field: 'updateTime',
+ title: '更新时间',
+ width: 180,
+ align: 'center',
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '操作',
+ width: 80,
+ fixed: 'right',
+ align: 'center',
+ slots: { default: 'actions' },
+ },
+ ];
+}
diff --git a/apps/web-antdv-next/src/views/iot/ota/task/record/modules/list.vue b/apps/web-antdv-next/src/views/iot/ota/task/record/modules/list.vue
new file mode 100644
index 000000000..106acb414
--- /dev/null
+++ b/apps/web-antdv-next/src/views/iot/ota/task/record/modules/list.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/product/category/data.ts b/apps/web-antdv-next/src/views/iot/product/category/data.ts
index cac6d9a89..b80510aac 100644
--- a/apps/web-antdv-next/src/views/iot/product/category/data.ts
+++ b/apps/web-antdv-next/src/views/iot/product/category/data.ts
@@ -1,5 +1,6 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { IotProductCategoryApi } from '#/api/iot/product/category';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
@@ -35,8 +36,8 @@ export function useFormSchema(): VbenFormSchema[] {
label: '分类排序',
component: 'InputNumber',
componentProps: {
+ class: '!w-full',
placeholder: '请输入分类排序',
- class: 'w-full',
min: 0,
precision: 0,
},
@@ -91,10 +92,10 @@ export function useGridFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
+export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
- type: 'seq',
+ field: 'id',
title: 'ID',
width: 80,
},
diff --git a/apps/web-antdv-next/src/views/iot/product/category/index.vue b/apps/web-antdv-next/src/views/iot/product/category/index.vue
index f660110db..31995c14b 100644
--- a/apps/web-antdv-next/src/views/iot/product/category/index.vue
+++ b/apps/web-antdv-next/src/views/iot/product/category/index.vue
@@ -16,8 +16,6 @@ import { $t } from '#/locales';
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
-defineOptions({ name: 'IoTProductCategory' });
-
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
@@ -87,7 +85,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
-
+
([]);
/** 处理选择变化 */
-function handleChange(value?: number) {
- emit('update:modelValue', value);
- emit('change', value);
+function handleChange(value: any) {
+ emit('update:modelValue', value as number | undefined);
+ emit('change', value as number | undefined);
}
/** 获取产品列表 */
@@ -47,11 +47,18 @@ onMounted(() => {
diff --git a/apps/web-antdv-next/src/views/iot/product/product/data.ts b/apps/web-antdv-next/src/views/iot/product/product/data.ts
index f4ef808fc..df0f4a0d3 100644
--- a/apps/web-antdv-next/src/views/iot/product/product/data.ts
+++ b/apps/web-antdv-next/src/views/iot/product/product/data.ts
@@ -1,10 +1,10 @@
-import type { VbenFormSchema } from '#/adapter/form';
+import type { VbenFormApi, VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
-import type { IotProductCategoryApi } from '#/api/iot/product/category';
+import type { IotProductApi } from '#/api/iot/product/product';
import { h } from 'vue';
-import { DICT_TYPE } from '@vben/constants';
+import { DeviceTypeEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'antdv-next';
@@ -12,13 +12,9 @@ import { Button } from 'antdv-next';
import { z } from '#/adapter/form';
import { getSimpleProductCategoryList } from '#/api/iot/product/category';
-/** 产品分类列表缓存 */
-let categoryList: IotProductCategoryApi.ProductCategory[] = [];
-getSimpleProductCategoryList().then((data) => (categoryList = data));
-
/** 基础表单字段(不含图标、图片、描述) */
export function useBasicFormSchema(
- formApi?: any,
+ formApi?: VbenFormApi,
generateProductKey?: () => string,
): VbenFormSchema[] {
return [
@@ -114,6 +110,13 @@ export function useBasicFormSchema(
buttonStyle: 'solid',
optionType: 'button',
},
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({
+ // 编辑时设备类型不可改
+ disabled: !!values.id,
+ }),
+ },
rules: 'required',
},
{
@@ -124,6 +127,14 @@ export function useBasicFormSchema(
options: getDictOptions(DICT_TYPE.IOT_NET_TYPE, 'number'),
placeholder: '请选择联网方式',
},
+ // 网关子设备走网关联网,不需要联网方式
+ dependencies: {
+ triggerFields: ['deviceType'],
+ show: (values) =>
+ [DeviceTypeEnum.DEVICE, DeviceTypeEnum.GATEWAY].includes(
+ values.deviceType,
+ ),
+ },
rules: 'required',
},
{
@@ -134,6 +145,7 @@ export function useBasicFormSchema(
options: getDictOptions(DICT_TYPE.IOT_PROTOCOL_TYPE, 'string'),
placeholder: '请选择协议类型',
},
+ defaultValue: 'mqtt',
rules: 'required',
},
{
@@ -144,22 +156,10 @@ export function useBasicFormSchema(
options: getDictOptions(DICT_TYPE.IOT_SERIALIZE_TYPE, 'string'),
placeholder: '请选择序列化类型',
},
+ defaultValue: 'json',
help: 'iot-gateway-server 默认根据接入的协议类型确定数据格式,仅 MQTT、EMQX 协议支持自定义序列化类型',
rules: 'required',
},
- // TODO @haohao:这个貌似不需要?!
- {
- fieldName: 'status',
- label: '产品状态',
- component: 'RadioGroup',
- componentProps: {
- options: getDictOptions(DICT_TYPE.IOT_PRODUCT_STATUS, 'number'),
- buttonStyle: 'solid',
- optionType: 'button',
- },
- defaultValue: 0,
- rules: 'required',
- },
];
}
@@ -180,11 +180,7 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
{
fieldName: 'icon',
label: '产品图标',
- component: 'IconPicker',
- componentProps: {
- placeholder: '请选择产品图标',
- prefix: 'carbon',
- },
+ component: 'ImageUpload',
},
{
fieldName: 'picUrl',
@@ -204,7 +200,7 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
}
/** 列表的字段 */
-export function useGridColumns(): VxeTableGridOptions['columns'] {
+export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
@@ -217,11 +213,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
minWidth: 150,
},
{
- field: 'categoryId',
+ field: 'categoryName',
title: '品类',
minWidth: 120,
- formatter: ({ cellValue }) =>
- categoryList.find((c) => c.id === cellValue)?.name || '未分类',
+ formatter: ({ row }) => row.categoryName || '未分类',
},
{
field: 'deviceType',
@@ -248,15 +243,6 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
name: 'CellImage',
},
},
- {
- field: 'status',
- title: '产品状态',
- minWidth: 100,
- cellRender: {
- name: 'CellDict',
- props: { type: DICT_TYPE.IOT_PRODUCT_STATUS },
- },
- },
{
field: 'createTime',
title: '创建时间',
diff --git a/apps/web-antdv-next/src/views/iot/product/product/detail/index.vue b/apps/web-antdv-next/src/views/iot/product/product/detail/index.vue
index 8425e44ba..a6f4a59c2 100644
--- a/apps/web-antdv-next/src/views/iot/product/product/detail/index.vue
+++ b/apps/web-antdv-next/src/views/iot/product/product/detail/index.vue
@@ -1,12 +1,13 @@
-
@@ -90,27 +96,41 @@ function handleUnpublish(product: IotProductApi.Product) {
{{ product.name }}
-
+
-
-
+
+
- 撤销发布
-
+
+
+
+
+
diff --git a/apps/web-antdv-next/src/views/iot/product/product/detail/modules/info.vue b/apps/web-antdv-next/src/views/iot/product/product/detail/modules/info.vue
index e8ed7c102..ac24e0e27 100644
--- a/apps/web-antdv-next/src/views/iot/product/product/detail/modules/info.vue
+++ b/apps/web-antdv-next/src/views/iot/product/product/detail/modules/info.vue
@@ -5,7 +5,13 @@ import { ref } from 'vue';
import { DeviceTypeEnum, DICT_TYPE } from '@vben/constants';
-import { Button, Card, Descriptions, message } from 'antdv-next';
+import {
+ Button,
+ Card,
+ Descriptions,
+ DescriptionsItem,
+ message,
+} from 'antdv-next';
import { DictTag } from '#/components/dict-tag';
@@ -19,7 +25,9 @@ const showProductSecret = ref(false); // 是否显示产品密钥
/** 格式化日期 */
function formatDate(date?: Date | string) {
- if (!date) return '-';
+ if (!date) {
+ return '-';
+ }
return new Date(date).toLocaleString('zh-CN');
}
@@ -74,21 +82,22 @@ async function copyToClipboard(text: string) {
-
+
{{ product.productSecret }}
********