feat(iot):优化 ota 的代码风格(v3)
parent
a70fcc9616
commit
9d54f60b10
|
|
@ -37,7 +37,7 @@ const routes: RouteRecordRaw[] = [
|
||||||
activePath: '/iot/ota',
|
activePath: '/iot/ota',
|
||||||
},
|
},
|
||||||
component: () =>
|
component: () =>
|
||||||
import('#/views/iot/ota/modules/firmware-detail/index.vue'),
|
import('#/views/iot/ota/firmware/detail/index.vue'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,9 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
export function useGridColumns(
|
||||||
|
getProductName?: (productId: number) => string | undefined,
|
||||||
|
): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
{ type: 'checkbox', width: 40 },
|
{ type: 'checkbox', width: 40 },
|
||||||
{
|
{
|
||||||
|
|
@ -147,13 +149,13 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
title: '固件描述',
|
title: '固件描述',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
},
|
},
|
||||||
// TODO DONE @AI:vben 用 row.productName(后端 VO 已返回);vue3 + ep 用 getProductName(productId) 本地查表;vben 更直接,且 IoTOtaFirmwareApi.Firmware.productName 已是 API 契约
|
// TODO DONE @AI:后端 firmware 没返回 productName,formatter 调 getProductName resolver;resolver + productList 反应式状态由 index.vue 注入(对齐 infra/codegen 模式)
|
||||||
// TODO @AI:实际后端没读取哈。看看怎么对齐下 vue3 + ep
|
|
||||||
{
|
{
|
||||||
field: 'productName',
|
field: 'productId',
|
||||||
title: '所属产品',
|
title: '所属产品',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
formatter: ({ row }) => row.productName || '未知产品',
|
formatter: ({ cellValue }) =>
|
||||||
|
getProductName?.(cellValue) || (cellValue ? '加载中...' : '-'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'fileUrl',
|
field: 'fileUrl',
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { getOtaFirmware } from '#/api/iot/ota/firmware';
|
import { getOtaFirmware } from '#/api/iot/ota/firmware';
|
||||||
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
import { getOtaTaskRecordStatusStatistics } from '#/api/iot/ota/task/record';
|
||||||
|
|
||||||
import OtaTaskList from '../../task/modules/list.vue';
|
import OtaTaskList from '../../task/modules/list.vue';
|
||||||
import UpgradeStatistics from '../../task/modules/upgrade-statistics.vue';
|
import UpgradeStatistics from '../../task/modules/statistics.vue';
|
||||||
import FirmwareInfo from './modules/info.vue';
|
import FirmwareInfo from './modules/info.vue';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
@ -50,23 +52,23 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-4">
|
<Page>
|
||||||
<!-- 固件信息 -->
|
<!-- 固件信息 -->
|
||||||
<FirmwareInfo :firmware="firmware" :loading="firmwareLoading" />
|
<FirmwareInfo :firmware="firmware" :loading="firmwareLoading" />
|
||||||
|
|
||||||
<!-- 升级设备统计 -->
|
<!-- 升级设备统计 -->
|
||||||
<!-- TODO @AI:需要和上面的 FirmwareInfo 有间隙 -->
|
<div class="mt-4">
|
||||||
<UpgradeStatistics
|
<UpgradeStatistics
|
||||||
:loading="firmwareStatisticsLoading"
|
:loading="firmwareStatisticsLoading"
|
||||||
:statistics="firmwareStatistics"
|
:statistics="firmwareStatistics"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<!-- 任务管理 -->
|
<!-- 任务管理 -->
|
||||||
<OtaTaskList
|
<div v-if="firmware?.productId" class="mt-4">
|
||||||
v-if="firmware?.productId"
|
<OtaTaskList
|
||||||
:firmware-id="firmwareId"
|
:firmware-id="firmwareId"
|
||||||
:product-id="firmware.productId"
|
:product-id="firmware.productId"
|
||||||
@success="getStatistics"
|
@success="getStatistics"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
||||||
|
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useDescription } from '#/components/description';
|
||||||
|
|
||||||
|
import { useDetailSchema } from '../../data';
|
||||||
|
|
||||||
|
/** IoT OTA 固件基本信息 */
|
||||||
|
// TODO DONE @AI:不需要;script setup 默认用文件名生成 __name(这里是 info),devtools 能识别;跟之前 upgrade-statistics / list 处理一致
|
||||||
|
defineProps<{
|
||||||
|
firmware: IoTOtaFirmwareApi.Firmware;
|
||||||
|
loading?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const [Description] = useDescription({
|
||||||
|
bordered: true,
|
||||||
|
column: 3,
|
||||||
|
schema: useDetailSchema(),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card title="固件信息" :loading="loading">
|
||||||
|
<Description :data="firmware" />
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
import type { IoTOtaFirmwareApi } from '#/api/iot/ota/firmware';
|
||||||
|
import type { IotProductApi } from '#/api/iot/product/product';
|
||||||
|
|
||||||
|
import { onMounted, ref } 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';
|
||||||
|
|
@ -11,6 +13,7 @@ import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
|
||||||
|
import { getSimpleProductList } from '#/api/iot/product/product';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import OtaFirmwareForm from './modules/form.vue';
|
import OtaFirmwareForm from './modules/form.vue';
|
||||||
|
|
@ -18,6 +21,18 @@ import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
|
|
||||||
|
const productList = ref<IotProductApi.Product[]>([]);
|
||||||
|
|
||||||
|
/** 根据产品编号查找名称 */
|
||||||
|
// TODO @AI:需要类似别的模块,拿到 data.ts 里么?
|
||||||
|
function getProductName(productId: number | undefined) {
|
||||||
|
if (!productId) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const product = productList.value.find((p) => p.id === productId);
|
||||||
|
return product ? product.name : '加载中...';
|
||||||
|
}
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: OtaFirmwareForm,
|
connectedComponent: OtaFirmwareForm,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
|
|
@ -65,7 +80,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(getProductName),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
|
|
@ -89,6 +104,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<IoTOtaFirmwareApi.Firmware>,
|
} as VxeTableGridOptions<IoTOtaFirmwareApi.Firmware>,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** 初始化加载产品列表 */
|
||||||
|
onMounted(async () => {
|
||||||
|
productList.value = (await getSimpleProductList()) || [];
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,37 @@
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
import { getDictOptions } from '@vben/hooks';
|
import { getDictLabel, getDictOptions } from '@vben/hooks';
|
||||||
|
import { formatDateTime } from '@vben/utils';
|
||||||
|
|
||||||
import { IoTOtaTaskDeviceScopeEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskDeviceScopeEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
|
/** 任务详情的描述字段 */
|
||||||
|
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[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
|
|
@ -71,21 +97,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 任务列表的搜索表单 */
|
// TODO DONE @AI:任务列表内嵌固件详情页,单字段搜索意义不大,已去掉搜索表单
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
fieldName: 'name',
|
|
||||||
label: '任务名称',
|
|
||||||
component: 'Input',
|
|
||||||
componentProps: {
|
|
||||||
placeholder: '请输入任务名称',
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 任务列表的字段 */
|
/** 任务列表的字段 */
|
||||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
|
|
@ -153,58 +165,4 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 升级记录的列表字段 */
|
// TODO DONE @AI:record schema 已挪到 task/record/data.ts;list 也独立成 task/record/modules/list.vue
|
||||||
export function useRecordGridColumns(): 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' },
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,21 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO @AI:是不是拆分下,task 里面在新建一个 record/modules 里;这样固件、任务、记录,都拆开了,也看起来更清晰。
|
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|
||||||
import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
|
import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
|
||||||
import type { IoTOtaTaskRecordApi } from '#/api/iot/ota/task/record';
|
|
||||||
|
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { DICT_TYPE } from '@vben/constants';
|
|
||||||
import { getDictLabel } from '@vben/hooks';
|
|
||||||
import { formatDate } from '@vben/utils';
|
|
||||||
|
|
||||||
import { Card, Descriptions, message, Tabs, Tag } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { getOtaTask } from '#/api/iot/ota/task';
|
import { getOtaTask } from '#/api/iot/ota/task';
|
||||||
import {
|
import {
|
||||||
cancelOtaTaskRecord,
|
|
||||||
getOtaTaskRecordPage,
|
|
||||||
getOtaTaskRecordStatusStatistics,
|
getOtaTaskRecordStatusStatistics,
|
||||||
} from '#/api/iot/ota/task/record';
|
} from '#/api/iot/ota/task/record';
|
||||||
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
|
||||||
|
|
||||||
import { useRecordGridColumns } from '../data';
|
import OtaTaskRecordList from '../record/modules/list.vue';
|
||||||
import UpgradeStatistics from './upgrade-statistics.vue';
|
import TaskInfo from './info.vue';
|
||||||
|
import UpgradeStatistics from './statistics.vue';
|
||||||
|
|
||||||
/** OTA 任务详情组件 */
|
// TODO @AI:是不是defineOptions、升级任务详情 注释需要?
|
||||||
|
/** IoT OTA 升级任务详情 */
|
||||||
defineOptions({ name: 'IoTOtaTaskDetail' });
|
defineOptions({ name: 'IoTOtaTaskDetail' });
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
@ -37,20 +27,6 @@ const task = ref<IoTOtaTaskApi.Task>({} as IoTOtaTaskApi.Task);
|
||||||
const taskStatisticsLoading = ref(false);
|
const taskStatisticsLoading = ref(false);
|
||||||
const taskStatistics = ref<Record<string, number>>({});
|
const taskStatistics = ref<Record<string, number>>({});
|
||||||
|
|
||||||
const activeTab = ref('');
|
|
||||||
|
|
||||||
/** 状态标签配置 */
|
|
||||||
const statusTabs = computed(() => {
|
|
||||||
const tabs = [{ key: '', label: '全部设备' }];
|
|
||||||
Object.values(IoTOtaTaskRecordStatusEnum).forEach((status) => {
|
|
||||||
tabs.push({
|
|
||||||
key: status.value.toString(),
|
|
||||||
label: status.label,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return tabs;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 获取任务详情 */
|
/** 获取任务详情 */
|
||||||
async function getTaskInfo() {
|
async function getTaskInfo() {
|
||||||
if (!taskId.value) {
|
if (!taskId.value) {
|
||||||
|
|
@ -80,54 +56,13 @@ async function getStatistics() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 切换标签 */
|
/** 单条记录取消后,刷新任务信息和统计 */
|
||||||
async function handleTabChange(tabKey: number | string) {
|
async function handleRecordCancelled() {
|
||||||
activeTab.value = String(tabKey);
|
|
||||||
await gridApi.query();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 取消升级 */
|
|
||||||
async function handleCancelUpgrade(record: IoTOtaTaskRecordApi.TaskRecord) {
|
|
||||||
await cancelOtaTaskRecord(record.id as number);
|
|
||||||
message.success('取消成功');
|
|
||||||
await gridApi.query();
|
|
||||||
await getStatistics();
|
await getStatistics();
|
||||||
await getTaskInfo();
|
await getTaskInfo();
|
||||||
emit('success');
|
emit('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
|
||||||
gridOptions: {
|
|
||||||
columns: useRecordGridColumns(),
|
|
||||||
height: 400,
|
|
||||||
rowConfig: {
|
|
||||||
keyField: 'id',
|
|
||||||
isHover: true,
|
|
||||||
},
|
|
||||||
pagerConfig: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
proxyConfig: {
|
|
||||||
ajax: {
|
|
||||||
query: async ({ page }) => {
|
|
||||||
if (!taskId.value) {
|
|
||||||
return { list: [], total: 0 };
|
|
||||||
}
|
|
||||||
return await getOtaTaskRecordPage({
|
|
||||||
pageNo: page.currentPage,
|
|
||||||
pageSize: page.pageSize,
|
|
||||||
taskId: taskId.value,
|
|
||||||
status: activeTab.value === '' ? undefined : Number(activeTab.value),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
toolbarConfig: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
} as VxeTableGridOptions<IoTOtaTaskRecordApi.TaskRecord>,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onOpenChange(isOpen: boolean) {
|
async onOpenChange(isOpen: boolean) {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
|
|
@ -138,94 +73,30 @@ const [Modal, modalApi] = useVbenModal({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
taskId.value = data.id;
|
taskId.value = data.id;
|
||||||
activeTab.value = '';
|
|
||||||
await Promise.all([getTaskInfo(), getStatistics()]);
|
await Promise.all([getTaskInfo(), getStatistics()]);
|
||||||
await gridApi.query();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Modal title="升级任务详情" class="w-5/6" :show-cancel-button="false" :show-confirm-button="false">
|
<Modal
|
||||||
<!-- TODO @AI:是不是不用 p-4 外面的? -->
|
title="升级任务详情"
|
||||||
<div class="p-4">
|
class="w-5/6"
|
||||||
<!-- 任务信息 -->
|
:show-cancel-button="false"
|
||||||
<!-- TODO @AI:需要抽成一个小 vue 组件。也使用 description 组件; -->
|
:show-confirm-button="false"
|
||||||
<Card title="任务信息" class="mb-5" :loading="taskLoading">
|
>
|
||||||
<Descriptions :column="3" bordered>
|
<!-- 任务信息 -->
|
||||||
<Descriptions.Item label="任务编号">{{ task.id }}</Descriptions.Item>
|
<TaskInfo :task="task" :loading="taskLoading" />
|
||||||
<Descriptions.Item label="任务名称">
|
<!-- 升级设备统计 -->
|
||||||
{{ task.name }}
|
<div class="mt-4">
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="升级范围">
|
|
||||||
<Tag>
|
|
||||||
{{ getDictLabel(DICT_TYPE.IOT_OTA_TASK_DEVICE_SCOPE, task.deviceScope) }}
|
|
||||||
</Tag>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="任务状态">
|
|
||||||
<Tag>
|
|
||||||
{{ getDictLabel(DICT_TYPE.IOT_OTA_TASK_STATUS, task.status) }}
|
|
||||||
</Tag>
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="创建时间">
|
|
||||||
{{
|
|
||||||
task.createTime
|
|
||||||
? formatDate(task.createTime, 'YYYY-MM-DD HH:mm:ss')
|
|
||||||
: '-'
|
|
||||||
}}
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="任务描述" :span="3">
|
|
||||||
{{ task.description }}
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<!-- 任务升级设备统计 -->
|
|
||||||
<!-- TODO @AI:和上面有间隙 -->
|
|
||||||
<UpgradeStatistics
|
<UpgradeStatistics
|
||||||
:loading="taskStatisticsLoading"
|
:loading="taskStatisticsLoading"
|
||||||
:statistics="taskStatistics"
|
:statistics="taskStatistics"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<!-- 设备管理 -->
|
<!-- 升级设备记录 -->
|
||||||
<!-- TODO @AI:需要抽成一个小 vue 组件。 -->
|
<div class="mt-4">
|
||||||
<!-- TODO @AI:和上面有间隙 -->
|
<OtaTaskRecordList :task-id="taskId" @cancelled="handleRecordCancelled" />
|
||||||
<Card title="升级设备记录">
|
|
||||||
<Tabs
|
|
||||||
v-model:active-key="activeTab"
|
|
||||||
@change="handleTabChange"
|
|
||||||
class="mb-4"
|
|
||||||
>
|
|
||||||
<Tabs.TabPane
|
|
||||||
v-for="tab in statusTabs"
|
|
||||||
:key="tab.key"
|
|
||||||
:tab="tab.label"
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
<Grid>
|
|
||||||
<template #actions="{ row }">
|
|
||||||
<TableAction
|
|
||||||
:actions="[
|
|
||||||
{
|
|
||||||
label: '取消',
|
|
||||||
type: 'link',
|
|
||||||
danger: true,
|
|
||||||
icon: ACTION_ICON.DELETE,
|
|
||||||
ifShow: [
|
|
||||||
IoTOtaTaskRecordStatusEnum.PENDING.value,
|
|
||||||
IoTOtaTaskRecordStatusEnum.PUSHED.value,
|
|
||||||
IoTOtaTaskRecordStatusEnum.UPGRADING.value,
|
|
||||||
].includes(row.status!),
|
|
||||||
popConfirm: {
|
|
||||||
title: '确认要取消该设备的升级任务吗?',
|
|
||||||
confirm: handleCancelUpgrade.bind(null, row),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</Grid>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useFormSchema } from '../data';
|
import { useFormSchema } from '../data';
|
||||||
|
|
||||||
|
// TODO @AI:是不是defineOptions、升级任务表单 注释需要?
|
||||||
/** IoT OTA 升级任务表单 */
|
/** IoT OTA 升级任务表单 */
|
||||||
defineOptions({ name: 'IoTOtaTaskForm' });
|
defineOptions({ name: 'IoTOtaTaskForm' });
|
||||||
|
|
||||||
|
|
@ -28,6 +29,7 @@ const [Form, formApi] = useVbenForm({
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO @AI:注释风格,需要对齐其他 form;
|
||||||
const [Modal, modalApi] = useVbenModal({
|
const [Modal, modalApi] = useVbenModal({
|
||||||
async onConfirm() {
|
async onConfirm() {
|
||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
|
||||||
|
|
||||||
|
import { Card } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { useDescription } from '#/components/description';
|
||||||
|
|
||||||
|
import { useDetailSchema } from '../data';
|
||||||
|
|
||||||
|
// TODO @AI:这里,是不是可以去掉;
|
||||||
|
/** IoT OTA 升级任务基本信息 */
|
||||||
|
defineOptions({ name: 'IoTOtaTaskInfo' });
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
loading?: boolean;
|
||||||
|
task: IoTOtaTaskApi.Task;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const [Description] = useDescription({
|
||||||
|
bordered: true,
|
||||||
|
column: 3,
|
||||||
|
schema: useDetailSchema(),
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card title="任务信息" :loading="loading">
|
||||||
|
<Description :data="task" />
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
@ -2,21 +2,19 @@
|
||||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
|
import type { IoTOtaTaskApi } from '#/api/iot/ota/task';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { Input, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { cancelOtaTask, getOtaTaskPage } from '#/api/iot/ota/task';
|
import { cancelOtaTask, getOtaTaskPage } from '#/api/iot/ota/task';
|
||||||
import { IoTOtaTaskStatusEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
|
import { useGridColumns } from '../data';
|
||||||
import OtaTaskDetail from './detail.vue';
|
import OtaTaskDetail from './detail.vue';
|
||||||
import OtaTaskForm from './form.vue';
|
import OtaTaskForm from './form.vue';
|
||||||
import { useGridColumns, useGridFormSchema } from '../data';
|
|
||||||
|
|
||||||
/** IoT OTA 任务列表 */
|
|
||||||
// TODO @AI:defineOptions 还需要么?
|
|
||||||
defineOptions({ name: 'IoTOtaTaskList' });
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
firmwareId: number;
|
firmwareId: number;
|
||||||
|
|
@ -25,6 +23,8 @@ const props = defineProps<{
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
const emit = defineEmits(['success']);
|
||||||
|
|
||||||
|
const searchName = ref('');
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: OtaTaskForm,
|
connectedComponent: OtaTaskForm,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
|
|
@ -41,6 +41,11 @@ async function handleRefresh() {
|
||||||
emit('success');
|
emit('success');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按任务名搜索(嵌入页面里,单字段搜索做成 toolbar 内联输入框,回车 / 清空触发查询) */
|
||||||
|
async function handleSearch() {
|
||||||
|
await gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
/** 新增任务 */
|
/** 新增任务 */
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
formModalApi
|
formModalApi
|
||||||
|
|
@ -61,21 +66,18 @@ async function handleCancel(row: IoTOtaTaskApi.Task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
|
||||||
schema: useGridFormSchema(),
|
|
||||||
},
|
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
maxHeight: 500,
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues) => {
|
query: async ({ page }) => {
|
||||||
return await getOtaTaskPage({
|
return await getOtaTaskPage({
|
||||||
pageNo: page.currentPage,
|
pageNo: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
firmwareId: props.firmwareId,
|
firmwareId: props.firmwareId,
|
||||||
...formValues,
|
name: searchName.value || undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -86,31 +88,44 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
},
|
},
|
||||||
toolbarConfig: {
|
toolbarConfig: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
search: true,
|
|
||||||
},
|
},
|
||||||
} as VxeTableGridOptions<IoTOtaTaskApi.Task>,
|
} as VxeTableGridOptions<IoTOtaTaskApi.Task>,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<!-- TODO @AI:是不是要有个高度? -->
|
|
||||||
<div>
|
<div>
|
||||||
<FormModal @success="handleRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
<DetailModal @success="handleRefresh" />
|
<DetailModal @success="handleRefresh" />
|
||||||
|
|
||||||
<!-- TODO @AI:上面有个【任务名称】,有没可能包在一起,不然太丑了。/Users/yunai/Downloads/iShot_2026-05-19_11.26.04.png -->
|
|
||||||
<Grid table-title="升级任务管理">
|
<Grid table-title="升级任务管理">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<TableAction
|
<div class="flex items-center gap-2">
|
||||||
:actions="[
|
<Input
|
||||||
{
|
v-model:value="searchName"
|
||||||
label: '新增',
|
placeholder="请输入任务名称"
|
||||||
type: 'primary',
|
allow-clear
|
||||||
icon: ACTION_ICON.ADD,
|
style="width: 200px"
|
||||||
onClick: handleCreate,
|
@press-enter="handleSearch"
|
||||||
},
|
@change="(e: any) => !e.target.value && handleSearch()"
|
||||||
]"
|
/>
|
||||||
/>
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '搜索',
|
||||||
|
type: 'default',
|
||||||
|
icon: 'ant-design:search-outlined',
|
||||||
|
onClick: handleSearch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新增',
|
||||||
|
type: 'primary',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// TODO @AI:是不是改成 statistics.vue 简化掉?
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { DICT_TYPE } from '@vben/constants';
|
||||||
|
import { getDictLabel } from '@vben/hooks';
|
||||||
|
|
||||||
import { Card, Col, Row } from 'ant-design-vue';
|
import { Card, Col, Row } from 'ant-design-vue';
|
||||||
|
|
||||||
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
/** OTA 升级设备统计卡片 */
|
|
||||||
// TODO @AI:是不是去掉 defineOptions
|
|
||||||
defineOptions({ name: 'IoTOtaUpgradeStatistics' });
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
statistics: Record<string, number>;
|
statistics: Record<string, number>;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
/** 取字典标签(同步) */
|
||||||
|
function dictLabel(value: number) {
|
||||||
|
return getDictLabel(DICT_TYPE.IOT_OTA_TASK_RECORD_STATUS, value);
|
||||||
|
}
|
||||||
|
|
||||||
/** 统计项配置 */
|
/** 统计项配置 */
|
||||||
const items = computed(() => [
|
const items = computed(() => [
|
||||||
{
|
{
|
||||||
|
|
@ -25,39 +30,38 @@ const items = computed(() => [
|
||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// TODO @AI:label 名字,是不是通过字典获取名字?包括 vue3 + ep 也是;
|
|
||||||
{
|
{
|
||||||
label: '待推送',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.PENDING.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-gray-400',
|
color: 'text-gray-400',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.PENDING.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.PENDING.value] || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '已推送',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.PUSHED.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-blue-400',
|
color: 'text-blue-400',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.PUSHED.value] || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '正在升级',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.UPGRADING.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-yellow-500',
|
color: 'text-yellow-500',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.UPGRADING.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.UPGRADING.value] || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '升级成功',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.SUCCESS.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-green-500',
|
color: 'text-green-500',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.SUCCESS.value] || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '升级失败',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.FAILURE.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-red-500',
|
color: 'text-red-500',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.FAILURE.value] || 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '升级取消',
|
label: dictLabel(IoTOtaTaskRecordStatusEnum.CANCELED.value),
|
||||||
span: 3,
|
span: 3,
|
||||||
color: 'text-gray-400',
|
color: 'text-gray-400',
|
||||||
value: props.statistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0,
|
value: props.statistics[IoTOtaTaskRecordStatusEnum.CANCELED.value] || 0,
|
||||||
|
|
@ -66,7 +70,7 @@ const items = computed(() => [
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card title="升级设备统计" class="mb-5" :loading="loading">
|
<Card title="升级设备统计" :loading="loading">
|
||||||
<Row :gutter="20" class="py-5">
|
<Row :gutter="20" class="py-5">
|
||||||
<Col v-for="item in items" :key="item.label" :span="item.span">
|
<Col v-for="item in items" :key="item.label" :span="item.span">
|
||||||
<div
|
<div
|
||||||
|
|
@ -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' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
|
import type { IoTOtaTaskRecordApi } from '#/api/iot/ota/task/record';
|
||||||
|
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { Card, message, Tabs } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
import {
|
||||||
|
cancelOtaTaskRecord,
|
||||||
|
getOtaTaskRecordPage,
|
||||||
|
} from '#/api/iot/ota/task/record';
|
||||||
|
import { IoTOtaTaskRecordStatusEnum } from '#/views/iot/utils/constants';
|
||||||
|
|
||||||
|
import { useGridColumns } from '../data';
|
||||||
|
|
||||||
|
/** IoT OTA 升级记录列表 */
|
||||||
|
// TODO @AI:defineOptions({ name: 'IoTOtaTaskRecordList' });还需要么?
|
||||||
|
defineOptions({ name: 'IoTOtaTaskRecordList' });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
taskId: number | undefined;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
cancelled: [];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const activeTab = ref('');
|
||||||
|
|
||||||
|
/** 状态标签配置 */
|
||||||
|
const statusTabs = computed(() => {
|
||||||
|
const tabs = [{ key: '', label: '全部设备' }];
|
||||||
|
Object.values(IoTOtaTaskRecordStatusEnum).forEach((status) => {
|
||||||
|
tabs.push({
|
||||||
|
key: status.value.toString(),
|
||||||
|
label: status.label,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return tabs;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 切换标签 */
|
||||||
|
async function handleTabChange(tabKey: number | string) {
|
||||||
|
activeTab.value = String(tabKey);
|
||||||
|
await gridApi.query();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消单条记录的升级 */
|
||||||
|
// TODO @AI:需要类似别的,写下注释么?
|
||||||
|
async function handleCancelUpgrade(record: IoTOtaTaskRecordApi.TaskRecord) {
|
||||||
|
await cancelOtaTaskRecord(record.id as number);
|
||||||
|
message.success('取消成功');
|
||||||
|
await gridApi.query();
|
||||||
|
emit('cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
|
gridOptions: {
|
||||||
|
columns: useGridColumns(),
|
||||||
|
height: 400,
|
||||||
|
rowConfig: {
|
||||||
|
keyField: 'id',
|
||||||
|
isHover: true,
|
||||||
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
proxyConfig: {
|
||||||
|
ajax: {
|
||||||
|
query: async ({ page }) => {
|
||||||
|
if (!props.taskId) {
|
||||||
|
return { list: [], total: 0 };
|
||||||
|
}
|
||||||
|
return await getOtaTaskRecordPage({
|
||||||
|
pageNo: page.currentPage,
|
||||||
|
pageSize: page.pageSize,
|
||||||
|
taskId: props.taskId,
|
||||||
|
// TODO @AI:别的模块,一般是独立的属性,还是设置到 formValues 里?(调研下;)
|
||||||
|
status: activeTab.value === '' ? undefined : Number(activeTab.value),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
} as VxeTableGridOptions<IoTOtaTaskRecordApi.TaskRecord>,
|
||||||
|
});
|
||||||
|
|
||||||
|
/** taskId 变化时重新查询 */
|
||||||
|
watch(
|
||||||
|
() => props.taskId,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
activeTab.value = '';
|
||||||
|
gridApi.query();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
defineExpose({ refresh: () => gridApi.query() });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card title="升级设备记录">
|
||||||
|
<Tabs
|
||||||
|
v-model:active-key="activeTab"
|
||||||
|
@change="handleTabChange"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<Tabs.TabPane
|
||||||
|
v-for="tab in statusTabs"
|
||||||
|
:key="tab.key"
|
||||||
|
:tab="tab.label"
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
<Grid>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '取消',
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
ifShow: [
|
||||||
|
IoTOtaTaskRecordStatusEnum.PENDING.value,
|
||||||
|
IoTOtaTaskRecordStatusEnum.PUSHED.value,
|
||||||
|
IoTOtaTaskRecordStatusEnum.UPGRADING.value,
|
||||||
|
].includes(row.status!),
|
||||||
|
popConfirm: {
|
||||||
|
title: '确认要取消该设备的升级任务吗?',
|
||||||
|
confirm: handleCancelUpgrade.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
Loading…
Reference in New Issue